<?php

declare(strict_types=1);

final class MicFormRepository
{
    public function __construct(private readonly PDO $pdo)
    {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listAll(): array
    {
        $statement = $this->pdo->query('SELECT * FROM form_definitions ORDER BY updated_at DESC, name ASC');
        return array_map([$this, 'hydrate'], $statement ? ($statement->fetchAll() ?: []) : []);
    }

    public function findById(string $id): ?array
    {
        $statement = $this->pdo->prepare('SELECT * FROM form_definitions WHERE id = :id LIMIT 1');
        $statement->execute([':id' => trim($id)]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        return is_array($row) ? $this->hydrate($row) : null;
    }

    public function create(array $data): array
    {
        $id = $this->generateFormId();
        $now = myronNow();
        $normalized = $this->normalize($data, null);
        $statement = $this->pdo->prepare(
            'INSERT INTO form_definitions (
                id, name, description, submit_button_label, success_message, fields_json,
                success_strategy, success_redirect_url, notification_email_id, embed_mode,
                cta_label, completion_routing_json, honeypot_field_name,
                created_by, created_at, updated_by, updated_at
             ) VALUES (
                :id, :name, :description, :submit_button_label, :success_message, :fields_json,
                :success_strategy, :success_redirect_url, :notification_email_id, :embed_mode,
                :cta_label, :completion_routing_json, :honeypot_field_name,
                :created_by, :created_at, :updated_by, :updated_at
             )'
        );
        $statement->execute([
            ':id' => $id,
            ':name' => $normalized['name'],
            ':description' => $normalized['description'],
            ':submit_button_label' => $normalized['submit_button_label'],
            ':success_message' => $normalized['success_message'],
            ':fields_json' => json_encode($normalized['fields'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':success_strategy' => $normalized['success_strategy'],
            ':success_redirect_url' => $normalized['success_redirect_url'],
            ':notification_email_id' => $normalized['notification_email_id'],
            ':embed_mode' => $normalized['embed_mode'],
            ':cta_label' => $normalized['cta_label'],
            ':completion_routing_json' => json_encode($normalized['completion_routing'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':honeypot_field_name' => 'mc_hp_' . bin2hex(random_bytes(4)),
            ':created_by' => (string) ($normalized['created_by'] ?? ''),
            ':created_at' => $now,
            ':updated_by' => (string) ($normalized['updated_by'] ?? ''),
            ':updated_at' => $now,
        ]);
        return $this->findById($id) ?? [];
    }

    public function update(string $id, array $data): array
    {
        $existing = $this->findById($id);
        if ($existing === null) {
            throw new InvalidArgumentException('Form not found.');
        }
        $normalized = $this->normalize(array_merge($existing, $data), $existing);
        $statement = $this->pdo->prepare(
            'UPDATE form_definitions
             SET name = :name,
                 description = :description,
                 submit_button_label = :submit_button_label,
                 success_message = :success_message,
                 fields_json = :fields_json,
                 success_strategy = :success_strategy,
                 success_redirect_url = :success_redirect_url,
                 notification_email_id = :notification_email_id,
                 embed_mode = :embed_mode,
                 cta_label = :cta_label,
                 completion_routing_json = :completion_routing_json,
                 updated_by = :updated_by,
                 updated_at = :updated_at
             WHERE id = :id'
        );
        $statement->execute([
            ':id' => trim($id),
            ':name' => $normalized['name'],
            ':description' => $normalized['description'],
            ':submit_button_label' => $normalized['submit_button_label'],
            ':success_message' => $normalized['success_message'],
            ':fields_json' => json_encode($normalized['fields'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':success_strategy' => $normalized['success_strategy'],
            ':success_redirect_url' => $normalized['success_redirect_url'],
            ':notification_email_id' => $normalized['notification_email_id'],
            ':embed_mode' => $normalized['embed_mode'],
            ':cta_label' => $normalized['cta_label'],
            ':completion_routing_json' => json_encode($normalized['completion_routing'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':updated_by' => (string) ($normalized['updated_by'] ?? ''),
            ':updated_at' => myronNow(),
        ]);
        return $this->findById($id) ?? [];
    }

    public function delete(string $id): bool
    {
        $statement = $this->pdo->prepare('DELETE FROM form_definitions WHERE id = :id');
        $statement->execute([':id' => trim($id)]);
        return $statement->rowCount() > 0;
    }

    public function clearNotificationEmail(string $notificationEmailId): void
    {
        $statement = $this->pdo->prepare('UPDATE form_definitions SET notification_email_id = NULL WHERE notification_email_id = :id');
        $statement->execute([':id' => trim($notificationEmailId)]);
    }

    /**
     * @return list<array{pageId:string,pageTitle:string,fieldPath:string}>
     */
    public function findReferencingPages(string $id): array
    {
        $token = '[mic-form-' . trim($id) . ']';
        $statement = $this->pdo->prepare(
            'SELECT pages.id AS page_id, pages.title AS page_title, page_versions.content_json
             FROM page_versions
             INNER JOIN pages ON pages.id = page_versions.page_id
             WHERE page_versions.content_json LIKE :needle
             ORDER BY pages.title ASC, pages.id ASC'
        );
        $statement->execute([':needle' => '%' . $token . '%']);
        $matches = [];
        foreach ($statement->fetchAll() ?: [] as $row) {
            $content = json_decode((string) ($row['content_json'] ?? '{}'), true);
            foreach ($this->findTokenPaths(is_array($content) ? $content : [], $token) as $path) {
                $matches[] = [
                    'pageId' => (string) ($row['page_id'] ?? ''),
                    'pageTitle' => (string) ($row['page_title'] ?? ''),
                    'fieldPath' => $path,
                ];
            }
        }
        return $matches;
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function hydrate(array $row): array
    {
        $fields = json_decode((string) ($row['fields_json'] ?? '[]'), true);
        $routing = json_decode((string) ($row['completion_routing_json'] ?? '[]'), true);
        return [
            'id' => (string) ($row['id'] ?? ''),
            'name' => (string) ($row['name'] ?? ''),
            'description' => (string) ($row['description'] ?? ''),
            'submit_button_label' => (string) ($row['submit_button_label'] ?? 'Submit'),
            'success_message' => (string) ($row['success_message'] ?? 'Thanks for submitting.'),
            'fields' => is_array($fields) ? array_values($fields) : [],
            'success_strategy' => (string) ($row['success_strategy'] ?? 'inline'),
            'success_redirect_url' => (string) ($row['success_redirect_url'] ?? ''),
            'notification_email_id' => (string) ($row['notification_email_id'] ?? ''),
            'embed_mode' => (string) ($row['embed_mode'] ?? 'inline'),
            'cta_label' => (string) ($row['cta_label'] ?? ''),
            'completion_routing' => is_array($routing) ? $routing : [],
            'honeypot_field_name' => (string) ($row['honeypot_field_name'] ?? ''),
            'created_by' => (string) ($row['created_by'] ?? ''),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_by' => (string) ($row['updated_by'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function normalize(array $data, ?array $existing): array
    {
        $name = trim((string) ($data['name'] ?? ''));
        if ($name === '') {
            throw new InvalidArgumentException('Form name is required.');
        }
        $embedMode = $this->enum((string) ($data['embed_mode'] ?? 'inline'), ['inline', 'cta-modal'], 'inline');
        $ctaLabel = trim((string) ($data['cta_label'] ?? ''));
        if ($embedMode === 'cta-modal' && $ctaLabel === '') {
            $ctaLabel = 'Open form';
        }
        return [
            'name' => $name,
            'description' => trim((string) ($data['description'] ?? '')),
            'submit_button_label' => trim((string) ($data['submit_button_label'] ?? 'Submit')) ?: 'Submit',
            'success_message' => trim((string) ($data['success_message'] ?? 'Thanks for submitting.')) ?: 'Thanks for submitting.',
            'fields' => $this->normalizeFields(is_array($data['fields'] ?? null) ? $data['fields'] : []),
            'success_strategy' => $this->enum((string) ($data['success_strategy'] ?? 'inline'), ['inline', 'redirect', 'modal-close'], 'inline'),
            'success_redirect_url' => trim((string) ($data['success_redirect_url'] ?? '')),
            'notification_email_id' => trim((string) ($data['notification_email_id'] ?? '')),
            'embed_mode' => $embedMode,
            'cta_label' => $ctaLabel,
            'completion_routing' => is_array($data['completion_routing'] ?? null) ? $data['completion_routing'] : [],
            'created_by' => (string) ($data['created_by'] ?? ($existing['created_by'] ?? '')),
            'updated_by' => (string) ($data['updated_by'] ?? ''),
        ];
    }

    private function normalizeFields(array $fields): array
    {
        $normalized = [];
        foreach ($fields as $index => $field) {
            if (!is_array($field)) {
                continue;
            }
            $label = myronRichtextSanitize((string) ($field['label'] ?? ''), ['singleLine' => true]);
            if ($label === '') {
                continue;
            }
            $type = $this->enum((string) ($field['type'] ?? 'text'), ['text', 'email', 'textarea', 'select', 'checkbox', 'radio', 'number', 'tel', 'url'], 'text');
            $key = trim((string) ($field['key'] ?? ''));
            if ($key === '') {
                $key = str_replace('-', '_', myronNormalizeSlug(strip_tags($label)));
            }
            $options = [];
            foreach (is_array($field['options'] ?? null) ? $field['options'] : [] as $option) {
                $value = trim((string) (is_array($option) ? ($option['value'] ?? $option['label'] ?? '') : $option));
                $optionLabel = trim((string) (is_array($option) ? ($option['label'] ?? $value) : $option));
                if ($value !== '') {
                    $options[] = ['value' => $value, 'label' => $optionLabel !== '' ? $optionLabel : $value];
                }
            }
            $normalized[] = [
                'key' => $key !== '' ? $key : 'field_' . ((int) $index + 1),
                'label' => $label,
                'help_text' => myronRichtextSanitize((string) ($field['help_text'] ?? ''), ['singleLine' => true]),
                'type' => $type,
                'required' => !empty($field['required']),
                'placeholder' => trim((string) ($field['placeholder'] ?? '')),
                'validation' => is_array($field['validation'] ?? null) ? $field['validation'] : [],
                'options' => $options,
            ];
        }
        return $normalized !== [] ? $normalized : [[
            'key' => 'email',
            'label' => 'Email',
            'help_text' => '',
            'type' => 'email',
            'required' => true,
            'placeholder' => 'you@example.com',
            'validation' => [],
            'options' => [],
        ]];
    }

    private function generateFormId(): string
    {
        do {
            $id = 'mic_' . bin2hex(random_bytes(8));
        } while ($this->findById($id) !== null);
        return $id;
    }

    private function enum(string $value, array $allowed, string $fallback): string
    {
        $value = trim($value);
        return in_array($value, $allowed, true) ? $value : $fallback;
    }

    private function findTokenPaths(array $value, string $token, string $path = '$'): array
    {
        $matches = [];
        foreach ($value as $key => $child) {
            $childPath = $path . '.' . (string) $key;
            if (is_string($child) && str_contains($child, $token)) {
                $matches[] = $childPath;
            } elseif (is_array($child)) {
                $matches = array_merge($matches, $this->findTokenPaths($child, $token, $childPath));
            }
        }
        return $matches;
    }
}
