<?php

namespace GriffinForms\Includes\Ai;

use GriffinForms\Includes\Ai\PromptRegistry\JsonExtractor;
use GriffinForms\Includes\Extensions\ExtensionRegistry;
use GriffinForms\Includes\Packages\PackageAdmission;

class IntentClassifier
{
    protected AiBroker $broker;
    protected AssistFailureFramework $failure_framework;
    protected \GriffinForms\Log $log;
    protected ExtensionRegistry $extension_registry;
    protected PackageAdmission $package_admission;
    protected JsonExtractor $extractor;

    /**
     * Maps every known suggestion_id directly to a handler key.
     * Rule 2: checked before any pattern matching — exact string match only.
     */
    private const SUGGESTION_ID_HANDLER_MAP = [
        'form.improve_description'              => 'generate_patch_plan',
        'row.improve_heading'                   => 'generate_patch_plan',
        'row.polish_description'                => 'generate_patch_plan',
        'field.polish_description'              => 'generate_field_label',
        'selectfield.polish_description'        => 'generate_field_label',
        'inputcheckbox.polish_description'      => 'generate_field_label',
        'inputradio.polish_description'         => 'generate_field_label',
        'inputtime.polish_description'          => 'generate_field_label',
        'inputpassword.polish_description'      => 'generate_field_label',
        'termsconditions.polish_description'    => 'generate_field_label',
        'payment.polish_description'            => 'generate_field_label',
        'signature.polish_description'          => 'generate_field_label',
        'inputrating.polish_description'        => 'generate_field_label',
        'inputrichtext.polish_description'      => 'generate_field_label',
        'textarea.polish_description'           => 'generate_field_label',
        'inputemail.polish_description'         => 'generate_field_label',
        'inputphone.polish_description'         => 'generate_field_label',
        'inputaddress.polish_description'       => 'generate_field_label',
        'inputnumber.polish_description'        => 'generate_field_label',
        'inputdate.polish_description'          => 'generate_field_label',
        'fileupload.polish_description'         => 'generate_field_label',
        'inputaddress.improve_sublabels'        => 'generate_patch_plan',
        'inputnumber.add_range_hint'            => 'generate_patch_plan',
        'selectfield.refine_options'            => 'generate_selectfield_options',
        'selectfield.add_option_groups'         => 'generate_selectfield_options',
        'termsconditions.improve_checkbox_label' => 'generate_patch_plan',
        'termsconditions.improve_anchor_text'   => 'generate_patch_plan',
        'payment.improve_product_descriptions'  => 'generate_patch_plan',
        'fileupload.describe_upload_requirements' => 'generate_patch_plan',
        'textarea.add_char_limit_hint'          => 'generate_patch_plan',
        'inputdate.add_date_window_hint'        => 'generate_patch_plan',
        'page.polish_copy'                      => 'generate_patch_plan',
        'form.apply_random_theme'               => 'generate_form_theme',
        'form.translate_to_site_language'       => 'generate_bulk_text_mutation',
        'form.update_submit_label'              => 'generate_patch_plan',
        'form.rewrite_name'                     => 'generate_patch_plan',
        'form.polish_copy'                      => 'generate_bulk_text_mutation',
        // Issue 4 — new operation types
        'form.rewrite_step_labels'              => 'generate_step_labels',
        'inputlikert.polish_row_labels'         => 'generate_field_options',
        'inputlikert.polish_column_labels'      => 'generate_field_options',
        'field.manage_options'                  => 'generate_field_options',
        'field.manage_payment_products'         => 'generate_payment_products',
        'field.generate_image_options'          => 'generate_field_image_options',
        'field.modify_rating'                   => 'generate_rating_field',
        'form.manage_notifications'             => 'generate_notification_action',
        'notification.edit_message'             => 'generate_message_mutation',
        'page.rearrange_layout'                 => 'generate_page_layout_plan',
    ];

    public function __construct(
        AiBroker $broker,
        AssistFailureFramework $failure_framework,
        \GriffinForms\Log $log,
        ExtensionRegistry $extension_registry,
        PackageAdmission $package_admission
    ) {
        $this->broker             = $broker;
        $this->failure_framework  = $failure_framework;
        $this->log                = $log;
        $this->extension_registry = $extension_registry;
        $this->package_admission  = $package_admission;
        $this->extractor          = new JsonExtractor();
    }

    public function classify(string $prompt, array $selected_context, array $request_context): ClassificationResult
    {
        // Rule 1 — advisory family → advice
        if (($request_context['response_family'] ?? 'mutation') === 'text') {
            return ClassificationResult::advice();
        }

        // Rule 2 — suggestion_id short-circuit (before any pattern matching)
        $suggestion_context = is_array($request_context['suggestion_context'] ?? null)
            ? (array) $request_context['suggestion_context']
            : [];
        $suggestion_id = $this->normalizeSuggestionId((string) ($suggestion_context['suggestion_id'] ?? ''));
        if ($suggestion_id !== '' && array_key_exists($suggestion_id, self::SUGGESTION_ID_HANDLER_MAP)) {
            return ClassificationResult::suggestionShortCircuit(
                $suggestion_id,
                self::SUGGESTION_ID_HANDLER_MAP[$suggestion_id],
                $suggestion_context
            );
        }

        // Rule 3a — operations that are definitively about the selected element or form-scope;
        // skip the scope check so these are never misclassified as scope mismatches.
        $bypasses_scope_check = $this->isStepLabelRewriteIntent($prompt, $selected_context)
            || $this->isNotificationManagementIntent($prompt, $selected_context)
            || $this->isMessageMutationIntent($prompt, $selected_context)
            || $this->isRatingFieldMutationIntent($prompt, $selected_context)
            || $this->isPaymentProductMutationIntent($prompt, $selected_context)
            || $this->isFieldOptionsManagementIntent($prompt, $selected_context)
            || $this->isImageOptionGenerationIntent($prompt, $selected_context)
            || $this->isStructuralAddIntent($prompt, $selected_context)
            || $this->isRowColumnSplitIntent($prompt, $selected_context)
            || $this->isPagePropertyMutationIntent($prompt, $selected_context)
            || $this->isBulkRowTextMutationIntent($prompt, $selected_context)
            || $this->isLayoutRearrangeIntent($prompt, $selected_context)
            || $this->isSelectedFormCopyIntent($prompt, $selected_context, $request_context);

        // Rule 3 — selected element scope check
        if (!$bypasses_scope_check && !empty($selected_context['item_type']) && !empty($selected_context['item_id'])) {
            $scope_match = $this->runScopeCheckModelCall($prompt, $selected_context, $request_context);
            if ($scope_match === false) {
                return ClassificationResult::scopeMismatch($selected_context, $this->buildScopeMismatchMessage($prompt, $selected_context));
            }
        }

        // Rule 4 — two-layer free-text classification
        $handler_key = $this->runPatternLayer($prompt, $selected_context, $request_context);
        if ($handler_key !== null) {
            $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
            $item_id   = absint($selected_context['item_id'] ?? 0);
            // generation-type prompts (no existing element selected)
            if ($handler_key === 'generate_foundation') {
                return ClassificationResult::generation($handler_key);
            }
            return ClassificationResult::single($handler_key, $item_type, $item_id);
        }

        return $this->runModelLayer($prompt, $selected_context, $request_context);
    }

