<?php


namespace App\Livewire\Calendar;

use App\Models\Event;
use Livewire\Component;
use Carbon\Carbon;

class Index extends Component
{
    public $view = 'month';
    public $date;
    public $events = [];
    public $search = '';
    public $from = null;
    public $to = null;

    public $rawEvents = [];
    public $holidays = [
        '2026-04-06' => 'Ostermontag',
    ];

    protected $queryString = [
        'view' => ['except' => 'month'],
        'date' => ['except' => ''],
        'search' => ['except' => ''],
        'from' => ['except' => ''],
        'to' => ['except' => ''],
    ];

    protected $listeners = ['eventCreated' => 'loadEvents'];

    public function mount()
    {
        $tz = auth()->user()->timezone;

        $this->date = $this->date
            ? Carbon::parse($this->date, $tz)
            : now($tz);

        $this->loadEvents();
    }

    public function updatedView()
    {
        $this->loadEvents();
    }

    public function setView($view)
    {
        $this->view = $view;
        $this->loadEvents();
    }

    public function loadEvents()
    {
        $tz = auth()->user()->timezone;
        $user = auth()->user();

        // 🔥 VIEW RANGE
        if ($this->view === 'month') {
            $start = $this->date->copy()->startOfMonth()->startOfWeek();
            $end = $this->date->copy()->endOfMonth()->endOfWeek();
        } elseif ($this->view === 'week') {
            $start = $this->date->copy()->startOfWeek();
            $end = $this->date->copy()->endOfWeek();
        } else {
            $start = $this->date->copy()->startOfDay();
            $end = $this->date->copy()->endOfDay();
        }

        $query = $user->events();

        // 🔥 SEARCH
        if ($this->search) {
            $query->where('title', 'like', '%' . $this->search . '%');
        }

        // 🔥 FILTER (richtig für Multi-Day!)
        if ($this->from) {
            $from = Carbon::parse($this->from)->startOfDay();

            $query->where(function ($q) use ($from) {
                $q->where('starts_at', '>=', $from)
                    ->orWhere('ends_at', '>=', $from);
            });
        }

        if ($this->to) {
            $to = Carbon::parse($this->to)->endOfDay();

            $query->where(function ($q) use ($to) {
                $q->where('starts_at', '<=', $to)
                    ->orWhere('ends_at', '<=', $to);
            });
        }

        // 🔥 RANGE (WICHTIG)
        $rawEvents = $query->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();

        $this->rawEvents = $rawEvents;
        // 🔥 SPLIT EVENTS IN DAYS
        $events = [];

        foreach ($rawEvents as $event) {

            $startDate = $event->starts_at
                ->copy()
                ->setTimezone($tz)
                ->startOfDay();

            $endDate = $event->ends_at
                ? $event->ends_at->copy()->setTimezone($tz)->startOfDay()
                : $startDate;

            for ($date = $startDate->copy(); $date <= $endDate; $date->addDay()) {
                $events[$date->format('Y-m-d')][] = $event;
            }
        }

        $this->events = collect($events)->map(fn($g) => collect($g));
    }

    public function updatedSearch()
    {
        $this->loadEvents();
    }

    public function updatedFrom()
    {
        $this->loadEvents();
    }

    public function updatedTo()
    {
        $this->loadEvents();
    }

    public function updateEventTime($eventId, $start, $end)
    {
        $event = \App\Models\Event::findOrFail($eventId);

        $tz = auth()->user()->timezone ?? 'UTC';

        $event->update([
            'starts_at' => Carbon::parse($start, $tz)->utc(),
            'ends_at'   => $end ? Carbon::parse($end, $tz)->utc() : null,
        ]);

        $this->loadEvents();
    }

