<?php

namespace GriffinForms\Admin\Ajax;

class Single extends \GriffinForms\Admin\Ajax\Format
{     
    /**
     * Creates a new item based on the provided item type and data.
     *
     * Validates the request, sanitizes the input, inserts the item into the database,
     * and returns a standardized JSON response indicating success or failure.
     * Automatically creates a page, row, and column if the item type is a form.
     *
     * @return void
     */
    public function createItem()
    {
        // Ensure required data is present
        $this->checkPostData(['item_type', 'item_data']);
    
        // Sanitize and clean the incoming data
        $item_type = sanitize_text_field(wp_unslash($_POST['item_type']));
        $item_data = wp_unslash($_POST['item_data']);
    
        // Initialize security helper
        $this->setHelper('secure', $item_type);
    
        // Verify nonce
        $this->checkNonce('create_item');
    
        // Decode and validate item data from JSON
        $data = json_decode($item_data, true);
        if (!is_array($data) || json_last_error() !== JSON_ERROR_NONE) {
            $this->returnError(__('Invalid item data.', 'griffinforms-form-builder'));
        }
    
        // If creating a field, check for field type
        if ($item_type === 'field') {
            $this->checkPostData('field_type');
            $field_type = sanitize_text_field(wp_unslash($_POST['field_type']));

            if (!$this->config->isRegisteredFieldType($field_type)) {
                $this->returnError(__('This field type is not available for manual Core creation.', 'griffinforms-form-builder'));
            }

            $this->secure->setData(['field_type' => $field_type]);
            $data['field_type'] = $field_type;
        }
    
        // Sanitize the item data.
        // Temporarily skip serialization for formtheme so theme JSON stays intact.
        $should_serialize = ($item_type !== 'formtheme');
        $data = $this->secure->secureData($data, $should_serialize);
        $data = array_merge($data, $this->addCreateMeta());
    
        // Set target table
        $this->sql->table = $this->config->getTable($item_type);
    
        // If name is not set, auto-generate it
        if (empty($data['name'])) {
            $sql_id = absint($this->sql->getLatestItemId()) + 1;
            $data['name'] = $item_type . '-' . $sql_id;
        }
    
        // Insert into database
        $insert = $this->sql->insertData($data);
    
        if ($insert === false) {
            $this->returnError(__('Failed to create item.', 'griffinforms-form-builder'));
        }
    
        // Special case for form: create page and row structure
        if ($item_type === 'form') {
            $this->addFormPage($insert);
        }
    
        // Set successful response
        $this->response['success'] = true;
        $this->response['msg']     = __('Item created successfully.', 'griffinforms-form-builder');
        $this->response['data']    = $insert;
        $this->response['time']    = wp_date(get_option('date_format') . ' ' . get_option('time_format'));
    
        // Output the final JSON response
        echo wp_json_encode($this->response);
        wp_die();
    }
    