    // -------------------------------------------------------------------------
    // Layer 1 — pattern-based classification
    // -------------------------------------------------------------------------

    protected function runPatternLayer(string $prompt, array $selected_context, array $request_context): ?string
    {
        $normalized = strtolower(trim($prompt));
        $suggestion_context = is_array($request_context['suggestion_context'] ?? null)
            ? (array) $request_context['suggestion_context']
            : [];

        if ($this->isSelectedFormThemeIntent($prompt, $selected_context, $request_context)) {
            return 'generate_form_theme';
        }

        if ($this->isSelectedFieldRemoveRequest($prompt, $selected_context)) {
            return '__unsupported_remove__';
        }

        if ($this->isSelectedFieldMoveRequest($prompt, $selected_context)) {
            return '__unsupported_move__';
        }

        $option_col = $this->extractRequestedOptionColumnCount($prompt);
        if ($option_col === -1 && $this->isSelectedChoiceFieldColumnSplitIntent($prompt, $selected_context)) {
            return '__unsupported_col_count__';
        }

        $suggestion_id = $this->normalizeSuggestionId((string) ($suggestion_context['suggestion_id'] ?? ''));
        $selectfield_options_prompt = !in_array($suggestion_id, ['selectfield.refine_options', 'selectfield.add_option_groups'], true)
            && $this->isSelectedSelectFieldOptionsIntent($prompt, $selected_context);

        if ($this->isSelectedFormCopyIntent($prompt, $selected_context, $request_context)) {
            return 'generate_bulk_text_mutation';
        }

        if ($this->isSelectedFieldLabelIntent($prompt, $selected_context)) {
            return 'generate_field_label';
        }

        if ($this->isSelectedFieldPlaceholderIntent($prompt, $selected_context)) {
            return 'generate_placeholder';
        }

        if ($selectfield_options_prompt) {
            return 'generate_selectfield_options';
        }

        $row_bulk_intent = preg_match('/\b(all|every|each|bulk)\b.*\b(row|rows|section|sections)\b.*\b(heading|headings|title|titles|description|descriptions|copy|text)\b/', $normalized)
            && (
                in_array(sanitize_key((string) ($selected_context['item_type'] ?? '')), ['pagerow', 'row', 'rowcolumn'], true)
                || in_array(sanitize_key((string) ($selected_context['parent_type'] ?? '')), ['pagerow', 'row'], true)
                || !empty($selected_context['item_id'])
                || !empty($selected_context['parent_id'])
            );

        $page_heading_patch_intent = preg_match('/\b(improve|rewrite|update|change|edit|refine|polish)\b/', $normalized)
            && preg_match('/\b(page headings?|page heading|page titles?|page title)\b/', $normalized)
            && (
                in_array(sanitize_key((string) ($selected_context['item_type'] ?? '')), ['formpage'], true)
                || in_array(sanitize_key((string) ($selected_context['parent_type'] ?? '')), ['formpage'], true)
                || !empty($selected_context['item_id'])
            );

        $field_growth_prompt = $this->isFieldGrowthPrompt($normalized)
            && !in_array(sanitize_key((string) ($selected_context['item_type'] ?? '')), ['field'], true);

        $likert_row_col_prompt = $this->isLikertRowColumnIntent($normalized, $selected_context);
        $payment_field_prompt  = $this->isSelectedPaymentFieldIntent($prompt, $selected_context);
        $choice_column_split   = $this->isSelectedChoiceFieldColumnSplitIntent($prompt, $selected_context);

        $payment_product_intent = $this->isPaymentProductMutationIntent($prompt, $selected_context);
        if ($payment_field_prompt && !$payment_product_intent) {
            return 'generate_patch_plan';
        }
        if ($field_growth_prompt) {
            return 'generate_patch_plan';
        }
        if ($page_heading_patch_intent) {
            return 'generate_patch_plan';
        }
        if ($row_bulk_intent) {
            return 'generate_patch_plan';
        }
        if ($likert_row_col_prompt) {
            return 'generate_field_options';
        }
        if ($choice_column_split) {
            return 'generate_bulk_text_mutation';
        }
        if (($request_context['semantic_intent'] ?? '') === 'builder_bulk_text'
            && !$this->isRatingFieldMutationIntent($prompt, $selected_context)
        ) {
            return 'generate_bulk_text_mutation';
        }

        // Issue 4 — new operation type routing (before catch-all element edit)
        if ($this->isLayoutRearrangeIntent($prompt, $selected_context)) {
            return 'generate_page_layout_plan';
        }
        if ($this->isStepLabelRewriteIntent($prompt, $selected_context)) {
            return 'generate_step_labels';
        }
        if ($this->isNotificationManagementIntent($prompt, $selected_context)) {
            return 'generate_notification_action';
        }
        if ($this->isMessageMutationIntent($prompt, $selected_context)) {
            return 'generate_message_mutation';
        }
        if ($this->isRatingFieldMutationIntent($prompt, $selected_context)) {
            return 'generate_rating_field';
        }
        if ($this->isPaymentProductMutationIntent($prompt, $selected_context)) {
            return 'generate_payment_products';
        }
        if ($this->isImageOptionGenerationIntent($prompt, $selected_context)) {
            return 'generate_field_image_options';
        }
        if ($this->isFieldOptionsManagementIntent($prompt, $selected_context)) {
            return 'generate_field_options';
        }

        if ($this->isSelectedElementEditIntent($prompt, $selected_context)) {
            return 'generate_patch_plan';
        }

        return null;
    }

    // -------------------------------------------------------------------------
    // Layer 2 — model-level classification (3.1.9.0: catalog-fed structured classifier)
    // -------------------------------------------------------------------------

