<?php

namespace App\Services;

use App\Models\RouteCardProcess;
use Carbon\Carbon;

class RouteCardCascadingRescheduler
{
    protected $officeStart = '09:00';
    protected $officeEnd = '18:30';

    /**
     * Recursively reschedule processes to avoid conflicts across all Route Cards.
     * @param int $processId
     * @param array $visitedProcesses
     */
    public function cascadeReschedule($processId, $visitedProcesses = [])
    {
        if (in_array($processId, $visitedProcesses)) {
            // Prevent infinite loop in circular dependencies!
            return;
        }
        $visitedProcesses[] = $processId;

        // 1. Get current process and its Route Card
        $process = RouteCardProcess::findOrFail($processId);
        $routeCard = $process->routeCard; // assumes relation is defined: routeCard() in RouteCardProcess

        // 2. Find all processes for this RC, ordered
        $allProcesses = $routeCard->processes()->orderBy('id')->get();

        // 3. Find position of this process in sequence
        $index = $allProcesses->search(fn($p) => $p->id == $processId);

        // 4. Reschedule all subsequent processes in this RC
        $prevEnd = Carbon::parse($process->end_date);
        for ($i = $index + 1; $i < $allProcesses->count(); $i++) {
            $proc = $allProcesses[$i];
            $duration = (int)$proc->cycle_hours * 60 + (int)$proc->cycle_minutes;
            [$newStart, $newEnd] = $this->scheduleWithinOfficeHours($prevEnd, $duration);

            $proc->start_date = $newStart->format('Y-m-d H:i:s');
            $proc->end_date = $newEnd->format('Y-m-d H:i:s');
            $proc->save();

            $prevEnd = $newEnd; // Next process should start after this
        }

        // 5. For the changed/shifted processes (including current), check for conflicts with other RCs
        $toCheck = collect([$process])->merge($allProcesses->slice($index + 1));

        foreach ($toCheck as $proc) {
            $conflicts = RouteCardProcess::where('id', '!=', $proc->id)
                ->where(function ($q) use ($proc) {
                    $q->where('machine', $proc->machine)
                      ->orWhere('operator', $proc->operator);
                })
                ->where(function ($q) use ($proc) {
                    $q->where('start_date', '<', $proc->end_date)
                      ->where('end_date', '>', $proc->start_date);
                })
                ->get();

            foreach ($conflicts as $conflict) {
                // Only if the conflict is in a different Route Card
                if ($conflict->route_card_id != $proc->route_card_id) {
                    // Shift conflicting process to start after this proc ends
                    $duration = (int)$conflict->cycle_hours * 60 + (int)$conflict->cycle_minutes;
                    [$newStart, $newEnd] = $this->scheduleWithinOfficeHours(Carbon::parse($proc->end_date), $duration);

                    $conflict->start_date = $newStart->format('Y-m-d H:i:s');
                    $conflict->end_date = $newEnd->format('Y-m-d H:i:s');
                    $conflict->save();

                    // Recursively reschedule everything downstream in that RC
                    $this->cascadeReschedule($conflict->id, $visitedProcesses);
                }
            }
        }
    }

    /**
     * Adjusts a start time to office hours.
     * @param Carbon $start
     * @return Carbon
     */
    protected function adjustToOfficeHours(Carbon $start)
    {
        $officeStart = $start->copy()->setTimeFromTimeString($this->officeStart);
        $officeEnd = $start->copy()->setTimeFromTimeString($this->officeEnd);

        if ($start->lt($officeStart)) {
            return $officeStart;
        }
        if ($start->gte($officeEnd)) {
            return $start->copy()->addDay()->setTimeFromTimeString($this->officeStart);
        }
        return $start;
    }

    /**
     * Schedules a process end time within office hours.
     * Returns array: [Carbon $start, Carbon $end]
     */
    protected function scheduleWithinOfficeHours(Carbon $start, $durationMinutes)
    {
        $current = $this->adjustToOfficeHours($start->copy());
        $minutesLeft = $durationMinutes;

        while ($minutesLeft > 0) {
            $officeEndToday = $current->copy()->setTimeFromTimeString($this->officeEnd);
            $minutesThisDay = min($minutesLeft, $current->diffInMinutes($officeEndToday, false));
            if ($minutesThisDay <= 0) {
                $current = $current->copy()->addDay()->setTimeFromTimeString($this->officeStart);
                continue;
            }
            $current = $current->copy()->addMinutes($minutesThisDay);
            $minutesLeft -= $minutesThisDay;
            if ($minutesLeft > 0) {
                $current = $current->copy()->addDay()->setTimeFromTimeString($this->officeStart);
            }
        }
        return [$start, $current];
    }
}
