<?php

namespace App\Livewire\Facilities;

use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceLayout;
use App\Domain\Facility\Models\SpaceReservation;
use App\Domain\Facility\Services\SpaceCollisionService;
use App\Domain\HR\Services\TrainerWorkloadService;
use App\Domain\Scheduling\Services\ScheduleConflictService;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\TrainingSchedule;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('جدولة المنشآت')]
class VisualScheduleBuilder extends Component
{
    use UsesBranchScope;

    #[Url]
    public string $facility_id = '';

    #[Url]
    public string $selectedDay = '';

    // Flexible time (to the minute)
    public string $selectedSlotStart = '';
    public string $selectedSlotEnd = '';
    public string $customStartTime = '';
    public string $customEndTime = '';
    public bool $useCustomTime = false;

    // Grid state
    public array $gridSegments = [];
    public array $assignments = [];
    public array $conflicts = [];
    public array $warnings = [];

    // Recurring booking
    public bool $showRecurring = false;
    public int $recurringWeeks = 8;
    public array $recurringDays = [];
    public bool $skipConflicts = true;

    // Trainer availability cache (for the sidebar indicators)
    public array $trainerAvailability = [];
    public array $groupAvailability = [];

    public function mount(): void
    {
        $this->authorize('schedules.manage');

        if (!$this->selectedDay) {
            $this->selectedDay = now()->toDateString();
        }

        // A ?facility_id= carried over from another branch (bookmark, back
        // button, shared link) must not survive the branch scope.
        if ($this->facility_id) {
            $branchId = $this->getActiveBranchId();
            $belongs = Facility::where('id', $this->facility_id)
                ->forBranch($branchId)
                ->exists();

            if (! $belongs) {
                $this->facility_id = '';
            }
        }
    }

    public function render()
    {
        // Only this branch's facilities. Showing every branch's courts on a
        // branch-scoped screen is how a receptionist books the wrong building.
        $facilities = Facility::active()
            ->forBranch($this->getActiveBranchId())
            ->with('branch')
            ->orderBy('name_ar')
            ->get();

        $facility = $this->facility_id ? Facility::with('activeLayout')->find($this->facility_id) : null;

        $timeSlots = $this->generateTimeSlots($facility);
        $existingReservations = $this->getExistingReservations($facility);
        $groups = $this->getAvailableGroups();
        $trainers = $this->getAvailableTrainers();

        return view('livewire.facilities.visual-schedule-builder', [
            'facilities' => $facilities,
            'facility' => $facility,
            'timeSlots' => $timeSlots,
            'existingReservations' => $existingReservations,
            'groups' => $groups,
            'trainers' => $trainers,
            'dayOfWeek' => $this->selectedDay ? Carbon::parse($this->selectedDay)->dayOfWeek : now()->dayOfWeek,
        ]);
    }

    // ─── Navigation ──────────────────────────────────────────────

    public function selectDay(string $date): void
    {
        $this->selectedDay = $date;
        $this->resetGrid();
        $this->refreshAvailability();
    }

    public function openSlot(string $start, string $end): void
    {
        $this->selectedSlotStart = $start;
        $this->selectedSlotEnd = $end;
        $this->customStartTime = $start;
        $this->customEndTime = $end;
        $this->useCustomTime = false;
        $this->loadGridForSlot();
        $this->refreshAvailability();
    }

    public function openCustomTime(): void
    {
        if (!$this->customStartTime || !$this->customEndTime) {
            $this->addError('time', __('يجب تحديد وقت البداية والنهاية'));
            return;
        }

        if ($this->customStartTime >= $this->customEndTime) {
            $this->addError('time', __('وقت البداية يجب أن يكون قبل وقت النهاية'));
            return;
        }

        $facility = Facility::find($this->facility_id);
        if ($facility) {
            $opStart = $facility->operating_start ?? '00:00';
            $opEnd = $facility->operating_end ?? '23:59';

            if ($this->customStartTime < $opStart || $this->customEndTime > $opEnd) {
                $this->addError('time', __('الوقت المحدد خارج ساعات عمل المنشأة') . " ({$opStart} - {$opEnd})");
                return;
            }
        }

        $this->selectedSlotStart = $this->customStartTime;
        $this->selectedSlotEnd = $this->customEndTime;
        $this->useCustomTime = true;
        $this->loadGridForSlot();
        $this->refreshAvailability();
    }

