<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

use GriffinForms\Includes\Ai\PatchPlanApplier;
use GriffinForms\Includes\Ai\PatchPlanParser;
use GriffinForms\Includes\Ai\PromptRegistry\StructureSnapshotBuilder;

class GenerateBulkTextMutationPromptHandler extends BasePromptHandler
{
    protected const MAX_REFLECTION_RETRIES = 1;

    protected PatchPlanParser $parser;
    protected PatchPlanApplier $applier;

    public function __construct()
    {
        parent::__construct();
        $this->parser = new PatchPlanParser();
        $this->applier = new PatchPlanApplier();
    }

    public function getPromptKey(): string
    {
        return 'generate_bulk_text_mutation';
    }

    public function getUnsupportedMessage(): string
    {
        return __('I can only apply bulk text changes for supported copy targets right now.', 'griffinforms-form-builder');
    }

    public function handle(int $form_id, string $prompt, string $provider, array $config): array
    {
        $selected_context = is_array($config['selected_context'] ?? null) ? (array) $config['selected_context'] : [];
        $request_context = is_array($config['request_context'] ?? null) ? (array) $config['request_context'] : [];
        $structure = (new StructureSnapshotBuilder())->buildFromForm($form_id);
        $normalized_prompt = strtolower(trim($prompt));

        $split_count = $this->extractRequestedOptionColumnCount($normalized_prompt);
        if ($this->isChoiceFieldColumnSplitPrompt($normalized_prompt, $structure, $selected_context, $split_count)) {
            if ($split_count === -1) {
                return $this->formatFailure(
                    __('Option columns must be a whole number between 1 and 4.', 'griffinforms-form-builder'),
                    'bulk_text_validation_failed'
                );
            }

            if ($split_count === null) {
                return $this->formatFailure(
                    __('Option columns must be a whole number between 1 and 4.', 'griffinforms-form-builder'),
                    'bulk_text_validation_failed'
                );
            }

            $direct_plan = $this->buildChoiceFieldColumnSplitPatch($structure, $selected_context, $normalized_prompt);
            if ($direct_plan === null) {
                return $this->formatFailure(
                    __('I could not safely divide those options into columns for the selected field.', 'griffinforms-form-builder'),
                    'bulk_text_validation_failed'
                );
            }

            $apply_result = $this->applier->apply($form_id, $direct_plan, $prompt);
            if (!empty($apply_result['success'])) {
                return [
                    'success' => true,
                    'status' => 'applied',
                    'message' => (string) ($apply_result['message'] ?? $direct_plan['success_message'] ?? __('Option columns updated successfully.', 'griffinforms-form-builder')),
                    'mutated_ids' => (array) ($apply_result['mutated_ids'] ?? []),
                    'prompt_key' => $this->getPromptKey(),
                    'patch_plan' => $direct_plan,
                    'patch_summary' => (array) ($apply_result['patch_summary'] ?? []),
                ];
            }

            return $this->formatFailure(
                (string) ($apply_result['message'] ?? __('Griffin Assist could not safely update the option columns.', 'griffinforms-form-builder')),
                'bulk_text_apply_failed'
            );
        }

        if ($this->isTranslatePrompt($normalized_prompt)) {
            $language = $this->extractRequestedLanguage($normalized_prompt);
            if ($language !== '') {
                // Fall through to reflection loop but with a refined system prompt for translation
                $config['translation_target'] = $language;
                $request_context['translation_target'] = $language;
            }
        }

        if ($this->isAllFieldLabelsPrompt($normalized_prompt)) {
            $direct_plan = $this->buildFieldLabelBulkPatch($structure);
            if ($direct_plan === null) {
                return $this->formatFailure(
                    __('I could not find any fields to update for that bulk field-label request.', 'griffinforms-form-builder'),
                    'bulk_text_validation_failed'
                );
            }


            $apply_result = $this->applier->apply($form_id, $direct_plan, $prompt);
            if (!empty($apply_result['success'])) {
                return [
                    'success' => true,
                    'status' => 'applied',
                    'message' => (string) ($apply_result['message'] ?? $direct_plan['success_message'] ?? __('Bulk text changes applied successfully.', 'griffinforms-form-builder')),
                    'mutated_ids' => (array) ($apply_result['mutated_ids'] ?? []),
                    'prompt_key' => $this->getPromptKey(),
                    'patch_plan' => $direct_plan,
                    'patch_summary' => (array) ($apply_result['patch_summary'] ?? []),
                ];
            }

            return $this->formatFailure(
                (string) ($apply_result['message'] ?? __('Griffin Assist could not safely apply the bulk text changes.', 'griffinforms-form-builder')),
                'bulk_text_apply_failed'
            );
        }

        $last_errors = [];
        $raw_content = '';

        for ($attempt = 0; $attempt <= self::MAX_REFLECTION_RETRIES; $attempt++) {
            $prompt_text = $attempt === 0
                ? $this->buildUserPrompt($prompt, $selected_context, $request_context, $structure)
                : $this->buildReflectionPrompt($prompt, $raw_content, $last_errors, $selected_context, $request_context, $structure);

            $response = $this->requestStrict(
                $provider,
                $prompt_text,
                ['system' => $this->buildSystemPrompt()],
                array_merge($config, ['timeout' => $attempt === 0 ? 30 : 20])
            );

            $raw_content = (string) ($response['content'] ?? '');
            [$decoded, $extract_error] = $this->extractor->extract($raw_content);

            $validation_errors = [];
            if ($extract_error !== null) {
                $validation_errors[] = $extract_error;
            } else {
                [$plan, $validation_errors] = $this->parser->parse($decoded, $structure, [
                    'selected_context' => $selected_context,
                    'request_context' => $request_context,
                ]);

                if (empty($validation_errors) && is_array($plan)) {
                    $bulk_errors = $this->validateBulkTextPlan($plan);
                    if (empty($bulk_errors)) {
                        $apply_result = $this->applier->apply($form_id, $plan, $prompt);
                        if (!empty($apply_result['success'])) {
                            return [
                                'success' => true,
                                'status' => 'applied',
                                'message' => (string) ($apply_result['message'] ?? $plan['success_message'] ?? __('Bulk text changes applied successfully.', 'griffinforms-form-builder')),
                                'mutated_ids' => (array) ($apply_result['mutated_ids'] ?? []),
                                'prompt_key' => $this->getPromptKey(),
                                'patch_plan' => $plan,
                                'patch_summary' => (array) ($apply_result['patch_summary'] ?? []),
                            ];
                        }

                        return $this->formatFailure(
                            (string) ($apply_result['message'] ?? __('Griffin Assist could not safely apply the bulk text changes.', 'griffinforms-form-builder')),
                            'bulk_text_apply_failed'
                        );
                    }

                    $validation_errors = $bulk_errors;
                }
            }

            $last_errors = $validation_errors;
            if (!empty($validation_errors)) {
            }
            $this->logRetryFailure($this->getPromptKey(), $attempt + 1, $validation_errors, $raw_content);
        }

        return $this->formatFailure(
            __('I could not convert that into safe bulk text changes. Try naming the target family more clearly, like field labels, page descriptions, or option labels.', 'griffinforms-form-builder'),
            'bulk_text_validation_failed'
        );
    }

