<?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\Models\SpaceSegment;
use App\Domain\Facility\Services\SpaceCollisionService;
use App\Domain\Facility\Services\SpaceLayoutService;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSchedule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('تعيين المجموعات على الملاعب')]
class SpaceAssignmentWizard extends Component
{
    // Branch is enforced by BranchScope on Facility, TrainingGroup and (through
    // its group) TrainingSchedule, so this component holds no branch state of
    // its own.
    //
    // Every id below is set by a wire:click handler on the server and read back
    // when the assignment is written. None is bound in the view, so #[Locked]
    // costs nothing and closes the round-trip where a re-set id would steer the
    // wizard onto a record the branch-filtered lists never offered.

    #[Locked]
    public int $step = 1;

    // Step 1: Select Group
    public string $groupSearch = '';

    #[Locked]
    public ?int $selectedGroupId = null;

    #[Locked]
    public ?array $selectedGroup = null;

    // Step 2: Select Schedule
    #[Locked]
    public ?int $selectedScheduleId = null;

    #[Locked]
    public array $schedules = [];

    // Step 3: Select Facility
    #[Locked]
    public ?int $selectedFacilityId = null;

    #[Locked]
    public array $facilities = [];

    // Step 4: Visual Grid Selection
    #[Locked]
    public ?int $resolvedLayoutId = null;

    #[Locked]
    public array $segments = [];

    #[Locked]
    public array $selectedSegmentIds = [];

    #[Locked]
    public array $occupiedSegments = [];

    public array $conflicts = [];
    public array $warnings = [];

    // Layout info
    public string $layoutType = 'grid';
    public int $gridRows = 0;
    public int $gridCols = 0;

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