    public function closeSlot(): void
    {
        $this->selectedSlotStart = '';
        $this->selectedSlotEnd = '';
        $this->customStartTime = '';
        $this->customEndTime = '';
        $this->useCustomTime = false;
        $this->gridSegments = [];
        $this->assignments = [];
        $this->conflicts = [];
        $this->warnings = [];
    }

    // ─── Assignment Actions ──────────────────────────────────────

    public function assignGroupToSegments(int $groupId, array $segmentIds): void
    {
        if (empty($segmentIds) || !$this->facility_id || !$this->selectedSlotStart) {
            return;
        }

        $this->conflicts = [];
        $this->warnings = [];

        $facility = Facility::find($this->facility_id);
        if (!$facility) return;

        // 1. Check space collision (physical segments overlap)
        $collisionService = app(SpaceCollisionService::class);
        $spaceConflicts = $collisionService->check(
            $facility->id,
            $this->selectedDay,
            $this->selectedSlotStart,
            $this->selectedSlotEnd,
            $segmentIds
        );

        if (!empty($spaceConflicts)) {
            $this->conflicts = array_map(fn ($c) => array_merge($c, ['category' => 'space']), $spaceConflicts);
            return;
        }

        // 2. Check segments aren't already claimed by another group (group-to-group overlap is blocked)
        //    Trainers CAN overlap with groups (supervision), but two groups cannot share cells.
        foreach ($this->assignments as $assignment) {
            $assignType = $assignment['type'] ?? '';
            if ($assignType === 'trainer') continue; // trainers don't block groups

            $occupiedSegments = $assignment['segment_ids'] ?? [];
            $overlap = array_intersect($segmentIds, $occupiedSegments);

            if (!empty($overlap)) {
                $ownerName = $assignment['group_name'] ?? $assignment['title'] ?? __('مجموعة أخرى');
                $this->conflicts = [[
                    'category' => 'segment_taken',
                    'title' => $ownerName,
                    'message' => __('الخلايا المحددة مشغولة بالفعل بواسطة') . ' "' . $ownerName . '" — ' . __('لا يمكن لمجموعتين مشاركة نفس المساحة'),
                    'time' => $this->selectedSlotStart . ' - ' . $this->selectedSlotEnd,
                ]];
                return;
            }
        }

        // 3. Check same group isn't already assigned in this slot
        foreach ($this->assignments as $assignment) {
            if (($assignment['group_id'] ?? null) === $groupId && ($assignment['type'] ?? '') !== 'existing') {
                $this->addError('assignment', __('هذه المجموعة معيّنة بالفعل في هذا الوقت'));
                return;
            }
        }

        // 3. Check group conflicts (can't be in two places at once)
        $conflictService = app(ScheduleConflictService::class);
        $validation = $conflictService->validateAssignment(
            'group',
            $groupId,
            $facility->id,
            $this->selectedDay,
            $this->selectedSlotStart,
            $this->selectedSlotEnd,
            $segmentIds
        );

        if (!$validation['valid']) {
            $this->conflicts = array_map(fn ($c) => array_merge($c, ['category' => 'group']), $validation['errors']);
            return;
        }

        // Warnings are non-blocking but shown to user
        if (!empty($validation['warnings'])) {
            $this->warnings = $validation['warnings'];
        }

        // 4. Sessions-per-week soft warning
        $groupForCheck = TrainingGroup::with('program')
            ->withCount(['schedules as active_schedules_count' => fn ($q) => $q->where('is_active', true)])
            ->find($groupId);
        if ($groupForCheck?->program) {
            $target = $groupForCheck->program->sessions_per_week;
            $afterAssign = $groupForCheck->active_schedules_count + 1;
            if ($afterAssign > $target) {
                $this->warnings[] = [
                    'message' => __('هذه المجموعة تجاوزت عدد الحصص المطلوبة') . " ({$afterAssign}/{$target} " . __('حصص أسبوعياً') . ")",
                ];
            }
        }

        // 5. Add to pending assignments
        $group = TrainingGroup::with('headTrainer')->find($groupId);
        $this->assignments[] = [
            'type' => 'group',
            'group_id' => $groupId,
            'group_name' => $group?->name_ar ?? '',
            'segment_ids' => $segmentIds,
            'implied_trainer' => $group?->headTrainer?->name_ar ?? $group?->headTrainer?->name,
            'implied_trainer_id' => $group?->head_trainer_id,
        ];
    }

