<?php

namespace App\Livewire\Calendar;

use App\Models\Event;
use Carbon\Carbon;
use Livewire\Attributes\Middleware;
use Livewire\Attributes\On;
use Livewire\Component;

#[Middleware('user')]
class WeekCanvas extends Component
{
    public string $view        = 'week';
    public string $currentDate = '';
    public int    $hourStart   = 0;
    public int    $hourEnd     = 24;

    public function getListeners(): array
    {
        $userId = auth()->id();

        return [
            'eventCreated' => '$refresh',
            "echo-private:calendar.{$userId},.calendar.updated" => '$refresh',
        ];
    }

    protected $queryString = [
        'view'        => ['except' => 'week'],          // ?view= nur wenn NICHT week
        'currentDate' => ['except' => '', 'as' => 'date'], // als ?date=YYYY-MM-DD in der URL
    ];

    public function mount(): void
    {
        // Livewire füllt $currentDate aus ?date= bereits vor mount() –
        // nur setzen wenn noch leer (kein URL-Parameter vorhanden)
        if (empty($this->currentDate)) {
            $this->currentDate = now(auth()->user()->timezone)->toDateString();
        }
    }

    public function navigate(string $direction): void
    {
        $date = Carbon::parse($this->currentDate, auth()->user()->timezone);

        match ($this->view) {
            'week' => $direction === 'prev' ? $date->subWeek() : $date->addWeek(),
            'month' => $direction === 'prev' ? $date->subMonth() : $date->addMonth(),
            'day' => $direction === 'prev' ? $date->subDay() : $date->addDay(),
        };

        $this->currentDate = $date->toDateString();
    }

    public function getTitleStringProperty(): string
    {
        $userTz = auth()->user()->timezone;
        $date = Carbon::parse($this->currentDate, $userTz);
        $months = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
            'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];

        return match ($this->view) {
            'month' => $months[$date->month - 1] . ' ' . $date->year,
            'week' => $this->weekTitleString($date, $months),
            'day' => $date->isoFormat('dddd, D. MMMM YYYY'),
        };
    }

    private function weekTitleString(Carbon $date, array $months): string
    {
        $start = $date->copy()->startOfWeek(Carbon::MONDAY);
        $end = $date->copy()->endOfWeek(Carbon::SUNDAY);

        if ($start->month === $end->month) {
            return $months[$start->month - 1] . ' ' . $start->year;
        }

        return $months[$start->month - 1] . ' – ' . $months[$end->month - 1] . ' ' . $end->year;
    }

    public function getWeekDaysProperty(): array
    {
        $userTz = auth()->user()->timezone;
        $start = Carbon::parse($this->currentDate, $userTz)->startOfWeek(Carbon::MONDAY);

        return collect(range(0, 6))
            ->map(fn($i) => $start->copy()->addDays($i))
            ->all();
    }

    public function rangeStart(): Carbon
    {
        $date = Carbon::parse($this->currentDate, auth()->user()->timezone);

        return match ($this->view) {
            'month' => $date->copy()->startOfMonth()->startOfWeek(Carbon::MONDAY)->startOfDay()->utc(),
            'week' => $date->copy()->startOfWeek(Carbon::MONDAY)->startOfDay()->utc(),
            'day' => $date->copy()->startOfDay()->utc(),
        };
    }

    public function rangeEnd(): Carbon
    {
        $date = Carbon::parse($this->currentDate, auth()->user()->timezone);

        return match ($this->view) {
            'month' => $date->copy()->endOfMonth()->endOfWeek(Carbon::SUNDAY)->endOfDay()->utc(),
            'week' => $date->copy()->endOfWeek(Carbon::SUNDAY)->endOfDay()->utc(),
            'day' => $date->copy()->endOfDay()->utc(),
        };
    }

