<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

use GriffinForms\Admin\Sql\Format;
use GriffinForms\Includes\Ai\PatchPlanParser;
use GriffinForms\Includes\Ai\PatchPlanApplier;

class GeneratePageLayoutPlanPromptHandler 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_page_layout_plan';
    }

    public function getUnsupportedMessage(): string
    {
        return __('Select a page to rearrange its layout.', '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'] : [];

        $page_id = absint($selected_context['item_id'] ?? 0);
        if ($page_id <= 0) {
            return $this->formatFailure(
                __('Select a page first, then try again.', 'griffinforms-form-builder'),
                'selection_required',
                409,
                ['request_context' => $request_context]
            );
        }

        $format_sql = Format::getInstance();

        // Build full page layout tree with field IDs, types, and labels.
        $layout = $this->buildPageLayoutTree($page_id, $form_id, $format_sql);
        if ($layout === null) {
            return $this->formatFailure(
                __('Griffin Assist could not read the page structure. Try again.', 'griffinforms-form-builder'),
                'layout_read_failed',
                500,
                ['request_context' => $request_context]
            );
        }

        $form = $format_sql->itemValues($form_id, 'form', ['name', 'heading']);

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

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

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

            if ($extract_error !== null) {
                $last_errors = [$extract_error];
                $this->logRetryFailure($this->getPromptKey(), $attempt + 1, $last_errors, $raw_content);
                continue;
            }

            if (!is_array($decoded)) {
                $last_errors = [__('Layout plan response must decode to an object.', 'griffinforms-form-builder')];
                $this->logRetryFailure($this->getPromptKey(), $attempt + 1, $last_errors, $raw_content);
                continue;
            }

            [$plan, $parse_errors] = $this->parser->parse($decoded, [], [
                'request_context'  => $request_context,
                'selected_context' => $selected_context,
            ]);

            if (!empty($parse_errors) || $plan === null) {
                $last_errors = $parse_errors ?: [__('Layout plan could not be parsed.', 'griffinforms-form-builder')];
                $this->logRetryFailure($this->getPromptKey(), $attempt + 1, $last_errors, $raw_content);
                continue;
            }

            if (($plan['mode'] ?? '') === 'no_op') {
                return [
                    'success'     => true,
                    'status'      => 'text',
                    'message'     => (string) ($plan['message'] !== '' ? $plan['message'] : __('The layout looks good — no changes needed.', 'griffinforms-form-builder')),
                    'mutated_ids' => [],
                    'prompt_key'  => $this->getPromptKey(),
                    'patch_plan'  => $plan,
                ];
            }

            $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'] ?? __('Layout updated.', 'griffinforms-form-builder')),
                    'mutated_ids'   => (array) ($apply_result['mutated_ids'] ?? []),
                    'prompt_key'    => $this->getPromptKey(),
                    'patch_plan'    => $plan,
                    'patch_summary' => [],
                ];
            }

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

        return $this->formatFailure(
            __('I could not rearrange the layout safely. Try giving me more specific instructions.', 'griffinforms-form-builder'),
            'layout_plan_validation_failed',
            422,
            ['request_context' => $request_context]
        );
    }

    protected function buildSystemPrompt(): string
    {
        return implode("\n", [
            'You are Griffin Assist in layout-rearrangement mode for GriffinForms.',
            'Return JSON only. No markdown fences. No commentary outside JSON.',
            'Top-level mode must be one of: patch, no_op.',
            'Patch plans must use only these operations: move_field, move_column, update_column, insert_column, remove_column, remove_row, move_row, remove_field.',
            'Targets must use semantic scopes: page, row, column, field.',
            '',
            'Layout Rules:',
            '- Your goal is a clean, balanced layout on the selected page. You may reorder rows, redistribute fields across columns, change column widths, and remove empty columns or rows.',
            '- Empty column: a column with field_count 0. Remove it unless it is the only column in the row.',
            '- Empty row: a row where every column has field_count 0. Remove it entirely.',
            '- When a row has exactly 1 column (width 12) with 2 or more fields, consider splitting it into a 2-column layout (6+6). To do this you MUST emit three operations in this exact order: (1) update_column on the existing column to set width to 6, (2) insert_column on the row with width 6, (3) move_field to move one field into the new column. Omitting the update_column step will break the 12-grid rule. Only split when the fields are peer-level inputs that benefit from side-by-side placement.',
            '- When a row has 2 or more columns but most are empty, consolidate the remaining fields into a single column and remove the empty ones.',
            '- Do not change column widths unless the resulting widths sum to exactly 12 per row.',
            '- Do not move fields across page boundaries — only rearrange within the selected page.',
            '- Prefer minimal changes: if the layout is already clean, return no_op.',
            '',
            'move_field cross-column shape:',
            '{"op":"move_field","target":{"scope":"field","item_id":<field_id>},"anchor":{"scope":"column","item_id":<target_column_id>},"position":"last"}',
            '',
            'update_column shape:',
            '{"op":"update_column","target":{"scope":"column","item_id":<col_id>},"changes":{"width":<int>}}',
            '',
            'remove_column shape:',
            '{"op":"remove_column","target":{"scope":"column","item_id":<col_id>}}',
            '',
            'remove_row shape:',
            '{"op":"remove_row","target":{"scope":"row","item_id":<row_id>}}',
            '',
            'insert_column shape (adds a column to a row):',
            '{"op":"insert_column","target":{"scope":"row","item_id":<row_id>},"column":{"width":<int>}}',
            '',
            'success_message must describe the layout changes made (e.g. "Reorganised 3 rows into a cleaner 2-column layout."). Never write user-facing form copy.',
            '',
            'REQUIRED response shape — top level must have "mode" and "operations" keys:',
            '{"mode":"patch","success_message":"...","operations":[{"op":"update_column","target":{"scope":"column","item_id":100},"changes":{"width":6}},{"op":"insert_column","target":{"scope":"row","item_id":200},"column":{"width":6}},{"op":"move_field","target":{"scope":"field","item_id":300},"anchor":{"scope":"column","item_id":101},"position":"last"}]}',
            'For no changes: {"mode":"no_op","message":"The layout is already clean."}',
            'NEVER use "op" as the top-level key. NEVER use "commands" instead of "operations".',
            'Column widths in a row MUST sum to exactly 12. When inserting a new column, always reduce existing column widths first so the total remains 12.',
        ]);
    }

    protected function buildUserPrompt(string $prompt, $form, array $layout): string
    {
        return implode("\n", [
            'User request:',
            $prompt,
            'Form context:',
            wp_json_encode([
                'form_name'    => sanitize_text_field((string) ($form->name ?? '')),
                'form_heading' => sanitize_text_field((string) ($form->heading ?? '')),
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Page layout (rows → columns → fields):',
            wp_json_encode($layout, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
            'Return a Griffin Assist layout patch plan JSON object.',
        ]);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors): string
    {
        return implode("\n", [
            'Your previous layout plan was invalid.',
            'Original request:',
            $prompt,
            'Validation errors:',
            wp_json_encode(array_values($errors), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Previous raw response:',
            $raw_content,
            'Return corrected JSON only.',
        ]);
    }

    /**
     * Builds a full layout tree for the given page:
     * [{"row_id":int,"heading":"","columns":[{"col_id":int,"width":int,"fields":[{"field_id":int,"field_type":"","label":""}]}]}]
     */
    protected function buildPageLayoutTree(int $page_id, int $form_id, Format $format_sql): ?array
    {
        $row_ids = $this->decodeValue($format_sql->getItemValue($page_id, 'pagerows', 'formpage'));
        $row_ids = is_array($row_ids) ? array_values(array_map('absint', $row_ids)) : [];

        $rows = [];
        foreach ($row_ids as $row_id) {
            $row_data   = $format_sql->itemValues($row_id, 'pagerow', ['heading', 'rowcolumns']);
            $col_ids    = $this->decodeValue($row_data->rowcolumns ?? []);
            $col_ids    = is_array($col_ids) ? array_values(array_map('absint', $col_ids)) : [];

            $columns = [];
            foreach ($col_ids as $col_id) {
                $col_data  = $format_sql->itemValues($col_id, 'rowcolumn', ['width', 'fields']);
                $field_ids = $this->decodeValue($col_data->fields ?? []);
                $field_ids = is_array($field_ids) ? array_values(array_filter(array_map('absint', $field_ids), static fn($id) => $id > 0)) : [];

                $fields = [];
                foreach ($field_ids as $field_id) {
                    $fdata    = $format_sql->itemValues($field_id, 'field', ['field_type', 'heading']);
                    $fields[] = [
                        'field_id'   => $field_id,
                        'field_type' => sanitize_key((string) ($fdata->field_type ?? '')),
                        'label'      => sanitize_text_field((string) ($fdata->heading ?? '')),
                    ];
                }

                $columns[] = [
                    'col_id'      => $col_id,
                    'width'       => absint($col_data->width ?? 12),
                    'field_count' => count($fields),
                    'fields'      => $fields,
                ];
            }

            $rows[] = [
                'row_id'  => $row_id,
                'heading' => sanitize_text_field((string) ($row_data->heading ?? '')),
                'columns' => $columns,
            ];
        }

        return $rows;
    }

    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;
        }

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

        return $value;
    }
}
