<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Repositories;

use App\Domain\SprintWorkerDay;
use PDO;

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

    /**
     * Build the full [sprint_worker_id][sprint_week_id] => days map for a sprint.
     * Cells with no DB row are simply absent from the map (read as 0 by callers).
     *
     * @return array<int, array<int, float>>
     */
    public function grid(int $sprintId): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT swd.sprint_worker_id, swd.sprint_week_id, swd.days
             FROM sprint_worker_days swd
             JOIN sprint_workers sw ON sw.id = swd.sprint_worker_id
             WHERE sw.sprint_id = ?'
        );
        $stmt->execute([$sprintId]);
        $out = [];
        foreach ($stmt as $row) {
            $swId  = (int) $row['sprint_worker_id'];
            $wkId  = (int) $row['sprint_week_id'];
            $days  = (float) $row['days'];
            $out[$swId][$wkId] = $days;
        }
        return $out;
    }

    public function find(int $swId, int $weekId): ?SprintWorkerDay
    {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM sprint_worker_days WHERE sprint_worker_id = ? AND sprint_week_id = ?'
        );
        $stmt->execute([$swId, $weekId]);
        $row = $stmt->fetch();
        return is_array($row) ? self::hydrate($row) : null;
    }

    /**
     * All cells for a given sprint_worker (used by removeWorker to snapshot
     * rows before the FK cascade wipes them).
     *
     * @return list<SprintWorkerDay>
     */
    public function allForSprintWorker(int $swId): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM sprint_worker_days WHERE sprint_worker_id = ?'
        );
        $stmt->execute([$swId]);
        $out = [];
        foreach ($stmt as $row) {
            $out[] = self::hydrate($row);
        }
        return $out;
    }

    /**
     * All cells for a given sprint_week (used by weeks-shrink to snapshot
     * rows before the FK cascade wipes them).
     *
     * @return list<SprintWorkerDay>
     */
    public function allForSprintWeek(int $weekId): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM sprint_worker_days WHERE sprint_week_id = ?'
        );
        $stmt->execute([$weekId]);
        $out = [];
        foreach ($stmt as $row) {
            $out[] = self::hydrate($row);
        }
        return $out;
    }

    /**
     * @param array<string,mixed> $row
     */
    private static function hydrate(array $row): SprintWorkerDay
    {
        return new SprintWorkerDay(
            id:              (int)   $row['id'],
            sprintWorkerId:  (int)   $row['sprint_worker_id'],
            sprintWeekId:    (int)   $row['sprint_week_id'],
            days:            (float) $row['days'],
        );
    }

    /**
     * Set days for a single (sprint_worker, sprint_week) cell.
     *
     * Rules:
     *   - If the row exists and days are unchanged → no DB write, action=NOOP.
     *   - If the row doesn't exist and days === 0 → don't insert, action=NOOP.
     *   - If the row doesn't exist and days > 0 → INSERT, action=CREATE.
     *   - Otherwise → UPDATE, action=UPDATE.
     *
     * @return array{action:string, before: ?SprintWorkerDay, after: ?SprintWorkerDay}
     */
    public function upsert(int $swId, int $weekId, float $days): array
    {
        $existing = $this->find($swId, $weekId);

        if ($existing !== null && abs($existing->days - $days) < 1e-9) {
            return ['action' => 'NOOP', 'before' => $existing, 'after' => $existing];
        }

        if ($existing === null) {
            if (abs($days) < 1e-9) {
                return ['action' => 'NOOP', 'before' => null, 'after' => null];
            }
            $stmt = $this->pdo->prepare(
                'INSERT INTO sprint_worker_days (sprint_worker_id, sprint_week_id, days)
                 VALUES (?, ?, ?)'
            );
            $stmt->execute([$swId, $weekId, $days]);
            $id    = (int) $this->pdo->lastInsertId();
            $after = new SprintWorkerDay($id, $swId, $weekId, $days);
            return ['action' => 'CREATE', 'before' => null, 'after' => $after];
        }

        $stmt = $this->pdo->prepare('UPDATE sprint_worker_days SET days = ? WHERE id = ?');
        $stmt->execute([$days, $existing->id]);
        $after = new SprintWorkerDay($existing->id, $swId, $weekId, $days);
        return ['action' => 'UPDATE', 'before' => $existing, 'after' => $after];
    }
}