    public function assignTrainerToSegments(int $trainerId, array $segmentIds): void
    {
        if (empty($segmentIds) || !$this->facility_id || !$this->selectedSlotStart) {
            return;
        }

        $this->conflicts = [];
        $this->warnings = [];

        // Check trainer conflicts
        $conflictService = app(ScheduleConflictService::class);
        $trainerConflicts = $conflictService->checkTrainerConflicts(
            $trainerId,
            $this->selectedDay,
            $this->selectedSlotStart,
            $this->selectedSlotEnd
        );

        if (!empty($trainerConflicts)) {
            $this->conflicts = array_map(fn ($c) => array_merge($c, ['category' => 'trainer']), $trainerConflicts);
            return;
        }

        $trainer = User::find($trainerId);
        $this->assignments[] = [
            'type' => 'trainer',
            'trainer_id' => $trainerId,
            'trainer_name' => $trainer?->name_ar ?? $trainer?->name ?? '',
            'segment_ids' => $segmentIds,
        ];
    }

    public function removeAssignment(int $index): void
    {
        unset($this->assignments[$index]);
        $this->assignments = array_values($this->assignments);
        $this->conflicts = [];
        $this->warnings = [];
    }

    public function dismissWarnings(): void
    {
        $this->warnings = [];
    }

    public function dismissConflicts(): void
    {
        $this->conflicts = [];
    }

    // ─── Save Operations ─────────────────────────────────────────

    public function saveAssignments(): void
    {
        $newAssignments = collect($this->assignments)->filter(fn ($a) => ($a['type'] ?? '') !== 'existing');

        if ($newAssignments->isEmpty() || !$this->facility_id || !$this->selectedSlotStart) {
            $this->addError('save', __('لا يوجد تعيينات جديدة للحفظ'));
            return;
        }

        $facility = Facility::find($this->facility_id);
        if (!$facility) return;

        $layout = $this->resolveLayout($facility);
        $dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;
        $conflictService = app(ScheduleConflictService::class);

        // Final validation pass before commit
        foreach ($newAssignments as $assignment) {
            if ($assignment['type'] === 'group') {
                $validation = $conflictService->validateAssignment(
                    'group',
                    $assignment['group_id'],
                    $facility->id,
                    $this->selectedDay,
                    $this->selectedSlotStart,
                    $this->selectedSlotEnd,
                    $assignment['segment_ids']
                );
                if (!$validation['valid']) {
                    $this->conflicts = $validation['errors'];
                    return;
                }
            } elseif ($assignment['type'] === 'trainer') {
                $trainerConflicts = $conflictService->checkTrainerConflicts(
                    $assignment['trainer_id'],
                    $this->selectedDay,
                    $this->selectedSlotStart,
                    $this->selectedSlotEnd
                );
                if (!empty($trainerConflicts)) {
                    $this->conflicts = $trainerConflicts;
                    return;
                }
            }
        }

        DB::transaction(function () use ($facility, $layout, $dayOfWeek, $newAssignments) {
            foreach ($newAssignments as $assignment) {
                if ($assignment['type'] === 'group') {
                    $this->persistGroupAssignment($assignment, $facility, $layout, $dayOfWeek);
                } elseif ($assignment['type'] === 'trainer') {
                    $this->persistTrainerAssignment($assignment, $facility, $dayOfWeek);
                }
            }
        });

        $this->dispatch('assignment-saved');
        session()->flash('success', __('تم حفظ الجدول بنجاح'));
        $this->loadGridForSlot();
        $this->refreshAvailability();
    }

