<?php

namespace App\Services;

use App\Models\FormTranslation;
use App\Services\TranslationDiff\BaseTranslationDiffService;

/**
 * Builds a user-friendly diff between two FormTranslation rows for the
 * franchise update/reset preview dialog. The output is intentionally NOT
 * a generic property-by-property diff: it groups changes into Form Details
 * and Form Fields, summarises added/removed fields as a single entry, and
 * skips technical/internal flags.
 */
class FormTranslationDiffService extends BaseTranslationDiffService
{
    private const GROUP_DETAILS = 'Form Details';
    private const GROUP_FIELDS = 'Form Fields';

    public function diff(FormTranslation $franchiseTranslation, FormTranslation $globalTranslation): array
    {
        return array_values(array_merge(
            $this->diffFormDetails($franchiseTranslation, $globalTranslation),
            $this->diffPages($franchiseTranslation, $globalTranslation),
            $this->diffFormFields($franchiseTranslation, $globalTranslation),
        ));
    }

    private function diffFormDetails(FormTranslation $franchise, FormTranslation $global): array
    {
        $changes = [];

        $simple = [
            'name' => 'Form Name',
            'description' => 'Description',
        ];
        foreach ($simple as $attr => $label) {
            $old = $franchise->getAttribute($attr);
            $new = $global->getAttribute($attr);
            if ($this->stringDiffers($old, $new)) {
                $changes[] = $this->makeChange(
                    self::GROUP_DETAILS . " — {$label} changed",
                    $old,
                    $new
                );
            }
        }

        $oldSetting = is_array($franchise->form_setting) ? $franchise->form_setting : [];
        $newSetting = is_array($global->form_setting) ? $global->form_setting : [];

        $settingFields = [
            'success_message' => 'Success Message',
            'featured_image' => 'Featured Image',
        ];
        foreach ($settingFields as $key => $label) {
            $old = $oldSetting[$key] ?? null;
            $new = $newSetting[$key] ?? null;
            if ($this->stringDiffers($old, $new)) {
                $changes[] = $this->makeChange(
                    self::GROUP_DETAILS . " — {$label} changed",
                    $old,
                    $new
                );
            }
        }

        return $changes;
    }

    private function diffPages(FormTranslation $franchise, FormTranslation $global): array
    {
        $oldPages = $this->normalizeList($franchise->pages);
        $newPages = $this->normalizeList($global->pages);

        if (json_encode($oldPages) === json_encode($newPages)) {
            return [];
        }

        return [$this->makeChange(
            self::GROUP_DETAILS . ' — Display Pages changed',
            $this->formatList($oldPages),
            $this->formatList($newPages)
        )];
    }