    /**
     * Verschiebt einen Termin auf ein neues Datum/Uhrzeit.
     *
     * @param  string|null  $grabDate  Das Datum der Kopie, die der User gegriffen hat.
     *                                  Wenn angegeben, wird der gesamte Termin relativ
     *                                  verschoben (dayDelta = dropDate – grabDate), statt
     *                                  den Start absolut auf $newDate zu setzen.
     *                                  Nötig für mehrtägige Events, die auf mehreren Tagen
     *                                  gleichzeitig angezeigt werden.
     */
    public function moveEvent(string $eventId, string $newDate, int $newHour, int $newMinute = 0, ?string $grabDate = null): void
    {
        $event  = Event::where('user_id', auth()->id())->findOrFail($eventId);
        $userTz = auth()->user()->timezone;

        if ($grabDate !== null) {
            // Relatives Verschieben: Drop-Tag – Grab-Tag = dayDelta
            $grabDT   = Carbon::parse($grabDate, $userTz)->startOfDay();
            $dropDT   = Carbon::parse($newDate,  $userTz)->startOfDay();
            $dayDelta = (int) $grabDT->diffInDays($dropDT, false); // signed

            $eventStartLocal = $event->starts_at->copy()->setTimezone($userTz)->addDays($dayDelta);
            $newStart = $eventStartLocal->setHour($newHour)->setMinute($newMinute)->setSecond(0)->utc();
        } else {
            $newStart = Carbon::parse($newDate, $userTz)
                ->setHour($newHour)->setMinute($newMinute)->setSecond(0)->utc();
        }

        $duration = $event->ends_at
            ? $event->starts_at->diffInMinutes($event->ends_at)
            : 60;

        $event->starts_at  = $newStart;
        $event->ends_at    = $newStart->copy()->addMinutes($duration);
        $event->start_date = $newStart->copy()->setTimezone($userTz)->toDateString();
        $event->end_date   = $newStart->copy()->addMinutes($duration)->setTimezone($userTz)->toDateString();
        $event->save();
    }

    public function moveAllDayEvent(string $eventId, int $dayDelta): void
    {
        if ($dayDelta === 0) return;

        $event  = Event::where('user_id', auth()->id())->findOrFail($eventId);
        $userTz = auth()->user()->timezone;

        // Originale Timestamps in User-TZ (Uhrzeit bleibt erhalten)
        $startLocal = $event->starts_at->copy()->setTimezone($userTz);
        $endLocal   = $event->ends_at
            ? $event->ends_at->copy()->setTimezone($userTz)
            : $startLocal->copy();

        // Beide Timestamps um exakt $dayDelta Tage verschieben (Uhrzeit bleibt)
        $newStartLocal = $startLocal->copy()->addDays($dayDelta);
        $newEndLocal   = $endLocal->copy()->addDays($dayDelta);

        // Datum-Spalten (lesbar) und Timestamp-Spalten (UTC) synchron schreiben
        $event->start_date = $newStartLocal->toDateString();
        $event->end_date   = $newEndLocal->toDateString();
        $event->starts_at  = $newStartLocal->copy()->utc();
        $event->ends_at    = $newEndLocal->copy()->utc();
        $event->save();
    }

    public function openSidebar(string $eventId, string $date): void
    {
        $this->dispatch('sidebar:openEvent', eventId: $eventId, date: $date);
    }

    public function createEventAt(string $date, int $hour = 9, int $minute = 0): void
    {
        $time = str_pad($hour, 2, '0', STR_PAD_LEFT) . ':' . str_pad($minute, 2, '0', STR_PAD_LEFT);
        $this->dispatch('sidebar:createEvent', date: $date, time: $time);
    }

    public function resizeEvent(string $eventId, float $deltaHours): void
    {
        $event = Event::where('user_id', auth()->id())->findOrFail($eventId);
        $userTz = auth()->user()->timezone;
        $minEnd = $event->starts_at->copy()->addMinutes(15);

        $newEnd = $event->ends_at
            ? $event->ends_at->copy()->addMinutes((int)round($deltaHours * 60))
            : $event->starts_at->copy()->addMinutes(60);

        if ($newEnd->lt($minEnd)) $newEnd = $minEnd;

        $event->ends_at  = $newEnd->copy()->utc();
        $event->end_date = $newEnd->copy()->setTimezone($userTz)->toDateString();
        $event->save();
    }