    public function saveRecurring(): void
    {
        $newAssignments = collect($this->assignments)->filter(fn ($a) => ($a['type'] ?? '') !== 'existing');

        if ($newAssignments->isEmpty() || !$this->facility_id || !$this->selectedSlotStart) {
            return;
        }

        $facility = Facility::find($this->facility_id);
        if (!$facility) return;

        $layout = $this->resolveLayout($facility);
        $startDate = Carbon::parse($this->selectedDay);
        $dayOfWeek = $startDate->dayOfWeek;
        $collisionService = app(SpaceCollisionService::class);
        $conflictService = app(ScheduleConflictService::class);

        $days = !empty($this->recurringDays) ? array_map('intval', $this->recurringDays) : [$dayOfWeek];
        $bookedCount = 0;
        $conflictDates = [];
        $trainerConflictDates = [];

        DB::transaction(function () use (
            $facility, $layout, $startDate, $days,
            $collisionService, $conflictService,
            $newAssignments, &$bookedCount, &$conflictDates, &$trainerConflictDates
        ) {
            foreach ($newAssignments as $assignment) {
                if ($assignment['type'] !== 'group') continue;

                $group = TrainingGroup::find($assignment['group_id']);
                if (!$group) continue;

                foreach ($days as $targetDay) {
                    $schedule = TrainingSchedule::updateOrCreate(
                        [
                            'academy_id' => $group->academy_id,
                            'training_group_id' => $group->id,
                            'day_of_week' => $targetDay,
                            'start_time' => $this->selectedSlotStart,
                        ],
                        [
                            'end_time' => $this->selectedSlotEnd,
                            'facility_id' => $facility->id,
                            'space_reservation_template' => $assignment['segment_ids'],
                            'effective_from' => $startDate->toDateString(),
                            'effective_until' => $startDate->copy()->addWeeks($this->recurringWeeks)->toDateString(),
                            'is_active' => true,
                        ]
                    );

                    for ($week = 0; $week < $this->recurringWeeks; $week++) {
                        $date = $startDate->copy()->addWeeks($week);
                        while ($date->dayOfWeek !== (int) $targetDay) {
                            $date->addDay();
                        }

                        $dateStr = $date->toDateString();

                        // Space collision check
                        $spaceConflicts = $collisionService->check(
                            $facility->id,
                            $dateStr,
                            $this->selectedSlotStart,
                            $this->selectedSlotEnd,
                            $assignment['segment_ids']
                        );

                        if (!empty($spaceConflicts)) {
                            if ($this->skipConflicts) {
                                $conflictDates[] = $dateStr;
                                continue;
                            }
                            $conflictDates[] = $dateStr;
                            continue;
                        }

                        // Group conflict check (can't be in two places)
                        $groupConflicts = $conflictService->checkGroupConflicts(
                            $group->id,
                            $dateStr,
                            $this->selectedSlotStart,
                            $this->selectedSlotEnd,
                            $schedule->id
                        );

                        if (!empty($groupConflicts)) {
                            $conflictDates[] = $dateStr;
                            continue;
                        }

                        // Trainer conflict check (implied trainer from group)
                        if ($group->head_trainer_id) {
                            $tc = $conflictService->checkTrainerConflicts(
                                $group->head_trainer_id,
                                $dateStr,
                                $this->selectedSlotStart,
                                $this->selectedSlotEnd,
                                $schedule->id
                            );
                            if (!empty($tc)) {
                                $trainerConflictDates[] = $dateStr;
                                // Don't block — warn only. Trainer conflicts on recurring are soft.
                            }
                        }

                        SpaceReservation::create([
                            'academy_id' => $group->academy_id,
                            'facility_id' => $facility->id,
                            'space_layout_id' => $layout?->id,
                            'status' => 'confirmed',
                            'reservation_date' => $dateStr,
                            'start_time' => $this->selectedSlotStart,
                            'end_time' => $this->selectedSlotEnd,
                            'segment_ids' => $assignment['segment_ids'],
                            'reservable_type' => TrainingSchedule::class,
                            'reservable_id' => $schedule->id,
                            'title' => $group->name_ar,
                            'is_recurring' => true,
                            'recurrence_pattern' => 'weekly',
                            'recurrence_end_date' => $startDate->copy()->addWeeks($this->recurringWeeks)->toDateString(),
                            'created_by' => auth()->id(),
                        ]);

                        $bookedCount++;
                    }
                }
            }
        });

        $msg = __('تم حجز') . " {$bookedCount} " . __('جلسة بنجاح');
        if (!empty($conflictDates)) {
            $msg .= '. ' . __('تم تخطي') . ' ' . count($conflictDates) . ' ' . __('تاريخ بسبب تعارضات مكانية');
        }
        if (!empty($trainerConflictDates)) {
            $msg .= '. ' . __('تحذير: تعارض مدرب في') . ' ' . count($trainerConflictDates) . ' ' . __('تاريخ');
        }

        session()->flash('success', $msg);
        $this->showRecurring = false;
        $this->loadGridForSlot();
        $this->refreshAvailability();
    }