    public function moveEventToDate($eventId, $date)
    {
        $event = \App\Models\Event::findOrFail($eventId);

        $tz = auth()->user()->timezone ?? 'UTC';

        $start = $event->starts_at->copy()->timezone($tz);
        $end   = $event->ends_at?->copy()->timezone($tz);

        /*
        |--------------------------------------------------------------------------
        | 🔥 FALL 1: ALL DAY ODER MULTI DAY
        |--------------------------------------------------------------------------
        */
        $isMultiDay = $end && $start->toDateString() !== $end->toDateString();

        if ($event->is_all_day || $isMultiDay) {

            // 👉 Tage-Differenz behalten (NICHT Sekunden!)
            $daySpan = $end
                ? $start->startOfDay()->diffInDays($end->startOfDay())
                : 0;

            // 👉 neuer Start (immer 00:00 bei allday)
            $newStart = Carbon::parse($date, $tz)->startOfDay();

            // 👉 neuer End
            $newEnd = $end
                ? $newStart->copy()->addDays($daySpan)->endOfDay()
                : null;

        } else {

            /*
            |--------------------------------------------------------------------------
            | 🔥 FALL 2: NORMALER TERMIN
            |--------------------------------------------------------------------------
            */
            $duration = $end
                ? $start->diffInSeconds($end)
                : 3600;

            $newStart = Carbon::parse(
                $date . ' ' . $start->format('H:i'),
                $tz
            );

            $newEnd = $end
                ? $newStart->copy()->addSeconds($duration)
                : null;
        }

        /*
        |--------------------------------------------------------------------------
        | SAVE (UTC!)
        |--------------------------------------------------------------------------
        */
        $event->update([
            'starts_at' => $newStart->utc(),
            'ends_at'   => $newEnd?->utc(),
        ]);

        $this->loadEvents();
    }

//    public function moveEventToDate($eventId, $date)
//    {
//        $event = \App\Models\Event::findOrFail($eventId);
//
//        $tz = auth()->user()->timezone ?? 'UTC';
//
//        // aktuelle Zeiten holen (in User TZ)
//        $start = $event->starts_at->copy()->timezone($tz);
//        $end   = $event->ends_at?->copy()->timezone($tz);
//
//        // 🔥 Dauer berechnen (wichtig für Multi-Day)
//        $duration = $end
//            ? $start->diffInSeconds($end)
//            : 3600;
//
//        // 🔥 neues Datum + gleiche Uhrzeit
//        $newStart = \Carbon\Carbon::parse(
//            $date . ' ' . $start->format('H:i'),
//            $tz
//        )->utc();
//
//        $newEnd = $end
//            ? $newStart->copy()->addSeconds($duration)
//            : null;
//
//        $event->update([
//            'starts_at' => $newStart,
//            'ends_at'   => $newEnd,
//        ]);
//
//        $this->loadEvents();
//    }

    public function goToToday()
    {
        $this->date = now(auth()->user()->timezone);
        $this->loadEvents();
    }

    public function next()
    {
        if ($this->view === 'month') $this->date->addMonth();
        if ($this->view === 'week') $this->date->addWeek();
        if ($this->view === 'day') $this->date->addDay();

        $this->loadEvents();
    }

    public function prev()
    {
        if ($this->view === 'month') $this->date->subMonth();
        if ($this->view === 'week') $this->date->subWeek();
        if ($this->view === 'day') $this->date->subDay();

        $this->loadEvents();
    }

    public function moveEventToDateTime($eventId, $date, $time)
    {
        $event = Event::findOrFail($eventId);

        $tz = auth()->user()->timezone ?? config('app.timezone');

        if (!preg_match('/^\d{2}:\d{2}$/', $time)) {
            return;
        }

        [$hour, $minute] = explode(':', $time);

        $hour = min(max((int)$hour, 0), 23);
        $minute = min(max((int)$minute, 0), 59);

        // 🔥 WICHTIG: alte Zeiten in USER TZ holen
        $oldStart = $event->starts_at->copy()->setTimezone($tz);
        $oldEnd   = $event->ends_at?->copy()->setTimezone($tz);

        $duration = $oldEnd
            ? $oldStart->diffInMinutes($oldEnd)
            : 60;

        if ($duration <= 0) {
            $duration = 60;
        }

        // 🔥 neue Zeit in USER TZ setzen
        $newStart = Carbon::parse($date, $tz)->setTime($hour, $minute);
        $newEnd   = $newStart->copy()->addMinutes($duration);

        // 🔥 zurück in UTC speichern
        $event->starts_at = $newStart->copy()->utc();
        $event->ends_at   = $newEnd->copy()->utc();

        $event->save();

        $this->loadEvents();
    }