        // No branch clause: Facility carries BranchScope, so this is already
        // this branch's list. Re-stating it here would only hide the fact that
        // the boundary is enforced at the model.
        $this->facilities = Facility::where('status', 'active')
            ->orderBy('name_ar')
            ->get(['id', 'name', 'name_ar', 'type'])
            ->toArray();
    }

    public function updatedGroupSearch(): void
    {
        // Search is handled in render()
    }

    public function selectGroup(int $id): void
    {
        $group = TrainingGroup::with(['program', 'schedules.trainer'])->findOrFail($id);

        // Picking a new group invalidates everything chosen for the old one.
        // resolveSchedule() would refuse a stale schedule id anyway, but a
        // half-reset wizard showing another group's court is not worth keeping.
        $this->selectedScheduleId = null;
        $this->selectedFacilityId = null;
        $this->selectedSegmentIds = [];
        $this->resolvedLayoutId = null;
        $this->segments = [];
        $this->occupiedSegments = [];
        $this->conflicts = [];
        $this->warnings = [];

        $this->selectedGroupId = $group->id;
        $this->selectedGroup = [
            'id' => $group->id,
            'name_ar' => $group->name_ar,
            'name' => $group->name,
            'program_name' => $group->program?->name_ar ?? '-',
            'current_count' => $group->current_count,
            'max_capacity' => $group->max_capacity,
        ];

        $this->schedules = $group->schedules
            ->map(fn ($s) => [
                'id' => $s->id,
                'day_of_week' => $s->day_of_week,
                'day_name' => $s->day_name,
                'start_time' => $s->start_time,
                'end_time' => $s->end_time,
                'trainer_name' => $s->trainer?->name_ar ?? '-',
                'facility_id' => $s->facility_id,
                'has_space_template' => !empty($s->space_reservation_template),
            ])
            ->toArray();

        $this->step = 2;
    }

    public function selectSchedule(int $id): void
    {
        $schedule = TrainingSchedule::where('training_group_id', $this->selectedGroupId)
            ->findOrFail($id);

        $this->selectedScheduleId = $schedule->id;

        // A facility already on the schedule is adopted only if it is still one
        // this branch may book. Stale rows exist — a schedule written before the
        // facility moved branch — and adopting one blindly would carry the whole
        // wizard onto a court the user cannot see.
        $adoptable = $schedule->facility_id
            && Facility::where('status', 'active')->whereKey($schedule->facility_id)->exists();

        if ($adoptable) {
            $this->selectedFacilityId = $schedule->facility_id;
            $this->selectedSegmentIds = $schedule->space_reservation_template ?? [];
            $this->step = 4;
            $this->resolveLayout();
        } else {
            $this->step = 3;
        }
    }

    /**
     * Re-resolved rather than trusted: saveAssignment() writes this id onto the
     * schedule, so the branch filtering that built $this->facilities has to be
     * binding at the point of the write, not only at the point of the render.
     */
    public function selectFacility(int $id): void
    {
        $facility = Facility::where('status', 'active')->findOrFail($id);

        $this->selectedFacilityId = $facility->id;
        $this->step = 4;
        $this->resolveLayout();
    }

    /**
     * The schedule this wizard is working on, resolved through the selected
     * group. TrainingSchedule is scoped through that group, so the branch
     * condition is applied here where the query is built.
     */
    private function resolveSchedule(): TrainingSchedule
    {
        return TrainingSchedule::where('training_group_id', $this->selectedGroupId)
            ->findOrFail($this->selectedScheduleId);
    }

    public function resolveLayout(): void
    {
        if (!$this->selectedFacilityId || !$this->selectedScheduleId) {
            return;
        }

        $schedule = $this->resolveSchedule();

        $layoutService = app(SpaceLayoutService::class);
        $today = now()->toDateString();
        $layout = $layoutService->resolveLayout(
            $this->selectedFacilityId,
            $today,
            $schedule->start_time
        );

        if (!$layout) {
            $layout = SpaceLayout::where('facility_id', $this->selectedFacilityId)
                ->where('is_active', true)
                ->where('is_recurring', true)
                ->where('effective_day_of_week', $schedule->day_of_week)
                ->first();
        }

        if (!$layout) {
            $layout = SpaceLayout::where('facility_id', $this->selectedFacilityId)
                ->where('is_active', true)
                ->first();
        }

        if (!$layout) {
            $this->segments = [];
            $this->resolvedLayoutId = null;
            return;
        }

        $this->resolvedLayoutId = $layout->id;
        $this->layoutType = $layout->layout_type->value;

        $config = $layout->layout_config;
        $this->gridRows = $config['rows'] ?? 0;
        $this->gridCols = $config['columns'] ?? 0;

        $this->segments = $layout->segments()
            ->orderBy('sort_order')
            ->get(['id', 'code', 'name', 'name_ar', 'row_index', 'col_index', 'lane_number', 'is_available', 'capacity'])
            ->toArray();

        $this->loadOccupiedSegments($schedule);
    }

    private function loadOccupiedSegments(TrainingSchedule $schedule): void
    {
        $nextDate = $this->getNextOccurrenceDate($schedule->day_of_week);

        $reservations = SpaceReservation::where('facility_id', $this->selectedFacilityId)
            ->where('reservation_date', $nextDate)
            ->where('status', 'confirmed')
            ->where('start_time', '<', $schedule->end_time)
            ->where('end_time', '>', $schedule->start_time)
            ->get();

        $this->occupiedSegments = [];
        foreach ($reservations as $res) {
            foreach ($res->segment_ids ?? [] as $segId) {
                $this->occupiedSegments[$segId] = [
                    'reservation_id' => $res->id,
                    'title' => $res->title ?? $res->notes ?? __('محجوز'),
                ];
            }
        }
    }

    private function getNextOccurrenceDate(int $dayOfWeek): string
    {
        $today = now();
        $currentDay = (int) $today->format('w');
        $diff = $dayOfWeek - $currentDay;
        if ($diff <= 0) {
            $diff += 7;
        }
        return $today->addDays($diff)->toDateString();
    }

    public function toggleSegment(int $segmentId): void
    {
        if (isset($this->occupiedSegments[$segmentId])) {
            return;
        }

        $segment = collect($this->segments)->firstWhere('id', $segmentId);
        if ($segment && !$segment['is_available']) {
            return;
        }

        if (in_array($segmentId, $this->selectedSegmentIds)) {
            $this->selectedSegmentIds = array_values(array_diff($this->selectedSegmentIds, [$segmentId]));
        } else {
            $this->selectedSegmentIds[] = $segmentId;
        }

        $this->checkConflicts();
    }

    public function selectAll(): void
    {
        $this->selectedSegmentIds = collect($this->segments)
            ->filter(fn ($s) => $s['is_available'] && !isset($this->occupiedSegments[$s['id']]))
            ->pluck('id')
            ->toArray();
        $this->checkConflicts();
    }

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

    private function checkConflicts(): void
    {
        if (empty($this->selectedSegmentIds) || !$this->selectedScheduleId) {
            $this->conflicts = [];
            $this->warnings = [];
            return;
        }

        $schedule = $this->resolveSchedule();
        $nextDate = $this->getNextOccurrenceDate($schedule->day_of_week);
        $collisionService = app(SpaceCollisionService::class);

        $this->conflicts = $collisionService->check(
            $this->selectedFacilityId,
            $nextDate,
            $schedule->start_time,
            $schedule->end_time,
            $this->selectedSegmentIds
        );

        $this->warnings = $collisionService->getTentativeWarnings(
            $this->selectedFacilityId,
            $nextDate,
            $schedule->start_time,
            $schedule->end_time,
            $this->selectedSegmentIds
        );
    }

    public function saveAssignment(): void
    {
        if (empty($this->selectedSegmentIds)) {
            session()->flash('error', __('يرجى اختيار مساحة واحدة على الأقل'));
            return;
        }

        if (!empty($this->conflicts)) {
            session()->flash('error', __('يوجد تعارض مع حجوزات مؤكدة. يرجى إزالة التعارض أولاً'));
            return;
        }

        // Both sides of this write are re-resolved through their scopes first:
        // the schedule through its group, the facility through BranchScope.
        // Writing either one straight from a component property is how a
        // group's session gets bound to another branch's pitch.
        $schedule = $this->resolveSchedule();
        $facility = Facility::findOrFail($this->selectedFacilityId);

        $schedule->update([
            'facility_id' => $facility->id,
            'space_reservation_template' => $this->selectedSegmentIds,
        ]);

        session()->flash('success', __('تم تعيين المساحة للمجموعة بنجاح! سيتم حجز المساحة تلقائياً عند إنشاء الحصص.'));
        $this->step = 5;
    }

    public function goToStep(int $step): void
    {
        if ($step < $this->step) {
            $this->step = $step;
        }
    }

    public function startOver(): void
    {
        $this->reset();
        $this->mount();
        $this->step = 1;
    }

    public function render()
    {
        $searchResults = collect();
        if (strlen($this->groupSearch) >= 2) {
            // BranchScope narrows TrainingGroup already; an explicit branch
            // clause here would be noise.
            $searchResults = TrainingGroup::with('program')
                ->where(function ($q) {
                    $q->where('name_ar', 'ilike', "%{$this->groupSearch}%")
                      ->orWhere('name', 'ilike', "%{$this->groupSearch}%")
                      ->orWhere('code', 'ilike', "%{$this->groupSearch}%");
                })
                ->where('status', 'active')
                ->limit(10)
                ->get();
        }

        $facilityType = collect($this->facilities)->firstWhere('id', $this->selectedFacilityId)['type'] ?? 'other';

        return view('livewire.facilities.space-assignment-wizard', [
            'searchResults' => $searchResults,
            'facilityType' => $facilityType,
        ]);
    }
}
