<?php
declare(strict_types=1);

namespace App\Theme;

use PDO;
use RuntimeException;

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

    /** @return list<array<string,mixed>> */
    public function all(): array
    {
        $rows = $this->pdo->query('SELECT id,name,slug,description,tokens_json,is_active,created_by,created_at,updated_at FROM theme_profiles ORDER BY is_active DESC,updated_at DESC,id DESC')->fetchAll(PDO::FETCH_ASSOC);
        return array_values(array_filter(array_map(fn(array $row): ?array => $this->decode($row), $rows)));
    }

    /** @return array<string,mixed>|null */
    public function find(int $id): ?array
    {
        if ($id <= 0) return null;
        $stmt=$this->pdo->prepare('SELECT id,name,slug,description,tokens_json,is_active,created_by,created_at,updated_at FROM theme_profiles WHERE id=:id LIMIT 1');
        $stmt->execute(['id'=>$id]); $row=$stmt->fetch(PDO::FETCH_ASSOC);
        return is_array($row) ? $this->decode($row) : null;
    }

    /** @param array<string,mixed> $tokens */
    public function create(string $name, string $description, array $tokens, ?int $createdBy = null): int
    {
        $slug=$this->uniqueSlug($name); $json=$this->encodeTokens($tokens);
        $stmt=$this->pdo->prepare('INSERT INTO theme_profiles (name,slug,description,tokens_json,is_active,created_by,created_at,updated_at) VALUES (:name,:slug,:description,:tokens,0,:created_by,NOW(),NOW())');
        $stmt->execute(['name'=>$name,'slug'=>$slug,'description'=>$description!==''?$description:null,'tokens'=>$json,'created_by'=>$createdBy&&$createdBy>0?$createdBy:null]);
        return (int)$this->pdo->lastInsertId();
    }

    /** @param array<string,mixed> $tokens */
    public function update(int $id, string $name, string $description, array $tokens, ?int $actorId = null): void
    {
        $current=$this->find($id); if($current===null)throw new RuntimeException('Theme profile not found.');
        $this->createVersion($id,(string)$current['name'],(array)$current['tokens'],$actorId);
        $stmt=$this->pdo->prepare('UPDATE theme_profiles SET name=:name,description=:description,tokens_json=:tokens,updated_at=NOW() WHERE id=:id');
        $stmt->execute(['id'=>$id,'name'=>$name,'description'=>$description!==''?$description:null,'tokens'=>$this->encodeTokens($tokens)]);
    }

    public function delete(int $id): void
    {
        $profile = $this->find($id);
        if ($profile === null) throw new RuntimeException('Theme profile not found.');
        if (!empty($profile['is_active'])) throw new RuntimeException('The active theme profile cannot be deleted.');
        $stmt = $this->pdo->prepare('DELETE FROM theme_profiles WHERE id=:id AND is_active=0');
        $stmt->execute(['id' => $id]);
    }

    public function clearActive(): void
    {
        $this->pdo->exec('UPDATE theme_profiles SET is_active=0 WHERE is_active<>0');
    }

    public function activate(int $id): void
    {
        if($this->find($id)===null)throw new RuntimeException('Theme profile not found.');
        $owns=!$this->pdo->inTransaction(); if($owns)$this->pdo->beginTransaction();
        try{$this->pdo->exec('UPDATE theme_profiles SET is_active=0 WHERE is_active<>0');$stmt=$this->pdo->prepare('UPDATE theme_profiles SET is_active=1,updated_at=NOW() WHERE id=:id');$stmt->execute(['id'=>$id]);if($owns)$this->pdo->commit();}
        catch(\Throwable $e){if($owns&&$this->pdo->inTransaction())$this->pdo->rollBack();throw $e;}
    }

    /** @return list<array<string,mixed>> */
    public function versions(int $profileId): array
    {
        $stmt=$this->pdo->prepare('SELECT id,theme_profile_id,version_number,name,tokens_json,created_by,created_at FROM theme_profile_versions WHERE theme_profile_id=:id ORDER BY version_number DESC');
        $stmt->execute(['id'=>$profileId]);$result=[];
        foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){try{$row['tokens']=ThemeTokenService::normalize((array)json_decode((string)$row['tokens_json'],true,48,JSON_THROW_ON_ERROR));unset($row['tokens_json']);$result[]=$row;}catch(\Throwable){}}
        return $result;
    }

    /** @return array<string,mixed>|null */
    public function version(int $profileId, int $versionId): ?array
    {
        $stmt=$this->pdo->prepare('SELECT id,theme_profile_id,version_number,name,tokens_json,created_by,created_at FROM theme_profile_versions WHERE id=:version_id AND theme_profile_id=:profile_id LIMIT 1');
        $stmt->execute(['version_id'=>$versionId,'profile_id'=>$profileId]);$row=$stmt->fetch(PDO::FETCH_ASSOC);if(!is_array($row))return null;
        try{$row['tokens']=ThemeTokenService::normalize((array)json_decode((string)$row['tokens_json'],true,48,JSON_THROW_ON_ERROR));unset($row['tokens_json']);return $row;}catch(\Throwable){return null;}
    }

    /** @param array<string,mixed> $tokens */
    public function createVersion(int $profileId, string $name, array $tokens, ?int $actorId = null): int
    {
        $stmt=$this->pdo->prepare('SELECT COALESCE(MAX(version_number),0)+1 FROM theme_profile_versions WHERE theme_profile_id=:id');$stmt->execute(['id'=>$profileId]);$number=max(1,(int)$stmt->fetchColumn());
        $insert=$this->pdo->prepare('INSERT INTO theme_profile_versions (theme_profile_id,version_number,name,tokens_json,created_by,created_at) VALUES (:profile_id,:version_number,:name,:tokens,:created_by,NOW())');
        $insert->execute(['profile_id'=>$profileId,'version_number'=>$number,'name'=>$name,'tokens'=>$this->encodeTokens($tokens),'created_by'=>$actorId&&$actorId>0?$actorId:null]);
        return (int)$this->pdo->lastInsertId();
    }

    /** @param array<string,mixed> $row @return array<string,mixed>|null */
    private function decode(array $row): ?array
    {
        try{$row['tokens']=ThemeTokenService::normalize((array)json_decode((string)($row['tokens_json']??'{}'),true,48,JSON_THROW_ON_ERROR));unset($row['tokens_json']);return $row;}catch(\Throwable){return null;}
    }
    /** @param array<string,mixed> $tokens */ private function encodeTokens(array $tokens): string{return json_encode(ThemeTokenService::normalize($tokens),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR);}
    private function uniqueSlug(string $name): string{$base=strtolower(trim((string)preg_replace('/[^a-z0-9]+/i','-', $name),'-'));if($base==='')$base='theme-'.substr(hash('sha256',$name.microtime(true)),0,8);$slug=substr($base,0,120);$i=2;while($this->slugExists($slug)){$suffix='-'.$i++;$slug=substr($base,0,120-strlen($suffix)).$suffix;}return $slug;}
    private function slugExists(string $slug):bool{$stmt=$this->pdo->prepare('SELECT 1 FROM theme_profiles WHERE slug=:slug LIMIT 1');$stmt->execute(['slug'=>$slug]);return $stmt->fetchColumn()!==false;}
}