    /**
     * Updates an existing item based on provided item type and ID.
     *
     * Validates the request, checks item existence, updates the database, 
     * and returns a standardized JSON response indicating success or failure.
     *
     * @return void
     */
    public function updateItem()
    {
        // Ensure required data is present in the POST request.
        $this->checkPostData(['item_type', 'item_id', 'item_data']);

        // Sanitize and clean the data.
        $item_type = sanitize_text_field(wp_unslash($_POST['item_type']));
        $item_id   = absint(wp_unslash($_POST['item_id']));
        $item_data = wp_unslash($_POST['item_data']);

        // Initialize handlers for the given item type.
        $this->setHelper('secure', $item_type);
        $this->setHelper('sql', $item_type);

        // Verify the nonce for security purposes.
        $this->checkNonce('update_item');

        // Ensure the item exists before attempting update.
        if (!$this->sql->itemExists($item_type, $item_id)) {
            $this->returnError(__('This item no longer exists.', 'griffinforms-form-builder'));
        }

        // Decode and validate item data from JSON.
        $data = json_decode(($item_data), true);

        if (!is_array($data) || json_last_error() !== JSON_ERROR_NONE) {
            $this->returnError(__('Invalid item data.', 'griffinforms-form-builder'));
        }

        // For field items, retrieve and set the field_type before sanitization
        if ($item_type === 'field') {
            $field_type = $this->sql->getItemValue($item_id, 'field_type', 'field');
            if ($field_type) {
                if (!$this->config->isRegisteredFieldType($field_type)) {
                    $extension    = $this->config->getFieldExtension($field_type);
                    $ext_name     = isset($extension['name']) && $extension['name'] !== ''
                        ? $extension['name']
                        : __('a required add-on', 'griffinforms-form-builder');
                    $this->returnError(sprintf(
                        /* translators: %s: extension name */
                        __('This field requires %s, which is not currently active. Activate the add-on before editing this field.', 'griffinforms-form-builder'),
                        $ext_name
                    ));
                }
                $this->secure->setData(['field_type' => $field_type]);
            }
        }

        // Sanitize the item data using secure handler.
        $should_serialize = ($item_type !== 'formtheme');
        $data = $this->secure->secureData($data, $should_serialize);

        $compliance_change_log = array();
        if ($item_type === 'form' && isset($data['compliance_flags'])) {
            $previous_flags_raw = $this->sql->getItemValue($item_id, 'compliance_flags', 'form');
            $previous_flags = maybe_unserialize($previous_flags_raw);
            if (!is_array($previous_flags)) {
                $previous_flags = array();
            }
            $previous_flags = \GriffinForms\Includes\Helpers\Compliance::resolveFlags($previous_flags);

            $new_flags_raw = maybe_unserialize($data['compliance_flags']);
            $new_flags = is_array($new_flags_raw) ? $new_flags_raw : array();
            $new_flags = \GriffinForms\Includes\Helpers\Compliance::resolveFlags($new_flags);

            if ((int) $previous_flags['store_ip'] !== (int) $new_flags['store_ip']) {
                $compliance_change_log[] = sprintf(
                    'Store IP %s.',
                    !empty($new_flags['store_ip']) ? 'enabled' : 'disabled'
                );
            }

            if ((int) $previous_flags['store_user_agent'] !== (int) $new_flags['store_user_agent']) {
                $compliance_change_log[] = sprintf(
                    'Store user agent %s.',
                    !empty($new_flags['store_user_agent']) ? 'enabled' : 'disabled'
                );
            }

            if ((string) $previous_flags['mode'] !== (string) $new_flags['mode']) {
                $compliance_change_log[] = sprintf(
                    'Compliance flags mode set to %s.',
                    $new_flags['mode'] === 'custom' ? 'custom' : 'inherit'
                );
            }
        }

        // Prepare WHERE condition for SQL update.
        $where = array('id' => $item_id);
        $update = $this->sql->updateData($data, $where);

        // Set response based on the result of the SQL update.
        if ($update === false) {
            $this->returnError(__('Failed to update item.', 'griffinforms-form-builder'));
        }

        // Set response based on the result of the SQL update.
        if ($update === 0) {
            $this->response['success'] = true;
            $this->response['msg']     = __('No changes detected.', 'griffinforms-form-builder');
            $this->response['data']    = 0;
            $this->response['time'] = wp_date(get_option('date_format') . ' ' . get_option('time_format'));
        } else {
            $this->sql->updateData($this->addUpdateMeta(), $where);
            // Set response based on the result of the SQL update.
            $this->response['success'] = true;
            $this->response['msg']     = __('Item updated successfully.', 'griffinforms-form-builder');
            $this->response['data']    = 1;
            $this->response['time'] = wp_date(get_option('date_format') . ' ' . get_option('time_format'));
        }

        if ($item_type === 'form') {
            $this->refreshFormLayoutMetadata($item_id, 'form_settings', array(
                'item_type' => $item_type,
                'item_id' => $item_id
            ));
        }

        if ($item_type === 'form' && !empty($compliance_change_log) && !empty($this->log)) {
            $this->log->add(
                'info',
                'form',
                $item_id,
                get_current_user_id(),
                implode(' ', $compliance_change_log),
                'audit'
            );
        }

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        if ($form_id > 0 && in_array($item_type, array('field', 'formpage', 'pagerow', 'rowcolumn'), true)) {
            $this->refreshFormLayoutMetadata($form_id, 'item_edit', array(
                'item_type' => $item_type,
                'item_id' => $item_id
            ));
        }

        // Output the final JSON response.
        echo wp_json_encode($this->response);
        wp_die();
    }  
    