    protected function isTranslatePrompt(string $normalized_prompt): bool
    {
        return (bool) preg_match('/\b(translate|convert|rewrite|turn|make)\b.*\b(to|into|in)\b/i', $normalized_prompt);
    }

    protected function buildSystemPrompt(): string
    {
        return implode("\n", [
            'You are Griffin Assist in bulk text mutation mode for GriffinForms.',
            'Return JSON only. No markdown fences, no commentary.',
            'Top-level mode must be exactly "patch" or "patch_with_user_input".',
            '',
            '=== CRITICAL: Allowed operation types (op field) ===',
            'update_form, update_page, update_row, update_column, update_field, no_op',
            'Do NOT use: translate_form, translate_field, translate_element, set_translation, or any other op name.',
            'Do NOT use: insert, move, remove, replace, delete, or any structural operation.',
            '',
            '=== Translation requests ===',
            'When asked to translate a form, generate standard update operations that set each text property to its translated value.',
            'A translation is just a bulk set of text changes using the same ops as any other update.',
            '',
            '=== Required JSON structure for a full-form translation ===',
            '{',
            '  "mode": "patch",',
            '  "success_message": "Form translated to [language].",',
            '  "operations": [',
            '    { "op": "update_form", "target": { "scope": "form" }, "changes": { "name": "[translated name]", "description": "[translated description]", "submit_label": "[translated label]" } },',
            '    { "op": "update_page", "target": { "scope": "page", "item_id": 45 }, "changes": { "heading": "[translated heading]", "description": "[translated description]" } },',
            '    { "op": "update_field", "target": { "scope": "field", "item_id": 98 }, "changes": { "label": "[translated label]", "placeholder": "[translated placeholder]" } }',
            '  ]',
            '}',
            '',
            '=== CRITICAL: How to specify targets ===',
            'For update_form: target = { "scope": "form" }. No item_id needed.',
            'For update_page: target = { "scope": "page", "item_id": [page item_id from structure] }.',
            'For update_row: target = { "scope": "row", "item_id": [row item_id from structure] }.',
            'For update_field: target = { "scope": "field", "item_id": [field item_id from structure] }.',
            'NEVER use "page": 1, "field_id": 123, or any other key. Only use "item_id" with the real integer ID from the structure.',
            '',
            '=== Available changes per op ===',
            'update_form → changes: name (internal slug), heading (public display title), description, submit_label',
            'For translations, translate BOTH name and heading in update_form changes.',
            'update_page → changes: heading, description, previous_label, next_label',
            'update_row → changes: heading, description',
            'update_field → changes: label (the visible field label displayed to users), description, placeholder',
            'For translations, generate an update_field operation for EVERY field in the structure, even if label appears short or obvious.',
            'For choice field (radio, checkbox) option text: use field.options (NOT changes.options), as array of {"Translated Label":"original_value"} objects:',
            'field.options example for radio/checkbox: { "op": "update_field", "target": { "scope": "field", "item_id": 98 }, "field": { "field_type": "inputradio", "options": [{"ਹਾਂ":"yes"},{"ਨਹੀਂ":"no"}] }, "changes": {} }',
            'For selectfield dropdown options: use field.options as {"Group Name": {"Translated Label": "original_value"}} nested object:',
            'field.options example for select: { "op": "update_field", "target": { "scope": "field", "item_id": 99 }, "field": { "field_type": "selectfield", "options": {"Options": {"ਵਿਕਲਪ 1": "option_1", "ਵਿਕਲਪ 2": "option_2"}} }, "changes": {} }',
            '',
            '=== Likert (inputlikert) field row and column labels ===',
            'Likert rows and columns are stored in field_settings, not in options or label.',
            'To translate or rewrite Likert rows and columns, use update_field with changes.field_settings containing rows and columns arrays.',
            'Each row is a string (the row label). Each column is a string (the column label).',
            'You MUST include ALL rows and ALL columns in your response — do not omit any.',
            'Likert translation example:',
            '{ "op": "update_field", "target": { "scope": "field", "item_id": 101 }, "changes": { "label": "[translated label]", "field_settings": { "rows": ["[translated row 1]", "[translated row 2]"], "columns": ["[translated col 1]", "[translated col 2]", "[translated col 3]"] } } }',
            'The structure summary includes likert_rows and likert_columns for each inputlikert field — use those as the base array to translate.',
            '',
            'If the request target family is ambiguous, return patch_with_user_input with required_inputs choices.',
        ]);
    }

