<?php
declare(strict_types=1);

/**
 * Formation Laravel — Module 01
 * Mini-projet : CLI Task Manager en PHP pur
 *
 * Usage :
 *   php solution.php add "Titre" --priority=high
 *   php solution.php list [--status=todo] [--priority=high]
 *   php solution.php update {id} --status=done
 *   php solution.php delete {id}
 *   php solution.php help
 */

// ── Couleurs ANSI ─────────────────────────────────────
const RESET  = "\033[0m";
const BOLD   = "\033[1m";
const RED    = "\033[31m";
const GREEN  = "\033[32m";
const YELLOW = "\033[33m";
const BLUE   = "\033[34m";
const CYAN   = "\033[36m";
const GRAY   = "\033[90m";

// ── Enums ─────────────────────────────────────────────
enum Priority: string {
    case Low    = 'low';
    case Medium = 'medium';
    case High   = 'high';

    public function label(): string {
        return match($this) {
            Priority::Low    => '🟢 Low',
            Priority::Medium => '🟡 Med',
            Priority::High   => '🔴 High',
        };
    }

    public function ansiColor(): string {
        return match($this) {
            Priority::Low    => GREEN,
            Priority::Medium => YELLOW,
            Priority::High   => RED,
        };
    }

    public static function fromInput(string $value): self {
        return self::tryFrom(strtolower($value))
            ?? throw new \InvalidArgumentException("Priorité invalide: '{$value}'. Valeurs: low, medium, high");
    }
}

enum Status: string {
    case Todo       = 'todo';
    case InProgress = 'in_progress';
    case Done       = 'done';

    public function label(): string {
        return match($this) {
            Status::Todo       => '⬜ Todo',
            Status::InProgress => '🔵 En cours',
            Status::Done       => '✅ Terminé',
        };
    }

    public static function fromInput(string $value): self {
        $normalized = str_replace('-', '_', strtolower($value));
        return self::tryFrom($normalized)
            ?? throw new \InvalidArgumentException("Statut invalide: '{$value}'. Valeurs: todo, in_progress, done");
    }
}

// ── Value Object Task (readonly class PHP 8.2) ────────
readonly class Task {
    public function __construct(
        public int      $id,
        public string   $titre,
        public Priority $priority,
        public Status   $status,
        public string   $createdAt,
    ) {}

    public function toArray(): array {
        return [
            'id'        => $this->id,
            'titre'     => $this->titre,
            'priority'  => $this->priority->value,
            'status'    => $this->status->value,
            'createdAt' => $this->createdAt,
        ];
    }

    public static function fromArray(array $data): self {
        return new self(
            id:        (int) $data['id'],
            titre:     $data['titre'],
            priority:  Priority::from($data['priority']),
            status:    Status::from($data['status']),
            createdAt: $data['createdAt'],
        );
    }

    public function withStatus(Status $status): self {
        return new self($this->id, $this->titre, $this->priority, $status, $this->createdAt);
    }

    public function withPriority(Priority $priority): self {
        return new self($this->id, $this->titre, $priority, $this->status, $this->createdAt);
    }
}

// ── Repository (persistance JSON) ────────────────────
class TaskRepository {
    private array $tasks = [];
    private int   $nextId = 1;

    public function __construct(private readonly string $filePath) {
        $this->load();
    }

    private function load(): void {
        if (!file_exists($this->filePath)) {
            $this->tasks  = [];
            $this->nextId = 1;
            return;
        }
        $content = file_get_contents($this->filePath);
        if ($content === false) return;
        $data = json_decode($content, true);
        if (!is_array($data)) return;
        $this->tasks  = array_map(Task::fromArray(...), $data['tasks'] ?? []);
        $this->nextId = (int) ($data['nextId'] ?? 1);
    }

    private function save(): void {
        $data = [
            'nextId' => $this->nextId,
            'tasks'  => array_map(fn(Task $t) => $t->toArray(), $this->tasks),
        ];
        file_put_contents($this->filePath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    }

    public function add(string $titre, Priority $priority): Task {
        $task = new Task(
            id:        $this->nextId++,
            titre:     $titre,
            priority:  $priority,
            status:    Status::Todo,
            createdAt: date('Y-m-d'),
        );
        $this->tasks[] = $task;
        $this->save();
        return $task;
    }

    /** @return Task[] */
    public function all(?Status $status = null, ?Priority $priority = null): array {
        return array_filter($this->tasks, function(Task $t) use ($status, $priority) {
            if ($status   !== null && $t->status   !== $status)   return false;
            if ($priority !== null && $t->priority !== $priority) return false;
            return true;
        });
    }

    public function findById(int $id): ?Task {
        foreach ($this->tasks as $task) {
            if ($task->id === $id) return $task;
        }
        return null;
    }

    public function update(Task $updated): bool {
        foreach ($this->tasks as $i => $task) {
            if ($task->id === $updated->id) {
                $this->tasks[$i] = $updated;
                $this->save();
                return true;
            }
        }
        return false;
    }

    public function delete(int $id): bool {
        foreach ($this->tasks as $i => $task) {
            if ($task->id === $id) {
                array_splice($this->tasks, $i, 1);
                $this->save();
                return true;
            }
        }
        return false;
    }

    public function count(): int {
        return count($this->tasks);
    }
}

// ── CLI Application ───────────────────────────────────
class TaskCli {
    public function __construct(private readonly TaskRepository $repo) {}

    public function run(array $argv): void {
        $command = $argv[1] ?? 'help';

        try {
            match($command) {
                'add'    => $this->cmdAdd($argv),
                'list'   => $this->cmdList($argv),
                'update' => $this->cmdUpdate($argv),
                'delete' => $this->cmdDelete($argv),
                'help'   => $this->cmdHelp(),
                default  => throw new \InvalidArgumentException("Commande inconnue: '{$command}'"),
            };
        } catch (\InvalidArgumentException $e) {
            echo RED . "Erreur: " . $e->getMessage() . RESET . "\n";
            exit(1);
        }
    }