    public function reloadItemHistory()
    {

        if (!isset($_POST['item_type'])) {
            $response = array('success' => false, 'msg' => __('Item type is missing.', 'griffinforms-form-builder'));
            echo wp_json_encode($response);
            wp_die();
        }

        $nonce_status = isset($_POST['nonce']) ? wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'reload_history') : false;
        if ($nonce_status != false) {
            $item_id = isset($_POST['item_id']) ? absint(wp_unslash($_POST['item_id'])) : 0;
            $item_type = sanitize_text_field(wp_unslash($_POST['item_type']));
            $item_class = '\GriffinForms\Items\\' . $item_type;
            $lang_class = '\GriffinForms\Admin\Language\\' . $item_type;
            $item = new $item_class();
            $lang = $lang_class::getInstance();
            $item->loadItem($item_id);
            $history = new \GriffinForms\Admin\Html\Elements\ItemHistory($lang, $item);
            echo wp_kses_post($history->getEditText());
            wp_die();
        } else {
            esc_html_e('Nonce error.', 'griffinforms-form-builder');
            wp_die();
        }
    }

    protected function addFormPage($form_id)
    {
        $this->sql->table = $this->config->getTable('formpage');
        $name = 'Page ' . (absint($this->sql->getLatestItemId()) + 1);
        $data = array('name' => $name, 'form_id' => $form_id);
        $data = array_merge($data, $this->addCreateMeta());
        $page_id = $this->sql->insertData($data);
        $this->sql->attachFormPage($form_id, $page_id);
        $this->addPageRow($page_id, $form_id);
    }
    
    protected function addPageRow($page_id, $form_id = 0)
    {
        $this->sql->table = $this->config->getTable('pagerow');
        $name = 'Row ' . (absint($this->sql->getLatestItemId()) + 1);
        $data = array('name' => $name);
        if ($form_id > 0) {
            $data['form_id'] = $form_id;
        }
        $data = array_merge($data, $this->addCreateMeta());
        $row_id = $this->sql->insertData($data);
        $this->sql->attachPageRow($page_id, $row_id);
        $this->addRowColumn($row_id, $form_id);
    }
    
    protected function addRowColumn($row_id, $form_id = 0)
    {
        $this->sql->table = $this->config->getTable('rowcolumn');
        $name = 'Column ' . (absint($this->sql->getLatestItemId()) + 1);
        $data = array('name' => $name, 'width' => 12);
        if ($form_id > 0) {
            $data['form_id'] = $form_id;
        }
        $data = array_merge($data, $this->addCreateMeta());
        $column_id = $this->sql->insertData($data);
        $this->sql->attachRowColumn($row_id, $column_id);
    }

    private function refreshFormLayoutMetadata(int $form_id, string $context = 'unknown', array $meta = array()): void
    {
        if ($form_id <= 0) {
            return;
        }

        global $wpdb;
        $form_table = $this->config->getTable('form');
        $snapshot_table = $this->config->getTable('layout_snapshot');
        $form_row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT compliance_mode, layout_version, layout_hash FROM %i WHERE id = %d",
                $form_table,
                $form_id
            ),
            ARRAY_A
        );

        if (!is_array($form_row)) {
            return;
        }

        $layout_data = $this->buildLayoutSnapshotData($form_id);
        $new_hash = $layout_data['layout_hash'];
        if ($new_hash === '') {
            return;
        }

        $current_hash = isset($form_row['layout_hash']) ? (string) $form_row['layout_hash'] : '';
        $current_version = isset($form_row['layout_version']) ? (int) $form_row['layout_version'] : 0;
        $updated_version = $current_version;

        if ($new_hash !== $current_hash) {
            $updated_version = $current_version + 1;
            $wpdb->update(
                $form_table,
                array(
                    'layout_version' => $updated_version,
                    'layout_hash' => $new_hash
                ),
                array('id' => $form_id),
                array('%d', '%s'),
                array('%d')
            );

            $this->log->add(
                'info',
                'form',
                $form_id,
                get_current_user_id(),
                sprintf('Form layout updated to version %d.', $updated_version),
                'audit'
            );

            if (!empty($form_row['compliance_mode']) && $layout_data['layout_json'] !== '') {
                $wpdb->insert(
                    $snapshot_table,
                    array(
                        'form_id' => $form_id,
                        'submission_id' => null,
                        'layout_version' => $updated_version,
                        'layout_hash' => $new_hash,
                        'layout_json' => $layout_data['layout_json'],
                        'created_at' => current_time('mysql', true),
                        'created_by' => get_current_user_id()
                    ),
                    array('%d', '%d', '%d', '%s', '%s', '%s', '%d')
                );
            }

            do_action(
                'griffinforms_form_layout_changed',
                $form_id,
                $context,
                array_merge(
                    array(
                        'previous_version' => $current_version,
                        'new_version' => $updated_version,
                        'previous_hash' => $current_hash,
                        'new_hash' => $new_hash,
                        'compliance_mode' => !empty($form_row['compliance_mode']) ? 1 : 0
                    ),
                    $meta
                )
            );
        }
    }

    public function restoreSubmissionRevision()
    {
        $this->checkNonce('restore_submission_revision');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to restore submission revisions.', 'griffinforms-form-builder'));
        }

        $this->checkPostData(array('submission_id', 'event_uid'));

        $submission_id = isset($_POST['submission_id']) ? absint(wp_unslash($_POST['submission_id'])) : 0;
        $event_uid = isset($_POST['event_uid']) ? sanitize_text_field(wp_unslash($_POST['event_uid'])) : '';
        if ($submission_id <= 0 || $event_uid === '') {
            $this->returnError(__('Invalid submission revision request.', 'griffinforms-form-builder'));
        }

        if (!$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('This submission no longer exists.', 'griffinforms-form-builder'));
        }

        $history = \GriffinForms\Includes\Pipelines\SubmissionHistoryManager::getInstance();
        $result = $history->restoreRevision(
            $submission_id,
            $event_uid,
            array(
                'actor_user_id' => get_current_user_id(),
                'actor_label' => wp_get_current_user()->display_name,
            )
        );

        if (empty($result['success'])) {
            $message = isset($result['message']) ? (string) $result['message'] : __('Submission rollback failed.', 'griffinforms-form-builder');
            $this->log->add(
                'error',
                'submission',
                $submission_id,
                get_current_user_id(),
                'Submission rollback failed: ' . $message,
                'submission_edit_history'
            );
            $this->returnError($message);
        }

        $revision_number = isset($result['revision']['revision_number']) ? (int) $result['revision']['revision_number'] : 0;
        $this->returnSuccess(array(
            'message' => sprintf(__('Submission restored to revision #%d.', 'griffinforms-form-builder'), $revision_number),
            'revision' => $result['revision'],
        ));
    }

    public function previewSubmissionRevision()
    {
        $this->checkNonce('preview_submission_revision');

        if (!current_user_can('manage_options')) {
            $this->returnError(__('You are not allowed to preview submission revisions.', 'griffinforms-form-builder'));
        }

        $this->checkPostData(array('submission_id', 'event_uid'));

        $submission_id = isset($_POST['submission_id']) ? absint(wp_unslash($_POST['submission_id'])) : 0;
        $event_uid     = isset($_POST['event_uid']) ? sanitize_text_field(wp_unslash($_POST['event_uid'])) : '';

        if ($submission_id <= 0 || $event_uid === '') {
            $this->returnError(__('Invalid preview request.', 'griffinforms-form-builder'));
        }

        if (!$this->sql->itemExists('submission', $submission_id)) {
            $this->returnError(__('This submission no longer exists.', 'griffinforms-form-builder'));
        }

        $history  = \GriffinForms\Includes\Pipelines\SubmissionHistoryManager::getInstance();
        $revision = $history->getRevisionByEventUid($submission_id, $event_uid);

        if (empty($revision)) {
            $this->returnError(__('Revision not found.', 'griffinforms-form-builder'));
        }

        $snapshot = isset($revision['snapshot']) && is_array($revision['snapshot']) ? $revision['snapshot'] : array();

        $page = new \GriffinForms\Admin\Html\Pages\Single\Submission($submission_id);

        ob_start();
        $page->renderSnapshotFields($snapshot, $submission_id);
        $html = ob_get_clean();

        $this->returnSuccess(array(
            'html'            => $html,
            'revision_number' => isset($revision['revision_number']) ? (int) $revision['revision_number'] : 0,
            'summary'         => isset($revision['summary']) ? (string) $revision['summary'] : '',
            'created'         => isset($revision['created']) ? (string) $revision['created'] : '',
            'actor_label'     => isset($revision['actor_label']) ? (string) $revision['actor_label'] : '',
            'event_type'      => isset($revision['event_type']) ? (string) $revision['event_type'] : '',
        ));
    }

    private function buildLayoutSnapshotData(int $form_id): array
    {
        $result = array(
            'layout_payload' => array(),
            'layout_json' => '',
            'layout_hash' => ''
        );

        if ($form_id <= 0) {
            return $result;
        }

        try {
            $exporter = new \GriffinForms\Includes\ImportExport\FormExport($form_id);
            $payload = $exporter->getFormData();
            if (!is_array($payload)) {
                return $result;
            }

            $payload = $this->normalizeLayoutPayload($payload);
            $result['layout_payload'] = $payload;
            $result['layout_json'] = \GriffinForms\Includes\Helpers\Compliance::canonicalJson($payload);
            $result['layout_hash'] = \GriffinForms\Includes\Helpers\Compliance::hashLayout($payload);
        } catch (\Throwable $t) {
            $this->log->add(
                'warning',
                'form',
                $form_id,
                get_current_user_id(),
                'Unable to compute form layout snapshot: ' . $t->getMessage(),
                'audit'
            );
        }

        return $result;
    }

    private function normalizeLayoutPayload(array $payload): array
    {
        $strip_keys = array(
            'author',
            'created',
            'last_editor',
            'last_edited',
            'created_at',
            'updated_at',
            'lifetime_submission_count',
            'layout_version',
            'layout_hash',
            'compliance_mode',
            'compliance_profile',
            'compliance_flags'
        );

        return \GriffinForms\Includes\Helpers\Compliance::stripKeysRecursive($payload, $strip_keys);
    }
    
    public function __construct()
    {
        $this->setAll();
        add_action('wp_ajax_createItem', array($this, 'createItem'));
        add_action('wp_ajax_updateItem', array($this, 'updateItem'));
        add_action('wp_ajax_reloadItemHistory', array($this, 'reloadItemHistory'));
        add_action('wp_ajax_griffinforms_restore_submission_revision', array($this, 'restoreSubmissionRevision'));
        add_action('wp_ajax_griffinforms_preview_submission_revision', array($this, 'previewSubmissionRevision'));
    }
}