    public function updateEventTimeAndDate($eventId, $date, $start, $end)
    {

        $event = Event::findOrFail($eventId);

        $tz = auth()->user()->timezone ?? config('app.timezone');

        $start = Carbon::parse($date.' '.$start, $tz)->utc();
        $end   = Carbon::parse($date.' '.$end, $tz)->utc();

        if ($end->lessThanOrEqualTo($start)) {
            $end = $start->copy()->addHour();
        }

        $event->update([
            'starts_at' => $start,
            'ends_at'   => $end,
        ]);

        $this->loadEvents();
    }


    public function updateEventRange($id, $startDate, $endDate, $startTime, $endTime)
    {
        $event = Event::findOrFail($id);

        $tz = auth()->user()->timezone;

        $start = Carbon::parse("$startDate $startTime", $tz)->utc();
        $end   = Carbon::parse("$endDate $endTime", $tz)->utc();

        if ($end->lessThanOrEqualTo($start)) {
            $end = $start->copy()->addHour();
        }

        $event->update([
            'starts_at' => $start,
            'ends_at'   => $end,
        ]);

        $this->loadEvents();
    }


    private function transformAllDayEvents($events)
    {
        return $events->map(function ($event) {

            $start = $event->starts_at->copy();
            $end   = $event->ends_at?->copy() ?? $start;

            $startDay = $start->startOfDay();
            $endDay   = $end->startOfDay();

            return [
                'id' => $event->id,
                'title' => $event->title,
                'color' => $event->color,

                'start_day' => $startDay,
                'end_day'   => $endDay,

                'duration_days' => $startDay->diffInDays($endDay) + 1,
            ];
        });
    }

    private function mapAllDayToWeek($events, $weekStart)
    {
        return $events->map(function ($event) use ($weekStart) {

            $startOffset = $event['start_day']->diffInDays($weekStart, false);
            $endOffset   = $event['end_day']->diffInDays($weekStart, false);

            // clamp auf Woche
            $start = max($startOffset, 0);
            $end   = min($endOffset, 6);

            $span = ($end - $start) + 1;

            return [
                ...$event,
                'left' => ($start / 7) * 100,
                'width' => ($span / 7) * 100,
            ];
        });
    }

