<?php

namespace WPSN\Modules\ImportExport;

defined('ABSPATH') || exit;

final class ImportAssistChecklistStateRepository
{
    private const OPTION_NAME = 'wpsn_import_assist_checklist_state';

    /**
     * @return array<string,array<string,array{state:string,updated_at:string}>>
     */
    public function all(): array
    {
        $raw = get_option(self::OPTION_NAME, []);
        if (!is_array($raw)) {
            return [];
        }

        $out = [];
        foreach ($raw as $entryId => $tasks) {
            $entryId = $this->normalizeEntryId((string) $entryId);
            if ($entryId === '' || !is_array($tasks)) {
                continue;
            }

            foreach ($tasks as $taskKey => $row) {
                $taskKey = sanitize_key((string) $taskKey);
                if ($taskKey === '') {
                    continue;
                }

                $normalized = $this->normalizeRow($row);
                if ($normalized === null) {
                    continue;
                }

                $out[$entryId][$taskKey] = $normalized;
            }
        }

        return $out;
    }

    /**
     * @param array<int,array<string,mixed>> $tasks
     * @return array<int,array<string,mixed>>
     */
    public function apply(array $tasks, string $entryId): array
    {
        $entryId = $this->normalizeEntryId($entryId);
        if ($entryId === '') {
            return $tasks;
        }

        $states = $this->all()[$entryId] ?? [];
        $validKeys = [];

        foreach ($tasks as $index => $task) {
            if (!is_array($task)) {
                continue;
            }

            $key = isset($task['key']) ? sanitize_key((string) $task['key']) : '';
            if ($key === '') {
                $tasks[$index]['checked'] = false;
                continue;
            }

            $validKeys[$key] = true;
            $row = $states[$key] ?? null;
            $checked = is_array($row) && (($row['state'] ?? '') === 'checked');
            $tasks[$index]['checked'] = $checked;
            if ($checked && !empty($row['updated_at'])) {
                $tasks[$index]['checked_at'] = (string) $row['updated_at'];
            }
        }

        $this->cleanupUnknownKeys($entryId, array_keys($validKeys));

        return $tasks;
    }

    public function checkedCount(string $entryId, array $tasks = []): int
    {
        $entryId = $this->normalizeEntryId($entryId);
        if ($entryId === '') {
            return 0;
        }

        $states = $this->all()[$entryId] ?? [];
        if (empty($tasks)) {
            return count($states);
        }

        $count = 0;
        foreach ($tasks as $task) {
            if (!is_array($task)) {
                continue;
            }
            $key = isset($task['key']) ? sanitize_key((string) $task['key']) : '';
            if ($key !== '' && isset($states[$key])) {
                $count++;
            }
        }

        return $count;
    }

    public function isChecked(string $entryId, string $taskKey): bool
    {
        $entryId = $this->normalizeEntryId($entryId);
        $taskKey = sanitize_key($taskKey);
        if ($entryId === '' || $taskKey === '') {
            return false;
        }

        return isset($this->all()[$entryId][$taskKey]);
    }

    public function setChecked(string $entryId, string $taskKey, bool $checked): bool
    {
        $entryId = $this->normalizeEntryId($entryId);
        $taskKey = sanitize_key($taskKey);
        if ($entryId === '' || $taskKey === '') {
            return false;
        }

        $all = $this->all();
        if ($checked) {
            $all[$entryId][$taskKey] = [
                'state' => 'checked',
                'updated_at' => gmdate('Y-m-d H:i:s'),
            ];
        } else {
            unset($all[$entryId][$taskKey]);
            if (empty($all[$entryId])) {
                unset($all[$entryId]);
            }
        }

        return $this->persist($all);
    }