    protected function runModelLayer(string $prompt, array $selected_context, array $request_context): ClassificationResult
    {
        $provider = (string) ($request_context['provider'] ?? '');
        $config   = (array) ($request_context['classifier_config'] ?? ['request_context' => $request_context]);

        $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $item_id   = absint($selected_context['item_id'] ?? 0);

        $catalog = $this->getOperationCatalog();

        // Build catalog description lines for the system prompt.
        $catalog_lines = [];
        foreach ($catalog as $entry) {
            $handler_key_str = sanitize_key((string) ($entry['handler_key'] ?? ''));
            $description_str = sanitize_text_field((string) ($entry['description'] ?? ''));
            if ($handler_key_str !== '') {
                $catalog_lines[] = sprintf('- %s: %s', $handler_key_str, $description_str);
            }
        }

        $system_prompt = implode("\n", [
            'You are a GriffinForms intent classifier. Given the user prompt, return a JSON object with a "matches" array.',
            'Each match has: handler_key (string), target_scope (string), target_attribute (string), batch (boolean), confidence ("high"|"medium"|"low"), user_label (string).',
            'Order matches by confidence descending. Return at most 3 matches.',
            '"batch" is true only when the operation should apply to multiple targets at once.',
            '"user_label" is a short human-readable description of the intent (max 8 words).',
            'Available operations:',
            implode("\n", $catalog_lines),
            'Return JSON only. Example: {"matches":[{"handler_key":"generate_patch_plan","target_scope":"form","target_attribute":"description","batch":false,"confidence":"high","user_label":"Improve the form description"}]}',
            'If nothing matches, return {"matches":[]}.',
        ]);

        $selection_text = '';
        if (!empty($selected_context['item_type'])) {
            $selection_text = "\nSelected element: " . wp_json_encode($selected_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        $matches = [];
        try {
            $response = $this->broker->requestStrict(
                $provider,
                'Classify this GriffinForms request. Return JSON only.' . "\nRequest: " . $prompt . $selection_text,
                ['system' => $system_prompt],
                array_merge($config, ['timeout' => 8])
            );

            [$decoded, $error] = $this->extractor->extract((string) ($response['content'] ?? ''));
            if ($error !== null || !is_array($decoded) || !isset($decoded['matches']) || !is_array($decoded['matches'])) {
                $this->log->add('warning', 'ai_butler_request', 0, (int) get_current_user_id(), 'Structured classifier parse failure — falling back to unsupported', 'ai_butler_request');
                return ClassificationResult::single('unsupported', $item_type, $item_id);
            }

            $matches = $decoded['matches'];
        } catch (\Throwable $e) {
            $this->log->add('warning', 'ai_butler_request', 0, (int) get_current_user_id(), 'Structured classifier call failed: ' . sanitize_text_field($e->getMessage()) . ' — falling back to unsupported', 'ai_butler_request');
            return ClassificationResult::single('unsupported', $item_type, $item_id);
        }

        if (empty($matches)) {
            return ClassificationResult::single('unsupported', $item_type, $item_id);
        }

        // Routing rules per spec.
        $high_matches   = array_values(array_filter($matches, static fn($m) => ($m['confidence'] ?? '') === 'high'));
        $medium_matches = array_values(array_filter($matches, static fn($m) => ($m['confidence'] ?? '') === 'medium'));
        $low_matches    = array_values(array_filter($matches, static fn($m) => ($m['confidence'] ?? '') === 'low'));

        // All low → potential_paths
        if (empty($high_matches) && empty($medium_matches) && !empty($low_matches)) {
            $user_id  = (int) get_current_user_id();
            $form_id  = absint($request_context['form_id'] ?? 0);
            $paths    = [];
            foreach (array_slice($low_matches, 0, 3) as $match) {
                $token          = bin2hex(random_bytes(4));
                $transient_key  = "gf_assist_paths_{$user_id}_{$form_id}_{$token}";
                set_transient($transient_key, [
                    'handler_key'      => sanitize_key((string) ($match['handler_key'] ?? '')),
                    'target_scope'     => sanitize_key((string) ($match['target_scope'] ?? '')),
                    'target_attribute' => sanitize_key((string) ($match['target_attribute'] ?? '')),
                    'batch'            => !empty($match['batch']),
                    'confidence'       => 'low',
                ], 300);
                $paths[] = [
                    'token'      => $token,
                    'user_label' => sanitize_text_field((string) ($match['user_label'] ?? $match['handler_key'] ?? '')),
                ];
            }
            return ClassificationResult::potentialPaths($paths);
        }

        // Use first high-confidence match; fall back to first medium match.
        $top_match  = $high_matches[0] ?? $medium_matches[0];
        $handler_key = sanitize_key((string) ($top_match['handler_key'] ?? ''));

        if ($handler_key === '' || $handler_key === 'unsupported') {
            return ClassificationResult::single('unsupported', $item_type, $item_id);
        }

        if ($handler_key === 'generate_foundation') {
            return ClassificationResult::generation($handler_key);
        }

        return ClassificationResult::single($handler_key, $item_type, $item_id);
    }

    // -------------------------------------------------------------------------
    // Scope-check model call (Rule 3)
    // -------------------------------------------------------------------------

    protected function runScopeCheckModelCall(string $prompt, array $selected_context, array $request_context): ?bool
    {
        $item_type  = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $item_id    = absint($selected_context['item_id'] ?? 0);
        $item_label = sanitize_text_field((string) ($selected_context['item_label'] ?? ''));

        $friendly_type_map = [
            'form'       => 'form',
            'formpage'   => 'page (also called a "step" or "page")',
            'pagerow'    => 'row (also called a "section")',
            'rowcolumn'  => 'column',
            'field'      => 'field',
        ];
        $friendly_type = $friendly_type_map[$item_type] ?? $item_type;

        $system_prompt = sprintf(
            "You are a scope classifier. The user has selected a %s element (ID: %d, label: '%s') in a form builder. Determine whether the user's prompt intends to modify this specific selected element. Return JSON only: {\"match\": true} or {\"match\": false}",
            $friendly_type,
            $item_id,
            $item_label
        );

        $provider = (string) ($request_context['provider'] ?? '');
        $config   = (array) ($request_context['classifier_config'] ?? []);

        try {
            $response = $this->broker->requestStrict(
                $provider,
                $prompt,
                ['system' => $system_prompt],
                array_merge($config, ['timeout' => 8])
            );

            [$decoded, $error] = $this->extractor->extract((string) ($response['content'] ?? ''));
            if ($error !== null || !is_array($decoded)) {
                $this->log->add('warning', 'ai_butler_request', 0, (int) get_current_user_id(), 'Scope check parse failure — assuming match', 'ai_butler_request');
                return null;
            }

            return (bool) ($decoded['match'] ?? true);
        } catch (\Throwable $e) {
            $this->log->add('warning', 'ai_butler_request', 0, (int) get_current_user_id(), 'Scope check model call failed: ' . sanitize_text_field($e->getMessage()) . ' — assuming match', 'ai_butler_request');
            return null;
        }
    }

    // -------------------------------------------------------------------------
    // Model fallback classifier (verbatim from processbutlerprompt.php — 3.1.8.0)
    // In 3.1.9.0 this is replaced with a catalog-fed structured classifier.
    // -------------------------------------------------------------------------

    protected function classifyPromptKey(string $provider, array $config, string $prompt, array $selected_context = []): string
    {
        $normalized_prompt = strtolower(trim($prompt));

        // Guard: step label rewrites must not fall into the bulk-text catch-all below.
        if ($this->isStepLabelRewriteIntent($prompt, $selected_context)) {
            return 'generate_step_labels';
        }

        if (
            (preg_match('/\b(page headings?|page heading|page descriptions?|page description|page titles?|page title|headings?|heading|descriptions?|description|titles?|title|field labels?|placeholders?|copy|form|text)\b/i', $normalized_prompt)
            && preg_match('/\b(change|translate|rewrite|align|normalize|update|make|convert|turn|polish|improve|edit|refine)\b/i', $normalized_prompt))
            || preg_match('/\b(translate|convert|turn|make)\b.*\b(to|into|in)\b.*\b(french|spanish|german|deutsch|italian|portuguese|dutch|hindi|punjabi|panjabi|french|francais|fr|es|de|it|pt|nl|hi|pa)\b/i', $normalized_prompt)
        ) {
            return 'generate_bulk_text_mutation';
        }

        if ($this->isSelectedChoiceFieldOptionTextIntent($normalized_prompt, $selected_context)) {
            return 'generate_bulk_text_mutation';
        }

        $selection_text = '';
        if (!empty($selected_context['item_type'])) {
            $selection_text = "\nSelected element context: " . wp_json_encode($selected_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        $request_context = (array) ($config['request_context'] ?? []);
        $routing_text = "\nRouting context: " . wp_json_encode([
            'surface'         => (string) ($request_context['surface'] ?? ''),
            'origin'          => (string) ($request_context['origin'] ?? ''),
            'response_mode'   => (string) ($request_context['response_mode'] ?? ''),
            'response_family' => (string) ($request_context['response_family'] ?? ''),
            'semantic_intent' => (string) ($request_context['semantic_intent'] ?? ''),
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $site_text = '';
        if (!empty($request_context['site_context']) && is_array($request_context['site_context'])) {
            $site_text = "\nSite context: " . wp_json_encode($request_context['site_context'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        $response = $this->broker->requestStrict(
            $provider,
            "Classify this GriffinForms request. Return JSON only.\nRequest: " . $prompt . $selection_text . $routing_text . $site_text,
            ['system' => $this->getClassifierSystemPrompt()],
            array_merge($config, ['timeout' => 5])
        );

        [$decoded, $extract_error] = $this->extractor->extract((string) ($response['content'] ?? ''));
        if ($extract_error !== null || !is_array($decoded)) {
            return 'unsupported';
        }

        return sanitize_key((string) ($decoded['prompt_key'] ?? 'unsupported'));
    }

    protected function getClassifierSystemPrompt(): string
    {
        return implode("\n", [
            'You classify GriffinForms builder requests.',
            'Return JSON only in this exact shape: {"prompt_key":"generate_patch_plan"}, {"prompt_key":"generate_foundation"}, or {"prompt_key":"unsupported"}',
            'Only classify mutation-capable builder requests. Advisory or text-only requests are routed earlier and should not reach this classifier.',
            'Choose "generate_patch_plan" for surgical incremental changes (add field, insert page, move row, remove element, rename label/page/section). This is the preferred mode for most specific user requests.',
            'Choose "generate_foundation" only for broad structural generation (e.g., "Create a contact form", "I need a multi-page survey") where the user expects a broad new structure or a complete rebuild.',
            'If a request is an incremental add or edit, always pick "generate_patch_plan".',
            'Choose "unsupported" for styling, integrations, payments setup, conditional logic, or non-structural requests.',
        ]);
    }

    // -------------------------------------------------------------------------
    // Operation catalog — extensibility contract for 3.1.9.0 model classifier.
    // Defined here so catalog entries can be added per-handler as Issue 4 ships.
    // runModelLayer() in 3.1.9.0 will use this instead of getClassifierSystemPrompt().
    // -------------------------------------------------------------------------

    protected function getOperationCatalog(): array
    {
        return [
            [
                'handler_key' => 'generate_foundation',
                'label'       => 'Generate a new form',
                'description' => 'Create a complete new form structure from scratch based on a user description. Use this when the user wants an entirely new form, not changes to an existing one.',
                'examples'    => [
                    'Create a contact form',
                    'Build me a multi-step survey',
                    'I need a job application form',
                    'Make a form for booking appointments',
                    'Generate a feedback form for my product',
                    'Start a new registration form',
                ],
                'scope' => 'form',
                'batch' => false,
            ],
            [
                'handler_key' => 'generate_patch_plan',
                'label'       => 'Edit visible form copy',
                'description' => 'Rewrite or improve user-visible text on the form — headings, descriptions, submit button label, page labels, row headings. Use for targeted single-element text edits.',
                'examples'    => [
                    'Polish the form description',
                    'Make the submit button say Send Message',
                    'Improve the heading on this page',
                    'Rewrite the description on this row',
                    'The intro text feels too formal, loosen it up',
                    'Change the next button label to Continue',
                    'This section heading is confusing',
                ],
                'scope' => 'form|formpage|pagerow',
                'batch' => false,
            ],
            [
                'handler_key' => 'generate_field_label',
                'label'       => 'Improve a field label',
                'description' => 'Rewrite or improve the label of the currently selected field.',
                'examples'    => [
                    'Improve this field label',
                    'Make the label clearer',
                    'Rewrite the label for this field',
                    'This label is too vague',
                    'Can you make the question wording better',
                    'The label feels clunky',
                ],
                'scope' => 'field',
                'batch' => false,
            ],
            [
                'handler_key' => 'generate_placeholder',
                'label'       => 'Generate a field placeholder',
                'description' => 'Write placeholder hint text shown inside an input field before the user types.',
                'examples'    => [
                    'Add a placeholder to this field',
                    'Write placeholder text',
                    'Set the hint text',
                    'Add an example value inside the field',
                    'Give this input some ghost text',
                    'What should the placeholder say for this field',
                ],
                'scope' => 'field',
                'batch' => false,
            ],
            [
                'handler_key' => 'generate_selectfield_options',
                'label'       => 'Generate options for a choice field',
                'description' => 'Generate a fresh set of options for a dropdown, radio, or checkbox field based on the field context.',
                'examples'    => [
                    'Generate options for this dropdown',
                    'Suggest choices for this radio field',
                    'Add options to this select',
                    'What options make sense here',
                    'Fill in some choices for this field',
                    'This dropdown needs options',
                ],
                'scope' => 'field',
                'batch' => false,
            ],
            [
                'handler_key' => 'generate_bulk_text_mutation',
                'label'       => 'Bulk text rewrite or translation',
                'description' => 'Rewrite, translate, or apply a consistent tone or style change across all text in the form at once.',
                'examples'    => [
                    'Translate this form to French',
                    'Make all field labels friendlier',
                    'Polish all the copy on this form',
                    'Rewrite everything in a more professional tone',
                    'Convert this form to Spanish',
                    'Make the whole form sound warmer',
                    'Clean up the wording across the entire form',
                ],
                'scope' => 'form',
                'batch' => true,
            ],
            [
                'handler_key' => 'generate_form_theme',
                'label'       => 'Apply or change a form theme',
                'description' => 'Apply a visual style or color theme to the form.',
                'examples'    => [
                    'Apply a dark theme',
                    'Change the form style',
                    'Pick a random theme',
                    'Give this form a professional look',
                    'Switch to a minimal design',
                    'Make it look more modern',
                ],
                'scope' => 'form',
                'batch' => false,
            ],
            // New handlers added in Issue 4 are appended here.
            // Required fields: handler_key, label, description, examples (min 3, target 5+), scope, batch.
            // Examples must cover: formal phrasing, casual phrasing, at least one implicit/indirect phrasing.
        ];
    }

    // -------------------------------------------------------------------------
    // Intent detection methods (moved verbatim from BuilderAssistRouter)
    // -------------------------------------------------------------------------

    protected function isSelectedFormCopyIntent(string $prompt, array $selected_context = [], array $request_context = []): bool
    {
        $normalized = strtolower(trim($prompt));
        $suggestion_context = is_array($request_context['suggestion_context'] ?? null) ? (array) $request_context['suggestion_context'] : [];
        $suggestion_id = $this->normalizeSuggestionId((string) ($suggestion_context['suggestion_id'] ?? ''));
        if ($suggestion_id === 'form.improve_description') {
            return true;
        }
        if (!preg_match('/\b(polish|improve|rewrite|update|refine|edit|adjust|tweak|enhance)\b/', $normalized)) {
            return false;
        }

        if (!preg_match('/\b(form copy|form text|form wording|form content|form description|form heading|submit button|submit label|submit text|copy)\b/', $normalized)) {
            return false;
        }

        return true;
    }

    protected function isSelectedFormThemeIntent(string $prompt, array $selected_context = [], array $request_context = []): bool
    {
        $suggestion_context = is_array($request_context['suggestion_context'] ?? null) ? (array) $request_context['suggestion_context'] : [];
        $suggestion_id = $this->normalizeSuggestionId((string) ($suggestion_context['suggestion_id'] ?? ''));
        if ($suggestion_id === 'form.apply_random_theme') {
            return true;
        }

        $normalized = strtolower(trim($prompt));
        if (!preg_match('/\b(apply|change|switch|pick|choose|select|random)\b.*\b(theme|style|look|design|appearance)\b|\b(theme|style|look|design|appearance)\b.*\b(apply|change|switch|pick|choose|select|random)\b/', $normalized)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        return in_array($selected_type, ['form', 'selected', ''], true);
    }

    protected function isSelectedFieldPlaceholderIntent(string $prompt, array $selected_context): bool
    {
        $user_line = explode("\n", $prompt, 2)[0];
        $normalized = strtolower(trim($user_line));
        if (!preg_match('/\bplaceholder\b/', $normalized)) {
            return false;
        }

        return sanitize_key((string) ($selected_context['item_type'] ?? '')) === 'field'
            && !empty($selected_context['item_id']);
    }

    protected function isSelectedSelectFieldOptionsIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));
        if (!preg_match('/\b(option|options|dropdown|select|choice|choices|refine options|improve options|polish options|rewrite options|option groups?|grouped options?)\b/', $normalized)) {
            return false;
        }

        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if ($field_type === 'selectfield') {
            return !empty($selected_context['item_id']);
        }

        $field_id = absint($selected_context['item_id'] ?? 0);
        if ($field_id > 0 && $field_type === '') {
            $format_sql = \GriffinForms\Admin\Sql\Format::getInstance();
            $field_row = $format_sql->itemValues($field_id, 'field', ['field_type']);
            if (is_object($field_row) && sanitize_key((string) ($field_row->field_type ?? '')) === 'selectfield') {
                return true;
            }
        }

        return false;
    }

    protected function isSelectedElementEditIntent(string $prompt, array $selected_context): bool
    {
        $selected_type = $this->resolveSelectedSemanticType($selected_context);
        if ($selected_type === '') {
            return false;
        }

        $normalized = strtolower(trim($prompt));
        $row_like_context = in_array(sanitize_key((string) ($selected_context['item_type'] ?? '')), ['pagerow', 'row', 'rowcolumn'], true)
            || in_array(sanitize_key((string) ($selected_context['parent_type'] ?? '')), ['pagerow', 'row'], true)
            || (!empty($selected_context['parent_id']) && !empty($selected_context['item_id']));
        $has_any_selection = !empty($selected_context['item_id']) || !empty($selected_context['parent_id']) || !empty($selected_context['form_id']);

        if (preg_match('/\b(description|label|placeholder|heading|title|headline|help text|instructions|subtext)\b/', $normalized)) {
            if ($row_like_context && preg_match('/\b(heading|title|description|help text|instructions|subtext)\b/', $normalized)) {
                return true;
            }

            return true;
        }

        if (
            ($selected_type === 'form' && preg_match('/\bsubmit\b.*\b(label|button)\b|\b(label|button)\b.*\bsubmit\b/', $normalized))
            || ($selected_type === 'formpage' && preg_match('/\b(prev|previous|back|next|continue)\b.*\b(label|button)\b|\b(label|button)\b.*\b(prev|previous|back|next|continue)\b/', $normalized))
        ) {
            return true;
        }

        if ($selected_type === 'field' && $this->isSelectedFieldOptionLayoutIntent($normalized)) {
            return true;
        }

        if ($selected_type === 'field' && preg_match('/\b(remove|delete|clear)\b.*\b(field|this|selected|current)\b|\b(field|this|selected|current)\b.*\b(remove|delete|clear)\b/', $normalized)) {
            return true;
        }

        if ($selected_type === 'field' && preg_match('/\b(move|shift|reorder|relocate)\b/', $normalized)) {
            return true;
        }

        if (($row_like_context || $has_any_selection) && preg_match('/\b(all|every|each|bulk)\b.*\b(row|rows|section|sections)\b.*\b(heading|headings|title|titles)\b/', $normalized)) {
            return true;
        }

        if ($row_like_context && preg_match('/\b(delete|remove|clear)\b/', $normalized)) {
            return true;
        }

        if ($row_like_context && preg_match('/\b(move|shift|reorder)\b(\s+row)?\s+\b(up|down|above|below|before|after|earlier|later|previous|next|top|bottom)\b/', $normalized)) {
            return true;
        }

        if ($row_like_context && preg_match('/\b(rewrite|change|update|edit|improve|make|set|add|generate|turn)\b.*\b(heading|title|description|help text|instructions|subtext)\b/', $normalized)) {
            return true;
        }

        return (bool) preg_match('/\b(rewrite|change|update|edit|improve|make|set|add|generate|turn)\b.*\b(more|better|warmer|friendlier|clearer|clear|question|questions)\b/', $normalized);
    }

    protected function isLikertRowColumnIntent(string $normalized_prompt, array $selected_context): bool
    {
        if (preg_match('/\b(likert|row labels?|column labels?|matrix rows?|matrix columns?)\b/', $normalized_prompt)) {
            return true;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if ($field_type !== 'inputlikert') {
            return false;
        }

        if (preg_match('/\b(rows?|columns?|labels?)\b/', $normalized_prompt)) {
            return true;
        }

        return false;
    }

    protected function isSelectedChoiceFieldOptionTextIntent(string $normalized_prompt, array $selected_context): bool
    {
        if (!preg_match('/\b(change|translate|rewrite|align|normalize|update|make|convert|turn|polish|improve|edit|refine)\b/', $normalized_prompt)) {
            return false;
        }

        if (!preg_match('/\b(option label|option labels|options|choices|choice|radio option|radio options|checkbox option|checkbox options)\b/', $normalized_prompt)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        return in_array($field_type, ['selectfield', 'inputcheckbox', 'inputradio'], true);
    }

    protected function isSelectedFieldOptionLayoutIntent(string $normalized_prompt): bool
    {
        if (!preg_match('/\b(option|options|choice|choices|radio|checkbox|column|columns)\b/', $normalized_prompt)) {
            return false;
        }

        return (bool) preg_match(
            '/\b(divide|split|arrange|distribute|spread|organize|group|regroup|place|put|move|reorder|re-arrange|rearrange|balance)\b/',
            $normalized_prompt
        );
    }

    protected function isSelectedFieldRemoveRequest(string $prompt, array $selected_context): bool
    {
        $selected_type = $this->resolveSelectedSemanticType($selected_context);
        if ($selected_type !== 'field') {
            return false;
        }

        $normalized = strtolower(trim($prompt));
        return (bool) preg_match('/\b(remove|delete|clear)\b.*\b(field|this|selected|current)\b|\b(field|this|selected|current)\b.*\b(remove|delete|clear)\b/', $normalized);
    }

    protected function isSelectedFieldMoveRequest(string $prompt, array $selected_context): bool
    {
        $selected_type = $this->resolveSelectedSemanticType($selected_context);
        if ($selected_type !== 'field') {
            return false;
        }

        $normalized = strtolower(trim($prompt));
        return (bool) preg_match(
            '/\b(move|shift|reorder|relocate|put|place)\b.*\b(field|this|selected|current)\b|\b(field|this|selected|current)\b.*\b(move|shift|reorder|relocate|put|place)\b|\b(field|this|selected|current)\b.*\b(up|down|top|bottom|before|after|above|below)\b|\b(to\s+the\s+)?(top|bottom|beginning|end)\b/',
            $normalized
        );
    }

    protected function isFieldGrowthPrompt(string $normalized_prompt): bool
    {
        return (bool) preg_match('/\b(add|append|insert|include|another|extra|additional|new)\b.*\b(field|fields|input|inputs|question|questions)\b|\b(field|fields|input|inputs|question|questions)\b.*\b(add|append|insert|include|another|extra|additional|new)\b/', $normalized_prompt);
    }

    protected function isSelectedPaymentFieldIntent(string $prompt, array $selected_context): bool
    {
        $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($item_type !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if ($field_type !== 'payment') {
            $field_id = absint($selected_context['item_id'] ?? 0);
            if ($field_id <= 0) {
                return false;
            }
            $format_sql = \GriffinForms\Admin\Sql\Format::getInstance();
            $field_row = $format_sql->itemValues($field_id, 'field', ['field_type']);
            $db_field_type = is_object($field_row) ? sanitize_key((string) ($field_row->field_type ?? '')) : '';
            if ($db_field_type !== 'payment') {
                return false;
            }
        }

        return true;
    }

    protected function isSelectedChoiceFieldColumnSplitIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));
        if (!preg_match('/\b(divide|split|spread|arrange|distribute|organize|reorganize|re-arrange|rearrange|group|group\s+into|grouping|section|sections)\b.*\b(options?|choices?)\b.*\b(columns?|col(?:umn)?s?|sections?)\b|\b(options?|choices?)\b.*\b(columns?|col(?:umn)?s?|sections?)\b.*\b(divide|split|spread|arrange|distribute|organize|reorganize|re-arrange|rearrange|group|group\s+into|grouping|section|sections)\b/', $normalized)) {
            return false;
        }

        $selected_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if ($selected_type !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if (in_array($field_type, ['selectfield', 'inputcheckbox', 'inputradio'], true)) {
            return true;
        }

        $field_id = absint($selected_context['item_id'] ?? 0);
        if ($field_id > 0) {
            $format_sql = \GriffinForms\Admin\Sql\Format::getInstance();
            $field_row = $format_sql->itemValues($field_id, 'field', ['field_type']);
            if (is_object($field_row)) {
                $db_field_type = sanitize_key((string) ($field_row->field_type ?? ''));
                return in_array($db_field_type, ['inputcheckbox', 'inputradio'], true);
            }
        }

        return false;
    }

    protected function isSelectedFieldLabelIntent(string $prompt, array $selected_context): bool
    {
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field' || empty($selected_context['item_id'])) {
            return false;
        }

        // Rating field mutations are always handled by generate_rating_field — don't intercept here.
        if ($this->isRatingFieldMutationIntent($prompt, $selected_context)) {
            return false;
        }

        // Only inspect the original user text — the router may append enrichment context after the first newline.
        $user_line = explode("\n", $prompt, 2)[0];
        $normalized = strtolower(trim($user_line));

        // Description / help-text targeting
        if (preg_match('/\b(description|help\s+text|helper\s+text|subtext|instructions?|hint)\b/', $normalized)
            && preg_match('/\b(add|write|update|change|edit|refine|polish|improve|rewrite|remove|clear|delete)\b/', $normalized)) {
            return true;
        }

        if (!preg_match('/\b(field\s+)?label\b/', $normalized)) {
            return false;
        }

        return (bool) preg_match('/\b(rewrite|improve|update|change|edit|refine|polish|turn|make|shorten|simplify|rename|fix|rephrase|reword|translate|add)\b/', $normalized);
    }

    // -------------------------------------------------------------------------
    // Helpers (moved verbatim from BuilderAssistRouter)
    // -------------------------------------------------------------------------

    protected function extractRequestedOptionColumnCount(string $prompt): ?int
    {
        $normalized = strtolower(trim($prompt));
        if (!preg_match('/\b(?:divide|split|spread|arrange|distribute|organize|group|group\s+into|grouping|section|sections)\b.*\b(?:field\s+)?(?:options?|choices?)\b.*\b(?:into\s+)?(?:([1-9]\d*)|one|two|three|four)\b.*\b(?:columns?|sections?)\b|\b(?:[1-9]\d*|one|two|three|four)\b.*\b(?:columns?|sections?)\b/', $normalized, $matches)) {
            return null;
        }

        $raw = strtolower(trim((string) ($matches[1] ?? '')));
        if ($raw === '') {
            if (preg_match('/\b(one|two|three|four)\b/', $normalized, $word_matches)) {
                $raw = (string) ($word_matches[1] ?? '');
            } elseif (preg_match('/\b([1-9]\d*)\b/', $normalized, $number_matches)) {
                $raw = (string) ($number_matches[1] ?? '');
            }
        }

        $map = ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4];
        if (isset($map[$raw])) {
            return $map[$raw];
        }

        $value = absint($raw);
        if ($value < 1 || $value > 4) {
            return -1;
        }

        return $value;
    }

    protected function resolveSelectedSemanticType(array $selected_context): string
    {
        $candidates = [
            sanitize_key((string) ($selected_context['item_type'] ?? '')),
            sanitize_key((string) ($selected_context['parent_type'] ?? '')),
        ];

        foreach ($candidates as $candidate) {
            if (in_array($candidate, ['form', 'field', 'formpage', 'pagerow', 'rowcolumn'], true)) {
                return $candidate;
            }
        }

        if (!empty($selected_context['item_id']) || !empty($selected_context['parent_id']) || !empty($selected_context['form_id'])) {
            return 'selected';
        }

        return '';
    }

    protected function normalizeSuggestionId(string $suggestion_id): string
    {
        $suggestion_id = strtolower(trim($suggestion_id));
        if ($suggestion_id === '') {
            return '';
        }

        return preg_replace('/[^a-z0-9._-]/', '', $suggestion_id) ?: '';
    }

    protected function decodeSuggestionMeta($suggestion_meta_raw): array
    {
        if (is_string($suggestion_meta_raw) && $suggestion_meta_raw !== '') {
            $decoded_meta = json_decode($suggestion_meta_raw, true);
            if (is_array($decoded_meta)) {
                return $decoded_meta;
            }
        } elseif (is_array($suggestion_meta_raw)) {
            return $suggestion_meta_raw;
        }

        return [];
    }

    // -------------------------------------------------------------------------
    // New intent detection methods — Issue 4
    // -------------------------------------------------------------------------

    protected function isStepLabelRewriteIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(rewrite|improve|polish|update|refine|edit|rename)\b.*\b(step\s+labels?|step\s+names?|navigation\s+labels?|tab\s+labels?|step\s+headings?)\b/i',
            $normalized
        ) || (bool) preg_match(
            '/\b(step\s+labels?|step\s+names?|navigation\s+labels?|tab\s+labels?|step\s+headings?)\b.*\b(rewrite|improve|polish|update|refine|edit|rename)\b/i',
            $normalized
        );
    }

    protected function isBulkRowTextMutationIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(all|every|each|bulk)\b.*\b(row|rows|section|sections)\b.*\b(heading|headings|title|titles|description|descriptions|copy|text)\b/i',
            $normalized
        );
    }

    protected function isLayoutRearrangeIntent(string $prompt, array $selected_context): bool
    {
        $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if (!in_array($item_type, ['formpage', 'form'], true)) {
            return false;
        }

        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(rearrange|reorganize|reorganise|improve|fix|polish|clean\s+up|optimize|optimise)\b.*\b(layout|structure)\b|\b(layout|structure)\b.*\b(rearrange|reorganize|reorganise|improve|fix|polish|clean\s+up|optimize|optimise)\b/i',
            $normalized
        );
    }

    protected function isFieldOptionsManagementIntent(string $prompt, array $selected_context): bool
    {
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if (!in_array($field_type, ['selectfield', 'inputradio', 'inputcheckbox', 'inputlikert'], true)) {
            return false;
        }

        $normalized = strtolower(trim($prompt));

        // Exclude fresh-generation prompts — those route to generate_selectfield_options
        if (preg_match('/\b(generate|suggest|create for me|make options for|write options for)\b.*\b(option|choice)s?\b/i', $normalized)) {
            return false;
        }

        return (bool) preg_match(
            '/\b(add|remove|delete|rename|reorder|rewrite|edit|update|change|sort|move|swap)\b.*\b(option|choice|answer|row|column|item)s?\b/i',
            $normalized
        ) || (bool) preg_match(
            '/\b(option|choice|answer|row|column|item)s?\b.*\b(add|remove|delete|rename|reorder|rewrite|edit|update|change|sort|move|swap)\b/i',
            $normalized
        );
    }

    protected function isPaymentProductMutationIntent(string $prompt, array $selected_context): bool
    {
        if (!$this->isSelectedPaymentFieldIntent($prompt, $selected_context)) {
            return false;
        }

        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(add|remove|delete|rename|update|change|edit|set|create|price|featured|product|plan|tier|option)\b/i',
            $normalized
        );
    }