    public function getCalendarDaysProperty(): \Illuminate\Support\Collection
    {
        $tz = auth()->user()->timezone;

        return collect(range(0, 6))->map(function ($i) use ($tz) {

            $day = $this->date->copy()->startOfWeek()->addDays($i);
            $key = $day->format('Y-m-d');

            $events = collect($this->events[$key] ?? []);

            /*
            |--------------------------------------------------------------------------
            | 🔥 ALLDAY + MULTIDAY RAUSFILTERN (WICHTIG!)
            |--------------------------------------------------------------------------
            */
            $filtered = $events->reject(function ($event) use ($tz) {

                $start = $event->starts_at->copy()->setTimezone($tz);
                $end   = $event->ends_at?->copy()->setTimezone($tz) ?? $start;

                // 👉 gleiche Logik wie in getAllDayEventsProperty
                $isMultiDay = $end->startOfDay()->gt($start->startOfDay());

                return $event->is_all_day || $isMultiDay;
            });

            /*
            |--------------------------------------------------------------------------
            | 🔥 TIMED EVENTS TRANSFORMIEREN
            |--------------------------------------------------------------------------
            */
            $mapped = $filtered
                ->map(fn($event) => $this->transformEvent($event, $day, $tz))
                ->sortBy('start')
                ->values();

            /*
            |--------------------------------------------------------------------------
            | 🔥 OVERLAP FIX
            |--------------------------------------------------------------------------
            */
            $timed = $mapped->map(function ($event) use ($mapped) {

                $overlapping = $mapped->filter(function ($e) use ($event) {
                    return $e['start'] < $event['end']
                        && $e['end'] > $event['start'];
                })->values();

                $count = max($overlapping->count(), 1);

                $index = $overlapping->pluck('id')->search($event['id']);
                $index = $index === false ? 0 : $index;

                $event['count'] = $count;
                $event['index'] = $index;

                return $event;
            });

            return [
                'date'   => $day,
                'key'    => $key,
                'timed'  => $timed,
            ];
        });
    }
//    public function getCalendarDaysProperty(): \Illuminate\Support\Collection
//    {
//        $tz = auth()->user()->timezone;
//
//        return collect(range(0, 6))->map(function ($i) use ($tz) {
//
//            $day = $this->date->copy()->startOfWeek()->addDays($i);
//            $key = $day->format('Y-m-d');
//
//            $events = collect($this->events[$key] ?? []);
//
//            /*
//            |--------------------------------------------------------------------------
//            | 🔥 NUR ALLDAY RAUS
//            |--------------------------------------------------------------------------
//            */
//            $mapped = $events
//                ->reject(fn($e) => $e->is_all_day)
//                ->map(fn($event) => $this->transformEvent($event, $day, $tz))
//                ->sortBy('start')
//                ->values();
//
//            /*
//            |--------------------------------------------------------------------------
//            | OVERLAP
//            |--------------------------------------------------------------------------
//            */
//            $timed = $mapped->map(function ($event) use ($mapped) {
//
//                $overlapping = $mapped->filter(function ($e) use ($event) {
//                    return $e['start'] < $event['end']
//                        && $e['end'] > $event['start'];
//                })->values();
//
//                $count = max($overlapping->count(), 1);
//
//                $index = $overlapping->pluck('id')->search($event['id']);
//                $index = $index === false ? 0 : $index;
//
//                $event['count'] = $count;
//                $event['index'] = $index;
//
//                return $event;
//            });
//
//            return [
//                'date'   => $day,
//                'key'    => $key,
//                'timed'  => $timed,
//            ];
//        });
//    }

