<?php
declare(strict_types=1);

namespace App\Services;

use App\Repositories\Container;

/**
 * Computes available 30-minute booking slots for an artist on a given date.
 *
 *  available(artist, date) =
 *     weeklyTemplate(artist, weekdayOf(date))
 *     minus exceptions(artist, date)
 *     minus bookedAppointments(artist, date)
 *     split into 30-minute chunks
 */
class Availability
{
    public const SLOT_MINUTES = 30;

    /**
     * Returns only free/available slots (used internally for conflict checks).
     */
    public static function slotsForArtistOnDate(string $artistId, string $date): array
    {
        $all = self::allSlotsForArtistOnDate($artistId, $date);
        return array_values(array_filter($all, fn($s) => ($s['status'] ?? 'free') === 'free'));
    }

    /**
     * Returns ALL schedule slots for the day with a `status` field:
     *   'free'   = bookable
     *   'taken'  = overlaps an existing appointment
     *   'past'   = time has already passed
     */
    public static function allSlotsForArtistOnDate(string $artistId, string $date): array
    {
        $schedules = Container::schedules();
        $appts     = Container::appointments();

        $weekday = (int)(new \DateTimeImmutable($date))->format('w');
        $weekly = array_values(array_filter($schedules->weeklyForArtist($artistId), fn($w) => (int)($w['weekday'] ?? -1) === $weekday));
        if (!$weekly) return [];

        $exceptions = $schedules->exceptionsForArtistOnDate($artistId, $date);
        foreach ($exceptions as $ex) {
            if (!empty($ex['is_day_off'])) return [];
        }

        $slots = [];
        foreach ($weekly as $w) {
            $slots = array_merge($slots, self::splitWindow($date, (string)$w['start_time'], (string)$w['end_time']));
        }

        foreach ($exceptions as $ex) {
            if (!empty($ex['start_time']) && !empty($ex['end_time'])) {
                $slots = self::removeRange($slots, $date, (string)$ex['start_time'], (string)$ex['end_time']);
            }
        }

        // Mark status instead of removing
        $booked = $appts->forArtistOnDate($artistId, $date);
        $nowIso = (new \DateTimeImmutable('now'))->format(\DateTimeInterface::ATOM);

        foreach ($slots as &$slot) {
            $slot['status'] = 'free';
            // Past check
            if ($slot['start'] <= $nowIso) {
                $slot['status'] = 'past';
                continue;
            }
            // Overlap check
            foreach ($booked as $b) {
                if (in_array($b['status'] ?? '', ['CANCELED', 'NO_SHOW'], true)) continue;
                if (empty($b['start_at']) || empty($b['end_at'])) continue;
                if (self::overlaps($slot['start'], $slot['end'], (string)$b['start_at'], (string)$b['end_at'])) {
                    $slot['status'] = 'taken';
                    break;
                }
            }
        }
        unset($slot);

        return $slots;
    }

    public static function monthAvailability(string $artistId, string $yearMonth): array
    {
        // yearMonth = "2026-04"
        try {
            $first = new \DateTimeImmutable($yearMonth . '-01');
        } catch (\Exception $e) {
            return [];
        }
        $daysInMonth = (int)$first->format('t');
        $out = [];
        for ($i = 1; $i <= $daysInMonth; $i++) {
            $date = $first->setDate((int)$first->format('Y'), (int)$first->format('m'), $i)->format('Y-m-d');
            $slots = self::slotsForArtistOnDate($artistId, $date);
            $out[$date] = count($slots);
        }
        return $out;
    }

    private static function splitWindow(string $date, string $startTime, string $endTime): array
    {
        $start = new \DateTimeImmutable($date . 'T' . substr($startTime, 0, 5) . ':00');
        $end   = new \DateTimeImmutable($date . 'T' . substr($endTime, 0, 5) . ':00');
        $slots = [];
        $cur = $start;
        while ($cur < $end) {
            $next = $cur->modify('+' . self::SLOT_MINUTES . ' minutes');
            if ($next > $end) break;
            $slots[] = [
                'start' => $cur->format(\DateTimeInterface::ATOM),
                'end'   => $next->format(\DateTimeInterface::ATOM),
                'label' => $cur->format('g:i A'),
            ];
            $cur = $next;
        }
        return $slots;
    }

    private static function removeRange(array $slots, string $date, string $start, string $end): array
    {
        $s = (new \DateTimeImmutable($date . 'T' . substr($start, 0, 5) . ':00'))->format(\DateTimeInterface::ATOM);
        $e = (new \DateTimeImmutable($date . 'T' . substr($end, 0, 5) . ':00'))->format(\DateTimeInterface::ATOM);
        return array_values(array_filter($slots, fn($slot) => !self::overlaps($slot['start'], $slot['end'], $s, $e)));
    }

    private static function overlaps(string $aStart, string $aEnd, string $bStart, string $bEnd): bool
    {
        return $aStart < $bEnd && $bStart < $aEnd;
    }

    /**
     * Check if a proposed time slot conflicts with any existing non-canceled appointment.
     * Returns the conflicting appointment if found, null if the slot is free.
     */
    public static function findConflict(string $artistId, string $startAt, string $endAt, ?string $excludeApptId = null): ?array
    {
        $date = (new \DateTimeImmutable($startAt))->format('Y-m-d');
        $booked = Container::appointments()->forArtistOnDate($artistId, $date);
        foreach ($booked as $b) {
            if (in_array($b['status'] ?? '', ['CANCELED', 'NO_SHOW'], true)) continue;
            if ($excludeApptId && ($b['id'] ?? '') === $excludeApptId) continue;
            if (empty($b['start_at']) || empty($b['end_at'])) continue;
            if (self::overlaps($startAt, $endAt, (string)$b['start_at'], (string)$b['end_at'])) {
                return $b;
            }
        }
        return null;
    }
}
