<?php

namespace GriffinForms\Admin\App\Html\Modals\ConditionalLogic;

trait Responses
{
    // Registers AJAX handlers that power the conditional logic modal endpoints.
    protected function register()
    {
        add_action('wp_ajax_getConditionItemTypes', array($this, 'getConditionItemTypes'));
        add_action('wp_ajax_getConditionFieldTargets', array($this, 'getConditionFieldTargets'));
        add_action('wp_ajax_getConditionAttributes', array($this, 'getConditionAttributes'));
        add_action('wp_ajax_getConditionEvaluators', array($this, 'getConditionEvaluators'));
        add_action('wp_ajax_getConditionInput', array($this, 'getConditionInput'));
        add_action('wp_ajax_getSubeditorOverlayFields', array($this, 'getSubeditorOverlayFields'));
        add_action('wp_ajax_getActionItemTypes', array($this, 'getActionItemTypes'));
        add_action('wp_ajax_getActionFieldTargets', array($this, 'getActionFieldTargets'));
        add_action('wp_ajax_getActionAttributes', array($this, 'getActionAttributes'));
        add_action('wp_ajax_getActionOperations', array($this, 'getActionOperations'));
        add_action('wp_ajax_getActionInput', array($this, 'getActionInput'));
        add_action('wp_ajax_saveRule', array($this, 'saveRule'));
        add_action('wp_ajax_deleteRuleEntry', array($this, 'deleteRuleEntry'));
        add_action('wp_ajax_deleteRule', array($this, 'deleteRule'));
        add_action('wp_ajax_getRule', array($this, 'getRule'));
        add_action('wp_ajax_fetchRules', array($this, 'fetchRules'));
    }
    // Returns the available condition item types for a given form context.
    public function getConditionItemTypes()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_condition_item_types');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $context_type_raw = isset($_POST['context_type']) ? sanitize_text_field(wp_unslash($_POST['context_type'])) : '';
        $context_type = strtolower($context_type_raw) === 'action' ? 'action' : 'condition';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $condition_group_raw = isset($_POST['condition_group']) ? sanitize_text_field(wp_unslash($_POST['condition_group'])) : '';
        $condition_group = strtolower($condition_group_raw) === 'any' ? 'any' : 'all';

        if ('action' === $context_type) {
            $condition_group = '';
        }