    //    public function getCalendarDaysProperty(): \Illuminate\Support\Collection
//    {
//        $tz = auth()->user()->timezone;
//
//        return collect(range(0, 6))->map(function ($i) use ($tz) {
//
//            $day = $this->date->copy()->startOfWeek()->addDays($i);
//            $key = $day->format('Y-m-d');
//
//            $events = collect($this->events[$key] ?? []);
//
//            // 👉 AllDay Events
//            $allDay = $events->filter(fn($e) => $e->is_all_day);
//
//            // 👉 Timed Events transformieren
//            $mapped = $events
//                ->reject(fn($e) => $e->is_all_day)
//                ->map(fn($event) => $this->transformEvent($event, $day, $tz))
//                ->sortBy('start') // 🔥 wichtig für stabile Reihenfolge
//                ->values();
//
//            // 👉 Overlap berechnen
//            $timed = $mapped->map(function ($event) use ($mapped) {
//
//                $overlapping = $mapped->filter(function ($e) use ($event) {
//                    return $e['start'] < $event['end']
//                        && $e['end'] > $event['start'];
//                })->values();
//
//                $count = max($overlapping->count(), 1);
//
//                $index = $overlapping->pluck('id')->search($event['id']);
//                $index = $index === false ? 0 : $index;
//
//                $event['count'] = $count;
//                $event['index'] = $index;
//
//                return $event;
//            });
//
//            return [
//                'date'   => $day,
//                'key'    => $key,
//                'allDay' => $allDay,
//                'timed'  => $timed,
//            ];
//        });
//    }


//    public function getAllDayEventsProperty()
//    {
//        $tz = auth()->user()->timezone;
//
//        $weekStart = $this->date->copy()->startOfWeek();
//        $weekEnd   = $this->date->copy()->endOfWeek();
//
//        return collect($this->rawEvents)
//            ->filter(fn($event) => $event->is_all_day == true)
//            ->map(function ($event) use ($weekStart, $weekEnd, $tz) {
//
//                $start = $event->starts_at->copy()->setTimezone($tz);
//                $end   = $event->ends_at
//                    ? $event->ends_at->copy()->setTimezone($tz)
//                    : $start;
//
//                if ($end->lt($weekStart) || $start->gt($weekEnd)) {
//                    return null;
//                }
//
//                $startClamped = $start->copy()->max($weekStart)->startOfDay();
//                $endClamped   = $end->copy()->min($weekEnd)->startOfDay();
//
//                $startOffset = $weekStart->diffInDays($startClamped);
//                $endOffset   = $weekStart->diffInDays($endClamped);
//
//                $span = max(($endOffset - $startOffset) + 1, 1);
//
//                return [
//                    'id' => $event->id,
//                    'title' => $event->title,
//                    'color' => $event->color,
//
//                    // 🔥 DAS HAT GEFEHLT
//                    'start' => $startClamped,
//                    'end' => $endClamped,
//
//                    'left' => ($startOffset / 7) * 100,
//                    'width' => ($span / 7) * 100,
//                ];
//            })
//            ->filter()
//            ->values();
//    }

//    public function getAllDayEventsProperty()
//    {
//        $tz = auth()->user()->timezone;
//
//        $weekStart = $this->date->copy()->startOfWeek()->startOfDay();
//        $weekEnd   = $this->date->copy()->endOfWeek()->endOfDay();
//
//        $events = collect($this->rawEvents)
//
//            ->filter(function ($event) use ($tz) {
//
//                $start = $event->starts_at->copy()->setTimezone($tz);
//                $end   = $event->ends_at?->copy()->setTimezone($tz) ?? $start;
//
//                // 👉 echtes AllDay oder MultiDay
//                return $event->is_all_day
//                    || $end->startOfDay()->gt($start->startOfDay());
//            })
//
//            ->map(function ($event) use ($tz, $weekStart, $weekEnd) {
//
//                $start = $event->starts_at->copy()->setTimezone($tz);
//                $end   = $event->ends_at
//                    ? $event->ends_at->copy()->setTimezone($tz)
//                    : $start;
//
//                /*
//                |--------------------------------------------------------------------------
//                | ❌ außerhalb → raus
//                |--------------------------------------------------------------------------
//                */
//                if ($end->lt($weekStart) || $start->gt($weekEnd)) {
//                    return null;
//                }
//
//                /*
//                |--------------------------------------------------------------------------
//                | 🔥 CLAMP AUF WOCHE
//                |--------------------------------------------------------------------------
//                */
//                $startClamped = $start->lt($weekStart)
//                    ? $weekStart->copy()
//                    : $start->copy();
//
//                $endClamped = $end->gt($weekEnd)
//                    ? $weekEnd->copy()
//                    : $end->copy();
//
//                /*
//                |--------------------------------------------------------------------------
//                | 🔥 WICHTIG: START/END auf DAY LEVEL
//                |--------------------------------------------------------------------------
//                */
//                $startDay = $startClamped->copy()->startOfDay();
//                $endDay   = $endClamped->copy()->startOfDay();
//
//                /*
//                |--------------------------------------------------------------------------
//                | 🔥 OFFSET BERECHNUNG (STABIL)
//                |--------------------------------------------------------------------------
//                */
//                $startOffset = $weekStart->diffInDays($startDay);
//                $endOffset   = $weekStart->diffInDays($endDay);
//
//                $span = max(1, ($endOffset - $startOffset) + 1);
//
//                return [
//                    'id'    => $event->id,
//                    'title' => $event->title,
//                    'color' => $event->color,
//
//                    'start' => $startDay,
//                    'end'   => $endDay,
//
//                    'left'  => ($startOffset / 7) * 100,
//                    'width' => ($span / 7) * 100,
//                ];
//            })
//
//            ->filter()
//            ->sortBy('start')
//            ->values();
//
//        return $this->stackAllDayEvents($events);
//    }