    // ─── Cancel Operations ───────────────────────────────────────

    public function cancelReservation(?int $reservationId = null, ?int $scheduleId = null): void
    {
        if ($reservationId) {
            $reservation = SpaceReservation::find($reservationId);
            if ($reservation) {
                $reservation->update([
                    'status' => 'cancelled',
                    'cancelled_by' => auth()->id(),
                    'cancelled_at' => now(),
                ]);
            }
        }

        if ($scheduleId) {
            $schedule = TrainingSchedule::find($scheduleId);
            if ($schedule) {
                $schedule->update(['is_active' => false, 'effective_until' => now()->toDateString()]);
            }
        }

        $this->loadGridForSlot();
        $this->refreshAvailability();
        session()->flash('success', __('تم إلغاء الحجز'));
    }

    public function cancelSeries(int $reservationId): void
    {
        $reservation = SpaceReservation::find($reservationId);
        if (!$reservation || !$reservation->reservable_id) return;

        $count = SpaceReservation::where('reservable_type', $reservation->reservable_type)
            ->where('reservable_id', $reservation->reservable_id)
            ->where('reservation_date', '>=', now()->toDateString())
            ->where('status', 'confirmed')
            ->count();

        SpaceReservation::where('reservable_type', $reservation->reservable_type)
            ->where('reservable_id', $reservation->reservable_id)
            ->where('reservation_date', '>=', now()->toDateString())
            ->where('status', 'confirmed')
            ->update([
                'status' => 'cancelled',
                'cancelled_by' => auth()->id(),
                'cancelled_at' => now(),
            ]);

        // Deactivate the schedule if all future reservations are cancelled
        if ($reservation->reservable_type === TrainingSchedule::class) {
            $schedule = TrainingSchedule::find($reservation->reservable_id);
            if ($schedule) {
                $schedule->update(['is_active' => false, 'effective_until' => now()->toDateString()]);
            }
        }

        $this->loadGridForSlot();
        $this->refreshAvailability();
        session()->flash('success', __('تم إلغاء') . " {$count} " . __('حجز مستقبلي'));
    }

    // ─── Availability Check (for sidebar indicators) ─────────────

    public function checkTrainerAvailability(int $trainerId): array
    {
        if (!$this->selectedSlotStart || !$this->selectedDay) return [];

        $conflictService = app(ScheduleConflictService::class);
        return $conflictService->checkTrainerConflicts(
            $trainerId,
            $this->selectedDay,
            $this->selectedSlotStart,
            $this->selectedSlotEnd
        );
    }

    public function getTrainerScheduleForDay(int $trainerId): array
    {
        $conflictService = app(ScheduleConflictService::class);
        return $conflictService->getTrainerOccupiedSlots($trainerId, $this->selectedDay);
    }

    public function getGroupScheduleForDay(int $groupId): array
    {
        $conflictService = app(ScheduleConflictService::class);
        return $conflictService->getGroupOccupiedSlots($groupId, $this->selectedDay);
    }

    // ─── Private Helpers ─────────────────────────────────────────

    private function refreshAvailability(): void
    {
        if (!$this->selectedSlotStart || !$this->selectedDay) {
            $this->trainerAvailability = [];
            $this->groupAvailability = [];
            return;
        }

        $conflictService = app(ScheduleConflictService::class);

        // Check each trainer
        $trainers = User::where('status', 'active')
            ->whereHas('primaryRole', fn ($q) => $q->whereIn('slug', ['trainer', 'head_trainer']))
            ->pluck('id');

        $this->trainerAvailability = [];
        foreach ($trainers as $trainerId) {
            $conflicts = $conflictService->checkTrainerConflicts(
                $trainerId,
                $this->selectedDay,
                $this->selectedSlotStart,
                $this->selectedSlotEnd
            );
            $this->trainerAvailability[$trainerId] = [
                'available' => empty($conflicts),
                'conflicts' => $conflicts,
            ];
        }

        // Check each active group
        $groups = TrainingGroup::whereIn('status', ['active', 'forming', 'full'])->pluck('id');

        $this->groupAvailability = [];
        foreach ($groups as $groupId) {
            $conflicts = $conflictService->checkGroupConflicts(
                $groupId,
                $this->selectedDay,
                $this->selectedSlotStart,
                $this->selectedSlotEnd
            );
            $this->groupAvailability[$groupId] = [
                'available' => empty($conflicts),
                'conflicts' => $conflicts,
            ];
        }
    }