    private function diffFormFields(FormTranslation $franchise, FormTranslation $global): array
    {
        $oldFields = $this->normalizeFieldsContainer($franchise->fields);
        $newFields = $this->normalizeFieldsContainer($global->fields);

        $changes = [];

        $oldSteps = $oldFields['steps'] ?? [];
        $newSteps = $newFields['steps'] ?? [];
        $maxSteps = max(count($oldSteps), count($newSteps));

        for ($i = 0; $i < $maxSteps; $i++) {
            $stepNumber = $i + 1;
            $oldStep = $oldSteps[$i] ?? null;
            $newStep = $newSteps[$i] ?? null;

            if ($oldStep && !$newStep) {
                $changes[] = $this->makeChange(
                    self::GROUP_FIELDS . " — Step {$stepNumber}: Step removed",
                    $oldStep['heading'] ?? "Step {$stepNumber}",
                    null
                );
                continue;
            }
            if (!$oldStep && $newStep) {
                $changes[] = $this->makeChange(
                    self::GROUP_FIELDS . " — Step {$stepNumber}: Step added",
                    null,
                    $newStep['heading'] ?? "Step {$stepNumber}"
                );
            }

            $changes = array_merge($changes, $this->diffStepFields($stepNumber, $oldStep, $newStep));
        }

        return $changes;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function diffStepFields(int $stepNumber, ?array $oldStep, ?array $newStep): array
    {
        $oldFields = is_array($oldStep['fields'] ?? null) ? $oldStep['fields'] : [];
        $newFields = is_array($newStep['fields'] ?? null) ? $newStep['fields'] : [];

        $oldByKey = $this->indexFieldsByStableKey($oldFields);
        $newByKey = $this->indexFieldsByStableKey($newFields);

        $changes = [];

        foreach ($oldByKey as $key => $oldField) {
            if (isset($newByKey[$key])) {
                continue;
            }
            $name = $this->fieldName($oldField);
            $changes[] = $this->makeChange(
                self::GROUP_FIELDS . " — Step {$stepNumber}: Field removed — {$name}",
                $this->describeField($oldField),
                null
            );
        }

        foreach ($newByKey as $key => $newField) {
            if (isset($oldByKey[$key])) {
                continue;
            }
            $name = $this->fieldName($newField);
            $changes[] = $this->makeChange(
                self::GROUP_FIELDS . " — Step {$stepNumber}: New field added — {$name}",
                null,
                $this->describeField($newField)
            );
        }

        $userFacingAttrs = [
            'field' => 'Label',
            'placeholder' => 'Placeholder',
            'required' => 'Required',
            'text' => 'Text',
            'rows' => 'Rows',
            'options' => 'Options',
        ];

        foreach ($oldByKey as $key => $oldField) {
            if (!isset($newByKey[$key])) {
                continue;
            }
            $newField = $newByKey[$key];
            $name = $this->fieldName($newField) ?: $this->fieldName($oldField);

            foreach ($userFacingAttrs as $attr => $label) {
                $oldVal = $oldField[$attr] ?? null;
                $newVal = $newField[$attr] ?? null;

                if (!$this->valueDiffers($oldVal, $newVal)) {
                    continue;
                }

                if ($attr === 'options') {
                    $changes[] = $this->makeChange(
                        self::GROUP_FIELDS . " — Step {$stepNumber}: {$name} — {$label} changed",
                        $this->formatList($oldVal),
                        $this->formatList($newVal)
                    );
                    continue;
                }

                if ($attr === 'required') {
                    $changes[] = $this->makeChange(
                        self::GROUP_FIELDS . " — Step {$stepNumber}: {$name} — {$label} changed",
                        $this->boolText($oldVal),
                        $this->boolText($newVal)
                    );
                    continue;
                }

                $changes[] = $this->makeChange(
                    self::GROUP_FIELDS . " — Step {$stepNumber}: {$name} — {$label} changed",
                    $oldVal,
                    $newVal
                );
            }
        }

        return $changes;
    }

    /**
     * Build a stable lookup key for each field: prefer `id` (string keys like
     * 'name', 'email', or UUIDs are common); fall back to type+order so the
     * matcher still pairs fields when ids are missing or have changed.
     *
     * @param  array<int, array<string, mixed>>  $fields
     * @return array<string, array<string, mixed>>
     */
    private function indexFieldsByStableKey(array $fields): array
    {
        $byKey = [];
        foreach ($fields as $idx => $field) {
            $id = $field['id'] ?? null;
            if (is_string($id) && $id !== '') {
                $byKey[$id] = $field;
                continue;
            }
            $byKey['pos:' . $idx] = $field;
        }
        return $byKey;
    }

    private function describeField(array $field): string
    {
        $lines = [];

        $type = (string) ($field['type'] ?? '');
        if ($type !== '') {
            $lines[] = 'Type: ' . $this->humanType($type);
        }

        $placeholder = (string) ($field['placeholder'] ?? '');
        if ($placeholder !== '') {
            $lines[] = 'Placeholder: ' . $placeholder;
        }

        if (array_key_exists('required', $field)) {
            $lines[] = 'Required: ' . $this->boolText($field['required']);
        }

        if (!empty($field['options']) && is_array($field['options'])) {
            $lines[] = 'Options: ' . $this->formatList($field['options']);
        }

        $rows = $field['rows'] ?? null;
        if (!empty($rows) && (is_int($rows) || is_string($rows))) {
            $lines[] = 'Rows: ' . $rows;
        }

        $text = (string) ($field['text'] ?? '');
        if ($text !== '') {
            $lines[] = 'Text: ' . $text;
        }

        return implode("\n", $lines);
    }

    private function fieldName(array $field): string
    {
        $candidates = [
            $field['field'] ?? null,
            $field['label'] ?? null,
            $field['id'] ?? null,
        ];
        foreach ($candidates as $candidate) {
            if (is_string($candidate) && trim($candidate) !== '') {
                return trim($candidate);
            }
        }
        return 'Untitled';
    }

    private function humanType(string $type): string
    {
        $map = [
            'text' => 'Text',
            'email' => 'Email',
            'textarea' => 'Textarea',
            'select' => 'Select',
            'radio' => 'Radio',
            'checkbox' => 'Checkbox',
            'number' => 'Number',
            'date' => 'Date',
            'cta' => 'Submit Button',
            'agree' => 'Agreement',
        ];
        return $map[$type] ?? ucfirst($type);
    }

    private function boolText(mixed $value): string
    {
        if (is_string($value)) {
            $low = strtolower(trim($value));
            if (in_array($low, ['1', 'true', 'yes', 'on'], true)) {
                return 'Yes';
            }
            return 'No';
        }
        return $value ? 'Yes' : 'No';
    }

    /**
     * @param  mixed  $value
     * @return array<int, mixed>
     */
    private function normalizeList(mixed $value): array
    {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return array_values($decoded);
            }
        }
        if (!is_array($value)) {
            return [];
        }
        return array_values($value);
    }

    private function formatList(mixed $value): string
    {
        $list = $this->normalizeList($value);
        if (empty($list)) {
            return '';
        }
        return implode(', ', array_map(
            static fn($item) => is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE) : (string) $item,
            $list
        ));
    }

    /**
     * @return array<string, mixed>
     */
    private function normalizeFieldsContainer(mixed $value): array
    {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }
        return is_array($value) ? $value : [];
    }

    private function stringDiffers(mixed $a, mixed $b): bool
    {
        if (is_null($a) && is_null($b)) {
            return false;
        }
        if (is_array($a) || is_array($b)) {
            return $this->valueDiffers($a, $b);
        }
        return (string) $a !== (string) $b;
    }

    private function valueDiffers(mixed $a, mixed $b): bool
    {
        if (is_array($a) || is_array($b)) {
            return json_encode($a, JSON_UNESCAPED_UNICODE) !== json_encode($b, JSON_UNESCAPED_UNICODE);
        }
        return $this->stringDiffers($a, $b);
    }

    private function makeChange(string $label, mixed $old, mixed $new): array
    {
        return [
            'field' => $label,
            'label' => $label,
            'old' => $old,
            'new' => $new,
            'changed' => true,
            'type' => 'text',
        ];
    }

    protected function includedFields(): array
    {
        return [];
    }

    protected function fieldLabels(): array
    {
        return [];
    }
}