    public function getAllDayEventsProperty()
    {
        $tz = auth()->user()->timezone;

        $weekStart = $this->date->copy()->startOfWeek()->startOfDay();
        $weekEnd   = $this->date->copy()->endOfWeek()->endOfDay();

        $events = collect($this->rawEvents)

            ->filter(function ($event) use ($tz) {

                $start = $event->starts_at->copy()->setTimezone($tz);
                $end   = $event->ends_at?->copy()->setTimezone($tz) ?? $start;

                $isMultiDay = $end->startOfDay()->gt($start->startOfDay());

                return $event->is_all_day || $isMultiDay;
            })

            ->map(function ($event) use ($tz, $weekStart, $weekEnd) {

                $start = $event->starts_at->copy()->setTimezone($tz)->startOfDay();
                $end   = $event->ends_at
                    ? $event->ends_at->copy()->setTimezone($tz)->startOfDay()
                    : $start;

                if ($end->lt($weekStart) || $start->gt($weekEnd)) {
                    return null;
                }

                $startClamped = $start->lt($weekStart) ? $weekStart->copy() : $start;
                $endClamped   = $end->gt($weekEnd) ? $weekEnd->copy() : $end;

                $startOffset = $weekStart->diffInDays($startClamped);
                $endOffset   = $weekStart->diffInDays($endClamped);

                $span = ($endOffset - $startOffset) + 1;

                return [
                    'id'    => $event->id,
                    'title' => $event->title,
                    'color' => $event->color,

                    'start' => $startClamped,
                    'end'   => $endClamped,

                    'col'   => $startOffset,
                    'span'  => $span,
                ];
            })

            ->filter()
            ->sortBy('start')
            ->values();

        return $this->stackAllDayEvents($events);
    }

    private function stackAllDayEvents($events)
    {
        $rows = [];

        foreach ($events as $event) {

            $placed = false;

            foreach ($rows as &$row) {

                $collision = collect($row)->first(function ($e) use ($event) {

                    $aStart = $event['col'];
                    $aEnd   = $event['col'] + $event['span'] - 1;

                    $bStart = $e['col'];
                    $bEnd   = $e['col'] + $e['span'] - 1;

                    return !($aEnd < $bStart || $aStart > $bEnd);
                });

                if (!$collision) {
                    $row[] = $event;
                    $placed = true;
                    break;
                }
            }

            if (!$placed) {
                $rows[] = [$event];
            }
        }

        return collect($rows)->map(function ($row, $rowIndex) {

            return collect($row)->map(function ($event) use ($rowIndex) {
                return [
                    ...$event,
                    'row' => $rowIndex
                ];
            });

        })->flatten(1);
    }

    private function transformEvent($event, $day, $tz)
    {
        $start = $event->starts_at->copy()->setTimezone($tz);
        $end   = $event->ends_at?->copy()->setTimezone($tz);

        $dayStart = $day->copy()->startOfDay();
        $dayEnd   = $day->copy()->endOfDay();

        // 👉 Seminar / MultiDay Fix
        if ($end && $start->diffInDays($end) > 0 && !$event->is_all_day) {

            $effectiveStart = $dayStart->copy()->setTime($start->hour, $start->minute);
            $effectiveEnd   = $dayStart->copy()->setTime($end->hour, $end->minute);

            // 👉 Exceptions
            $exceptions = is_array($event->exceptions)
                ? $event->exceptions
                : json_decode($event->exceptions ?? '[]', true);

            $exception = collect($exceptions)
                ->firstWhere('date', $day->format('Y-m-d'));

            if ($exception) {
                if (!empty($exception['start'])) {
                    [$h, $m] = explode(':', $exception['start']);
                    $effectiveStart->setTime($h, $m);
                }

                if (!empty($exception['end'])) {
                    [$h, $m] = explode(':', $exception['end']);
                    $effectiveEnd->setTime($h, $m);
                }
            }

        } else {
            $effectiveStart = $start;
            $effectiveEnd   = $end ?? $start->copy()->addHour();
        }

        $hourHeight = 64;

        $top = ($effectiveStart->hour * $hourHeight)
            + (($effectiveStart->minute / 60) * $hourHeight);

        $height = max($effectiveStart->diffInMinutes($effectiveEnd), 1) / 60 * $hourHeight;


        return [
            'model' => $event,
            'id' => $event->id,
            'title' => $event->title,
            'top' => $top,
            'height' => $height,
            'start' => $effectiveStart,
            'end' => $effectiveEnd,
        ];
    }

    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.index')
            ->layout('layouts.app');
    }
}