    private function persistGroupAssignment(array $assignment, Facility $facility, ?SpaceLayout $layout, int $dayOfWeek): void
    {
        $group = TrainingGroup::find($assignment['group_id']);
        if (!$group) return;

        $schedule = TrainingSchedule::updateOrCreate(
            [
                'academy_id' => $group->academy_id,
                'training_group_id' => $group->id,
                'day_of_week' => $dayOfWeek,
                'start_time' => $this->selectedSlotStart,
            ],
            [
                'end_time' => $this->selectedSlotEnd,
                'facility_id' => $facility->id,
                'space_reservation_template' => $assignment['segment_ids'],
                'effective_from' => $this->selectedDay,
                'is_active' => true,
            ]
        );

        SpaceReservation::create([
            'academy_id' => $group->academy_id,
            'facility_id' => $facility->id,
            'space_layout_id' => $layout?->id,
            'status' => 'confirmed',
            'reservation_date' => $this->selectedDay,
            'start_time' => $this->selectedSlotStart,
            'end_time' => $this->selectedSlotEnd,
            'segment_ids' => $assignment['segment_ids'],
            'reservable_type' => TrainingSchedule::class,
            'reservable_id' => $schedule->id,
            'title' => $group->name_ar,
            'created_by' => auth()->id(),
        ]);
    }

    private function persistTrainerAssignment(array $assignment, Facility $facility, int $dayOfWeek): void
    {
        // Find schedule(s) in this slot and attach trainer
        $schedules = TrainingSchedule::where('facility_id', $facility->id)
            ->where('day_of_week', $dayOfWeek)
            ->where('start_time', $this->selectedSlotStart)
            ->where('end_time', $this->selectedSlotEnd)
            ->where('is_active', true)
            ->get();

        if ($schedules->isEmpty()) {
            // No group schedule at this time — create a standalone reservation for the trainer
            SpaceReservation::create([
                'academy_id' => app('current_academy')?->id,
                'facility_id' => $facility->id,
                'space_layout_id' => null,
                'status' => 'confirmed',
                'reservation_date' => $this->selectedDay,
                'start_time' => $this->selectedSlotStart,
                'end_time' => $this->selectedSlotEnd,
                'segment_ids' => $assignment['segment_ids'],
                'title' => ($assignment['trainer_name'] ?? __('مدرب')) . ' — ' . __('إشراف'),
                'created_by' => auth()->id(),
            ]);
            return;
        }

        // Attach to the relevant schedule(s) that overlap with the trainer's segments
        foreach ($schedules as $schedule) {
            $scheduleSegments = $schedule->space_reservation_template ?? [];
            $overlap = array_intersect($assignment['segment_ids'], $scheduleSegments);

            if (!empty($overlap) || empty($scheduleSegments)) {
                if (!$schedule->trainer_id) {
                    $schedule->update(['trainer_id' => $assignment['trainer_id']]);
                } elseif (!$schedule->assistant_trainer_id) {
                    $schedule->update(['assistant_trainer_id' => $assignment['trainer_id']]);
                }
            }
        }
    }

    private function resetGrid(): void
    {
        $this->selectedSlotStart = '';
        $this->selectedSlotEnd = '';
        $this->customStartTime = '';
        $this->customEndTime = '';
        $this->useCustomTime = false;
        $this->gridSegments = [];
        $this->assignments = [];
        $this->conflicts = [];
        $this->warnings = [];
    }

