<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

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

class GenerateFieldLabelPromptHandler extends BasePromptHandler
{
    protected const MAX_REFLECTION_RETRIES = 1;

    protected PatchPlanApplier $applier;

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

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

    public function getUnsupportedMessage(): string
    {
        return __('I can only rewrite labels for a selected field 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);
        $field = $this->resolveSelectedFieldSnapshot($selected_context, $structure);

        if ($field === null) {
            return $this->formatFailure(
                __('I could not find a selected field to update its label.', 'griffinforms-form-builder'),
                'selection_required',
                409,
                ['request_context' => $request_context]
            );
        }

        $field_type = (string) ($field['field_type'] ?? '');
        $normalized_prompt = strtolower(trim($prompt));
        $is_description_intent = (bool) preg_match('/\b(description|help\s+text|helper\s+text|subtext|instructions?|hint)\b/', $normalized_prompt)
            && !preg_match('/\b(label|placeholder)\b/', $normalized_prompt);

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

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

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

            $validation_errors = [];
            if ($extract_error !== null) {
                $validation_errors[] = $extract_error;
            } elseif (!is_array($decoded)) {
                $validation_errors[] = __('Response must decode to an object.', 'griffinforms-form-builder');
            } else {
                $validation_errors = $is_description_intent ? $this->validateDescriptionPayload($decoded) : $this->validatePayload($decoded);
            }

            if (empty($validation_errors) && is_array($decoded)) {
                if ($is_description_intent) {
                    $description = $this->normalizeText((string) ($decoded['description'] ?? ''), 500);
                    $remove = !empty($decoded['remove']);
                    if ($description === '' && !$remove) {
                        $validation_errors[] = __('Description response must include a non-empty description value or set remove to true.', 'griffinforms-form-builder');
                    } else {
                        $patch = [
                            'mode' => 'patch',
                            'success_message' => $remove ? __('Field description removed.', 'griffinforms-form-builder') : __('Field description updated.', 'griffinforms-form-builder'),
                            'operations' => [[
                                'op' => 'update_field',
                                'target' => [
                                    'scope' => 'field',
                                    'item_id' => absint($field['item_id'] ?? 0),
                                ],
                                'changes' => [
                                    'description' => $remove ? '' : $description,
                                ],
                            ]],
                        ];

                        $patch = $this->attachPlanContext($patch, $selected_context, $request_context, $structure);
                        $apply_result = $this->applier->apply($form_id, $patch, $prompt);
                        if (!empty($apply_result['success'])) {
                            return [
                                'success' => true,
                                'status' => 'applied',
                                'message' => (string) ($apply_result['message'] ?? $patch['success_message'] ?? __('Updated the field description.', 'griffinforms-form-builder')),
                                'mutated_ids' => (array) ($apply_result['mutated_ids'] ?? []),
                                'prompt_key' => $this->getPromptKey(),
                                'patch_plan' => $patch,
                                'patch_summary' => (array) ($apply_result['patch_summary'] ?? []),
                            ];
                        }

                        return $this->formatFailure(
                            (string) ($apply_result['message'] ?? __('Griffin Assist could not safely apply the description update.', 'griffinforms-form-builder')),
                            'patch_apply_failed',
                            500,
                            ['request_context' => $request_context]
                        );
                    }
                }

                $label = $this->normalizeText((string) ($decoded['label'] ?? ''), 180);
                if ($label === '') {
                    $validation_errors[] = __('Label response must include a non-empty label value.', 'griffinforms-form-builder');
                } else {
                    $patch = [
                        'mode' => 'patch',
                        'success_message' => __('Field label updated.', 'griffinforms-form-builder'),
                        'operations' => [[
                            'op' => 'update_field',
                            'target' => [
                                'scope' => 'field',
                                'item_id' => absint($field['item_id'] ?? 0),
                            ],
                            'changes' => [
                                'label' => $label,
                            ],
                        ]],
                    ];

                    $patch = $this->attachPlanContext($patch, $selected_context, $request_context, $structure);
                    $apply_result = $this->applier->apply($form_id, $patch, $prompt);
                    if (!empty($apply_result['success'])) {
                        return [
                            'success' => true,
                            'status' => 'applied',
                            'message' => (string) ($apply_result['message'] ?? $patch['success_message'] ?? __('Updated the selected field label.', 'griffinforms-form-builder')),
                            'mutated_ids' => (array) ($apply_result['mutated_ids'] ?? []),
                            'prompt_key' => $this->getPromptKey(),
                            'patch_plan' => $patch,
                            'patch_summary' => (array) ($apply_result['patch_summary'] ?? []),
                        ];
                    }

                    return $this->formatFailure(
                        (string) ($apply_result['message'] ?? __('Griffin Assist could not safely apply the label update.', 'griffinforms-form-builder')),
                        'patch_apply_failed',
                        500,
                        ['request_context' => $request_context]
                    );
                }
            }

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

        return $this->formatFailure(
            __('I could not generate a safe label rewrite. Try giving me a more specific label target.', 'griffinforms-form-builder'),
            'label_validation_failed',
            422,
            ['request_context' => $request_context]
        );
    }

    protected function buildSystemPrompt(): string
    {
        return implode("\n", [
            'You are Griffin Assist in field label rewrite mode for GriffinForms.',
            'Return JSON only.',
            'Exact shape: {"label":"string"}',
            'Use the selected field label, description, required flag, validation rules, sibling labels, average sibling label length, form heading, parent page heading, and field options as context.',
            'The new label must be public-facing, concise, and specific to the field purpose.',
            'For dropdowns, use the form heading, parent page heading, field description, current options, and nearby field labels to infer a better category label.',
            'Keep the label length close to the average length of the other field labels unless the content clearly needs a longer or shorter label.',
            'Do not return required_inputs, choices, explanations, markdown, or code fences.',
            'Do not return an empty label.',
            'Do not ask the user to choose from alternatives.',
        ]);
    }

    protected function buildDescriptionSystemPrompt(): string
    {
        return implode("\n", [
            'You are Griffin Assist in field description write mode for GriffinForms.',
            'Return JSON only.',
            'Exact shape: {"description":"string"} — or {"description":"","remove":true} when the user asks to remove the description.',
            'Write a concise, user-facing help text that explains what the field is for or what the user should enter.',
            'The description appears below the field label and above the input. Keep it to 1–2 short sentences.',
            'Use the field label, field type, form heading, and sibling field labels as context.',
            'Do not return required_inputs, choices, patch plans, or markdown.',
            'Do not return an empty description unless the user explicitly asks to remove or clear it.',
        ]);
    }

    protected function validateDescriptionPayload($decoded): array
    {
        if (!is_array($decoded)) {
            return [__('Description payload must decode to an object.', 'griffinforms-form-builder')];
        }

        if (!empty($decoded['remove'])) {
            return [];
        }

        $description = $this->normalizeText((string) ($decoded['description'] ?? ''), 500);
        if ($description === '') {
            return [__('Description response must include a non-empty description value.', 'griffinforms-form-builder')];
        }

        return [];
    }

    protected function buildUserPrompt(string $prompt, array $field, array $selected_context, array $request_context, array $structure): string
    {
        return implode("\n", [
            'User request:',
            $prompt,
            'Selected field context:',
            wp_json_encode($this->buildFieldContext($field, $structure), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Selected builder 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'] ?? ''),
                'response_family' => (string) ($request_context['response_family'] ?? ''),
                'semantic_intent' => (string) ($request_context['semantic_intent'] ?? ''),
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Write one polished label that fits the field purpose and validation context.',
        ]);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors, array $field, array $selected_context, array $request_context, array $structure): string
    {
        return implode("\n", [
            'Your previous field label 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 field context:',
            wp_json_encode($this->buildFieldContext($field, $structure), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Selected builder 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),
            'Return corrected JSON only with a non-empty label string.',
        ]);
    }

    protected function validatePayload($decoded): array
    {
        if (!is_array($decoded)) {
            return [__('Label payload must decode to an object.', 'griffinforms-form-builder')];
        }

        $errors = [];
        $label = $this->normalizeText((string) ($decoded['label'] ?? ''), 180);
        if ($label === '') {
            $errors[] = __('Label response must include a non-empty label value.', 'griffinforms-form-builder');
        }

        return $errors;
    }

    protected function buildFieldContext(array $field, array $structure): array
    {
        $field_id = absint($field['item_id'] ?? 0);
        $field_path = $this->resolveSelectedFieldPath($field_id, $structure);
        $page = $field_path !== null ? (array) ($structure['pages'][$field_path['page_index']] ?? []) : [];
        $siblings = $this->resolveSiblingLabels($field, $structure);
        $average_length = $this->calculateAverageLabelLength($siblings);
        $field_settings = $this->normalizeContextArray((array) ($field['field_settings'] ?? []));
        $supported_file_types = [];
        if (($field['field_type'] ?? '') === 'fileupload' && !empty($field_settings['custom_mimes']) && is_array($field_settings['custom_mimes'])) {
            $supported_file_types = array_values(array_filter(array_map('sanitize_text_field', array_map(static fn($item) => (string) $item, $field_settings['custom_mimes']))));
        }

        return [
            'item_id' => $field_id,
            'field_type' => (string) ($field['field_type'] ?? ''),
            'label' => (string) ($field['label'] ?? ''),
            'description' => (string) ($field['description'] ?? ''),
            'placeholder' => (string) ($field['placeholder'] ?? ''),
            'required' => !empty($field['required']),
            'field_settings' => $field_settings,
            'supported_file_types' => $supported_file_types,
            'validations' => (array) ($field['validations'] ?? []),
            'validation_summary' => $this->buildValidationSummary((array) ($field['validations'] ?? []), (string) ($field['field_type'] ?? '')),
            'sibling_labels' => $siblings,
            'average_sibling_label_length' => $average_length,
            'form_heading' => (string) ($structure['form']['heading'] ?? ''),
            'parent_page_heading' => (string) ($page['heading'] ?? ''),
            'current_options' => (array) ($field['options'] ?? []),
        ];
    }

    protected function attachPlanContext(array $plan, array $selected_context, array $request_context, array $structure): array
    {
        $plan['selected_context'] = $selected_context;
        $plan['request_context'] = $request_context;
        $plan['structure'] = $structure;

        return $plan;
    }

    protected function resolveSiblingLabels(array $field, array $structure): array
    {
        $field_path = $this->resolveSelectedFieldPath(absint($field['item_id'] ?? 0), $structure);
        if ($field_path === null) {
            return [];
        }

        $page = (array) ($structure['pages'][$field_path['page_index']] ?? []);
        $row = (array) ($page['rows'][$field_path['row_index']] ?? []);
        $column = (array) ($row['columns'][$field_path['column_index']] ?? []);
        $labels = [];

        foreach ((array) ($column['fields'] ?? []) as $sibling) {
            $sibling = (array) $sibling;
            if (absint($sibling['item_id'] ?? 0) === absint($field['item_id'] ?? 0)) {
                continue;
            }

            $label = $this->normalizeText((string) ($sibling['label'] ?? ''), 180);
            if ($label !== '') {
                $labels[] = $label;
            }
        }

        return array_values(array_unique($labels));
    }

    protected function normalizeContextArray(array $value): array
    {
        $normalized = [];

        foreach ($value as $key => $item) {
            if ($item === null || $item === '' || $item === []) {
                continue;
            }

            if (is_array($item)) {
                $nested = $this->normalizeContextArray($item);
                if (!empty($nested)) {
                    $normalized[sanitize_key((string) $key)] = $nested;
                }
                continue;
            }

            $normalized[sanitize_key((string) $key)] = is_bool($item) || is_int($item) || is_float($item)
                ? $item
                : sanitize_text_field((string) $item);
        }

        return $normalized;
    }

    protected function resolveSelectedFieldPath(int $selected_id, array $structure): ?array
    {
        if ($selected_id <= 0) {
            return null;
        }

        foreach ((array) ($structure['pages'] ?? []) as $page_index => $page) {
            foreach ((array) ($page['rows'] ?? []) as $row_index => $row) {
                foreach ((array) ($row['columns'] ?? []) as $column_index => $column) {
                    foreach ((array) ($column['fields'] ?? []) as $field_index => $field) {
                        if (absint($field['item_id'] ?? 0) === $selected_id) {
                            return [
                                'page_index' => $page_index,
                                'row_index' => $row_index,
                                'column_index' => $column_index,
                                'field_index' => $field_index,
                            ];
                        }
                    }
                }
            }
        }

        foreach ((array) ($structure['fields'] ?? []) as $field_index => $field) {
            if (absint($field['item_id'] ?? 0) === $selected_id) {
                return [
                    'page_index' => 0,
                    'row_index' => 0,
                    'column_index' => 0,
                    'field_index' => $field_index,
                ];
            }
        }

        return null;
    }

    protected function calculateAverageLabelLength(array $labels): int
    {
        if (empty($labels)) {
            return 0;
        }

        $total = 0;
        foreach ($labels as $label) {
            $total += strlen((string) $label);
        }

        return (int) round($total / max(count($labels), 1));
    }

    protected function buildValidationSummary(array $validations, string $field_type): array
    {
        $summary = [];
        foreach ($validations as $key => $value) {
            $semantic_key = $this->semanticValidationKey((string) $key, $field_type);
            $summary[$semantic_key] = $this->normalizeValidationValue($value);
        }

        return $summary;
    }

    protected function resolveSelectedFieldSnapshot(array $selected_context, array $structure): ?array
    {
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) !== 'field') {
            return null;
        }

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

        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) === $selected_id) {
                            return (array) $field;
                        }
                    }
                }
            }
        }

        foreach ((array) ($structure['fields'] ?? []) as $field) {
            if (absint($field['item_id'] ?? 0) === $selected_id) {
                return (array) $field;
            }
        }

        return null;
    }

    protected function semanticValidationKey(string $key, string $field_type): string
    {
        $key = sanitize_key($key);

        if ($field_type === 'inputurl') {
            if ($key === 'restricted_tld') {
                return 'allowed_tlds';
            }

            if ($key === 'excluded_tld') {
                return 'blocked_tlds';
            }
        }

        return $key;
    }

    protected function normalizeValidationValue($value)
    {
        if (is_array($value)) {
            return array_map(function ($item) {
                return $this->normalizeValidationValue($item);
            }, $value);
        }

        if (is_object($value)) {
            return $this->normalizeValidationValue((array) $value);
        }

        return $value;
    }
}
