<?php

namespace App\Services\Calendar;

use Carbon\Carbon;
use Illuminate\Http\Request;

class CalendarRangeResolver
{
    public const VIEWS = ['day', 'week', 'month', 'four'];

    /**
     * Normalize view + date inputs into a resolved view and date range.
     *
     * @return array [$view, ['start' => Carbon, 'end' => Carbon]]
     */
    public function resolveRange(
        Request $request,
        string $tz,
        int $weekStart,
        int $weekEnd,
        string $defaultView,
        string $defaultDate
    ): array {
        $requestView = $request->query('view', $defaultView);
        $view = in_array($requestView, self::VIEWS, true)
            ? $requestView
            : 'month';

        $date = $request->query('date', $defaultDate);
        $anchor = $this->safeDate($date, $tz, $defaultDate);

        switch ($view) {
            case 'day':
                $start = $anchor->copy()->startOfDay();
                $end   = $anchor->copy()->endOfDay();
                break;
            case 'week':
                $start = $anchor->copy()->startOfWeek($weekStart);
                $end   = $anchor->copy()->endOfWeek($weekEnd);
                break;
            case 'four':
                $start = $anchor->copy()->startOfDay();
                $end   = $anchor->copy()->addDays(3)->endOfDay();
                break;
            default: // month
                $start = $anchor->copy()->startOfMonth();
                $end   = $anchor->copy()->endOfMonth();
        }

        return [$view, ['start' => $start, 'end' => $end]];
    }

    /**
     * Calendar grid span differs from logical range (e.g. month padding).
     */
    public function gridSpan(string $view, array $range, int $weekStart, int $weekEnd): array
    {
        switch ($view) {
            case 'day':
                $start = $range['start']->copy()->startOfDay();
                $end   = $range['start']->copy()->endOfDay();
                break;
            case 'week':
                $start = $range['start']->copy()->startOfWeek($weekStart);
                $end   = $range['start']->copy()->endOfWeek($weekEnd);
                break;
            case 'four':
                $start = $range['start']->copy()->startOfDay();
                $end   = $range['start']->copy()->addDays(3);
                break;
            default: // month
                $start = $range['start']->copy()->startOfMonth()->startOfWeek($weekStart);
                $end   = $range['end']->copy()->endOfMonth()->endOfWeek($weekEnd);
        }

        return ['start' => $start, 'end' => $end];
    }

    /**
     * Navigation dates for header controls.
     */
    public function navDates(string $view, Carbon $start, string $tz): array
    {
        $start = $start->copy()->tz($tz);

        return match ($view) {
            'day' => [
                'prev'  => $start->copy()->subDay()->toDateString(),
                'next'  => $start->copy()->addDay()->toDateString(),
                'today' => Carbon::today($tz)->toDateString(),
            ],
            'week' => [
                'prev'  => $start->copy()->subWeek()->toDateString(),
                'next'  => $start->copy()->addWeek()->toDateString(),
                'today' => Carbon::today($tz)->toDateString(),
            ],
            'four' => [
                'prev'  => $start->copy()->subDays(4)->toDateString(),
                'next'  => $start->copy()->addDays(4)->toDateString(),
                'today' => Carbon::today($tz)->toDateString(),
            ],
            default => [
                'prev'  => $start->copy()->subMonth()->startOfMonth()->toDateString(),
                'next'  => $start->copy()->addMonth()->startOfMonth()->toDateString(),
                'today' => Carbon::today($tz)->toDateString(),
            ],
        };
    }

    /**
     * Title text for the calendar header.
     */
    public function headerTitle(string $view, Carbon $start, Carbon $end): array
    {
        $sameDay   = $start->isSameDay($end);
        $sameMonth = $start->isSameMonth($end);
        $sameYear  = $start->isSameYear($end);

        $strong = $start->format('F');
        $span   = $start->format('Y');

        if ($view === 'day' || $sameDay) {
            return [
                'strong' => $start->format('F j'),
                'span'   => $start->format('Y'),
            ];
        }

        if (in_array($view, ['week', 'four'], true)) {
            if ($sameMonth && $sameYear) {
                return [
                    'strong' => $start->format('F j') . ' to ' . $end->format('j'),
                    'span'   => $start->format('Y'),
                ];
            }

            if ($sameYear) {
                return [
                    'strong' => $start->format('F') . ' to ' . $end->format('F'),
                    'span'   => $start->format('Y'),
                ];
            }

            return [
                'strong' => $start->format('F Y') . ' to ' . $end->format('F Y'),
                'span'   => null,
            ];
        }

        return ['strong' => $strong, 'span' => $span];
    }

    /**
     * Safe date parsing with fallback to a default date string.
     */
    public function safeDate(string $date, string $tz, string $fallbackDate): Carbon
    {
        try {
            return Carbon::createFromFormat('Y-m-d', $date, $tz)->startOfDay();
        } catch (\Throwable $e) {
            return Carbon::createFromFormat('Y-m-d', $fallbackDate, $tz)->startOfDay();
        }
    }
}