    protected function buildUserPrompt(string $prompt, array $selected_context, array $request_context, array $structure): string
    {
        $translation_target = (string) ($request_context['translation_target'] ?? '');
        $language_hint = '';
        if ($translation_target !== '') {
            $language_hint = sprintf("\nTarget Translation Language: %s", $this->resolveLanguageName($translation_target));
        }

        return implode("\n", [
            'User request:',
            $prompt . $language_hint,
            'Selected context:',
            wp_json_encode($selected_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Routing context:',
            wp_json_encode([
                'surface' => (string) ($request_context['surface'] ?? ''),
                'origin' => (string) ($request_context['origin'] ?? ''),
                'response_mode' => (string) ($request_context['response_mode'] ?? ''),
                'semantic_intent' => (string) ($request_context['semantic_intent'] ?? ''),
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Site context:',
            wp_json_encode((array) ($request_context['site_context'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Structure summary (use item_id values from pages and fields for target.item_id):',
            wp_json_encode($this->condenseStructure($structure), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Mapping rules:',
            '- update_form target: { "scope": "form" } — no item_id required.',
            '- update_form changes: name (internal slug), heading (the public-facing form title shown to users), description, submit_label.',
            '- IMPORTANT: For translations, always include heading in update_form changes, not just name.',
            '- update_page target: { "scope": "page", "item_id": <item_id from structure pages array> }',
            '- update_row target: { "scope": "row", "item_id": <item_id from structure rows array> }',
            '- update_field target: { "scope": "field", "item_id": <item_id from structure fields array> }',
            '- Page headings map to update_page changes.heading.',
            '- Page descriptions map to update_page changes.description.',
            '- Row headings map to update_row changes.heading.',
            '- Row descriptions map to update_row changes.description.',
            '- Field labels map to update_field changes.label.',
            '- Submit button label maps to update_form changes.submit_label.',
            '- Next button labels (on multi-page forms) map to update_page changes.next_label.',
            '- Previous button labels (on multi-page forms) map to update_page changes.previous_label.',
            '- For radio/checkbox field options: field.options = [{"Translated Label":"original_value"}, ...]',
            '- For select/dropdown field options: field.options = {"Group Name": {"Translated Label": "original_value", ...}}',
            '- For Likert (inputlikert) row and column labels: changes.field_settings = { "rows": ["row 1", ...], "columns": ["col 1", ...] }.',
            '- Likert field_settings rows/columns are listed in the structure summary under likert_rows and likert_columns — translate all of them.',
            '- You MUST include a translation operation for every single field in the structure. Do not skip any field.',
            'Return only a bulk text mutation patch plan.',
        ]);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors, array $selected_context, array $request_context, array $structure): string
    {
        return implode("\n", [
            'Your previous bulk text mutation response was invalid.',
            'Original request:',
            $prompt,
            'Validation errors:',
            wp_json_encode(array_values($errors), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Previous raw response:',
            $raw_content,
            'Selected context:',
            wp_json_encode($selected_context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Routing context:',
            wp_json_encode([
                'surface' => (string) ($request_context['surface'] ?? ''),
                'response_mode' => (string) ($request_context['response_mode'] ?? ''),
                'semantic_intent' => (string) ($request_context['semantic_intent'] ?? ''),
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Structure summary:',
            wp_json_encode($this->condenseStructure($structure), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Return corrected JSON only.',
        ]);
    }

    protected function validateBulkTextPlan(array $plan): array
    {
        $mode = sanitize_key((string) ($plan['mode'] ?? ''));
        if (!in_array($mode, ['patch', 'patch_with_user_input'], true)) {
            return [__('Bulk text plan must be patch or patch_with_user_input.', 'griffinforms-form-builder')];
        }

        if ($mode === 'patch_with_user_input') {
            return [];
        }

        $errors = [];
        foreach ((array) ($plan['operations'] ?? []) as $index => $operation) {
            $op = sanitize_key((string) ($operation['op'] ?? ''));
            if (!in_array($op, ['update_form', 'update_page', 'update_row', 'update_column', 'update_field', 'no_op'], true)) {
                $errors[] = sprintf(__('Bulk text operation %d used a non-text operation.', 'griffinforms-form-builder'), $index + 1);
                continue;
            }

            if ($op === 'no_op') {
                continue;
            }

            $changes = (array) ($operation['changes'] ?? []);
            $field = (array) ($operation['field'] ?? []);
            if (empty($changes) && empty($field)) {
                $errors[] = sprintf(__('Bulk text operation %d did not contain any text changes.', 'griffinforms-form-builder'), $index + 1);
                continue;
            }

            foreach (array_keys($changes) as $property) {
                if (!in_array((string) $property, ['name', 'heading', 'description', 'label', 'placeholder', 'submit_label', 'previous_label', 'next_label', 'options', 'field_settings'], true)) {
                    $errors[] = sprintf(__('Bulk text operation %d used an unsupported property.', 'griffinforms-form-builder'), $index + 1);
                    continue;
                }

                if ((string) $property === 'options') {
                    $field = (array) ($operation['field'] ?? []);
                    $field_type = sanitize_key((string) ($field['field_type'] ?? ''));
                    if (!in_array($field_type, ['inputcheckbox', 'inputradio'], true)) {
                        $errors[] = sprintf(__('Bulk text operation %d only supports option-column updates for checkbox and radio fields.', 'griffinforms-form-builder'), $index + 1);
                    }
                }

                if ((string) $property === 'field_settings') {
                    $field_settings_val = (array) ($changes['field_settings'] ?? []);
                    if (empty($field_settings_val['rows']) && empty($field_settings_val['columns'])) {
                        $errors[] = sprintf(__('Bulk text operation %d used field_settings without rows or columns.', 'griffinforms-form-builder'), $index + 1);
                    }
                }
            }
        }

        return array_values(array_unique($errors));
    }

    protected function isAllFieldLabelsPrompt(string $normalized_prompt): bool
    {
        return (bool) preg_match('/\b(all|every|each)\b.*\b(field labels?|labels?)\b|\b(field labels?|labels?)\b.*\b(all|every|each)\b/', $normalized_prompt);
    }

    protected function isChoiceFieldColumnSplitPrompt(string $normalized_prompt, array $structure, array $selected_context, ?int $split_count = null): bool
    {
        $field_snapshot = $this->findFieldSnapshot($structure, absint($selected_context['item_id'] ?? 0));
        if (!is_array($field_snapshot)) {
            return false;
        }

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

        if ($split_count !== null && ($split_count < 1 || $split_count > 4)) {
            return true;
        }

        return (bool) preg_match('/\b(divide|split|spread|arrange|organize|reorganize|re-arrange|rearrange|group|group\s+into|grouping|section|sections)\b.*\b(field\s+)?options?\b.*\b(columns?|col(?:umn)?s?|sections?)\b|\b(field\s+)?options?\b.*\b(columns?|col(?:umn)?s?|sections?)\b.*\b(divide|split|spread|arrange|organize|reorganize|re-arrange|rearrange|group|group\s+into|grouping|section|sections)\b/', $normalized_prompt);
    }

    protected function buildChoiceFieldColumnSplitPatch(array $structure, array $selected_context, string $normalized_prompt): ?array
    {
        $column_count = $this->extractRequestedOptionColumnCount($normalized_prompt);
        if ($column_count === null || $column_count === -1) {
            return null;
        }

        $field_id = absint($selected_context['item_id'] ?? 0);
        if ($field_id <= 0) {
            return null;
        }

        $field_snapshot = $this->findFieldSnapshot($structure, $field_id);
        if (!is_array($field_snapshot)) {
            return null;
        }

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

        $options = (array) ($field_snapshot['options'] ?? []);
        $flat_options = $this->flattenChoiceFieldOptions($options);
        if (empty($flat_options)) {
            return null;
        }

        $groups = $this->splitOptionsIntoColumns($flat_options, $column_count);
        if (empty($groups)) {
            return null;
        }

        return [
            'mode' => 'patch',
            'success_message' => sprintf(__('Divided the selected options into %d columns.', 'griffinforms-form-builder'), $column_count),
            'operations' => [[
                'op' => 'update_field',
                'target' => [
                    'scope' => 'field',
                    'item_id' => $field_id,
                ],
                'field' => [
                    'field_type' => $field_type,
                ],
                'changes' => [
                    'options' => $groups,
                ],
            ]],
        ];
    }

    protected function extractRequestedOptionColumnCount(string $normalized_prompt): ?int
    {
        if (!preg_match('/\b(?:divide|split|spread|arrange|organize|reorganize|re-arrange|rearrange|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_prompt, $matches)) {
            return null;
        }

        $raw = strtolower(trim((string) ($matches[1] ?? '')));
        if ($raw === '') {
            if (preg_match('/\b(one|two|three|four)\b/', $normalized_prompt, $word_matches)) {
                $raw = (string) ($word_matches[1] ?? '');
            } elseif (preg_match('/\b([1-4])\b/', $normalized_prompt, $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 findFieldSnapshot(array $structure, int $field_id): ?array
    {
        foreach ((array) ($structure['pages'] ?? []) as $page) {
            foreach ((array) ($page['rows'] ?? []) as $row) {
                foreach ((array) ($row['columns'] ?? []) as $column) {
                    foreach ((array) ($column['fields'] ?? []) as $field) {
                        if (absint($field['item_id'] ?? 0) === $field_id) {
                            return (array) $field;
                        }
                    }
                }
            }
        }

        return null;
    }

    protected function flattenChoiceFieldOptions(array $options): array
    {
        $flat = [];
        foreach ($options as $group) {
            foreach ((array) ($group['options'] ?? []) as $option) {
                $label = $this->normalizeText((string) ($option['label'] ?? ''), 120);
                $value = $this->normalizeText((string) ($option['value'] ?? ''), 120);
                if ($label === '' || $value === '') {
                    continue;
                }

                $flat[] = [
                    'label' => $label,
                    'value' => $value,
                ];
            }
        }

        return $flat;
    }

    protected function splitOptionsIntoColumns(array $options, int $column_count): array
    {
        $column_count = max(1, min(4, $column_count));
        $count = count($options);
        if ($count === 0) {
            return [];
        }

        $base = intdiv($count, $column_count);
        $remainder = $count % $column_count;
        $offset = 0;
        $groups = [];

        for ($index = 0; $index < $column_count; $index++) {
            $slice_length = $base + ($index < $remainder ? 1 : 0);
            if ($slice_length <= 0) {
                continue;
            }

            $slice = array_slice($options, $offset, $slice_length);
            $offset += $slice_length;

            $groups[] = [
                'column' => $index + 1,
                'options' => array_map(static function (array $option) {
                    return [
                        'label' => sanitize_text_field((string) ($option['label'] ?? '')),
                        'value' => sanitize_text_field((string) ($option['value'] ?? '')),
                    ];
                }, $slice),
            ];
        }

        return $groups;
    }

    protected function buildFieldLabelBulkPatch(array $structure): ?array
    {
        $operations = [];
        foreach ((array) ($structure['pages'] ?? []) as $page) {
            foreach ((array) ($page['rows'] ?? []) as $row) {
                foreach ((array) ($row['columns'] ?? []) as $column) {
                    foreach ((array) ($column['fields'] ?? []) as $field) {
                        $field_id = absint($field['item_id'] ?? 0);
                        if ($field_id <= 0) {
                            continue;
                        }

                        $label = $this->resolveFieldLabelValue((array) $field);
                        if ($label === '') {
                            continue;
                        }

                        $operations[] = [
                            'op' => 'update_field',
                            'target' => [
                                'scope' => 'field',
                                'item_id' => $field_id,
                            ],
                            'changes' => [
                                'label' => $label,
                            ],
                        ];
                    }
                }
            }
        }

        if (empty($operations)) {
            return null;
        }

        return [
            'mode' => 'patch',
            'success_message' => __('Updated all field labels.', 'griffinforms-form-builder'),
            'operations' => $operations,
        ];
    }

    protected function resolveFieldLabelValue(array $field): string
    {
        $field_type = sanitize_key((string) ($field['field_type'] ?? ''));
        $field_name = $this->normalizeText((string) ($field['name'] ?? ''), 120);
        $field_label = $this->normalizeText((string) ($field['label'] ?? ''), 120);

        $type_labels = [
            'inputemail' => __('Email Address', 'griffinforms-form-builder'),
            'inputphone' => __('Phone Number', 'griffinforms-form-builder'),
            'inputaddress' => __('Mailing Address', 'griffinforms-form-builder'),
            'textarea' => __('Message', 'griffinforms-form-builder'),
            'inputnumber' => __('Number', 'griffinforms-form-builder'),
            'inputdate' => __('Date', 'griffinforms-form-builder'),
            'fileupload' => __('Upload File', 'griffinforms-form-builder'),
        ];

        if (isset($type_labels[$field_type])) {
            return (string) $type_labels[$field_type];
        }

        $candidate = $this->humanizeFieldName($field_name);
        if ($candidate !== '') {
            return $candidate;
        }

        return $field_label;
    }

    protected function humanizeFieldName(string $value): string
    {
        $value = $this->normalizeText($value, 120);
        if ($value === '') {
            return '';
        }

        $value = str_replace(['_', '-'], ' ', $value);
        $value = preg_replace('/\s+/', ' ', trim($value));
        if ($value === '') {
            return '';
        }

        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
    }

    protected function condenseStructure(array $structure): array
    {
        $summary = [
            'form' => [
                'name' => sanitize_text_field((string) ($structure['form']['name'] ?? '')),
                'heading' => sanitize_text_field((string) ($structure['form']['heading'] ?? '')),
                'description' => sanitize_textarea_field((string) ($structure['form']['description'] ?? '')),
                'submit_label' => sanitize_text_field((string) ($structure['form']['submit_label'] ?? '')),
            ],
            'page_count' => count((array) ($structure['pages'] ?? [])),
            'field_count' => count((array) ($structure['fields'] ?? [])),
            'pages' => [],
            'fields' => [],
        ];

        foreach (array_values((array) ($structure['pages'] ?? [])) as $page) {
            $page_summary = [
                'item_id' => absint($page['item_id'] ?? 0),
                'heading' => sanitize_text_field((string) ($page['heading'] ?? $page['name'] ?? '')),
                'description' => sanitize_textarea_field((string) ($page['description'] ?? '')),
                'previous_label' => sanitize_text_field((string) ($page['previous_label'] ?? '')),
                'next_label' => sanitize_text_field((string) ($page['next_label'] ?? '')),
                'rows' => [],
            ];

            foreach (array_values((array) ($page['rows'] ?? [])) as $row) {
                $page_summary['rows'][] = [
                    'item_id' => absint($row['item_id'] ?? 0),
                    'heading' => sanitize_text_field((string) ($row['heading'] ?? $row['name'] ?? '')),
                    'description' => sanitize_textarea_field((string) ($row['description'] ?? '')),
                ];
            }

            $summary['pages'][] = $page_summary;
        }

        foreach (array_values((array) ($structure['fields'] ?? [])) as $field) {
            $field_type = sanitize_key((string) ($field['field_type'] ?? ''));
            $field_entry = [
                'item_id' => absint($field['item_id'] ?? 0),
                'field_type' => $field_type,
                'label' => sanitize_text_field((string) ($field['label'] ?? '')),
                'description' => sanitize_textarea_field((string) ($field['description'] ?? '')),
                'placeholder' => sanitize_text_field((string) ($field['placeholder'] ?? '')),
                'page_label' => sanitize_text_field((string) ($field['page_label'] ?? '')),
                'options' => (array) ($field['options'] ?? []),
            ];

            if ($field_type === 'inputlikert') {
                $fs = is_array($field['field_settings'] ?? null) ? (array) $field['field_settings'] : [];
                $likert_rows = array_values((array) ($fs['rows'] ?? []));
                $likert_columns = array_values((array) ($fs['columns'] ?? []));
                if (!empty($likert_rows) || !empty($likert_columns)) {
                    $field_entry['likert_rows'] = $likert_rows;
                    $field_entry['likert_columns'] = $likert_columns;
                }
            }

            $summary['fields'][] = $field_entry;
        }

        return $summary;
    }
}