    /**
     * @param array<int,string> $taskKeys
     */
    public function setCheckedMany(string $entryId, array $taskKeys, bool $checked): int
    {
        $entryId = $this->normalizeEntryId($entryId);
        if ($entryId === '') {
            return 0;
        }

        $taskKeys = $this->normalizeTaskKeys($taskKeys);
        if (empty($taskKeys)) {
            return 0;
        }

        $all = $this->all();
        $changed = 0;

        if ($checked) {
            foreach ($taskKeys as $taskKey) {
                $existing = $all[$entryId][$taskKey] ?? null;
                if (is_array($existing) && (($existing['state'] ?? '') === 'checked')) {
                    continue;
                }
                $all[$entryId][$taskKey] = [
                    'state' => 'checked',
                    'updated_at' => gmdate('Y-m-d H:i:s'),
                ];
                $changed++;
            }
        } else {
            if (empty($all[$entryId])) {
                return 0;
            }
            foreach ($taskKeys as $taskKey) {
                if (!isset($all[$entryId][$taskKey])) {
                    continue;
                }
                unset($all[$entryId][$taskKey]);
                $changed++;
            }
            if (empty($all[$entryId])) {
                unset($all[$entryId]);
            }
        }

        if ($changed > 0) {
            $this->persist($all);
        }

        return $changed;
    }

    public function clearEntry(string $entryId): bool
    {
        $entryId = $this->normalizeEntryId($entryId);
        if ($entryId === '') {
            return false;
        }

        $all = $this->all();
        if (!isset($all[$entryId])) {
            return true;
        }

        unset($all[$entryId]);
        return $this->persist($all);
    }

    public function clear(): bool
    {
        return delete_option(self::OPTION_NAME);
    }

    /**
     * @param array<int,string> $validKeys
     */
    private function cleanupUnknownKeys(string $entryId, array $validKeys): void
    {
        $entryId = $this->normalizeEntryId($entryId);
        if ($entryId === '') {
            return;
        }

        $valid = [];
        foreach ($validKeys as $key) {
            $key = sanitize_key((string) $key);
            if ($key !== '') {
                $valid[$key] = true;
            }
        }

        $all = $this->all();
        if (empty($all[$entryId])) {
            return;
        }

        $changed = false;
        foreach (array_keys($all[$entryId]) as $taskKey) {
            if (isset($valid[$taskKey])) {
                continue;
            }
            unset($all[$entryId][$taskKey]);
            $changed = true;
        }

        if (empty($all[$entryId])) {
            unset($all[$entryId]);
            $changed = true;
        }

        if ($changed) {
            $this->persist($all);
        }
    }

    /**
     * @param mixed $row
     * @return array{state:string,updated_at:string}|null
     */
    private function normalizeRow($row): ?array
    {
        if (!is_array($row)) {
            return null;
        }

        $state = isset($row['state']) ? sanitize_key((string) $row['state']) : '';
        if ($state !== 'checked') {
            return null;
        }

        $updatedAt = isset($row['updated_at']) ? trim((string) $row['updated_at']) : '';
        if ($updatedAt === '') {
            $updatedAt = gmdate('Y-m-d H:i:s');
        }

        return [
            'state' => 'checked',
            'updated_at' => $updatedAt,
        ];
    }

    /**
     * @param array<int,string> $taskKeys
     * @return array<int,string>
     */
    private function normalizeTaskKeys(array $taskKeys): array
    {
        $normalized = [];
        foreach ($taskKeys as $taskKey) {
            $taskKey = sanitize_key((string) $taskKey);
            if ($taskKey !== '') {
                $normalized[$taskKey] = true;
            }
        }

        return array_keys($normalized);
    }

    private function normalizeEntryId(string $entryId): string
    {
        $entryId = strtolower(trim($entryId));
        return preg_replace('/[^a-z0-9_-]/', '', $entryId) ?? '';
    }

    /**
     * @param array<string,array<string,array{state:string,updated_at:string}>> $rows
     */
    private function persist(array $rows): bool
    {
        return update_option(self::OPTION_NAME, $rows, false);
    }
}