    private function loadGridForSlot(): void
    {
        $facility = Facility::find($this->facility_id);
        if (!$facility) return;

        $layout = $this->resolveLayout($facility);
        if (!$layout) {
            $this->gridSegments = [];
            return;
        }

        $segments = $layout->segments()->orderBy('sort_order')->get();
        $this->gridSegments = $segments->map(fn ($seg) => [
            'id' => $seg->id,
            'code' => $seg->code,
            'name' => $seg->name_ar ?: $seg->name,
            'row' => $seg->row_index,
            'col' => $seg->col_index,
            'lane' => $seg->lane_number,
            'available' => $seg->is_available,
            'capacity' => $seg->capacity,
        ])->toArray();

        // Load existing reservations for this slot
        $this->assignments = [];
        $reservations = SpaceReservation::where('facility_id', $facility->id)
            ->where('reservation_date', $this->selectedDay)
            ->where('status', 'confirmed')
            ->where('start_time', '<', $this->selectedSlotEnd)
            ->where('end_time', '>', $this->selectedSlotStart)
            ->with('reservable')
            ->get();

        $reservedScheduleIds = [];

        foreach ($reservations as $res) {
            $trainerName = null;
            if ($res->reservable && $res->reservable instanceof TrainingSchedule) {
                $trainerName = $res->reservable->trainer?->name_ar ?? $res->reservable->trainer?->name;
                $reservedScheduleIds[] = $res->reservable_id;
            }

            $this->assignments[] = [
                'type' => 'existing',
                'reservation_id' => $res->id,
                'title' => $res->title,
                'segment_ids' => $res->segment_ids ?? [],
                'is_recurring' => $res->is_recurring,
                'reservable_type' => $res->reservable_type,
                'reservable_id' => $res->reservable_id,
                'trainer_name' => $trainerName,
                'time' => substr($res->start_time, 0, 5) . ' - ' . substr($res->end_time, 0, 5),
            ];
        }

        // Also show active training_schedules that match this day/time but have no reservation
        $dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;
        $orphanSchedules = TrainingSchedule::where('facility_id', $facility->id)
            ->where('day_of_week', $dayOfWeek)
            ->where('is_active', true)
            ->where('start_time', '<', $this->selectedSlotEnd)
            ->where('end_time', '>', $this->selectedSlotStart)
            ->when(!empty($reservedScheduleIds), fn ($q) => $q->whereNotIn('id', $reservedScheduleIds))
            ->with(['group', 'trainer'])
            ->get();

        foreach ($orphanSchedules as $schedule) {
            $this->assignments[] = [
                'type' => 'existing',
                'reservation_id' => null,
                'schedule_id' => $schedule->id,
                'title' => $schedule->group?->name_ar ?? __('جدول بدون مجموعة'),
                'segment_ids' => $schedule->space_reservation_template ?? [],
                'is_recurring' => true,
                'reservable_type' => TrainingSchedule::class,
                'reservable_id' => $schedule->id,
                'trainer_name' => $schedule->trainer?->name_ar ?? $schedule->trainer?->name,
                'time' => substr($schedule->start_time, 0, 5) . ' - ' . substr($schedule->end_time, 0, 5),
            ];
        }
    }

    private function generateTimeSlots(?Facility $facility): array
    {
        if (!$facility) return [];

        $start = $facility->operating_start ?? '06:00';
        $end = $facility->operating_end ?? '23:00';

        $slots = [];
        $current = Carbon::parse($this->selectedDay . ' ' . $start);
        $endTime = Carbon::parse($this->selectedDay . ' ' . $end);

        while ($current < $endTime) {
            $slotEnd = $current->copy()->addHour();
            if ($slotEnd > $endTime) $slotEnd = $endTime;

            $slots[] = [
                'start' => $current->format('H:i'),
                'end' => $slotEnd->format('H:i'),
                'label' => $current->format('H:i') . ' - ' . $slotEnd->format('H:i'),
            ];

            $current->addHour();
        }

        return $slots;
    }

    private function getExistingReservations(?Facility $facility): array
    {
        if (!$facility || !$this->selectedDay) return [];

        $reservations = SpaceReservation::where('facility_id', $facility->id)
            ->where('reservation_date', $this->selectedDay)
            ->where('status', 'confirmed')
            ->orderBy('start_time')
            ->get()
            ->map(fn ($r) => [
                'id' => $r->id,
                'title' => $r->title ?? __('حجز'),
                'start' => substr($r->start_time, 0, 5),
                'end' => substr($r->end_time, 0, 5),
                'segment_ids' => $r->segment_ids ?? [],
                'is_recurring' => $r->is_recurring,
                'source' => 'reservation',
            ])
            ->toArray();

        // Also include active schedules for this day that have no reservation
        $dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;
        $reservedScheduleIds = SpaceReservation::where('facility_id', $facility->id)
            ->where('reservation_date', $this->selectedDay)
            ->where('status', 'confirmed')
            ->where('reservable_type', TrainingSchedule::class)
            ->pluck('reservable_id')
            ->toArray();

        $orphanSchedules = TrainingSchedule::where('facility_id', $facility->id)
            ->where('day_of_week', $dayOfWeek)
            ->where('is_active', true)
            ->when(!empty($reservedScheduleIds), fn ($q) => $q->whereNotIn('id', $reservedScheduleIds))
            ->with('group')
            ->orderBy('start_time')
            ->get();

        foreach ($orphanSchedules as $schedule) {
            $reservations[] = [
                'id' => $schedule->id,
                'title' => $schedule->group?->name_ar ?? __('جدول'),
                'start' => substr($schedule->start_time, 0, 5),
                'end' => substr($schedule->end_time, 0, 5),
                'segment_ids' => $schedule->space_reservation_template ?? [],
                'is_recurring' => true,
                'source' => 'schedule',
            ];
        }

        return $reservations;
    }