    public function render()
    {
        $userTz = auth()->user()->timezone;

        $events = Event::where('user_id', auth()->id())
            ->where(function ($q) {
                $q->whereBetween('starts_at', [$this->rangeStart(), $this->rangeEnd()])
                    ->orWhereBetween('ends_at', [$this->rangeStart(), $this->rangeEnd()])
                    ->orWhere(fn($q2) => $q2
                        ->where('starts_at', '<=', $this->rangeStart())
                        ->where('ends_at', '>=', $this->rangeEnd())
                    );
            })
            ->orderBy('starts_at')
            ->get()
            ->map(function ($event) use ($userTz) {
                $event->starts_local = $event->starts_at->setTimezone($userTz);
                $event->ends_local = $event->ends_at?->setTimezone($userTz);
                return $event;
            });

        // Mehrtägige Timed-Events (Start- und Enddatum unterschiedlich) gehören
        // zusammen mit ganztägigen Events in den All-Day-Strip oben.
        $allDayEvents = $events->filter(fn($e) =>
            $e->is_all_day
            || ($e->ends_local && $e->starts_local->toDateString() !== $e->ends_local->toDateString())
        );
        $timedEvents  = $events->filter(fn($e) =>
            !$e->is_all_day
            && (!$e->ends_local || $e->starts_local->toDateString() === $e->ends_local->toDateString())
        );

        // Timed Events pro Wochentag – inkl. mehrtägiger Events, die den Tag überspannen
        $weekDaysList = $this->weekDays;
        $eventsByDay  = collect(range(0, 6))->map(function ($i) use ($timedEvents, $weekDaysList) {
            $dayDate = $weekDaysList[$i]->toDateString();
            return $timedEvents
                ->filter(function ($e) use ($dayDate) {
                    $eStart = $e->starts_local->toDateString();
                    $eEnd   = $e->ends_local ? $e->ends_local->toDateString() : $eStart;
                    return $eStart <= $dayDate && $eEnd >= $dayDate;
                })
                ->values();
        })->all();

        // ── All-Day Events: einzelne Blöcke mit spaltenübergreifenden Positionen ──────
        $weekStart = $weekDaysList[0]->toDateString();
        $weekEnd   = $weekDaysList[6]->toDateString();

        // Sortiere nach Startdatum → längere Events bekommen zuerst eine Zeile
        $sortedAllDay = $allDayEvents->sortBy(fn($e) => $e->starts_local->toDateString())->values();

        $rowSlots   = []; // $rowSlots[$row][$col] = true  → belegt
        $allDayRows = [];

        foreach ($sortedAllDay as $e) {
            $eStart = $e->starts_local->toDateString();
            $eEnd   = $e->ends_local ? $e->ends_local->toDateString() : $eStart;

            // Auf sichtbare Woche clippen
            $visStart = max($eStart, $weekStart);
            $visEnd   = min($eEnd,   $weekEnd);

            $startCol = (int) min(6, max(0, $weekDaysList[0]->copy()->diffInDays(Carbon::parse($visStart))));
            $endCol   = (int) min(6, max(0, $weekDaysList[0]->copy()->diffInDays(Carbon::parse($visEnd))));

            // Erste freie Zeile ohne Konflikt suchen
            $row = 0;
            while (true) {
                $conflict = false;
                for ($c = $startCol; $c <= $endCol; $c++) {
                    if (!empty($rowSlots[$row][$c])) { $conflict = true; break; }
                }
                if (!$conflict) break;
                $row++;
            }
            for ($c = $startCol; $c <= $endCol; $c++) {
                $rowSlots[$row][$c] = true;
            }

            $allDayRows[] = [
                'id'           => $e->id,
                'title'        => $e->title,
                'color'        => $e->color,
                'startCol'     => $startCol,
                'endCol'       => $endCol,
                'row'          => $row,
                'startDate'    => $eStart,
                'startsInWeek' => $eStart >= $weekStart,
                'endsInWeek'   => $eEnd   <= $weekEnd,
                'isAllDay'     => (bool) $e->is_all_day,
                'startTime'    => $e->is_all_day ? null : $e->starts_local->format('H:i'),
                'endTime'      => $e->is_all_day ? null : $e->ends_local?->format('H:i'),
            ];
        }

        $maxRow       = empty($allDayRows) ? 0 : max(array_column($allDayRows, 'row'));
        $allDayHeight = ($maxRow + 1) * 26 + 8; // 26 px pro Zeile + 8 px Padding
        $hasAllDay    = !empty($allDayRows);

        // ── Überlappungs-Layout: gestapeltes Stacking nur bei echter Zeitüberschneidung ──
        // Algorithmus: Greedy-Tiefenzuweisung (Intervall-Graph-Coloring)
        // depth=0 → volle Breite / kein Offset; depth=N → N×OFFSET_PX links eingerückt
        $layoutsByDay = [];
        foreach (range(0, 6) as $dayIndex) {
            $dayEvs = collect($eventsByDay[$dayIndex] ?? [])
                ->sortBy(fn($e) => $e->starts_local->hour * 60 + $e->starts_local->minute)
                ->values();

            $assigned = []; // ['start', 'end', 'depth'] – bereits verarbeitete Events
            $layouts  = [];

            foreach ($dayEvs as $event) {
                $startLocal = $event->starts_local;
                $endLocal   = $event->ends_local ?? $startLocal->copy()->addHour();
                $startDec   = $startLocal->hour + $startLocal->minute / 60;
                $endDec     = min($endLocal->hour + $endLocal->minute / 60, 24.0);

                // Tiefen aller zeitlich überlappenden Vorgänger sammeln
                $usedDepths = [];
                foreach ($assigned as $a) {
                    if ($startDec < $a['end'] && $endDec > $a['start']) {
                        $usedDepths[] = $a['depth'];
                    }
                }

                // Kleinste freie Tiefe wählen
                $depth = 0;
                while (in_array($depth, $usedDepths)) {
                    $depth++;
                }

                $assigned[]          = ['start' => $startDec, 'end' => $endDec, 'depth' => $depth];
                $layouts[$event->id] = ['depth' => $depth];
            }

            $layoutsByDay[$dayIndex] = $layouts;
        }

//        return match ($this->view) {
//            'month' => view('livewire.calendar.month-canvas', [
//                'events' => $events,
//                'currentDate' => $this->currentDate,
//            ]),
//
//            'day' => view('livewire.calendar.day-canvas', [
//                'events' => $events,
//                'currentDate' => $this->currentDate,
//            ]),
//
//            default => view('livewire.calendar.week-canvas', [
//                'eventsByDay'  => $eventsByDay,
//                'layoutsByDay' => $layoutsByDay,
//                'allDayRows'   => $allDayRows,
//                'allDayHeight' => $allDayHeight,
//                'hasAllDay'    => $hasAllDay,
//                'weekDays'     => $this->weekDays,
//                'hourStart'    => 0,
//                'hourEnd'      => 24,
//            ])
//        };

        return view('livewire.calendar.week-canvas', [
            'currentDate'  => $this->currentDate,
            'events'       => $events,
            'eventsByDay'  => $eventsByDay,
            'layoutsByDay' => $layoutsByDay,
            'allDayRows'   => $allDayRows,
            'allDayHeight' => $allDayHeight,
            'hasAllDay'    => $hasAllDay,
            'weekDays'     => $this->weekDays,
            'hourStart'    => 0,
            'hourEnd'      => 24,
        ]);
    }
}