        if ($form_id <= 0) {
            wp_send_json_error([
                'msg' => __('Invalid form context supplied.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->choices)) {
            $this->setChoices();
        }

        if (!is_object($this->choices) || !method_exists($this->choices, 'ifItems')) {
            wp_send_json_error([
                'msg' => __('Unable to load available condition items.', 'griffinforms-form-builder'),
            ]);
        }

        $options = (array) $this->choices->ifItems();

        if (empty($options)) {
            wp_send_json_error([
                'msg' => __('No conditional items available.', 'griffinforms-form-builder'),
            ]);
        }

        $selected_item = null;

        if (null !== $rule_index && null !== $entry_index) {
            $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
            $rules = maybe_unserialize($raw_rules);

            if (is_array($rules) && isset($rules[$rule_index]) && is_array($rules[$rule_index])) {
                if ('action' === $context_type) {
                    $entries = isset($rules[$rule_index]['actions']) && is_array($rules[$rule_index]['actions'])
                        ? $rules[$rule_index]['actions']
                        : [];
                } else {
                    $target_group = ('any' === $condition_group) ? 'any' : 'all';
                    $entries = isset($rules[$rule_index][$target_group]) && is_array($rules[$rule_index][$target_group])
                        ? $rules[$rule_index][$target_group]
                        : [];
                }

                if (isset($entries[$entry_index]) && is_array($entries[$entry_index])) {
                    $entry = $entries[$entry_index];
                    if (isset($entry['item']) && is_scalar($entry['item'])) {
                        $selected_item = (string) $entry['item'];
                    }
                }
            }
        }

        ob_start();
        $this->subeditorDropdown(__('If', 'griffinforms-form-builder'), $options, 'items', $selected_item);
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Item types loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }
    // Returns the available action item types for the rule editor.
    public function getActionItemTypes()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_action_item_types');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $context_type_raw = isset($_POST['context_type']) ? sanitize_text_field(wp_unslash($_POST['context_type'])) : '';
        $context_type = strtolower($context_type_raw) === 'condition' ? 'condition' : 'action';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $selected_item_request = isset($_POST['selected_item_value']) ? sanitize_text_field(wp_unslash($_POST['selected_item_value'])) : '';

        if ($form_id <= 0) {
            wp_send_json_error([
                'msg' => __('Invalid form context supplied.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->choices)) {
            $this->setChoices();
        }

        if (!is_object($this->choices) || !method_exists($this->choices, 'actionItemTypes')) {
            wp_send_json_error([
                'msg' => __('Unable to load available action items.', 'griffinforms-form-builder'),
            ]);
        }

        $options = (array) $this->choices->actionItemTypes();

        if (empty($options)) {
            wp_send_json_error([
                'msg' => __('No action items available.', 'griffinforms-form-builder'),
            ]);
        }

        $selected_item = null;

        if (null !== $rule_index && null !== $entry_index) {
            $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
            $rules = maybe_unserialize($raw_rules);

            if (is_array($rules) && isset($rules[$rule_index]) && is_array($rules[$rule_index])) {
                $actions = isset($rules[$rule_index]['actions']) && is_array($rules[$rule_index]['actions'])
                    ? $rules[$rule_index]['actions']
                    : [];

                if (isset($actions[$entry_index]) && is_array($actions[$entry_index])) {
                    $entry = $actions[$entry_index];
                    if (isset($entry['item']) && is_scalar($entry['item'])) {
                        $selected_item = (string) $entry['item'];
                    }
                }
            }
        }

        if (null === $selected_item && '' !== $selected_item_request) {
            $selected_item = $selected_item_request;
        }

        ob_start();
        $this->subeditorDropdown(
            __('Then adjust', 'griffinforms-form-builder'),
            $options,
            'action-items',
            $selected_item,
            [
                'wrapper_id'      => '',
                'wrapper_classes' => 'text-center griffinforms-logic-subeditor-control-wrapper',
                'select_id'       => 'griffinforms-logic-subeditor-action-items',
                'select_classes'  => 'griffinforms-logic-subeditor-control',
                'select_name'     => 'item',
            ]
        );
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Action items loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }
    // Loads the list of fields that an action can target.
    public function getActionFieldTargets()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_action_field_targets');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $context_type_raw = isset($_POST['context_type']) ? sanitize_text_field(wp_unslash($_POST['context_type'])) : '';
        $context_type = strtolower($context_type_raw) === 'action' ? 'action' : 'condition';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $selected_field_request = isset($_POST['selected_field_id']) ? sanitize_text_field(wp_unslash($_POST['selected_field_id'])) : '';
        $item_type_raw = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : 'field';
        $item_type = in_array(strtolower($item_type_raw), ['field', 'form', 'row'], true)
            ? strtolower($item_type_raw)
            : 'field';
        $option_groups_raw = isset($_POST['field_option_groups']) ? wp_unslash($_POST['field_option_groups']) : '';
        $available_option_groups = [];

        if ($form_id <= 0 || 'action' !== $context_type) {
            wp_send_json_error([
                'msg' => __('Invalid action context supplied.', 'griffinforms-form-builder'),
            ]);
        }

        $selected_field = null;

        if (null !== $rule_index && null !== $entry_index) {
            $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
            $rules = maybe_unserialize($raw_rules);

            if (is_array($rules) && isset($rules[$rule_index]) && is_array($rules[$rule_index])) {
                $actions = isset($rules[$rule_index]['actions']) && is_array($rules[$rule_index]['actions'])
                    ? $rules[$rule_index]['actions']
                    : [];

                if (isset($actions[$entry_index]) && is_array($actions[$entry_index])) {
                    $entry = $actions[$entry_index];
                    if (isset($entry['item_id']) && is_scalar($entry['item_id'])) {
                        $selected_field = (string) $entry['item_id'];
                    }
                }
            }
        }

        if (null === $selected_field && '' !== $selected_field_request) {
            $selected_field = $selected_field_request;
        }

        if ('form' === $item_type) {
            $target_value = '' !== $selected_field ? $selected_field : 'this_form';
            $html  = '<div class="text-center griffinforms-logic-subeditor-control-wrapper">';
            $html .= '<div class="form-text text-muted mb-2">' . esc_html__('This rule targets the current form.', 'griffinforms-form-builder') . '</div>';
            $html .= '<div class="fw-semibold mb-2">' . esc_html__('This form', 'griffinforms-form-builder') . '</div>';
            $html .= '<input type="hidden" id="griffinforms-logic-subeditor-action-field" name="item_id" value="' . esc_attr($target_value) . '" data-gf-field-type="form" />';
            $html .= '</div>';

            wp_send_json_success([
                'msg'  => __('Form target loaded successfully.', 'griffinforms-form-builder'),
                'html' => $html,
            ]);
        }

        if ('row' === $item_type) {
            $html = $this->getRowList(
                $form_id,
                $selected_field,
                [
                    'wrapper_id'      => '',
                    'wrapper_classes' => 'text-center griffinforms-logic-subeditor-control-wrapper',
                    'select_id'       => 'griffinforms-logic-subeditor-action-field',
                    'select_name'     => 'item_id',
                ],
                __('named', 'griffinforms-form-builder')
            );
        } else {
            $html = $this->getFieldList(
                $form_id,
                $selected_field,
                [
                    'wrapper_id'      => '',
                    'wrapper_classes' => 'text-center griffinforms-logic-subeditor-control-wrapper',
                    'select_id'       => 'griffinforms-logic-subeditor-action-field',
                    'select_name'     => 'item_id',
                ],
                __('named', 'griffinforms-form-builder')
            );
        }

        wp_send_json_success([
            'msg'  => __('Fields loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }
    // Fetches attribute options for a given action item selection.
    public function getActionAttributes()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_action_attributes');

        $form_id    = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $field_id   = isset($_POST['field_id']) ? sanitize_text_field(wp_unslash($_POST['field_id'])) : '';
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $selected_attribute_request = isset($_POST['selected_attribute']) ? sanitize_text_field(wp_unslash($_POST['selected_attribute'])) : '';
        $item_type_raw = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : 'field';
        $item_type = in_array(strtolower($item_type_raw), ['field', 'form', 'row'], true)
            ? strtolower($item_type_raw)
            : 'field';
        $option_groups_raw = isset($_POST['field_option_groups']) ? wp_unslash($_POST['field_option_groups']) : '';
        $available_option_groups = $this->parseOptionGroupList($option_groups_raw);

        if ($form_id <= 0 || '' === $field_id) {
            wp_send_json_error([
                'msg' => __('Invalid action field selection.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->choices)) {
            $this->setChoices();
        }

        $options = [];
        if ('form' === $item_type) {
            if (!is_object($this->choices) || !method_exists($this->choices, 'actionFormAttributes')) {
                wp_send_json_error([
                    'msg' => __('Unable to load action attributes.', 'griffinforms-form-builder'),
                ]);
            }
            $options = (array) $this->choices->actionFormAttributes();
        } elseif ('row' === $item_type) {
            if (!is_object($this->choices) || !method_exists($this->choices, 'actionRowAttributes')) {
                wp_send_json_error([
                    'msg' => __('Unable to load action attributes.', 'griffinforms-form-builder'),
                ]);
            }
            $options = (array) $this->choices->actionRowAttributes();
        } else {
            if (!is_object($this->choices) || !method_exists($this->choices, 'actionFieldAttributes')) {
                wp_send_json_error([
                    'msg' => __('Unable to load action attributes.', 'griffinforms-form-builder'),
                ]);
            }
            if ('selectfield' === strtolower($field_type)) {
                $available_option_groups = $this->getSelectFieldOptionGroups($field_id, $option_groups_raw);
            }

            $options = (array) $this->choices->actionFieldAttributes($field_type);
            if ('selectfield' === strtolower($field_type)) {
                $has_option_groups = !empty($available_option_groups);
                $options['option_groups'] = __('options group', 'griffinforms-form-builder');
            }
        }

        if (empty($options)) {
            wp_send_json_error([
                'msg' => __('No attributes available for this action.', 'griffinforms-form-builder'),
            ]);
        }

        $selected_attribute = null;

        if (null !== $rule_index && null !== $entry_index) {
            $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
            $rules = maybe_unserialize($raw_rules);

            if (is_array($rules) && isset($rules[$rule_index]) && is_array($rules[$rule_index])) {
                $actions = isset($rules[$rule_index]['actions']) && is_array($rules[$rule_index]['actions'])
                    ? $rules[$rule_index]['actions']
                    : [];

                if (isset($actions[$entry_index]) && is_array($actions[$entry_index])) {
                    $entry = $actions[$entry_index];
                    if (isset($entry['attribute']) && is_scalar($entry['attribute'])) {
                        $selected_attribute = (string) $entry['attribute'];
                    }
                }
            }
        }

        if (null === $selected_attribute && '' !== $selected_attribute_request) {
            $selected_attribute = $selected_attribute_request;
        }

        $option_attrs = [];
        if ('selectfield' === strtolower($field_type)) {
            $has_option_groups = !empty($available_option_groups);
            $option_attrs['option_groups'] = $has_option_groups
                ? []
                : [
                    'disabled' => 'disabled',
                    'title'    => __('This dropdown has no option groups to control.', 'griffinforms-form-builder'),
                ];
        }

        if ('inputlikert' === strtolower($field_type)) {
            $likert_rows_raw = isset($_POST['field_likert_rows']) ? wp_unslash($_POST['field_likert_rows']) : '';
            $available_likert_rows = $this->parseLikertRowList($likert_rows_raw, $field_id);
            $option_attrs['rows'] = !empty($available_likert_rows)
                ? []
                : [
                    'disabled' => 'disabled',
                    'title'    => __('This Likert field has no rows to control.', 'griffinforms-form-builder'),
                ];
        }

        ob_start();
        $this->subeditorDropdown(
            __('so that its', 'griffinforms-form-builder'),
            $options,
            'action-attributes',
            $selected_attribute,
            [
                'wrapper_id'      => '',
                'wrapper_classes' => 'text-center griffinforms-logic-subeditor-control-wrapper',
                'select_id'       => 'griffinforms-logic-subeditor-action-attributes',
                'select_classes'  => 'griffinforms-logic-subeditor-control',
                'select_name'     => 'attribute',
                'option_attrs'    => $option_attrs,
            ]
        );
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Action attributes loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }

    // Returns the available operations for a given action attribute.
    public function getActionOperations()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_action_operations');

        $form_id   = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $attribute = isset($_POST['attribute']) ? sanitize_text_field(wp_unslash($_POST['attribute'])) : '';
        $field_id  = isset($_POST['field_id']) ? sanitize_text_field(wp_unslash($_POST['field_id'])) : '';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $selected_action_request = isset($_POST['selected_action']) ? sanitize_text_field(wp_unslash($_POST['selected_action'])) : '';
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';
        $item_type_raw = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : 'field';
        $item_type = in_array(strtolower($item_type_raw), ['field', 'form', 'row'], true)
            ? strtolower($item_type_raw)
            : 'field';
        $option_groups_raw = isset($_POST['field_option_groups']) ? wp_unslash($_POST['field_option_groups']) : '';
        $available_option_groups = [];

        if ($form_id <= 0 || '' === $attribute) {
            wp_send_json_error([
                'msg' => __('Invalid action attribute selection.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->choices)) {
            $this->setChoices();
        }

        if (!is_object($this->choices) || !method_exists($this->choices, 'getActionOperations')) {
            wp_send_json_error([
                'msg' => __('Unable to load action operations.', 'griffinforms-form-builder'),
            ]);
        }

        $options = (array) $this->choices->getActionOperations($attribute, $field_type, $item_type);

        if ('selectfield' === strtolower($field_type) && 'option_groups' === strtolower($attribute)) {
            $available_option_groups = $this->getSelectFieldOptionGroups($field_id, isset($_POST['field_option_groups']) ? wp_unslash($_POST['field_option_groups']) : '');
        }

        if ('option_groups' === strtolower($attribute) && empty($available_option_groups)) {
            wp_send_json_error([
                'msg' => __('This field has no option groups to control.', 'griffinforms-form-builder'),
            ]);
        }

        $available_likert_rows = [];
        if ('inputlikert' === strtolower($field_type) && 'rows' === strtolower($attribute)) {
            $likert_rows_raw = isset($_POST['field_likert_rows']) ? wp_unslash($_POST['field_likert_rows']) : '';
            $available_likert_rows = $this->parseLikertRowList($likert_rows_raw, $field_id);
        }

        if ('rows' === strtolower($attribute) && empty($available_likert_rows)) {
            wp_send_json_error([
                'msg' => __('This Likert field has no rows to control.', 'griffinforms-form-builder'),
            ]);
        }

        if (empty($options)) {
            wp_send_json_error([
                'msg' => __('No actions available for this attribute.', 'griffinforms-form-builder'),
            ]);
        }

        if ('form' === $item_type && 'success_message' === $attribute) {
            if (!$this->hasFormSuccessMessageEnabled($form_id)) {
                wp_send_json_error([
                    'msg' => __('The success message action requires the form to be set to "Show a success message" in the form\'s After Submission settings.', 'griffinforms-form-builder'),
                ]);
            }
        }

        $option_attrs = [];

        $selected_action = null;

        if (null !== $rule_index && null !== $entry_index) {
            $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
            $rules = maybe_unserialize($raw_rules);

            if (is_array($rules) && isset($rules[$rule_index]) && is_array($rules[$rule_index])) {
                $actions = isset($rules[$rule_index]['actions']) && is_array($rules[$rule_index]['actions'])
                    ? $rules[$rule_index]['actions']
                    : [];

                if (isset($actions[$entry_index]) && is_array($actions[$entry_index])) {
                    $entry = $actions[$entry_index];
                    if (isset($entry['action']) && is_scalar($entry['action'])) {
                        $selected_action = (string) $entry['action'];
                    }
                }
            }
        }

        if (null === $selected_action && '' !== $selected_action_request) {
            $selected_action = $selected_action_request;
        }

        ob_start();
        $this->subeditorDropdown(
            __('will', 'griffinforms-form-builder'),
            $options,
            'action-operations',
            $selected_action,
            [
                'wrapper_id'      => '',
                'wrapper_classes' => 'text-center griffinforms-logic-subeditor-control-wrapper',
                'select_id'       => 'griffinforms-logic-subeditor-action-operation',
                'select_classes'  => 'griffinforms-logic-subeditor-control',
                'select_name'     => 'action',
                'option_attrs'    => $option_attrs,
            ]
        );
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Action options loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }

    // Renders the input field required for a specific action operation.
    public function getActionInput()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_action_input');

        $form_id   = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $field_id  = isset($_POST['field_id']) ? sanitize_text_field(wp_unslash($_POST['field_id'])) : '';
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';
        $attribute = isset($_POST['attribute']) ? sanitize_text_field(wp_unslash($_POST['attribute'])) : '';
        $operation = isset($_POST['operation']) ? sanitize_text_field(wp_unslash($_POST['operation'])) : '';
        $input_value = isset($_POST['input_value']) ? wp_unslash($_POST['input_value']) : '';
        $item_type_raw = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : 'field';
        $item_type = in_array(strtolower($item_type_raw), ['field', 'form', 'row'], true)
            ? strtolower($item_type_raw)
            : 'field';
        $option_groups_raw = isset($_POST['field_option_groups']) ? wp_unslash($_POST['field_option_groups']) : '';
        $available_option_groups = $this->parseOptionGroupList($option_groups_raw);

        if ($form_id <= 0 || '' === $attribute || '' === $operation) {
            wp_send_json_error([
                'msg' => __('Missing or invalid request parameters.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->choices)) {
            $this->setChoices();
        }

        if (!is_object($this->choices) || !method_exists($this->choices, 'getActionOperationConfig')) {
            wp_send_json_error([
                'msg' => __('Unable to load action operation details.', 'griffinforms-form-builder'),
            ]);
        }

        $operation_config = (array) $this->choices->getActionOperationConfig($attribute, $field_type, $item_type);
        $operation_meta = isset($operation_config[$operation]) && is_array($operation_config[$operation])
            ? $operation_config[$operation]
            : null;

        if (null === $operation_meta) {
            wp_send_json_success([
                'msg'  => __('No additional input required for this action.', 'griffinforms-form-builder'),
                'html' => '',
                'meta' => [
                    'needs_input' => false,
                    'input_type'  => null,
                ],
            ]);
        }

        if ('selectfield' === strtolower($field_type) && 'option_groups' === strtolower($attribute)) {
            $available_option_groups = $this->getSelectFieldOptionGroups($field_id, $option_groups_raw);
        }

        if ('option_groups' === strtolower($attribute)) {
            if (empty($available_option_groups)) {
                wp_send_json_error([
                    'msg' => __('This field has no option groups to control.', 'griffinforms-form-builder'),
                ]);
            }

            $selected_groups = $this->parseSelectedOptionGroups($input_value, $available_option_groups);
            $html = $this->buildOptionGroupInputHtml(
                $available_option_groups,
                $selected_groups,
                strtolower($operation)
            );

            wp_send_json_success([
                'msg'  => __('Select the option groups to modify.', 'griffinforms-form-builder'),
                'html' => $html,
                'meta' => [
                    'needs_input' => true,
                    'input_type'  => 'option_groups',
                ],
            ]);
        }

        if ('inputlikert' === strtolower($field_type) && 'rows' === strtolower($attribute)) {
            $likert_rows_raw = isset($_POST['field_likert_rows']) ? wp_unslash($_POST['field_likert_rows']) : '';
            $available_likert_rows = $this->parseLikertRowList($likert_rows_raw, $field_id);

            if (empty($available_likert_rows)) {
                wp_send_json_error([
                    'msg' => __('This Likert field has no rows to control.', 'griffinforms-form-builder'),
                ]);
            }

            $selected_row_ids = $this->parseSelectedLikertRows($input_value, $available_likert_rows);
            $html = $this->buildLikertRowInputHtml(
                $available_likert_rows,
                $selected_row_ids,
                strtolower($operation)
            );

            wp_send_json_success([
                'msg'  => __('Select the rows to modify.', 'griffinforms-form-builder'),
                'html' => $html,
                'meta' => [
                    'needs_input' => true,
                    'input_type'  => 'likert_rows',
                ],
            ]);
        }

        $needs_input = !empty($operation_meta['needs_input']);
        $input_type = $needs_input && isset($operation_meta['input_type']) ? (string) $operation_meta['input_type'] : null;

        $html = '';
        if ($needs_input) {
            $input_context = [
                'form_id'    => $form_id,
                'field_id'   => $field_id,
                'field_type' => $field_type,
                'value'      => $input_value,
            ];

            if ('visibility' === $attribute) {
                if (in_array($operation, ['show_error', 'show_warning', 'show_info'], true)) {
                    $input_context['placeholder'] = __('Enter the message to display', 'griffinforms-form-builder');
                    $input_context['rows'] = 3;
                } elseif ('show_popup' === $operation) {
                    $input_context['placeholder'] = __('Enter the popup message', 'griffinforms-form-builder');
                    $input_context['rows'] = 3;
                }
            } elseif ('success_message' === $attribute) {
                $input_context['placeholder'] = __('Enter the success message text', 'griffinforms-form-builder');
                $input_context['rows'] = 3;
            } elseif ('redirection' === $attribute) {
                $input_context['placeholder'] = __('Enter a URL, string, or variables to use for redirecting the form.', 'griffinforms-form-builder');
                $input_context['rows'] = 2;
            } elseif ('gateway' === $attribute && 'payment' === strtolower((string) $field_type)) {
                $gateway_options = [];
                if (class_exists('\GriffinForms\Includes\Integrations\Categories\Payment')) {
                    $payment_category = \GriffinForms\Includes\Integrations\Categories\Payment::getInstance();
                    foreach ($payment_category->getProviderOptions() as $provider) {
                        $id = isset($provider['id']) ? (string) $provider['id'] : '';
                        if ('' === $id) {
                            continue;
                        }
                        $label = isset($provider['label']) ? (string) $provider['label'] : $id;
                        $gateway_options[$id] = $label;
                    }
                }
                $input_context['options'] = $gateway_options;
            }

            $html = $this->buildActionInputHtml(
                $input_type,
                $attribute,
                $operation,
                $input_context
            );
        }

        wp_send_json_success([
            'msg'  => $needs_input
                ? __('Enter a value for this action.', 'griffinforms-form-builder')
                : __('No additional input required for this action.', 'griffinforms-form-builder'),
            'html' => $needs_input ? $html : '',
            'meta' => [
                'needs_input' => (bool) $needs_input,
                'input_type'  => $needs_input ? $input_type : null,
            ],
        ]);
    }

    // Loads the list of fields that a condition can target.
    public function getConditionFieldTargets()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_condition_field_targets');

        $item_type = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
        $item_type = strtolower($item_type);
        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $selected_item_id = isset($_POST['selected_item_id']) ? sanitize_text_field(wp_unslash($_POST['selected_item_id'])) : '';
        $selected_item_value = isset($_POST['selected_item_value']) ? sanitize_text_field(wp_unslash($_POST['selected_item_value'])) : '';

        $allowed = array_keys($this->choices->ifItems());
        if (!in_array($item_type, $allowed, true)) {
            wp_send_json_error([
                'msg' => __('Invalid item type.', 'griffinforms-form-builder'),
            ]);
        }

        $method = 'get' . ucfirst($item_type) . 'List';
        if (!method_exists($this, $method)) {
            wp_send_json_error([
                'msg' => __('Sorry, we couldn’t load those items right now.', 'griffinforms-form-builder'),
            ]);
        }

        $args = [$form_id];
        if ('getFieldList' === $method) {
            $args[] = '' !== $selected_item_id ? $selected_item_id : $selected_item_value;
        }

        $html = call_user_func_array([$this, $method], $args);

        wp_send_json_success([
            'msg'      => __('Items fetched successfully.', 'griffinforms-form-builder'),
            'item_type'=> $item_type,
            'html'     => $html,
        ]);
    }

    // Fetches overlay fields for the subeditor modal.
    public function getSubeditorOverlayFields()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_subeditor_overlay_fields');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        if ($form_id <= 0) {
            wp_send_json_error([
                'msg' => __('Invalid form context provided.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->sql)) {
            $this->setSql();
        }

        if (!is_object($this->sql) || !method_exists($this->sql, 'fieldsInForm')) {
            wp_send_json_error([
                'msg' => __('Unable to load fields for this form.', 'griffinforms-form-builder'),
            ]);
        }

        $field_ids = $this->sql->fieldsInForm($form_id);
        if (!is_array($field_ids)) {
            $field_ids = [];
        }

        $fields = [];

        foreach ($field_ids as $field_id) {
            $field_id = (int) $field_id;
            if ($field_id <= 0) {
                continue;
            }

            $values = $this->sql->itemValues($field_id, 'field', ['name', 'field_type', 'iterations']);
            if (!is_object($values)) {
                continue;
            }

            $label = isset($values->name) && is_scalar($values->name) ? (string) $values->name : '';
            $field_type = isset($values->field_type) && is_scalar($values->field_type)
                ? strtolower((string) $values->field_type)
                : '';

            $iteration_raw = isset($values->iterations) ? $values->iterations : [];
            $iteration_settings = $this->maybeDecode($iteration_raw);
            if (!is_array($iteration_settings)) {
                $iteration_settings = [];
            }

            $allow_iterations = !empty($iteration_settings['allow']);
            $min_iterations = isset($iteration_settings['min']) && is_numeric($iteration_settings['min'])
                ? (int) $iteration_settings['min']
                : 0;
            $max_iterations = isset($iteration_settings['max']) && is_numeric($iteration_settings['max'])
                ? (int) $iteration_settings['max']
                : 0;

            $fields[] = [
                'id'        => $field_id,
                'label'     => $label !== '' ? $label : sprintf(__('Field #%d', 'griffinforms-form-builder'), $field_id),
                'type'      => $field_type,
                'iterations'=> [
                    'allow' => (bool) $allow_iterations,
                    'min'   => $min_iterations,
                    'max'   => $max_iterations,
                ],
            ];
        }

        usort($fields, function ($first, $second) {
            $label_a = isset($first['label']) ? (string) $first['label'] : '';
            $label_b = isset($second['label']) ? (string) $second['label'] : '';
            return strcasecmp($label_a, $label_b);
        });

        wp_send_json_success([
            'msg'    => __('Overlay options loaded successfully.', 'griffinforms-form-builder'),
            'fields' => $fields,
        ]);
    }

    // Returns the available attributes for a given condition item type.
    public function getConditionAttributes()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_condition_attributes');

        $item_type  = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
        $form_id    = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $field_id   = isset($_POST['field_id']) ? absint(wp_unslash($_POST['field_id'])) : 0;
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';

        if (! $item_type || ! $form_id) {
            wp_send_json_error([
                'msg' => __('Missing or invalid request parameters.', 'griffinforms-form-builder'),
            ]);
        }

        $method = $item_type . 'Attrs';

        if (!method_exists($this->choices, $method)) {
            wp_send_json_error([
                'msg' => sprintf(__('Invalid item type method: %s', 'griffinforms-form-builder'), esc_html($method)),
            ]);
        }

        $selected_attribute = isset($_POST['selected_attribute']) ? sanitize_text_field(wp_unslash($_POST['selected_attribute'])) : '';

        $options = $this->choices->$method($field_type);

        if (!is_array($options)) {
            wp_send_json_error([
                'msg' => __('No options available for this selection type.', 'griffinforms-form-builder'),
            ]);
        }

        ob_start();
        $this->subeditorDropdown(__('has a', 'griffinforms-form-builder'), $options, 'attributes', '' !== $selected_attribute ? $selected_attribute : null);
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Attributes loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }

    // Returns the available evaluators for a condition attribute.
    public function getConditionEvaluators()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_condition_evaluators');

        $item_type  = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
        $form_id    = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $field_id   = isset($_POST['field_id']) ? absint(wp_unslash($_POST['field_id'])) : 0;
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';
        $attr       = isset($_POST['attr']) ? sanitize_text_field(wp_unslash($_POST['attr'])) : '';

        if (! $item_type || ! $form_id || ! $attr) {
            wp_send_json_error([
                'msg' => __('Missing or invalid request parameters.', 'griffinforms-form-builder'),
            ]);
        }

        $method = $item_type . $attr . 'Evaluators';

        if (!method_exists($this->choices, $method)) {
            wp_send_json_error([
                'msg' => sprintf(__('Invalid item type method: %s', 'griffinforms-form-builder'), esc_html($method)),
            ]);
        }

        $selected_evaluator = isset($_POST['selected_evaluator']) ? sanitize_text_field(wp_unslash($_POST['selected_evaluator'])) : '';

        $options = $this->choices->$method($field_type);

        if (!is_array($options)) {
            wp_send_json_error([
                'msg' => __('No options available for this selection type.', 'griffinforms-form-builder'),
            ]);
        }

        ob_start();
        $this->subeditorDropdown(__('that', 'griffinforms-form-builder'), $options, 'attribute-evaluators', '' !== $selected_evaluator ? $selected_evaluator : null);
        $html = ob_get_clean();

        wp_send_json_success([
            'msg'  => __('Attribute evaluators loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }

    // Renders the input field required for a specific condition evaluator.
    public function getConditionInput()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_condition_input');

        $item_type  = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
        $form_id    = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $field_id   = isset($_POST['field_id']) ? absint(wp_unslash($_POST['field_id'])) : 0;
        $field_type = isset($_POST['field_type']) ? sanitize_text_field(wp_unslash($_POST['field_type'])) : '';
        $attr       = isset($_POST['attr']) ? sanitize_text_field(wp_unslash($_POST['attr'])) : '';
        $condition  = isset($_POST['condition']) ? sanitize_text_field(wp_unslash($_POST['condition'])) : '';
        $input_value = isset($_POST['input_value']) ? wp_unslash($_POST['input_value']) : '';

        if (! $item_type || ! $form_id || ! $attr || ! $condition) {
            wp_send_json_error([
                'msg' => __('Missing or invalid request parameters.', 'griffinforms-form-builder'),
            ]);
        }

        $method = 'getConditionInput';

        if (!method_exists($this->choices, $method)) {
            wp_send_json_error([
                'msg' => sprintf(__('Unable to fetch input value.', 'griffinforms-form-builder'), esc_html($method)),
            ]);
        }

        $condition_input = $this->choices->$method(
            $item_type,
            $attr,
            $condition,
            [
                'form_id'    => $form_id,
                'field_id'   => $field_id,
                'field_type' => $field_type,
            ]
        );

        // Payment field specific option hydration for select-based inputs.
        if ('field' === $item_type && 'payment' === strtolower((string) $field_type)) {
            $options = [];
            if (!is_object($this->sql)) {
                $this->setSql();
            }
            if (is_object($this->sql)) {
                $values_obj = $this->sql->itemValues((int) $field_id, 'field', ['field_options']);
                $raw_opts = (is_object($values_obj) && isset($values_obj->field_options)) ? $values_obj->field_options : [];
                $decoded  = $this->maybeDecode($raw_opts);
                if (is_array($decoded)) {
                    foreach ($decoded as $product) {
                        if (!is_array($product) || empty($product['id'])) {
                            continue;
                        }
                        $label = isset($product['name']) ? (string) $product['name'] : (string) $product['id'];
                        $options[$product['id']] = $label;
                    }
                }
            }

            if ('selected_product' === $attr) {
                $condition_input['options'] = $options;
                $condition_input['multiple'] = in_array($condition, ['contains', 'not_contains'], true);
            } elseif ('selected_gateway' === $attr) {
                $gateway_options = [];
                if (class_exists('\GriffinForms\Includes\Integrations\Categories\Payment')) {
                    $payment_category = \GriffinForms\Includes\Integrations\Categories\Payment::getInstance();
                    foreach ($payment_category->getProviderOptions() as $provider) {
                        $id = isset($provider['id']) ? (string) $provider['id'] : '';
                        if ('' === $id) {
                            continue;
                        }
                        $label = isset($provider['label']) ? (string) $provider['label'] : $id;
                        $gateway_options[$id] = $label;
                    }
                }
                $condition_input['options'] = !empty($gateway_options) ? $gateway_options : $options;
            }
        }

        if (
            'field' === $item_type
            && 'imageselection' === strtolower((string) $field_type)
            && 'value' === $attr
            && is_array($condition_input)
            && !empty($condition_input['needs_input'])
            && (($condition_input['input_type'] ?? '') === 'choice')
        ) {
            $options = [];
            if (!is_object($this->sql)) {
                $this->setSql();
            }
            if (is_object($this->sql)) {
                $values_obj = $this->sql->itemValues((int) $field_id, 'field', ['field_settings']);
                $raw_settings = (is_object($values_obj) && isset($values_obj->field_settings)) ? $values_obj->field_settings : [];
                $decoded_settings = $this->maybeDecode($raw_settings);
                $field_settings_data = is_array($decoded_settings) ? $decoded_settings : [];
                $image_options = isset($field_settings_data['options']) && is_array($field_settings_data['options'])
                    ? $field_settings_data['options']
                    : [];

                foreach ($image_options as $option) {
                    if (!is_array($option)) {
                        continue;
                    }

                    $value = sanitize_key((string) ($option['value'] ?? ''));
                    if ('' === $value) {
                        continue;
                    }

                    $label = isset($option['title']) ? trim((string) $option['title']) : '';
                    $options[$value] = '' !== $label ? $label : $value;
                }
            }

            $condition_input['options'] = $options;
            $condition_input['multiple'] = in_array($condition, ['contains', 'does_not_contain'], true);
        }

        $needs_input = is_array($condition_input) && !empty($condition_input['needs_input']);
        $input_type  = $needs_input && isset($condition_input['input_type'])
            ? (string) $condition_input['input_type']
            : null;

        $html = '';

        if ($needs_input) {
            $html = $this->buildConditionInputHtml(
                $input_type,
                $item_type,
                $attr,
                $condition,
                [
                    'form_id'    => $form_id,
                    'field_id'   => $field_id,
                    'field_type' => $field_type,
                    'value'      => $input_value,
                    'options'    => isset($condition_input['options']) ? $condition_input['options'] : [],
                    'multiple'   => !empty($condition_input['multiple']),
                ]
            );
        }

        wp_send_json_success([
            'msg'  => __('Input value retrieved successfully.', 'griffinforms-form-builder'),
            'html' => $html,
            'meta' => [
                'needs_input' => (bool) $needs_input,
                'input_type'  => $needs_input ? $input_type : null,
            ],
        ]);
    }

    // Saves a conditional logic rule or entry.
    public function saveRule()
    {
        $this->response = array('success' => false);
        $this->checkRequest('save_rule');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        if ($form_id <= 0) {
            wp_send_json_error([
                'msg' => __('Invalid form context supplied.', 'griffinforms-form-builder'),
            ]);
        }

        $context_type = isset($_POST['context_type']) ? sanitize_text_field(wp_unslash($_POST['context_type'])) : '';
        $context_type = ('action' === strtolower($context_type)) ? 'action' : 'condition';
        $rule_index_raw = isset($_POST['context_rule_index']) ? wp_unslash($_POST['context_rule_index']) : null;
        $entry_index_raw = isset($_POST['context_entry_index']) ? wp_unslash($_POST['context_entry_index']) : null;

        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $condition_group_raw = isset($_POST['condition_group']) ? sanitize_text_field(wp_unslash($_POST['condition_group'])) : '';
        $condition_group = strtolower($condition_group_raw);
        if ('any' !== $condition_group) {
            $condition_group = 'all';
        }
        if ('action' === $context_type) {
            $condition_group = '';
        }

        $item = isset($_POST['item']) ? sanitize_text_field(wp_unslash($_POST['item'])) : '';
        $item_id = isset($_POST['item_id']) ? sanitize_text_field(wp_unslash($_POST['item_id'])) : '';
        $attribute = isset($_POST['attribute']) ? sanitize_text_field(wp_unslash($_POST['attribute'])) : '';
        $evaluator = isset($_POST['evaluator']) ? sanitize_text_field(wp_unslash($_POST['evaluator'])) : '';
        $operation_value = isset($_POST['operation']) ? sanitize_text_field(wp_unslash($_POST['operation'])) : '';
        $input_value_raw = isset($_POST['input']) ? wp_unslash($_POST['input']) : '';
        $input_value = is_string($input_value_raw) ? sanitize_textarea_field($input_value_raw) : '';
        if (
            in_array($attribute, ['visibility', 'label'], true)
            && in_array($operation_value, ['add_class', 'remove_class'], true)
        ) {
            $input_value = $this->normalizeClassListInput($input_value);
        }

        if ('action' === $context_type && 'form' === $item && 'success_message' === $attribute) {
            if (!$this->hasFormSuccessMessageEnabled($form_id)) {
                wp_send_json_error([
                    'msg' => __('Cannot save this action: the form must be set to "Show a success message" in its After Submission settings.', 'griffinforms-form-builder'),
                ]);
            }
        }

        $save_payload = array(
            'item'      => $item,
            'item_id'   => $item_id,
            'attribute' => $attribute,
            'evaluator' => $evaluator,
            'action'    => $operation_value,
            'input'     => $input_value,
        );

        $result = $this->sql->saveRule($form_id, $context_type, $rule_index, $entry_index, $save_payload, $condition_group);

        if (is_array($result) && !empty($result['success'])) {
            if (is_object($this->log)) {
                $this->log->add(
                    'info',
                    'form',
                    $form_id,
                    (int) get_current_user_id(),
                    __('Conditional logic updated.', 'griffinforms-form-builder'),
                    'audit'
                );
            }
            $this->fireFormModifiedHook($form_id, 'save_rule');
            wp_send_json_success([
                'msg'         => isset($result['msg']) ? $result['msg'] : __('Rule saved successfully.', 'griffinforms-form-builder'),
                'form_id'     => $form_id,
                'rule_index'  => isset($result['rule_index']) ? (int) $result['rule_index'] : $rule_index,
                'entry_index' => isset($result['entry_index']) ? (int) $result['entry_index'] : $entry_index,
                'context'     => isset($result['context']) ? $result['context'] : $context_type,
                'condition_group' => isset($result['condition_group']) ? $result['condition_group'] : $condition_group,
            ]);
        }

        $error_msg = is_array($result) && isset($result['msg'])
            ? $result['msg']
            : __('Unable to save rule. Please try again.', 'griffinforms-form-builder');

        if (!empty($error_msg) && is_object($this->log)) {
            $this->log->add(
                'error',
                'form',
                $form_id,
                (int) get_current_user_id(),
                $error_msg,
                'conditional_logic'
            );
        }

        wp_send_json_error([
            'msg' => $error_msg,
        ]);
    }

    // Normalizes a string or array of CSS classes into a unique, space-separated list.
    protected function normalizeClassListInput($value)
    {
        if (is_array($value)) {
            $value = implode(' ', $value);
        }

        $value = trim((string) $value);
        if ('' === $value) {
            return '';
        }

        $value = str_replace(["\r\n", "\r"], "\n", $value);
        $value = str_replace(',', ' ', $value);
        $parts = preg_split('/[\s\n]+/', $value, -1, PREG_SPLIT_NO_EMPTY);
        if (!is_array($parts)) {
            return '';
        }

        $classes = [];

        foreach ($parts as $token) {
            $normalized = ltrim(trim((string) $token), '.');
            if ('' === $normalized) {
                continue;
            }
            $normalized = preg_replace('/[^A-Za-z0-9_-]/', '', $normalized);
            if ('' === $normalized) {
                continue;
            }
            $classes[$normalized] = true;
        }

        return implode(' ', array_keys($classes));
    }

    // Deletes an entry from a conditional logic rule.
    public function deleteRuleEntry()
    {
        $this->response = array('success' => false);
        $this->checkRequest('delete_rule_entry');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $context_raw = isset($_POST['context_type']) ? sanitize_text_field(wp_unslash($_POST['context_type'])) : '';
        $context_type = ('action' === strtolower($context_raw)) ? 'action' : 'condition';
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $entry_index_raw = isset($_POST['entry_index']) ? wp_unslash($_POST['entry_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;
        $entry_index = is_numeric($entry_index_raw) ? (int) $entry_index_raw : null;
        $condition_group_raw = isset($_POST['condition_group']) ? sanitize_text_field(wp_unslash($_POST['condition_group'])) : '';
        $condition_group = strtolower($condition_group_raw) === 'any' ? 'any' : 'all';

        if ('action' === $context_type) {
            $condition_group = '';
        }

        if ($form_id <= 0 || !is_int($rule_index) || $rule_index < 0 || !is_int($entry_index) || $entry_index < 0) {
            wp_send_json_error([
                'msg' => __('Invalid entry reference supplied.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->sql)) {
            $this->setSql();
        }

        if (!is_object($this->sql) || !method_exists($this->sql, 'deleteRuleEntry')) {
            wp_send_json_error([
                'msg' => __('Unable to delete the requested entry.', 'griffinforms-form-builder'),
            ]);
        }

        $result = $this->sql->deleteRuleEntry($form_id, $context_type, $rule_index, $entry_index, $condition_group);

        if (is_array($result) && !empty($result['success'])) {
            if (is_object($this->log)) {
                $this->log->add(
                    'info',
                    'form',
                    $form_id,
                    (int) get_current_user_id(),
                    __('Conditional logic updated.', 'griffinforms-form-builder'),
                    'audit'
                );
            }
            $this->fireFormModifiedHook($form_id, 'delete_rule_entry');
            wp_send_json_success([
                'msg'             => isset($result['msg']) ? $result['msg'] : __('Entry deleted successfully.', 'griffinforms-form-builder'),
                'form_id'         => $form_id,
                'rule_index'      => $rule_index,
                'context'         => $context_type,
                'condition_group' => $context_type === 'condition' ? $condition_group : '',
            ]);
        }

        $error_msg = is_array($result) && isset($result['msg'])
            ? $result['msg']
            : __('Unable to delete the requested entry. Please try again.', 'griffinforms-form-builder');

        if (!empty($error_msg) && is_object($this->log)) {
            $this->log->add(
                'error',
                'form',
                $form_id,
                (int) get_current_user_id(),
                $error_msg,
                'conditional_logic'
            );
        }

        wp_send_json_error([
            'msg' => $error_msg,
        ]);
    }

    // Deletes a conditional logic rule from the form.
    public function deleteRule()
    {
        $this->response = array('success' => false);
        $this->checkRequest('delete_rule');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;

        if ($form_id <= 0 || $rule_index === null || $rule_index < 0) {
            wp_send_json_error([
                'msg' => __('Invalid rule reference supplied.', 'griffinforms-form-builder'),
            ]);
        }

        if (!is_object($this->sql)) {
            $this->setSql();
        }

        $result = $this->sql->deleteRule($form_id, $rule_index);

        if (is_array($result) && !empty($result['success'])) {
            if (is_object($this->log)) {
                $this->log->add(
                    'info',
                    'form',
                    $form_id,
                    (int) get_current_user_id(),
                    __('Conditional logic updated.', 'griffinforms-form-builder'),
                    'audit'
                );
            }
            $this->fireFormModifiedHook($form_id, 'delete_rule');
            wp_send_json_success([
                'msg'        => isset($result['msg']) ? $result['msg'] : __('Rule deleted successfully.', 'griffinforms-form-builder'),
                'form_id'    => $form_id,
                'rule_index' => $rule_index,
            ]);
        }

        $error_msg = is_array($result) && isset($result['msg'])
            ? $result['msg']
            : __('Unable to delete rule. Please try again.', 'griffinforms-form-builder');

        if (!empty($error_msg) && is_object($this->log)) {
            $this->log->add(
                'error',
                'form',
                $form_id,
                (int) get_current_user_id(),
                $error_msg,
                'conditional_logic'
            );
        }

        wp_send_json_error([
            'msg' => $error_msg,
        ]);
    }

    /**
     * Fires a unified form modified hook for conditional logic updates.
     *
     * @param int    $form_id Form ID being modified.
     * @param string $action  Modification action (save_rule, delete_rule_entry, delete_rule).
     */
    protected function fireFormModifiedHook(int $form_id, string $action): void
    {
        if ($form_id <= 0) {
            return;
        }

        /**
         * Fires after a form is modified anywhere in the admin.
         *
         * @param int    $form_id Form ID being modified.
         * @param string $source  Source area triggering the modification.
         * @param array  $meta    Additional context about the modification.
         */
        do_action(
            'griffinforms_form_modified',
            $form_id,
            'conditional_logic',
            array(
                'action' => $action,
            )
        );
    }

    // Loads and renders all conditional logic rules for the form.
    public function fetchRules()
    {
        $this->response = array('success' => false);
        $this->checkRequest('fetch_rules');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        if ($form_id <= 0) {
            wp_send_json_error([
                'msg' => __('Invalid form context supplied.', 'griffinforms-form-builder'),
            ]);
        }

        $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
        $rules = maybe_unserialize($raw_rules);

        $html = '';

        if (is_array($rules) && !empty($rules)) {
            foreach ($rules as $index => $rule_data) {
                if (!is_array($rule_data)) {
                    continue;
                }
                $html .= $this->ruleHtml($rule_data, $index);
            }
        }

        if ('' === trim($html)) {
            $no_rules_message = __('No conditional logic rules configured yet.', 'griffinforms-form-builder');
            $helper_heading = __('Need ideas?', 'griffinforms-form-builder');
            $helper_body = __('Conditional Logic lets you show fields, send alerts, or trigger custom actions only when specific answers are given. For example, display an extra text field when someone selects “Other” as the reason for contacting you, or show a confirmation message that references a dynamic value like {"type":"field","id":"23","attr":"value"} when a VIP checkbox is checked.', 'griffinforms-form-builder');

            $html  = '<div class="griffinforms-logic-rules-empty-wrapper">';
            $html .= '<div class="griffinforms-logic-rules-empty text-muted mb-2">' . esc_html($no_rules_message) . '</div>';
            $html .= '<p class="griffinforms-logic-rules-helper text-muted small mb-4"><strong>' . esc_html($helper_heading) . '</strong> ' . esc_html($helper_body) . '</p>';
            $html .= '</div>';
        }

        wp_send_json_success([
            'msg'  => __('Rules loaded successfully.', 'griffinforms-form-builder'),
            'html' => $html,
        ]);
    }

    // Loads and renders a specific conditional logic rule.
    public function getRule()
    {
        $this->response = array('success' => false);
        $this->checkRequest('get_rule');

        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
        $rule_index_raw = isset($_POST['rule_index']) ? wp_unslash($_POST['rule_index']) : null;
        $rule_index = is_numeric($rule_index_raw) ? (int) $rule_index_raw : null;

        if ($form_id <= 0 || $rule_index === null) {
            wp_send_json_error([
                'msg' => __('Invalid rule reference supplied.', 'griffinforms-form-builder'),
            ]);
        }

        $raw_rules = $this->sql->getItemValue($form_id, 'conditional_logic', 'form');
        $rules = maybe_unserialize($raw_rules);

        if (!is_array($rules) || !isset($rules[$rule_index]) || !is_array($rules[$rule_index])) {
            wp_send_json_error([
                'msg' => __('Requested rule could not be found.', 'griffinforms-form-builder'),
            ]);
        }

        $rule = $rules[$rule_index];
        $conditions_all = isset($rule['all']) && is_array($rule['all']) ? $rule['all'] : array();
        $conditions_any = isset($rule['any']) && is_array($rule['any']) ? $rule['any'] : array();
        $actions = isset($rule['actions']) && is_array($rule['actions']) ? $rule['actions'] : array();

        $response = array(
            'msg'        => __('Rule loaded successfully.', 'griffinforms-form-builder'),
            'rule_index' => $rule_index,
            'rule'       => $rule,
            'label'      => $this->getRuleTitle($rule_index, $rule),
            'html'       => array(
                'rule'       => $this->ruleHtml($rule, $rule_index),
                'conditions' => array(
                    'all' => $this->renderConditionEntries($conditions_all, $rule_index, 'all'),
                    'any' => $this->renderConditionEntries($conditions_any, $rule_index, 'any'),
                ),
                'actions'    => $this->renderActionEntries($actions, $rule_index),
            ),
            'counts'     => array(
                'all'     => count($conditions_all),
                'any'     => count($conditions_any),
                'actions' => count($actions),
            ),
        );

        wp_send_json_success($response);
    }

    // Retrieves the option groups for a select field.
    protected function getSelectFieldOptionGroups($field_id, $fallback_raw = '')
    {
        $field_id = absint($field_id);
        $labels = [];

        if ($field_id > 0) {
            if (!is_object($this->sql)) {
                $this->setSql();
            }

            if (is_object($this->sql) && method_exists($this->sql, 'itemValues')) {
                $values = $this->sql->itemValues($field_id, 'field', ['field_type', 'field_options']);
                if (is_object($values) && isset($values->field_type, $values->field_options)) {
                    if ('selectfield' === strtolower((string) $values->field_type)) {
                        $groups = $this->normalizeSelectFieldOptionGroups($values->field_options);
                        if (is_array($groups)) {
                            $labels = $groups;
                        }
                    }
                }
            }
        }

        if (empty($labels)) {
            $labels = $this->parseOptionGroupList($fallback_raw);
        }

        if (!is_array($labels)) {
            return [];
        }

        $unique = [];
        foreach ($labels as $label) {
            $text = trim((string) $label);
            if ('' === $text) {
                continue;
            }
            if (!in_array($text, $unique, true)) {
                $unique[] = $text;
            }
        }

        return $unique;
    }

    // Parses a raw option group list into an array of unique labels.
    protected function parseOptionGroupList($raw_data)
    {
        if (is_array($raw_data)) {
            $raw = $raw_data;
        } else {
            $raw_string = trim((string) $raw_data);
            if ('' === $raw_string) {
                return [];
            }

            $decoded = json_decode($raw_string, true);
            if (is_array($decoded)) {
                $raw = $decoded;
            } else {
                $raw = preg_split('/[,\n]/', $raw_string);
            }
        }

        if (!is_array($raw)) {
            return [];
        }

        $labels = [];
        foreach ($raw as $entry) {
            if (is_array($entry)) {
                $label = isset($entry['label']) ? $entry['label'] : '';
            } else {
                $label = $entry;
            }

            $label = trim((string) $label);
            if ('' === $label) {
                continue;
            }

            if (!in_array($label, $labels, true)) {
                $labels[] = $label;
            }
        }

        return $labels;
    }

    // Parses selected option groups from a raw value, filtering by available groups.
    protected function parseSelectedOptionGroups($raw_value, array $available)
    {
        $available_map = [];
        foreach ($available as $label) {
            $available_map[$label] = true;
        }

        $selected = [];

        $candidates = [];
        if (is_string($raw_value)) {
            $raw_value = trim($raw_value);
            if ('' !== $raw_value) {
                $decoded = json_decode($raw_value, true);
                if (is_array($decoded)) {
                    $candidates = $decoded;
                } else {
                    $candidates = preg_split('/[,\n]/', $raw_value);
                }
            }
        } elseif (is_array($raw_value)) {
            $candidates = $raw_value;
        }

        foreach ((array) $candidates as $entry) {
            if (is_array($entry)) {
                $label = isset($entry['label']) ? $entry['label'] : '';
            } else {
                $label = $entry;
            }
            $label = trim((string) $label);
            if ('' === $label || !isset($available_map[$label])) {
                continue;
            }
            if (!in_array($label, $selected, true)) {
                $selected[] = $label;
            }
        }

        return $selected;
    }

    // Builds the HTML for the option group input control.
    protected function buildOptionGroupInputHtml(array $available_groups, array $selected_groups, $operation)
    {
        if (empty($available_groups)) {
            return '';
        }

        $operation = strtolower((string) $operation);
        $instruction = ('show' === $operation)
            ? __('Choose the option groups to show when this rule matches.', 'griffinforms-form-builder')
            : __('Choose the option groups to hide when this rule matches.', 'griffinforms-form-builder');

        $note = __('Groups left unchecked are unaffected.', 'griffinforms-form-builder');

        $selected_map = array_fill_keys($selected_groups, true);
        $encoded_value = wp_json_encode(array_keys($selected_map));
        if (false === $encoded_value) {
            $encoded_value = '[]';
        }

        $html  = '<div class="griffinforms-option-groups-control text-start mt-4 border bg-light rounded p-2">';
        $html .= '<div class="">' . esc_html($instruction) . '</div>';
        $html .= '<div class="small text-muted mb-3">' . esc_html($note) . '</div>';

        foreach ($available_groups as $index => $group_label) {
            $group_label = (string) $group_label;
            $slug = 'griffinforms-option-group-' . substr(md5($group_label . '|' . $index), 0, 10);
            $is_checked = isset($selected_map[$group_label]);

            $html .= '<div class="options-group-item mb-2">';
            $html .= '<label>';
            $html .= '<input type="checkbox" class="griffinforms-option-group-toggle"';
            $html .= ' id="' . esc_attr($slug) . '"';
            $html .= ' data-gf-group="' . esc_attr($group_label) . '"';
            if ($is_checked) {
                $html .= ' checked="checked"';
            }
            $html .= ' /> ';
            $html .= '<span class="label-text">' . esc_html($group_label) . '</span>';
            $html .= '</label>';
            $html .= '</div>';
        }

        $html .= '<input type="hidden" name="input" id="griffinforms-action-value" class="griffinforms-action-input" data-gf-input-type="option_groups" value="' . esc_attr($encoded_value) . '" />';
        $html .= '</div>';

        return $html;
    }

    // Returns [{id, label}, ...] from a JSON string or direct DB lookup for a Likert field.
    protected function parseLikertRowList($raw_data, $field_id = 0)
    {
        $field_id = absint($field_id);
        $rows = [];

        if ($field_id > 0) {
            if (!is_object($this->sql)) {
                $this->setSql();
            }

            if (is_object($this->sql) && method_exists($this->sql, 'itemValues')) {
                $values = $this->sql->itemValues($field_id, 'field', ['field_type', 'field_settings']);
                if (is_object($values) && isset($values->field_type, $values->field_settings)) {
                    if ('inputlikert' === strtolower((string) $values->field_type)) {
                        $rows = $this->normalizeLikertRows($values->field_settings);
                    }
                }
            }
        }

        if (empty($rows) && '' !== trim((string) $raw_data)) {
            $decoded = json_decode(trim((string) $raw_data), true);
            if (is_array($decoded)) {
                foreach ($decoded as $item) {
                    if (!is_array($item)) {
                        continue;
                    }
                    $id    = isset($item['id'])    ? trim((string) $item['id'])    : '';
                    $label = isset($item['label']) ? trim((string) $item['label']) : '';
                    if ('' !== $id) {
                        $rows[] = ['id' => $id, 'label' => $label];
                    }
                }
            }
        }

        return $rows;
    }

    // Parses a saved input value (JSON array of row IDs) into a list of selected IDs.
    protected function parseSelectedLikertRows($raw_value, array $available_rows)
    {
        $available_ids = array_column($available_rows, 'id');

        if ('' === trim((string) $raw_value)) {
            return [];
        }

        $decoded = json_decode(trim((string) $raw_value), true);
        if (!is_array($decoded)) {
            return [];
        }

        $selected = [];
        foreach ($decoded as $row_id) {
            $row_id = trim((string) $row_id);
            if ('' !== $row_id && in_array($row_id, $available_ids, true) && !in_array($row_id, $selected, true)) {
                $selected[] = $row_id;
            }
        }

        return $selected;
    }

    // Builds the HTML for the Likert row checkbox list input control.
    protected function buildLikertRowInputHtml(array $available_rows, array $selected_row_ids, $operation)
    {
        if (empty($available_rows)) {
            return '';
        }

        $operation = strtolower((string) $operation);
        if ('show_rows' === $operation) {
            $instruction = __('Choose the rows to show when this rule matches. All other rows will be hidden.', 'griffinforms-form-builder');
        } else {
            $instruction = __('Choose the rows to hide when this rule matches.', 'griffinforms-form-builder');
        }

        $selected_map = array_fill_keys($selected_row_ids, true);
        $encoded_value = wp_json_encode(array_keys($selected_map));
        if (false === $encoded_value) {
            $encoded_value = '[]';
        }

        $html  = '<div class="griffinforms-likert-rows-control text-start mt-4 mx-2 border bg-light rounded p-2">';
        $html .= '<div class="">' . esc_html($instruction) . '</div>';
        $html .= '<div class="small text-muted mb-3">' . esc_html__('Select one or more rows.', 'griffinforms-form-builder') . '</div>';

        foreach ($available_rows as $row) {
            $row_id    = (string) ($row['id'] ?? '');
            $row_label = (string) ($row['label'] ?? $row_id);
            $slug      = 'griffinforms-likert-row-' . esc_attr($row_id);
            $is_checked = isset($selected_map[$row_id]);

            $html .= '<div class="options-group-item mb-2">';
            $html .= '<label>';
            $html .= '<input type="checkbox" class="griffinforms-likert-row-toggle"';
            $html .= ' id="' . esc_attr($slug) . '"';
            $html .= ' data-gf-row-id="' . esc_attr($row_id) . '"';
            if ($is_checked) {
                $html .= ' checked="checked"';
            }
            $html .= ' /> ';
            $html .= '<span class="label-text">' . esc_html($row_label) . '</span>';
            $html .= '</label>';
            $html .= '</div>';
        }

        $html .= '<input type="hidden" name="input" id="griffinforms-action-value" class="griffinforms-action-input" data-gf-input-type="likert_rows" value="' . esc_attr($encoded_value) . '" />';
        $html .= '</div>';

        return $html;
    }

    // Checks if the success message is enabled for the given form.
    private function hasFormSuccessMessageEnabled($form_id)
    {
        $form_id = absint($form_id);
        if ($form_id <= 0) {
            return false;
        }

        static $cache = [];
        if (array_key_exists($form_id, $cache)) {
            return $cache[$form_id];
        }

        if (!is_object($this->sql)) {
            $this->setSql();
        }

        $enabled = false;

        if (is_object($this->sql) && method_exists($this->sql, 'itemExists') && $this->sql->itemExists('form', $form_id)) {
            $form = new \GriffinForms\Items\Form();
            $form->loadItem($form_id);
            $user_action = $form->getProp('user_action');
            $enabled = is_array($user_action)
                && isset($user_action['type'])
                && 'show_success_message' === $user_action['type'];
        }

        $cache[$form_id] = $enabled;

        return $enabled;
    }
}
