<?php

declare(strict_types=1);

final class MyronPageRoleService
{
    public const ROLES = ['home', 'search', 'not-found'];

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

    /** @return array{role:string,pageId:string,assignedAt:string,assignedByUserId:string,movedFromPageId:?string,changed:bool} */
    public function assign(string $role, string $pageId, string $assignedByUserId = ''): array
    {
        $role = $this->normalizeRole($role);
        $pageId = trim($pageId);
        if ($pageId === '') {
            throw new InvalidArgumentException('pageId is required.');
        }

        $page = $this->pdo->prepare('SELECT id FROM pages WHERE id = :page_id LIMIT 1');
        $page->execute([':page_id' => $pageId]);
        if ($page->fetchColumn() === false) {
            throw new MyronActionPreconditionError('Page not found: ' . $pageId);
        }

        $existing = $this->assignment($role);
        if (($existing['pageId'] ?? '') === $pageId) {
            return $existing + ['movedFromPageId' => null, 'changed' => false];
        }

        $assignedAt = myronNow();
        $this->pdo->beginTransaction();
        try {
            $delete = $this->pdo->prepare('DELETE FROM site_page_roles WHERE role_key = :role_key');
            $delete->execute([':role_key' => $role]);
            $insert = $this->pdo->prepare(
                'INSERT INTO site_page_roles (role_key, page_id, assigned_at, assigned_by_user_id)
                 VALUES (:role_key, :page_id, :assigned_at, :assigned_by_user_id)'
            );
            $insert->execute([
                ':role_key' => $role,
                ':page_id' => $pageId,
                ':assigned_at' => $assignedAt,
                ':assigned_by_user_id' => trim($assignedByUserId),
            ]);
            $this->pdo->commit();
        } catch (Throwable $exception) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $exception;
        }

        myronInvalidatePublicPageCache('page role assigned');
        return [
            'role' => $role,
            'pageId' => $pageId,
            'assignedAt' => $assignedAt,
            'assignedByUserId' => trim($assignedByUserId),
            'movedFromPageId' => isset($existing['pageId']) ? (string) $existing['pageId'] : null,
            'changed' => true,
        ];
    }

    /** @return array{role:string,pageId:?string,cleared:bool} */
    public function clear(string $role, ?string $expectedPageId = null): array
    {
        $role = $this->normalizeRole($role);
        $expectedPageId = trim((string) $expectedPageId);
        $existing = $this->assignment($role);
        if ($existing === null || ($expectedPageId !== '' && ($existing['pageId'] ?? '') !== $expectedPageId)) {
            return ['role' => $role, 'pageId' => $existing['pageId'] ?? null, 'cleared' => false];
        }

        $statement = $this->pdo->prepare('DELETE FROM site_page_roles WHERE role_key = :role_key AND page_id = :page_id');
        $statement->execute([':role_key' => $role, ':page_id' => (string) $existing['pageId']]);
        $cleared = $statement->rowCount() > 0;
        if ($cleared) {
            myronInvalidatePublicPageCache('page role cleared');
        }
        return ['role' => $role, 'pageId' => (string) $existing['pageId'], 'cleared' => $cleared];
    }

    /** @return array{role:string,pageId:string,assignedAt:string,assignedByUserId:string}|null */
    public function assignment(string $role): ?array
    {
        $role = $this->normalizeRole($role);
        $statement = $this->pdo->prepare(
            'SELECT role_key, page_id, assigned_at, assigned_by_user_id FROM site_page_roles WHERE role_key = :role_key LIMIT 1'
        );
        $statement->execute([':role_key' => $role]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($row)) {
            return null;
        }
        return [
            'role' => (string) $row['role_key'],
            'pageId' => (string) $row['page_id'],
            'assignedAt' => (string) $row['assigned_at'],
            'assignedByUserId' => (string) $row['assigned_by_user_id'],
        ];
    }

    /** @return list<string> */
    public function rolesForPage(string $pageId): array
    {
        $statement = $this->pdo->prepare('SELECT role_key FROM site_page_roles WHERE page_id = :page_id ORDER BY role_key ASC');
        $statement->execute([':page_id' => trim($pageId)]);
        return array_values(array_map('strval', $statement->fetchAll(PDO::FETCH_COLUMN) ?: []));
    }

    /** @return array<string, string> */
    public function assignments(): array
    {
        $rows = $this->pdo->query('SELECT role_key, page_id FROM site_page_roles ORDER BY role_key ASC')?->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $out = [];
        foreach ($rows as $row) {
            $out[(string) $row['role_key']] = (string) $row['page_id'];
        }
        return $out;
    }

    private function normalizeRole(string $role): string
    {
        $role = strtolower(trim($role));
        if (!in_array($role, self::ROLES, true)) {
            throw new InvalidArgumentException('role must be one of: ' . implode(', ', self::ROLES) . '.');
        }
        return $role;
    }
}