//namespace App\Livewire\Calendar;
//
//use Livewire\Component;
//use App\Models\Event;
//use Carbon\Carbon;
//
//class WeekCanvas extends Component
//{
//    public string $view = 'week';
//    public string $currentDate;
//
//    public function mount()
//    {
//        $this->currentDate = now()->toDateString();
//    }
//
//    public function navigate(string $direction)
//    {
//        $date = Carbon::parse($this->currentDate);
//        match ($this->view) {
//            'week' => $direction === 'prev' ? $date->subWeek() : $date->addWeek(),
//            'month' => $direction === 'prev' ? $date->subMonth() : $date->addMonth(),
//            'day' => $direction === 'prev' ? $date->subDay() : $date->addDay(),
//        };
//        $this->currentDate = $date->toDateString();
//    }
//
//    public function moveEvent(string $eventId, string $newDate, int $newHour): void
//    {
//        $event    = Event::where('user_id', auth()->id())->findOrFail($eventId);
//        $userTz   = auth()->user()->timezone;
//        $duration = $event->ends_at
//            ? $event->starts_at->diffInMinutes($event->ends_at)
//            : 60;
//
//        $newStart = Carbon::parse($newDate, $userTz)
//            ->setHour($newHour)
//            ->setMinute(0)
//            ->setSecond(0)
//            ->utc();
//
//        $event->starts_at  = $newStart;
//        $event->ends_at    = $newStart->copy()->addMinutes($duration);
//        $event->start_date = $newStart->copy()->setTimezone($userTz)->toDateString();
//        $event->end_date   = $newStart->copy()->addMinutes($duration)->setTimezone($userTz)->toDateString();
//        $event->save();
//    }
//
//    public function resizeEvent(string $eventId, float $deltaHours): void
//    {
//        $event   = Event::where('user_id', auth()->id())->findOrFail($eventId);
//        $userTz  = auth()->user()->timezone;
//        $minEnd  = $event->starts_at->copy()->addMinutes(30);
//
//        $newEnd = $event->ends_at
//            ? $event->ends_at->copy()->addMinutes((int)($deltaHours * 60))
//            : $event->starts_at->copy()->addMinutes(60);
//
//        // Minimum 30 Minuten
//        if ($newEnd->lt($minEnd)) {
//            $newEnd = $minEnd;
//        }
//
//        $event->ends_at  = $newEnd->utc();
//        $event->end_date = $newEnd->setTimezone($userTz)->toDateString();
//        $event->save();
//    }
//
//    public function rangeStart(): Carbon
//    {
//        $date = Carbon::parse($this->currentDate, auth()->user()->timezone);
//
//        return match($this->view) {
//            'month' => $date->copy()->startOfMonth()->startOfWeek(Carbon::MONDAY)->startOfDay()->utc(),
//            'week'  => $date->copy()->startOfWeek(Carbon::MONDAY)->startOfDay()->utc(),
//            'day'   => $date->copy()->startOfDay()->utc(),
//        };
//    }
//
//    public function rangeEnd(): Carbon
//    {
//        $date = Carbon::parse($this->currentDate, auth()->user()->timezone);
//
//        return match($this->view) {
//            'month' => $date->copy()->endOfMonth()->endOfWeek(Carbon::SUNDAY)->endOfDay()->utc(),
//            'week'  => $date->copy()->endOfWeek(Carbon::SUNDAY)->endOfDay()->utc(),
//            'day'   => $date->copy()->endOfDay()->utc(),
//        };
//    }
//
//    public function render()
//    {
//        $userTz = auth()->user()->timezone;
//
//        $events = Event::where('user_id', auth()->id())
//            ->where(function ($q) {
//                // Events die in den Range fallen — auch mehrtägige
//                $q->whereBetween('starts_at', [$this->rangeStart(), $this->rangeEnd()])
//                    ->orWhereBetween('ends_at', [$this->rangeStart(), $this->rangeEnd()])
//                    ->orWhere(function ($q2) {
//                        // Events die den ganzen Range überspannen
//                        $q2->where('starts_at', '<=', $this->rangeStart())
//                            ->where('ends_at', '>=', $this->rangeEnd());
//                    });
//            })
//            ->orderBy('starts_at')
//            ->get()
//            ->map(function ($event) use ($userTz) {
//                // UTC → User-Timezone für die Anzeige
//                $event->starts_at_local = $event->starts_at->setTimezone($userTz);
//                $event->ends_at_local   = $event->ends_at?->setTimezone($userTz);
//                return $event;
//            });
//
//        return view('livewire.calendar.week-canvas', [
//            'events'   => $events,
//            'userTz'   => $userTz,
//            'weekDays' => $this->getWeekDays(),
//        ]);
//    }
//
//    private function getWeekDays(): array
//    {
//        $userTz  = auth()->user()->timezone;
//        $start   = Carbon::parse($this->currentDate, $userTz)->startOfWeek(Carbon::MONDAY);
//
//        return collect(range(0, 6))->map(
//            fn($i) => $start->copy()->addDays($i)
//        )->all();
//    }
//
//}
//    public $date;
//    public $events = [];
//
//    public function mount()
//    {
//        $tz = auth()->user()->timezone;
//
//        $this->date = $this->date
//            ? Carbon::parse($this->date, $tz)
//            : now($tz);
//
//        $this->loadEvents();
//    }
//
//    public function loadEvents()
//    {
//        $tz = auth()->user()->timezone;
//        $user = auth()->user();
//
//        $start = $this->date->copy()->startOfWeek();
//        $end   = $this->date->copy()->endOfWeek();
//
//        $this->events = $user->events()
//            ->where(function ($q) use ($start, $end) {
//                $q->whereBetween('starts_at', [$start, $end])
//                    ->orWhereBetween('ends_at', [$start, $end])
//                    ->orWhere(function ($q2) use ($start, $end) {
//                        $q2->where('starts_at', '<=', $start)
//                            ->where('ends_at', '>=', $end);
//                    });
//            })
//            ->get()
//            ->map(fn($event) => $this->transformEvent($event, $start, $tz));
//    }
//
//    public function updateEventTimeAndDate($id, $date, $start)
//    {
//        $event = \App\Models\Event::findOrFail($id);
//
//        $tz = auth()->user()->timezone;
//
//        $start = \Carbon\Carbon::parse("$date $start", $tz);
//
//        $duration = $event->ends_at
//            ? $event->starts_at->diffInSeconds($event->ends_at)
//            : 3600;
//
//        $event->update([
//            'starts_at' => $start->utc(),
//            'ends_at'   => $start->copy()->addSeconds($duration)->utc(),
//        ]);
//
//        $this->loadEvents();
//    }
//
//
//    public function updateEventRange($id, $startDate, $endDate, $startTime, $endTime)
//    {
//        $event = \App\Models\Event::findOrFail($id);
//
//        $tz = auth()->user()->timezone;
//
//        if (!preg_match('/^\d{2}:\d{2}$/', $startTime)) return;
//        if (!preg_match('/^\d{2}:\d{2}$/', $endTime)) return;
//
//        $start = \Carbon\Carbon::parse("$startDate $startTime", $tz);
//        $end   = \Carbon\Carbon::parse("$endDate $endTime", $tz);
//
//        // Sicherheit
//        if ($end->lessThanOrEqualTo($start)) {
//            $end = $start->copy()->addHour();
//        }
//
//        $event->update([
//            'starts_at' => $start->utc(),
//            'ends_at'   => $end->utc(),
//        ]);
//
//        $this->loadEvents();
//    }
//
//    private function transformEvent($event, $weekStart, $tz)
//    {
//        $start = $event->starts_at->copy()->setTimezone($tz);
//        $end   = $event->ends_at?->copy()->setTimezone($tz) ?? $start->copy()->addHour();
//
//        /*
//        |--------------------------------------------------------------------------
//        | POSITION
//        |--------------------------------------------------------------------------
//        */
//
//        $dayIndex = $weekStart->diffInDays($start->copy()->startOfDay());
//        $dayIndex = max(0, min(6, $dayIndex));
//
//        $days = $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1;
//
//        /*
//        |--------------------------------------------------------------------------
//        | TIME
//        |--------------------------------------------------------------------------
//        */
//
//        $hourHeight = 64;
//
//        $gap = 3; // px Abstand
//
//        $top = ($start->hour * $hourHeight)
//            + (($start->minute / 60) * $hourHeight)
//            + ($gap / 2);
//
//        $height = max($start->diffInMinutes($end), 30) / 60 * $hourHeight
//            - $gap;
//
//        $gap = 0.25; // %
//
//        return [
//            'id' => $event->id,
//            'title' => $event->title,
//            'color' => $event->color,
//            'left' => ($dayIndex / 7) * 100 + $gap,
//            'width' => ($days / 7) * 100 - ($gap * 2),
//            'top' => $top,
//            'height' => $height,
//            'start' => $start,
//            'end' => $end,
//        ];
//    }
//
//
//    public function colorToRgba($color, $opacity = 1): string
//    {
//        if (!$color) return 'rgba(99,102,241,0.8)'; // fallback
//
//        $color = ltrim($color, '#');
//
//        $r = hexdec(substr($color, 0, 2));
//        $g = hexdec(substr($color, 2, 2));
//        $b = hexdec(substr($color, 4, 2));
//
//        return "rgba($r, $g, $b, $opacity)";
//    }
//
//    public function render()
//    {
//        return view('livewire.calendar.week-canvas');
//    }
//}