    protected function isImageOptionGenerationIntent(string $prompt, array $selected_context): bool
    {
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if (!in_array($field_type, ['imageselection', 'payment'], true)) {
            return false;
        }

        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(generate|create|add|set|update|change)\b.*\b(image|photo|picture|thumbnail)\b/i',
            $normalized
        );
    }

    protected function isRatingFieldMutationIntent(string $prompt, array $selected_context): bool
    {
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field') {
            return false;
        }

        $field_type = sanitize_key((string) ($selected_context['field_type'] ?? ''));
        if ($field_type !== 'inputrating') {
            $field_id = absint($selected_context['item_id'] ?? 0);
            if ($field_id <= 0) {
                return false;
            }
            $format_sql = \GriffinForms\Admin\Sql\Format::getInstance();
            $field_row = $format_sql->itemValues($field_id, 'field', ['field_type']);
            $field_type = is_object($field_row) ? sanitize_key((string) ($field_row->field_type ?? '')) : '';
            if ($field_type !== 'inputrating') {
                return false;
            }
        }

        return true;
    }

    protected function isNotificationManagementIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));

        // Questions about notifications → advisory, not mutation
        if (preg_match('/^(does|is|has|do|can|will|would|what|show|explain|tell|describe|how|why|which)\b/i', $normalized)) {
            return false;
        }

        // Message content edits are handled by generate_message_mutation, not here.
        if ($this->isMessageMutationIntent($normalized, $selected_context)) {
            return false;
        }

        return (bool) preg_match(
            '/\b(notification|alert|autoresponder|email\s+alert|confirmation\s+email|notify|recipient|send.*email|add.*notification|disable.*notification|enable.*notification)\b/i',
            $normalized
        );
    }

    protected function isMessageMutationIntent(string $prompt, array $selected_context): bool
    {
        $normalized = strtolower(trim($prompt));

        return (bool) preg_match(
            '/\b(rewrite|update|change|edit|improve|set|modify)\b.*\b(message|subject|body|email\s+body|confirmation|autoresponder|notification\s+text)\b/i',
            $normalized
        ) || (bool) preg_match(
            '/\b(message|subject|body|email\s+body|confirmation|autoresponder|notification\s+text)\b.*\b(rewrite|update|change|edit|improve|set|modify)\b/i',
            $normalized
        );
    }

    protected function isStructuralAddIntent(string $prompt, array $selected_context): bool
    {
        $item_type  = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $normalized = strtolower(trim($prompt));

        if (!preg_match('/^(add|insert|include|append|create|put)\b/i', $normalized)) {
            return false;
        }

        // Always bypass for form and page selections.
        if (in_array($item_type, ['form', 'formpage'], true)) {
            return true;
        }

        // Bypass for row/section/page add prompts regardless of what is selected.
        // "Add new row", "Add a section", "Add row with 3 columns" must never hit the
        // scope check — the intent is structural growth, not modification of the selection.
        if (preg_match('/\b(row|rows|section|sections|page|pages)\b/i', $normalized)) {
            return true;
        }

        // For field selections: bypass when the prompt is adding a new field/row/page,
        // not modifying the selected field itself.
        if ($item_type === 'field') {
            return (bool) preg_match(
                '/\b(field|row|page|section|column)\b/i',
                $normalized
            );
        }

        return false;
    }

    protected function buildScopeMismatchMessage(string $prompt, array $selected_context): string
    {
        $item_type  = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $item_label = sanitize_text_field((string) ($selected_context['item_label'] ?? ''));
        $normalized = strtolower(trim($prompt));

        $selected_name = match ($item_type) {
            'form'      => $item_label !== '' ? sprintf(__('the form "%s"', 'griffinforms-form-builder'), $item_label) : __('the form', 'griffinforms-form-builder'),
            'formpage'  => $item_label !== '' ? sprintf(__('the page "%s"', 'griffinforms-form-builder'), $item_label) : __('a page', 'griffinforms-form-builder'),
            'pagerow'   => __('a row', 'griffinforms-form-builder'),
            'rowcolumn' => __('a column', 'griffinforms-form-builder'),
            'field'     => $item_label !== '' ? sprintf(__('the field "%s"', 'griffinforms-form-builder'), $item_label) : __('a field', 'griffinforms-form-builder'),
            default     => __('the selected element', 'griffinforms-form-builder'),
        };

        // Detect what the prompt is actually targeting and suggest the right selection.
        if (preg_match('/\b(split|divide|break|separate)\b.*\b(row|section)\b|\b(row|section)\b.*\b(split|divide|into)\b/i', $normalized)) {
            return __('To split a row, select the row you want to split first, then try again.', 'griffinforms-form-builder');
        }
        if (preg_match('/\b(add|insert|create|new)\b.*\b(row|section)\b/i', $normalized)) {
            return __('To add a row, select the page or an existing row first, then try again.', 'griffinforms-form-builder');
        }
        if (preg_match('/\b(add|insert|create|new)\b.*\b(field|input|question)\b/i', $normalized)) {
            return __('To add a field, select the form, a page, or a row first, then try again.', 'griffinforms-form-builder');
        }
        if (preg_match('/\b(page headings?|page descriptions?|page titles?)\b/i', $normalized)) {
            return __('Select a page first, then try again.', 'griffinforms-form-builder');
        }
        if (preg_match('/\b(improve|rewrite|update|change|edit|polish)\b.*\b(label|heading|description|placeholder)\b/i', $normalized)) {
            return sprintf(
                __('Your prompt looks like a field or element edit, but %s is currently selected. Select the specific element you want to change and try again.', 'griffinforms-form-builder'),
                $selected_name
            );
        }

        // Generic fallback — still more informative than before.
        return sprintf(
            __('Your prompt doesn\'t match %s. Deselect or select the element you want to change and try again.', 'griffinforms-form-builder'),
            $selected_name
        );
    }

    protected function isRowColumnSplitIntent(string $prompt, array $selected_context): bool
    {
        $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        if (!in_array($item_type, ['pagerow', 'row'], true)) {
            return false;
        }
        $normalized = strtolower(trim($prompt));
        return (bool) preg_match('/\b(split|divide|break|separate)\b.*\b(row|section|columns?)\b|\b(row|section)\b.*\b(split|divide|into)\b/i', $normalized);
    }

    protected function isPagePropertyMutationIntent(string $prompt, array $selected_context): bool
    {
        $item_type = sanitize_key((string) ($selected_context['item_type'] ?? ''));
        $normalized = strtolower(trim($prompt));

        if (in_array($item_type, ['form', 'formpage'], true) && preg_match(
            '/\b(prev|previous|back|next|continue)\b.*\b(label|button)\b|\b(label|button)\b.*\b(prev|previous|back|next|continue)\b/i',
            $normalized
        )) {
            return true;
        }

        if (!in_array($item_type, ['formpage', 'pagerow', 'rowcolumn'], true)) {
            return false;
        }

        return (bool) preg_match(
            '/\b(add|improve|rewrite|update|change|edit|refine|polish|generate|write|set)\b.*\b(heading|description|title|name|label)\b|\b(heading|description|title|name|label)\b.*\b(add|improve|rewrite|update|change|edit|refine|polish|generate|write|set)\b/i',
            $normalized
        );
    }

    public function getKnownFieldTypeAliases(): array
    {
        return [
            'inputtext'    => ['text field', 'text', 'name', 'single line text'],
            'inputemail'   => ['email field', 'email address', 'email'],
            'inputphone'   => ['phone field', 'phone number', 'telephone', 'phone'],
            'inputaddress' => ['address field', 'mailing address', 'billing address', 'shipping address', 'address'],
            'textarea'     => ['textarea', 'text area', 'message', 'comment', 'notes', 'long text'],
            'selectfield'  => ['dropdown', 'select field', 'select'],
            'inputcheckbox' => ['checkbox', 'checkbox field', 'check box'],
            'inputradio'   => ['radio', 'radio field', 'single choice', 'choice'],
            'inputnumber'  => ['number field', 'number', 'amount', 'quantity'],
            'inputdate'    => ['date field', 'date', 'calendar date'],
            'fileupload'   => ['file upload', 'upload field', 'attachment', 'file'],
            'signature'    => ['signature field', 'signature', 'sign here', 'e-signature'],
            'inputrating'    => ['rating field', 'rating', 'star rating', 'score'],
            'inputrichtext'  => ['rich text field', 'rich text', 'formatted text', 'wysiwyg', 'editor'],
            'inputlikert'    => ['likert field', 'likert scale', 'matrix question', 'likert'],
            'imageselection' => ['image selection field', 'image choice', 'image picker', 'visual selector'],
        ];
    }
}
