<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

use GriffinForms\Admin\Sql\Format;

class GenerateRatingFieldPromptHandler extends BasePromptHandler
{
    protected const MAX_REFLECTION_RETRIES = 1;

    protected const SCALE_MIN      = 2;
    protected const SCALE_MAX      = 10;
    protected const ICON_SIZE_MIN  = 16;
    protected const ICON_SIZE_MAX  = 64;
    protected const ICON_SPACING_MIN = 0;
    protected const ICON_SPACING_MAX = 24;
    protected const VALID_ICON_GROUPS = ['bolt', 'circle', 'diamond', 'face', 'flag', 'heart', 'star', 'thumb', 'custom'];

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

    public function getUnsupportedMessage(): string
    {
        return __('I can only modify settings for a selected rating field.', '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'] : [];

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

        if ($field_id <= 0 || $field_type !== 'inputrating') {
            return $this->formatFailure(
                __('Please select a rating field before asking to modify its settings.', 'griffinforms-form-builder'),
                'selection_required',
                409,
                ['request_context' => $request_context]
            );
        }

        $format_sql = Format::getInstance();
        $field = $format_sql->itemValues($field_id, 'field', ['heading', 'field_type', 'field_settings']);

        $current_settings = $this->decodeValue($field->field_settings ?? null);
        if (!is_array($current_settings)) {
            $current_settings = $this->defaultSettings();
        }

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

            $response    = $this->requestStrict(
                $provider,
                $prompt_text,
                ['system' => $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[] = __('Rating field settings response must decode to an object.', 'griffinforms-form-builder');
            } else {
                $validation_errors = $this->validatePayload($decoded);
            }

            if (empty($validation_errors) && is_array($decoded)) {
                $new_settings = $this->normalizeSettings((array) ($decoded['field_settings'] ?? []), $current_settings);
                $format_sql->updateItemValue('field', $field_id, 'field_settings', wp_json_encode($new_settings));
                return [
                    'success'       => true,
                    'status'        => 'applied',
                    'message'       => $this->normalizeText((string) ($decoded['success_message'] ?? __('Updated the rating field settings.', 'griffinforms-form-builder')), 140),
                    'mutated_ids'   => [$field_id],
                    'prompt_key'    => $this->getPromptKey(),
                    'patch_plan'    => [],
                    'patch_summary' => [],
                ];
            }

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

        return $this->formatFailure(
            __('I could not update the rating field settings safely. Try being more specific.', 'griffinforms-form-builder'),
            'rating_settings_validation_failed',
            422,
            ['request_context' => $request_context]
        );
    }

    protected function buildSystemPrompt(): string
    {
        $icon_groups = implode(', ', self::VALID_ICON_GROUPS);

        return implode("\n", [
            'You are Griffin Assist modifying settings for an inputrating field in GriffinForms.',
            'Return JSON only.',
            'Exact shape: {"field_settings":{"scale":int,"icon_group":"string","icon_size":int,"icon_spacing":int,"color_filled":"#hex","color_empty":"#hex","custom_image_id":0,"step_image_ids":[...],"step_labels":[...],"allow_clear":0},"success_message":"string"}',
            'Rules:',
            '- scale: integer 2–10. If changed, resize step_image_ids and step_labels to match (pad with 0/"", truncate if shrinking).',
            '- icon_group: one of: ' . $icon_groups . '.',
            '- icon_size: integer 16–64.',
            '- icon_spacing: integer 0–24.',
            '- color_filled and color_empty: hex color strings (e.g. "#f5a623").',
            '- step_image_ids: array of ints, length must equal scale. Use 0 for no custom image.',
            '- step_labels: array of strings, length must equal scale. Use "" for no label.',
            '- allow_clear: 0 or 1.',
            '- Return the full field_settings object.',
            '- Do not return markdown, code fences, or required_inputs.',
        ]);
    }

    protected function buildUserPrompt(string $prompt, $field, array $current_settings, array $selected_context, array $request_context): string
    {
        return implode("\n", [
            'User request:',
            $prompt,
            'Selected rating field:',
            wp_json_encode([
                'field_id'         => absint($selected_context['item_id'] ?? 0),
                'label'            => sanitize_text_field((string) ($field->heading ?? '')),
                'current_settings' => $current_settings,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Apply the requested change. Return the full updated field_settings object.',
        ]);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors, array $current_settings): string
    {
        return implode("\n", [
            'Your previous rating settings 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,
            'Current settings for reference:',
            wp_json_encode($current_settings, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Return corrected JSON only with a valid field_settings object.',
        ]);
    }

    protected function validatePayload(array $decoded): array
    {
        $errors   = [];
        $settings = $decoded['field_settings'] ?? null;

        if (!is_array($settings)) {
            $errors[] = __('field_settings must be an object.', 'griffinforms-form-builder');
            return $errors;
        }

        $scale = isset($settings['scale']) ? (int) $settings['scale'] : null;
        if ($scale === null || $scale < self::SCALE_MIN || $scale > self::SCALE_MAX) {
            $errors[] = sprintf(__('scale must be an integer between %d and %d.', 'griffinforms-form-builder'), self::SCALE_MIN, self::SCALE_MAX);
        }

        $icon_group = isset($settings['icon_group']) ? sanitize_key((string) $settings['icon_group']) : '';
        if (!in_array($icon_group, self::VALID_ICON_GROUPS, true)) {
            $errors[] = sprintf(__('icon_group must be one of: %s.', 'griffinforms-form-builder'), implode(', ', self::VALID_ICON_GROUPS));
        }

        if (isset($settings['icon_size'])) {
            $icon_size = (int) $settings['icon_size'];
            if ($icon_size < self::ICON_SIZE_MIN || $icon_size > self::ICON_SIZE_MAX) {
                $errors[] = sprintf(__('icon_size must be between %d and %d.', 'griffinforms-form-builder'), self::ICON_SIZE_MIN, self::ICON_SIZE_MAX);
            }
        }

        if (isset($settings['icon_spacing'])) {
            $icon_spacing = (int) $settings['icon_spacing'];
            if ($icon_spacing < self::ICON_SPACING_MIN || $icon_spacing > self::ICON_SPACING_MAX) {
                $errors[] = sprintf(__('icon_spacing must be between %d and %d.', 'griffinforms-form-builder'), self::ICON_SPACING_MIN, self::ICON_SPACING_MAX);
            }
        }

        if ($scale !== null && $scale >= self::SCALE_MIN) {
            $step_image_ids = $settings['step_image_ids'] ?? null;
            $step_labels    = $settings['step_labels'] ?? null;

            if (!is_array($step_image_ids) || count($step_image_ids) !== $scale) {
                $errors[] = sprintf(__('step_image_ids must be an array with exactly %d elements (one per scale step).', 'griffinforms-form-builder'), $scale);
            }

            if (!is_array($step_labels) || count($step_labels) !== $scale) {
                $errors[] = sprintf(__('step_labels must be an array with exactly %d elements (one per scale step).', 'griffinforms-form-builder'), $scale);
            }
        }

        return $errors;
    }

    protected function normalizeSettings(array $new, array $current): array
    {
        $scale = isset($new['scale']) ? max(self::SCALE_MIN, min(self::SCALE_MAX, (int) $new['scale'])) : (int) ($current['scale'] ?? 5);

        $step_image_ids = is_array($new['step_image_ids'] ?? null) ? $new['step_image_ids'] : ($current['step_image_ids'] ?? []);
        $step_labels    = is_array($new['step_labels'] ?? null) ? $new['step_labels'] : ($current['step_labels'] ?? []);

        // Enforce array length invariant
        $step_image_ids = $this->resizeArray($step_image_ids, $scale, 0);
        $step_labels    = $this->resizeArray($step_labels, $scale, '');

        $icon_group = isset($new['icon_group']) && in_array(sanitize_key((string) $new['icon_group']), self::VALID_ICON_GROUPS, true)
            ? sanitize_key((string) $new['icon_group'])
            : sanitize_key((string) ($current['icon_group'] ?? 'star'));

        $icon_size = isset($new['icon_size'])
            ? max(self::ICON_SIZE_MIN, min(self::ICON_SIZE_MAX, (int) $new['icon_size']))
            : (int) ($current['icon_size'] ?? 32);

        $icon_spacing = isset($new['icon_spacing'])
            ? max(self::ICON_SPACING_MIN, min(self::ICON_SPACING_MAX, (int) $new['icon_spacing']))
            : (int) ($current['icon_spacing'] ?? 4);

        return [
            'scale'            => $scale,
            'icon_group'       => $icon_group,
            'icon_size'        => $icon_size,
            'icon_spacing'     => $icon_spacing,
            'color_filled'     => $this->sanitizeHexColor((string) ($new['color_filled'] ?? $current['color_filled'] ?? '#f5a623')),
            'color_empty'      => $this->sanitizeHexColor((string) ($new['color_empty'] ?? $current['color_empty'] ?? '#d1d5db')),
            'custom_image_id'  => absint($new['custom_image_id'] ?? $current['custom_image_id'] ?? 0),
            'step_image_ids'   => array_map('absint', $step_image_ids),
            'step_labels'      => array_map(fn($l) => $this->normalizeText((string) $l, 120), $step_labels),
            'allow_clear'      => (int) (bool) ($new['allow_clear'] ?? $current['allow_clear'] ?? 0),
        ];
    }

    protected function resizeArray(array $arr, int $size, $pad): array
    {
        $current_size = count($arr);
        if ($current_size === $size) {
            return $arr;
        }
        if ($current_size > $size) {
            return array_slice($arr, 0, $size);
        }
        return array_merge($arr, array_fill(0, $size - $current_size, $pad));
    }

    protected function sanitizeHexColor(string $color): string
    {
        $color = trim($color);
        if (preg_match('/^#[0-9a-fA-F]{6}$/', $color) || preg_match('/^#[0-9a-fA-F]{3}$/', $color)) {
            return strtolower($color);
        }
        return '#000000';
    }

    protected function defaultSettings(): array
    {
        return [
            'scale'           => 5,
            'icon_group'      => 'star',
            'icon_size'       => 32,
            'icon_spacing'    => 4,
            'color_filled'    => '#f5a623',
            'color_empty'     => '#d1d5db',
            'custom_image_id' => 0,
            'step_image_ids'  => [0, 0, 0, 0, 0],
            'step_labels'     => ['', '', '', '', ''],
            'allow_clear'     => 0,
        ];
    }

    protected function decodeValue($value)
    {
        if (is_array($value)) {
            return $value;
        }

        if (!is_string($value) || $value === '') {
            return $value;
        }

        $maybe_unserialized = maybe_unserialize($value);
        if ($maybe_unserialized !== $value || $value === 'b:0;') {
            return $maybe_unserialized;
        }

        $decoded_json = json_decode($value, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            return $decoded_json;
        }

        return $value;
    }
}