    private function resolveLayout(?Facility $facility): ?SpaceLayout
    {
        if (!$facility) return null;

        $dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;

        // Date-specific override first
        $layout = SpaceLayout::where('facility_id', $facility->id)
            ->where('is_active', true)
            ->where('is_recurring', false)
            ->where('effective_date', $this->selectedDay)
            ->first();

        if ($layout) return $layout;

        // Recurring for this day
        $layout = SpaceLayout::where('facility_id', $facility->id)
            ->where('is_active', true)
            ->where('is_recurring', true)
            ->where('effective_day_of_week', $dayOfWeek)
            ->first();

        if ($layout) return $layout;

        // Any active layout as fallback
        return SpaceLayout::where('facility_id', $facility->id)
            ->where('is_active', true)
            ->orderBy('sort_order')
            ->first();
    }

    /**
     * Groups offerable in the selected facility.
     *
     * Scoped two ways: the facility's branch, and the sports that facility can
     * host. A football court must not list swimming groups. A facility that
     * declares no activities hosts anything, so nothing breaks for academies
     * that have not filled the link in yet.
     */
    private function getAvailableGroups(): array
    {
        $facility = $this->facility_id
            ? Facility::with('activities:id')->find($this->facility_id)
            : null;

        $branchId = $facility?->branch_id ?? $this->getActiveBranchId();
        $activityIds = $facility ? $facility->activities->pluck('id')->all() : [];

        return TrainingGroup::whereIn('status', ['active', 'forming', 'full'])
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->when($activityIds, fn ($q) => $q->whereHas(
                'program',
                fn ($p) => $p->whereIn('activity_id', $activityIds)
            ))
            ->with(['program', 'headTrainer'])
            ->withCount(['schedules as active_schedules_count' => fn ($q) => $q->where('is_active', true)])
            ->orderBy('name_ar')
            ->get()
            ->map(fn ($g) => [
                'id' => $g->id,
                'name' => $g->name_ar,
                'code' => $g->code,
                'program' => $g->program?->name_ar,
                'count' => $g->current_count,
                'max' => $g->max_capacity,
                'trainer' => $g->headTrainer?->name_ar ?? $g->headTrainer?->name,
                'trainer_id' => $g->head_trainer_id,
                'color' => $this->groupColor($g->id),
                'available' => $this->groupAvailability[$g->id]['available'] ?? true,
                'conflict_reason' => $this->groupAvailability[$g->id]['conflicts'][0]['message'] ?? null,
                'scheduled_sessions' => $g->active_schedules_count,
                'sessions_per_week' => $g->program?->sessions_per_week ?? 2,
            ])
            ->toArray();
    }

    private function getAvailableTrainers(): array
    {
        $users = User::where('status', 'active')
            ->whereHas('primaryRole', fn ($q) => $q->whereIn('slug', ['trainer', 'head_trainer']))
            ->orderBy('name_ar')
            ->get();

        $userIds = $users->pluck('id')->toArray();
        $workloads = app(TrainerWorkloadService::class)->getWorkloadsForUsers($userIds);

        return $users->map(fn ($u) => [
            'id' => $u->id,
            'name' => $u->name_ar ?? $u->name,
            'available' => $this->trainerAvailability[$u->id]['available'] ?? true,
            'conflict_reason' => $this->trainerAvailability[$u->id]['conflicts'][0]['message'] ?? null,
            'workload' => $workloads[$u->id] ?? null,
        ])->toArray();
    }

    private function groupColor(int $id): string
    {
        // Shared with every printed sheet — screen and paper must agree.
        return \App\Domain\Training\Support\GroupColor::for($id);
    }
}