    private function parseOptions(array $argv): array {
        $opts = [];
        foreach ($argv as $arg) {
            if (str_starts_with($arg, '--')) {
                $parts = explode('=', substr($arg, 2), 2);
                $opts[$parts[0]] = $parts[1] ?? true;
            }
        }
        return $opts;
    }

    private function cmdAdd(array $argv): void {
        $titre = $argv[2] ?? null;
        if (empty($titre)) {
            throw new \InvalidArgumentException("Usage: add \"Titre\" [--priority=low|medium|high]");
        }
        $opts     = $this->parseOptions($argv);
        $priority = Priority::fromInput($opts['priority'] ?? 'medium');
        $task     = $this->repo->add($titre, $priority);

        echo GREEN . "✓ Tâche #{$task->id} créée : " . BOLD . $task->titre . RESET . "\n";
        echo GRAY . "  Priorité: " . $task->priority->label() . RESET . "\n";
    }

    private function cmdList(array $argv): void {
        $opts     = $this->parseOptions($argv);
        $status   = isset($opts['status'])   ? Status::fromInput($opts['status'])     : null;
        $priority = isset($opts['priority']) ? Priority::fromInput($opts['priority']) : null;

        $tasks = array_values($this->repo->all($status, $priority));

        if (empty($tasks)) {
            echo GRAY . "Aucune tâche trouvée." . RESET . "\n";
            return;
        }

        $this->printTable($tasks);
        echo GRAY . count($tasks) . " tâche(s) affichée(s)" . RESET . "\n";
    }

    private function printTable(array $tasks): void {
        $w = [4, 30, 9, 11, 12];
        $line = '├' . implode('┼', array_map(fn($w) => str_repeat('─', $w + 2), $w)) . '┤';
        $top  = '┌' . implode('┬', array_map(fn($w) => str_repeat('─', $w + 2), $w)) . '┐';
        $bot  = '└' . implode('┴', array_map(fn($w) => str_repeat('─', $w + 2), $w)) . '┘';

        echo $top . "\n";
        echo $this->row(['ID', 'Titre', 'Priorité', 'Statut', 'Créé le'], $w, true);
        echo $line . "\n";

        foreach ($tasks as $task) {
            echo $this->row([
                (string) $task->id,
                mb_substr($task->titre, 0, 30),
                $task->priority->label(),
                $task->status->label(),
                $task->createdAt,
            ], $w);
        }

        echo $bot . "\n";
    }

    private function row(array $cols, array $widths, bool $header = false): string {
        $cells = [];
        foreach ($cols as $i => $col) {
            $padded = mb_str_pad($col, $widths[$i]);
            $cells[] = $header ? BOLD . $padded . RESET : $padded;
        }
        return '│ ' . implode(' │ ', $cells) . ' │' . "\n";
    }

    private function cmdUpdate(array $argv): void {
        $id   = isset($argv[2]) ? (int) $argv[2] : null;
        $opts = $this->parseOptions($argv);

        if ($id === null || $id <= 0) {
            throw new \InvalidArgumentException("Usage: update {id} [--status=...] [--priority=...]");
        }

        $task = $this->repo->findById($id);
        if ($task === null) {
            throw new \InvalidArgumentException("Tâche #{$id} introuvable.");
        }

        if (isset($opts['status'])) {
            $task = $task->withStatus(Status::fromInput($opts['status']));
        }
        if (isset($opts['priority'])) {
            $task = $task->withPriority(Priority::fromInput($opts['priority']));
        }

        $this->repo->update($task);
        echo GREEN . "✓ Tâche #{$id} mise à jour." . RESET . "\n";
        echo GRAY . "  Statut: " . $task->status->label() . " · Priorité: " . $task->priority->label() . RESET . "\n";
    }

    private function cmdDelete(array $argv): void {
        $id = isset($argv[2]) ? (int) $argv[2] : null;
        if ($id === null || $id <= 0) {
            throw new \InvalidArgumentException("Usage: delete {id}");
        }
        if (!$this->repo->delete($id)) {
            throw new \InvalidArgumentException("Tâche #{$id} introuvable.");
        }
        echo GREEN . "✓ Tâche #{$id} supprimée." . RESET . "\n";
    }

    private function cmdHelp(): void {
        echo BOLD . CYAN . "CLI Task Manager — PHP 8.x" . RESET . "\n\n";
        echo BOLD . "Commandes disponibles :" . RESET . "\n";
        $cmds = [
            ['add "Titre" [--priority=low|medium|high]', 'Ajouter une tâche'],
            ['list [--status=todo|in_progress|done] [--priority=...]', 'Lister les tâches'],
            ['update {id} [--status=...] [--priority=...]', 'Modifier une tâche'],
            ['delete {id}', 'Supprimer une tâche'],
            ['help', 'Afficher cette aide'],
        ];
        foreach ($cmds as [$cmd, $desc]) {
            printf("  %s%-55s%s %s\n", CYAN, "php solution.php {$cmd}", RESET, $desc);
        }
        echo "\n" . GRAY . "Total : " . $this->repo->count() . " tâche(s)" . RESET . "\n";
    }
}

// ── Point d'entrée ────────────────────────────────────
if (PHP_SAPI !== 'cli') {
    die("Ce script doit être exécuté en ligne de commande.\n");
}

$dbPath = __DIR__ . '/tasks.json';
(new TaskCli(new TaskRepository($dbPath)))->run($argv);
