<?php

namespace App\Http\Controllers\SuperAdmin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\RouteCard;
use App\Models\RouteCardProcess;
use App\Models\CustomerVendor;
use App\Models\Machines;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Exception;
use Illuminate\Support\Facades\DB;
use App\Models\ProductType;
use App\Models\Process;
use App\Models\Grn;
use Carbon\Carbon;
use App\Models\EmployeeDetails;


class SuperAdminRouteCardController extends Controller
{
    protected $officeStart = '09:00';
    protected $officeEnd = '18:30';
    public function routeCardPage()
    {
        try {
            Log::info('Loading route card page.');
    
            $last = RouteCard::orderBy('id', 'desc')->first();
            if ($last && preg_match('/(\d{4})$/', $last->route_no, $matches)) {
                $number = intval($matches[1]) + 1;
            } else {
                $number = 1;
            }
            $nextRouteNo = 'UEPL/RC/' . str_pad($number, 4, '0', STR_PAD_LEFT);
            Log::info('Next Route No generated: ' . $nextRouteNo);
    
            $companies = CustomerVendor::all();
            $machines = Machines::all();
            $now = \Carbon\Carbon::now();
    
            // MACHINE AVAILABILITY (MAX END DATE LOGIC)
            $machineAvailability = [];
            foreach ($machines as $machine) {
                $maxEndDate = \DB::table('route_card_processes')
                    ->where('machine', $machine->machine_id)
                    ->max('end_date');
    
                if ($maxEndDate && \Carbon\Carbon::parse($maxEndDate)->gt($now)) {
                    $availDate = 'Available from: ' . \Carbon\Carbon::parse($maxEndDate)->format('d-m-Y H:i');
                } else {
                    $availDate = 'Available now';
                }
                $machineAvailability[$machine->machine_id] = $availDate;
            }
            Log::info('Machine availability calculated.');
    
            $usedGrnNos = RouteCard::pluck('grn_no')->toArray();
            $availableGrns = Grn::whereNotIn('grn_no', $usedGrnNos)->get();
    
            $productTypes = ProductType::all();
            $employees = EmployeeDetails::all();
    
            // OPERATOR AVAILABILITY (MAX END DATE LOGIC)
            $operatorAvailability = [];
            foreach ($employees as $operator) {
                $maxEndDate = \DB::table('route_card_processes')
                    ->where('operator', $operator->id)
                    ->max('end_date');
    
                if ($maxEndDate && \Carbon\Carbon::parse($maxEndDate)->gt($now)) {
                    $availDate = 'Available from: ' . \Carbon\Carbon::parse($maxEndDate)->format('d-m-Y H:i');
                } else {
                    $availDate = 'Available now';
                }
                $operatorAvailability[$operator->id] = $availDate;
            }
            Log::info('Operator availability calculated.');
$processAssignments = [];
$processes = \App\Models\Process::with(['machines', 'operators'])->get();
foreach ($processes as $process) {
    // Key is product_type_id|process_name (both as strings)
    $key = $process->product_type_id . '|' . $process->name;
    $processAssignments[$key] = [
        'operators' => $process->operators->pluck('id')->map(fn($id) => (string)$id)->toArray(),
        'machines' => $process->machines->pluck('machine_id')->map(fn($id) => (string)$id)->toArray(),
    ];
}
Log::info('processAssignments:', $processAssignments);
Log::info('machines:', $machines->toArray());
Log::info('employees:', $employees->toArray());
            return view('superadmin.routecard.routecardorder', compact(
    'nextRouteNo', 'companies', 'machines', 'productTypes',
    'employees', 'availableGrns', 'machineAvailability', 'operatorAvailability',
    'processAssignments'
));
        } catch (Exception $e) {
            Log::error('Error loading route card page: ' . $e->getMessage(), [
                'exception' => $e,
                'trace' => $e->getTraceAsString(),
            ]);
            return back()->withErrors('Error loading route card page.');
        }
    }
   protected function getAllProcessesInOrder()
{
    // Get all RCs in order
    $rcs = RouteCard::orderBy('project_start_date')->with(['processes' => function($q) {
        $q->orderBy('id'); // Or use your process order field
    }])->get();

    $allProcesses = [];
    foreach ($rcs as $rc) {
        foreach ($rc->processes as $proc) {
            $proc->setRelation('rc', $rc); // attach RC to each process
            $allProcesses[] = $proc;
        }
    }
    return $allProcesses;
}
public function rescheduleFromProcess($changedProcessId)
{
    $allProcesses = $this->getAllProcessesInOrder();

    // Find index of changed process
    $startIdx = null;
    foreach ($allProcesses as $idx => $proc) {
        if ($proc->id == $changedProcessId) {
            $startIdx = $idx;
            break;
        }
    }
    if ($startIdx === null) return; // not found

    // Reschedule processes from $startIdx onward
    for ($i = $startIdx; $i < count($allProcesses); $i++) {
        $proc = $allProcesses[$i];
        $rc = $proc->rc;

        // For first process in RC
        if (
            ($i == 0) ||
            ($rc->id != $allProcesses[$i - 1]->rc->id)
        ) {
            // Get last process end from previous RC if exists
            $prevProc = ($i > 0) ? $allProcesses[$i - 1] : null;
            $rcProjectStart = Carbon::parse($rc->project_start_date);
            $start = $rcProjectStart;
            if ($prevProc && $prevProc->rc->id != $rc->id) {
                $prevEnd = Carbon::parse($prevProc->end_date);
                if ($prevEnd->gt($rcProjectStart)) {
                    $start = $prevEnd->copy();
                }
            }
        } else {
            // Not first process in RC, start after previous process in same RC
            $start = Carbon::parse($allProcesses[$i - 1]->end_date);
        }

        // Office hour logic
        $start = $this->adjustToOfficeHours($start);
        $duration = ((int)$proc->cycle_hours * 60) + (int)$proc->cycle_minutes;

        // Machine/operator availability
        $machineAvailable = $proc->machine
    ? $this->getMachineAvailableTime($proc->machine, $start, $duration, $proc->id)
    : $start;
$operatorAvailable = $proc->operator
    ? $this->getOperatorAvailableTime($proc->operator, $start, $duration, $proc->id)
    : $start;
$finalStart = max($start, $machineAvailable, $operatorAvailable);
     

        // Final office hour adjustment
        $finalStart = $this->adjustToOfficeHours($finalStart);
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($finalStart, $duration);

        // Never start before RC project_start_date
        if ($realStart->lt(Carbon::parse($rc->project_start_date))) {
            $realStart = Carbon::parse($rc->project_start_date);
            [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($realStart, $duration);
        }

        // Save
        $proc->start_date = $realStart->format('Y-m-d H:i');
        $proc->end_date = $realEnd->format('Y-m-d H:i');
        $proc->save();
    }
}

protected function hasResourceConflict($machine, $operator, $start, $end, $excludeIds = [])
{
    $q = \App\Models\RouteCardProcess::query();

    if ($machine) {
        $q->orWhere(function($query) use ($machine, $start, $end, $excludeIds) {
            $query->where('machine', $machine)
                ->whereNotIn('id', $excludeIds)
                ->where(function($query2) use ($start, $end) {
                    $query2->where(function($w) use ($start, $end) {
                        $w->where('start_date', '<', $end)
                          ->where('end_date', '>', $start);
                    });
                });
        });
    }
    if ($operator) {
        $q->orWhere(function($query) use ($operator, $start, $end, $excludeIds) {
            $query->where('operator', $operator)
                ->whereNotIn('id', $excludeIds)
                ->where(function($query2) use ($start, $end) {
                    $query2->where(function($w) use ($start, $end) {
                        $w->where('start_date', '<', $end)
                          ->where('end_date', '>', $start);
                    });
                });
        });
    }
    return $q->first();
}

    public function approvedList()
    {
        $cards = RouteCard::where('status', 'Approved')->orderByDesc('route_date')->get();
        return response()->json($cards);
    }
    public function approve(Request $request, $id)
    {
        $validated = $request->validate([
            'status' => 'required|in:Approved,Rejected,Pending',
            'approved_by' => 'nullable|string|max:255',
        ]);
        $card = RouteCard::findOrFail($id);
        $card->status = $validated['status'];
        $card->approved_by = $validated['approved_by'];
        $card->approved_at = in_array($validated['status'], ['Approved', 'Rejected']) ? now() : null;
        $card->save();

        return response()->json(['success' => true, 'status' => $card->status, 'approved_by' => $card->approved_by, 'approved_at' => $card->approved_at]);
    }
    public function routeCardDetails()
    {
        try {
            $cards = RouteCard::with('processes')->latest()->get();
            return view('superadmin.routecard.routecarddetails', compact('cards'));
        } catch (Exception $e) {
            return back()->withErrors('Error loading route card details.');
        }
    }
    public function getProcessList(Request $request)
    {
        $type = $request->query('product_type');
        $productType = ProductType::where('name', $type)->first();
        if (!$productType) return response()->json([]);
        $processes = Process::where('product_type_id', $productType->id)->pluck('name');
        return response()->json($processes);
    }

//     public function store(Request $request)
// {
//     $validated = $request->validate([
//         'route_no' => 'required',
//         'route_date' => 'required|date',
//         'project_start_date' => 'required|date',
//         // Add other required validations as needed
//     ]);

//     DB::beginTransaction();
//     try {
//         // Create Route Card
//         $card = RouteCard::create([
//             'route_no' => $request->route_no,
//             'route_date' => $request->route_date,
//             'grn_no' => $request->grn_no,
//             'grn_date' => $request->grn_date,
//             'order_no' => $request->order_no,
//             'order_date' => $request->order_date,
//             'company_name' => $request->company_name,
//             'company_address' => $request->company_address,
//             'company_gstn' => $request->company_gstn,
//             'company_email' => $request->company_email,
//             'company_phone' => $request->company_phone,
//             'order_ref_no' => $request->order_ref_no,
//             'quotation_no' => $request->quotation_no,
//             'quotation_date' => $request->quotation_date,
//             'rfq_no' => $request->rfq_no,
//             'rfq_date' => $request->rfq_date,
//             'part_no' => $request->part_no,
//             'project_material_no' => $request->project_material_no,
//             'drawing_no' => $request->drawing_no,
//             'drawing_rev' => $request->drawing_rev,
//             'description' => $request->description,
//             'product_type' => $request->product_type,
//             'project_start_date' => $request->project_start_date,
//             'created_by' => auth()->id() ?? 'system',
//         ]);

//         // Step 1: Create all processes, store their IDs with their index (+1 for 1-based user display)
//         $processIds = [];
//         $processRecords = [];
//         if ($request->processes && is_array($request->processes)) {
//             foreach ($request->processes as $idx => $proc) {
//                 $record = $card->processes()->create([
//                     'process' => $proc['process'] ?? ($proc['process_other'] ?? ''),
//                     'machine' => $proc['machine'] ?? '',
//                     'operator' => $proc['operator'] ?? '',
//                     'cycle_hours' => intval($proc['cycle_hours'] ?? 0),
//                     'cycle_minutes' => intval($proc['cycle_minutes'] ?? 0),
//                     'source' => $proc['source'] ?? 'inhouse',
//                     'description' => $proc['description'] ?? '',
//                     // Scheduling fields below will be filled in Step 2!
//                 ]);
//                 $processIds[$idx + 1] = $record->id;
//                 $processRecords[$idx + 1] = $record;
//             }
//         }

//         // Step 2: Schedule all processes with office hour logic and set previous_process_id
//         $scheduled = [];
//         $officeStart = $this->officeStart;
//         $officeEnd = $this->officeEnd;
//         $currentStart = Carbon::parse($request->project_start_date);

//         foreach ($request->processes as $idx => $proc) {
//             $model = $processRecords[$idx + 1];
//             $hours = intval($proc['cycle_hours'] ?? 0);
//             $minutes = intval($proc['cycle_minutes'] ?? 0);

//             // Get previous process index (from dropdown, 1-based)
//             $previous_process_idx = isset($proc['previous_process']) && $proc['previous_process'] !== ''
//                 ? intval($proc['previous_process'])
//                 : null;

//             // Decide start date: if a dependency exists, use the END of that process
//             if ($previous_process_idx && isset($scheduled[$previous_process_idx])) {
//                 $startDate = Carbon::parse($scheduled[$previous_process_idx]['end']);
//             } elseif (!empty($scheduled)) {
//                 // If no explicit dependency, chain after the last scheduled process
//                 $last = end($scheduled);
//                 $startDate = Carbon::parse($last['end']);
//             } else {
//                 // First process: use project_start_date
//                 $startDate = $currentStart->copy();
//             }
            
//             // If startDate is on a weekend, move to next Monday
//             // while (in_array($startDate->format('N'), [6, 7])) { // 6 = Saturday, 7 = Sunday
//             //     $startDate->addDay();
//             // }

//             // Office hour adjustment
//             $startDate = $this->adjustToOfficeHours($startDate);

//             // Duration in minutes
//             $durationMinutes = $hours * 60 + $minutes;

//             // Calculate end date, respecting office hours (returns [$realStart, $realEnd])
//             [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($startDate, $durationMinutes);

//             // Update process with schedule and dependency
//             $model->start_date = $realStart->format('Y-m-d H:i');
//             $model->end_date = $realEnd->format('Y-m-d H:i');
//             $model->previous_process_id = $previous_process_idx ? $processIds[$previous_process_idx] : null;
//             $model->save();

//             // Store for next scheduling step
//             $scheduled[$idx + 1] = [
//                 'start' => $realStart,
//                 'end' => $realEnd,
//             ];
//         }

//         DB::commit();
//         return redirect()->route('superadmin.routecard.routecardorder')->with('success', 'Route Card created!');
//     } catch (Exception $e) {
//         DB::rollBack();
//         return back()->withErrors('Error creating Route Card: ' . $e->getMessage())->withInput();
//     }
// }
public function store(Request $request)
{
    $validated = $request->validate([
        'route_no' => 'required',
        'route_date' => 'required|date',
        'project_start_date' => 'required|date',
        // Add other required validations as needed
    ]);

    // ---- 0. Check project_start_date is not already used ----
    $projectStartDate = $request->project_start_date;
    $duplicateRC = \App\Models\RouteCard::where('project_start_date', $projectStartDate)->first();
    if ($duplicateRC) {
        $rcNo = $duplicateRC->route_no ?? '';
        return back()
            ->withInput()
            ->withErrors([
                'project_start_date' => "Already this date has been assigned for Route Card " . $rcNo . ". Please select a different start date."
            ]);
    }

    $processInputs = $request->input('processes', []);

    // ---- 1. Pre-calculate ALL process schedules before save, and check for conflicts ----
    $officeStart = $this->officeStart;
    $officeEnd = $this->officeEnd;
    $currentStart = Carbon::parse($request->project_start_date);
    $scheduled = [];
    $conflictMsg = null;

    // Pre-create dummy process schedule for validation
    foreach ($processInputs as $idx => $proc) {
        $hours = intval($proc['cycle_hours'] ?? 0);
        $minutes = intval($proc['cycle_minutes'] ?? 0);

        // Get previous process index (from dropdown, 1-based)
        $previous_process_idx = isset($proc['previous_process']) && $proc['previous_process'] !== ''
            ? intval($proc['previous_process'])
            : null;

        // Decide start date
        if ($previous_process_idx && isset($scheduled[$previous_process_idx])) {
            $startDate = Carbon::parse($scheduled[$previous_process_idx]['end']);
        } elseif (!empty($scheduled)) {
            $last = end($scheduled);
            $startDate = Carbon::parse($last['end']);
        } else {
            $startDate = $currentStart->copy();
        }

        // Office hour adjustment
        $startDate = $this->adjustToOfficeHours($startDate);
        $durationMinutes = $hours * 60 + $minutes;
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($startDate, $durationMinutes);

        // ---- Conflict Validation ----
        $machine = $proc['machine'] ?? null;
        $operator = $proc['operator'] ?? null;

        if ($machine || $operator) {
    $conflictQuery = \App\Models\RouteCardProcess::whereHas('routeCard', function($query) {
            $query->where('status', 'In Production');
        })
        ->where(function($q) use ($machine, $operator, $realStart, $realEnd) {
            if ($machine) {
                $q->orWhere(function($q2) use ($machine, $realStart, $realEnd) {
                    $q2->where('machine', $machine)
                        ->where(function($w) use ($realStart, $realEnd) {
                            $w->where('start_date', '<', $realEnd)
                                ->where('end_date', '>', $realStart);
                        });
                });
            }

            if ($operator) {
                $q->orWhere(function($q2) use ($operator, $realStart, $realEnd) {
                    $q2->where('operator', $operator)
                        ->where(function($w) use ($realStart, $realEnd) {
                            $w->where('start_date', '<', $realEnd)
                                ->where('end_date', '>', $realStart);
                        });
                });
            }
        })
        ->first();

    if ($conflictQuery) {
        $rc = \App\Models\RouteCard::find($conflictQuery->route_card_id);
        $rcNo = $rc ? $rc->route_no : 'N/A';
        $pName = $conflictQuery->process ?? 'N/A';
        $conflictMsg = "Conflict: Route Card $rcNo, Process '$pName' already scheduled from {$conflictQuery->start_date} to {$conflictQuery->end_date} on the same machine/operator.";
        break; // Stop on first conflict
    }
}

        $scheduled[$idx + 1] = [
            'start' => $realStart,
            'end' => $realEnd,
        ];
    }

    if ($conflictMsg) {
        return back()->withInput()->withErrors(['processes' => $conflictMsg]);
    }

    // ---- 2. NO conflicts, proceed to create RC + processes ----
    DB::beginTransaction();
    try {
        $card = RouteCard::create([
            'route_no' => $request->route_no,
            'route_date' => $request->route_date,
            'grn_no' => $request->grn_no,
            'grn_date' => $request->grn_date,
            'order_no' => $request->order_no,
            'order_date' => $request->order_date,
            'company_name' => $request->company_name,
            'company_address' => $request->company_address,
            'company_gstn' => $request->company_gstn,
            'company_email' => $request->company_email,
            'company_phone' => $request->company_phone,
            'order_ref_no' => $request->order_ref_no,
            'quotation_no' => $request->quotation_no,
            'quotation_date' => $request->quotation_date,
            'rfq_no' => $request->rfq_no,
            'rfq_date' => $request->rfq_date,
            'part_no' => $request->part_no,
            'project_material_no' => $request->project_material_no,
            'drawing_no' => $request->drawing_no,
            'drawing_rev' => $request->drawing_rev,
            'description' => $request->description,
            'product_type' => $request->product_type,
            'project_start_date' => $request->project_start_date,
            'created_by' => auth()->id() ?? 'system',
        ]);

        // Reuse $scheduled to fill start/end dates:
        $processIds = [];
        $processRecords = [];
        foreach ($processInputs as $idx => $proc) {
            $model = $card->processes()->create([
                'process' => $proc['process'] ?? ($proc['process_other'] ?? ''),
                'machine' => $proc['machine'] ?? '',
                'operator' => $proc['operator'] ?? '',
                'cycle_hours' => intval($proc['cycle_hours'] ?? 0),
                'cycle_minutes' => intval($proc['cycle_minutes'] ?? 0),
                'source' => $proc['source'] ?? 'inhouse',
                'description' => $proc['description'] ?? '',
                'start_date' => $scheduled[$idx + 1]['start']->format('Y-m-d H:i'),
                'end_date' => $scheduled[$idx + 1]['end']->format('Y-m-d H:i'),
                'previous_process_id' => isset($proc['previous_process']) && $proc['previous_process'] !== '' ? $processIds[$proc['previous_process']] : null,
            ]);
            $processIds[$idx + 1] = $model->id;
            $processRecords[$idx + 1] = $model;
        }

        DB::commit();
        return redirect()->route('superadmin.routecard.routecardorder')->with('success', 'Route Card created!');
    } catch (\Exception $e) {
        DB::rollBack();
        return back()->withErrors('Error creating Route Card: ' . $e->getMessage())->withInput();
    }
}


    public function show($id)
    {
        $routeCard = RouteCard::with(['processes' => function($q) {
            $q->orderBy('start_date');
        }])->findOrFail($id);
        $routeCardProcesses = $routeCard->processes;
        $machines = Machines::all();    // For machine name display
        $employees = EmployeeDetails::all();   // For operator name display
        return view('superadmin.routecard.show', compact('routeCard', 'routeCardProcesses', 'machines', 'employees'));
    }

   public function edit($id)
{
    $card = RouteCard::with('processes')->findOrFail($id);
    $companies = CustomerVendor::all();
    $machines = Machines::all();
    $productTypes = ProductType::all();
    $employees = EmployeeDetails::all();

    // Machine availability logic (max end date)
    $now = \Carbon\Carbon::now();
    $machineAvailability = [];
    foreach ($machines as $machine) {
        $maxEndDate = \DB::table('route_card_processes')
            ->where('machine', $machine->machine_id)
            ->max('end_date');
        if ($maxEndDate && \Carbon\Carbon::parse($maxEndDate)->gt($now)) {
            $availDate = 'Available from: ' . \Carbon\Carbon::parse($maxEndDate)->format('d-m-Y H:i');
        } else {
            $availDate = 'Available now';
        }
        $machineAvailability[$machine->machine_id] = $availDate;
    }

    // Operator availability logic (max end date)
    $operatorAvailability = [];
    foreach ($employees as $emp) {
        $maxEndDate = \DB::table('route_card_processes')
            ->where('operator', $emp->id)
            ->max('end_date');
        if ($maxEndDate && \Carbon\Carbon::parse($maxEndDate)->gt($now)) {
            $availDate = 'Available from: ' . \Carbon\Carbon::parse($maxEndDate)->format('d-m-Y H:i');
        } else {
            $availDate = 'Available now';
        }
        $operatorAvailability[$emp->id] = $availDate;
    }

    // 1. Fetch process assignments for all product types and processes
$processAssignments = [];
foreach ($productTypes as $pt) {
    $ptProcesses = \App\Models\Process::with(['machines', 'operators'])
        ->where('product_type_id', $pt->id)
        ->get();
    foreach ($ptProcesses as $proc) {
        $key = $pt->id . '|' . $proc->name;
        $processAssignments[$key] = [
            'machines' => $proc->machines->pluck('machine_id')->map('strval')->toArray(),
            'operators' => $proc->operators->pluck('id')->map('strval')->toArray(),
        ];
    }
}

// 2. Product type name => ID mapping
$productTypeNameToId = [];
foreach ($productTypes as $pt) {
    $productTypeNameToId[$pt->name] = $pt->id;
}

// 3. Add to compact:
return view('superadmin.routecard.edit', compact(
    'card', 'companies', 'machines', 'productTypes', 'employees',
    'machineAvailability', 'operatorAvailability',
    'processAssignments', 'productTypeNameToId'
));
}

//     public function update(Request $request, $id)
// {
//     $validated = $request->validate([
//         'route_no' => 'required',
//         'route_date' => 'required|date',
//         'project_start_date' => 'required|date',
//         // (Add other validation as needed)
//     ]);

//     $card = RouteCard::findOrFail($id);
//     $card->update($request->except('processes'));

//     // Remove old processes
//     $card->processes()->delete();

//     // Create all processes first, store by index
//     $processIds = [];
//     $processRecords = [];
//     if ($request->processes && is_array($request->processes)) {
//         foreach ($request->processes as $idx => $proc) {
//             $record = $card->processes()->create([
//                 'process' => $proc['process'] ?? ($proc['process_other'] ?? ''),
//                 'machine' => $proc['machine'] ?? '',
//                 'operator' => $proc['operator'] ?? '',
//                 'cycle_hours' => intval($proc['cycle_hours'] ?? 0),
//                 'cycle_minutes' => intval($proc['cycle_minutes'] ?? 0),
//                 'source' => $proc['source'] ?? 'inhouse',
//                 'description' => $proc['description'] ?? '',
//                 // scheduling/dependencies will be set below
//             ]);
//             $processIds[$idx + 1] = $record->id; // 1-based index!
//             $processRecords[$idx + 1] = $record;
//         }
//     }

//     // Step 2: Schedule all processes and set previous_process_id
//     $scheduled = [];
//     $currentStart = \Carbon\Carbon::parse($request->project_start_date);

//     foreach ($request->processes as $idx => $proc) {
//         $model = $processRecords[$idx + 1];
//         $hours = intval($proc['cycle_hours'] ?? 0);
//         $minutes = intval($proc['cycle_minutes'] ?? 0);

//         // Get previous process index (from dropdown, 1-based)
//         $previous_process_idx = isset($proc['previous_process']) && $proc['previous_process'] !== ''
//             ? intval($proc['previous_process'])
//             : null;

//         // Decide start date
//         if ($previous_process_idx && isset($scheduled[$previous_process_idx])) {
//             $startDate = \Carbon\Carbon::parse($scheduled[$previous_process_idx]['end']);
//         } elseif (!empty($scheduled)) {
//             $last = end($scheduled);
//             $startDate = \Carbon\Carbon::parse($last['end']);
//         } else {
//             $startDate = $currentStart->copy();
//         }

//         // Office hours logic (optional, add your own office hour logic if needed)
//         // $startDate = $this->adjustToOfficeHours($startDate);

//         // Calculate end date
//         $endDate = $startDate->copy()->addHours($hours)->addMinutes($minutes);

//         // Update process with schedule and dependency
//         $model->start_date = $startDate;
//         $model->end_date = $endDate;
//         $model->previous_process_id = $previous_process_idx ? $processIds[$previous_process_idx] : null;
//         $model->save();

//         $scheduled[$idx + 1] = [
//             'start' => $startDate,
//             'end' => $endDate,
//         ];
//     }

//     return redirect()->route('superadmin.routecard.routecarddetails')->with('success', 'Route Card updated successfully!');
// }
public function update(Request $request, $id)
{
    \Log::info('RouteCard UPDATE request received', [
        'route_card_id' => $id,
        'request' => $request->all()
    ]);

    $validated = $request->validate([
        'route_no' => 'required',
        'route_date' => 'required|date',
        'project_start_date' => 'required|date',
        // Add other validation as needed
    ]);

    // ---- 0. Check project_start_date is not already used by other Route Card ----
    $projectStartDate = $request->project_start_date;
    $duplicateRC = \App\Models\RouteCard::where('project_start_date', $projectStartDate)
        ->where('id', '!=', $id)
        ->first();

    if ($duplicateRC) {
        $rcNo = $duplicateRC->route_no ?? '';
        \Log::warning('Project start date conflict on update', [
            'route_card_id' => $id,
            'project_start_date' => $projectStartDate,
            'conflict_with' => $rcNo
        ]);
        return back()
            ->withInput()
            ->withErrors([
                'project_start_date' => "Already this date has been assigned for Route Card " . $rcNo . ". Please select a different start date."
            ]);
    }

    $card = RouteCard::findOrFail($id);

    $processInputs = $request->input('processes', []);
    \Log::info('Processes input for update', [
        'route_card_id' => $id,
        'processInputs' => $processInputs
    ]);

    // --- 1. Pre-calculate process schedules and check for conflicts before saving ---
    $officeStart = $this->officeStart;
    $officeEnd = $this->officeEnd;
    $currentStart = \Carbon\Carbon::parse($request->project_start_date);
    $scheduled = [];
    $oldProcessIds = $card->processes()->pluck('id')->toArray();
    $conflictMsg = null;

    foreach ($processInputs as $idx => $proc) {
        $hours = intval($proc['cycle_hours'] ?? 0);
        $minutes = intval($proc['cycle_minutes'] ?? 0);

        // Get previous process index (from dropdown, 1-based)
        $previous_process_idx = isset($proc['previous_process']) && $proc['previous_process'] !== ''
            ? intval($proc['previous_process'])
            : null;

        // Decide start date
        if ($previous_process_idx && isset($scheduled[$previous_process_idx])) {
            $startDate = \Carbon\Carbon::parse($scheduled[$previous_process_idx]['end']);
        } elseif (!empty($scheduled)) {
            $last = end($scheduled);
            $startDate = \Carbon\Carbon::parse($last['end']);
        } else {
            $startDate = $currentStart->copy();
        }

        // Office hour adjustment
        $startDate = $this->adjustToOfficeHours($startDate);
        $durationMinutes = $hours * 60 + $minutes;
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($startDate, $durationMinutes);

        // ---- Conflict Validation ----
        $machine = $proc['machine'] ?? null;
        $operator = $proc['operator'] ?? null;

        // If editing, avoid checking conflict with itself
        $ignoreProcessId = $oldProcessIds[$idx] ?? null;

        if ($machine || $operator) {
            $conflictQuery = \App\Models\RouteCardProcess::where(function($q) use ($machine, $operator, $realStart, $realEnd) {
                if ($machine) {
                    $q->orWhere(function($q2) use ($machine, $realStart, $realEnd) {
                        $q2->where('machine', $machine)
                            ->where(function($w) use ($realStart, $realEnd) {
                                $w->where('start_date', '<', $realEnd)
                                    ->where('end_date', '>', $realStart);
                            });
                    });
                }
                if ($operator) {
                    $q->orWhere(function($q2) use ($operator, $realStart, $realEnd) {
                        $q2->where('operator', $operator)
                            ->where(function($w) use ($realStart, $realEnd) {
                                $w->where('start_date', '<', $realEnd)
                                    ->where('end_date', '>', $realStart);
                            });
                    });
                }
            })
            ->when($ignoreProcessId, function($query) use ($ignoreProcessId) {
                $query->where('id', '!=', $ignoreProcessId);
            })
            ->first();

            if ($conflictQuery) {
    $rc = \App\Models\RouteCard::find($conflictQuery->route_card_id);
    $rcNo = $rc ? $rc->route_no : 'N/A';
    $pName = $conflictQuery->process ?? 'N/A';

    // Get operator name
    $operatorName = '';
    if (!empty($conflictQuery->operator)) {
        $operator = \App\Models\EmployeeDetails::find($conflictQuery->operator);
        $operatorName = $operator ? $operator->name : 'Unknown Operator';
    }

    // Get machine info (optional, if you want)
    $machineLabel = '';
    if (!empty($conflictQuery->machine)) {
        $machineLabel = " on machine '{$conflictQuery->machine}'";
    }

    // Build user-friendly conflict message
    $conflictMsg = "Conflict: Route Card $rcNo, Process '$pName' already scheduled from {$conflictQuery->start_date} to {$conflictQuery->end_date}"
        . (!empty($operatorName) ? " with Operator '$operatorName'" : '')
        . $machineLabel
        . ".";

    break; // Stop on first conflict
}

        }

        $scheduled[$idx + 1] = [
            'start' => $realStart,
            'end' => $realEnd,
        ];
    }

    if ($conflictMsg) {
        \Log::info('RouteCard update stopped due to process conflict', [
            'route_card_id' => $id,
            'conflictMsg' => $conflictMsg,
            'processInputs' => $processInputs,
            'scheduled' => $scheduled
        ]);
        return back()->withInput()->withErrors(['processes' => $conflictMsg]);
    }

    // ---- 2. NO conflicts, update Route Card and processes ----
    \Log::info('Updating RouteCard (no conflicts)', [
        'route_card_id' => $id,
        'update_data' => $request->except('processes')
    ]);
    $card->update($request->except('processes'));

    // Remove old processes
    $card->processes()->delete();
    \Log::info('Deleted old processes', [
        'route_card_id' => $id
    ]);

    // Create and schedule new processes
    $processIds = [];
    $processRecords = [];
    foreach ($processInputs as $idx => $proc) {
        $model = $card->processes()->create([
            'process' => $proc['process'] ?? ($proc['process_other'] ?? ''),
            'machine' => $proc['machine'] ?? '',
            'operator' => $proc['operator'] ?? '',
            'cycle_hours' => intval($proc['cycle_hours'] ?? 0),
            'cycle_minutes' => intval($proc['cycle_minutes'] ?? 0),
            'source' => $proc['source'] ?? 'inhouse',
            'description' => $proc['description'] ?? '',
            'start_date' => $scheduled[$idx + 1]['start']->format('Y-m-d H:i'),
            'end_date' => $scheduled[$idx + 1]['end']->format('Y-m-d H:i'),
            'previous_process_id' => isset($proc['previous_process']) && $proc['previous_process'] !== '' ? $processIds[$proc['previous_process']] : null,
        ]);
        $processIds[$idx + 1] = $model->id;
        $processRecords[$idx + 1] = $model;
        \Log::info('Created new process for updated RouteCard', [
            'route_card_id' => $id,
            'process_id' => $model->id,
            'process_input' => $proc,
            'scheduled_start' => $scheduled[$idx + 1]['start'],
            'scheduled_end' => $scheduled[$idx + 1]['end'],
        ]);
    }

    \Log::info('RouteCard update successful', [
        'route_card_id' => $id,
        'processIds' => $processIds
    ]);

    return redirect()->route('superadmin.routecard.routecarddetails')->with('success', 'Route Card updated successfully!');
}

    public function destroy($id)
    {
        $card = RouteCard::findOrFail($id);
        
        // Capture info for rescheduling
        $projectStartDate = $card->project_start_date;
        $routeDate = $card->route_date; // Fallback or secondary sort? Usually project_start_date is key.

        // Delete (or soft delete if using SoftDeletes, but here force delete logic implies physical removal of processes)
        $card->processes()->delete();
        $card->delete();

        // Reschedule subsequent projects to fill gap
        // We find all RCs that might need moving. Effectively those starting on or after the deleted one.
        // Or just ALL RCs? To be safe, maybe all active?
        // Let's filter by project_start_date >= deleted card's start.
        
        $subsequentRCs = RouteCard::whereDate('project_start_date', '>=', $projectStartDate)
                                  ->orderBy('project_start_date')
                                  ->orderBy('id')
                                  ->get();

        foreach ($subsequentRCs as $rc) {
             $this->rescheduleEntireRouteCard($rc->id);
        }

        return response()->json([
            'success' => true,
            'message' => 'Route Card deleted and schedule updated!',
        ]);
    }
    // In SuperAdminRouteCardController

    public function updateProcess(Request $request, $id)
    {
        $process = RouteCardProcess::findOrFail($id);
        $data = $request->all();
        $process->update($data);
        return response()->json(['success' => true, 'process' => $process]);
    }

    public function storeProcess(Request $request)
    {
        $data = $request->all();
        $process = RouteCardProcess::create($data);
        return response()->json(['success' => true, 'process' => $process]);
    }


    public function deleteProcess($id)
    {
        $process = RouteCardProcess::findOrFail($id);
        $process->delete();
        return response()->json(['success' => true]);
    }
    /**
     * Get the next available time for a machine across all route card processes.
     */
    protected function getMachineAvailableTime($machine, $proposedStart, $durationMinutes, $skipProcessId = null)
    {
        $conflicts = RouteCardProcess::where('machine', $machine)
            ->when($skipProcessId, function ($q) use ($skipProcessId) {
                $q->where('id', '!=', $skipProcessId);
            })
            ->where(function ($q) use ($proposedStart, $durationMinutes) {
                $start = Carbon::parse($proposedStart);
                $end = $start->copy()->addMinutes($durationMinutes);
                $q->where(function ($w) use ($start, $end) {
                    $w->where('start_date', '<', $end)
                        ->where('end_date', '>', $start);
                });
            })
            ->orderBy('end_date', 'desc')
            ->first();

        if ($conflicts) {
            return Carbon::parse($conflicts->end_date);
        }
        return Carbon::parse($proposedStart);
    }

    /**
     * Adjust to office hours (returns 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 an end time within office hours.
     */
    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];
    }

    /**
     * Reschedule this process and all downstream processes recursively.
     */
    protected function rescheduleProcesses($process, $visited = [])
    {
        if ($process->source === 'outsourced') return;
        if (in_array($process->id, $visited)) return;
        $visited[] = $process->id;

        // Find when this process can start:
        $prevEnd = null;
        if ($process->previous_process_id) {
            $prevProc = RouteCardProcess::find($process->previous_process_id);
            $prevEnd = $prevProc ? Carbon::parse($prevProc->end_date) : null;
        }
        $proposedStart = $prevEnd ?: Carbon::parse($process->start_date);

        // Machine check: find when this machine is free (any RouteCardProcess, except this one)
        $duration = ((int)$process->cycle_hours * 60) + (int)$process->cycle_minutes;
        $machineAvailable = $this->getMachineAvailableTime($process->machine, $proposedStart, $duration, $process->id);

        // Office hours: can't start before 09:00, can't end after 18:30
        $start = max($proposedStart, $machineAvailable);
        $start = $this->adjustToOfficeHours($start);

        // End time (office hours aware)
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($start, $duration);

        // Save changes
        $process->start_date = $realStart->format('Y-m-d H:i');
        $process->end_date = $realEnd->format('Y-m-d H:i');
        $process->save();

        // Now reschedule all processes where this process is the previous_process
        $children = RouteCardProcess::where('previous_process_id', $process->id)->get();
        foreach ($children as $child) {
            if ($child->source !== 'outsourced') {
                $this->rescheduleProcesses($child, $visited);
            }
        }
    }


    /**
     * Update a process and reschedule downstream.
     */
//     public function updateProcessAndReschedule(Request $request, $id)
// {
//     \Log::info("updateProcessAndReschedule called for process ID: $id", ['input' => $request->all()]);
//     $process = RouteCardProcess::findOrFail($id);
//     \Log::info('Process loaded', ['process' => $process->toArray()]);

//     // Cycle time in minutes
//     $hours = $request->ct_hours ?? $request->cycle_hours ?? 0;
//     $minutes = $request->ct_minutes ?? $request->cycle_minutes ?? 0;
//     $durationMinutes = (int)$hours * 60 + (int)$minutes;

//     // Set start_date (from user input), adjust for office hours
//     $startDate = $request->start_date
//         ? \Carbon\Carbon::parse($request->start_date)
//         : \Carbon\Carbon::parse($process->start_date);

//     // Always adjust to office hours on update
//     $startDate = $this->adjustToOfficeHours($startDate);

//     // Calculate end date within office hours
//     [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($startDate, $durationMinutes);

//     // Update the process
//     $process->fill([
//         'process' => $request->process,
//         'description' => $request->description,
//         'machine' => $request->machine,
//         'operator' => $request->operator,
//         'cycle_hours' => $hours,
//         'cycle_minutes' => $minutes,
//         'source' => $request->source,
//         'previous_process_id' => $request->previous_process_id,
//         'start_date' => $realStart->format('Y-m-d H:i'),
//         'end_date' => $realEnd->format('Y-m-d H:i'),
//     ]);
//     $process->save();
//     \Log::info('Process updated', ['updated_process' => $process->toArray()]);

//     // Now reschedule downstream processes...
//     $this->globalRescheduleFrom($process->id);

//     \Log::info("Rescheduling done for process $id");
//     return response()->json(['success' => true]);
// }
public function updateProcessAndReschedule(Request $request, $id)
{
    \Log::info("updateProcessAndReschedule called for process ID: $id", ['input' => $request->all()]);
    $process = \App\Models\RouteCardProcess::findOrFail($id);

    // Parse cycle hours/minutes
    $hours = $request->ct_hours ?? $request->cycle_hours ?? 0;
    $minutes = $request->ct_minutes ?? $request->cycle_minutes ?? 0;
    $durationMinutes = (int)$hours * 60 + (int)$minutes;

    // Get requested start date (if empty, fallback to DB value)
    $startDate = $request->start_date
        ? \Carbon\Carbon::parse($request->start_date)
        : \Carbon\Carbon::parse($process->start_date);

    // Always adjust to office hours (customize as needed)
    $startDate = $this->adjustToOfficeHours($startDate);
    [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($startDate, $durationMinutes);

    // === 1. Restrict first process from preponing before project_start_date ===
    if (empty($request->previous_process_id) || $request->previous_process_id == null) {
        $routeCard = \App\Models\RouteCard::find($process->route_card_id ?? $request->route_card_id);
        if ($routeCard && $realStart->lt(\Carbon\Carbon::parse($routeCard->project_start_date))) {
            $msg = "First process cannot start before the Route Card's Project Start Date ({$routeCard->project_start_date}).";
            \Log::warning($msg);
            return response()->json(['success' => false, 'message' => $msg], 422);
        }
    }

    // Check if preponed (moved earlier than old start)
    $isPreponed = \Carbon\Carbon::parse($realStart)->lt(\Carbon\Carbon::parse($process->start_date));

    // --- Get all downstream process IDs (recursive) ---
    $downstreamIds = $this->getDownstreamProcessIds($process->id);

    // --- Conflict check: Only block when preponed and conflict with other processes not in downstream ---
    if ($isPreponed) {
        $conflictQuery = \App\Models\RouteCardProcess::where('id', '!=', $id)
            ->whereNotIn('id', $downstreamIds)
            ->where(function($q) use ($request, $realStart, $realEnd) {
                if ($request->machine) {
                    $q->orWhere(function($q2) use ($request, $realStart, $realEnd) {
                        $q2->where('machine', $request->machine)
                            ->where(function($w) use ($realStart, $realEnd) {
                                $w->where('start_date', '<', $realEnd)
                                  ->where('end_date', '>', $realStart);
                            });
                    });
                }
                if ($request->operator) {
                    $q->orWhere(function($q2) use ($request, $realStart, $realEnd) {
                        $q2->where('operator', $request->operator)
                            ->where(function($w) use ($realStart, $realEnd) {
                                $w->where('start_date', '<', $realEnd)
                                  ->where('end_date', '>', $realStart);
                            });
                    });
                }
            })
            ->first();

        if ($conflictQuery) {
            // Get Route Card info for the error message
            $conflictRC = \App\Models\RouteCard::find($conflictQuery->route_card_id);
            $conflictProcessName = $conflictQuery->process ?? 'N/A';
            $conflictRCNo = $conflictRC ? $conflictRC->route_no : 'N/A';

            // Get operator name for user-friendly message
            $operatorName = '';
            if (!empty($conflictQuery->operator)) {
                $operatorModel = \App\Models\EmployeeDetails::find($conflictQuery->operator);
                $operatorName = $operatorModel ? $operatorModel->name : 'Unknown Operator';
            }

            // Get machine label (optional)
            $machineLabel = '';
            if (!empty($conflictQuery->machine)) {
                $machineLabel = " on Machine '{$conflictQuery->machine}'";
            }

            // Build user-friendly conflict message
            $errorMsg = "Conflict: Route Card {$conflictRCNo}, Process '{$conflictProcessName}' already scheduled from {$conflictQuery->start_date} to {$conflictQuery->end_date}"
                . (!empty($operatorName) ? " with Operator '{$operatorName}'" : "")
                . $machineLabel
                . ".";
            \Log::warning($errorMsg);
            return response()->json(['success' => false, 'message' => $errorMsg], 422);
        }
    }

    // --- Update the process ---
    $process->fill([
        'process' => $request->process,
        'description' => $request->description,
        'machine' => $request->machine,
        'operator' => $request->operator,
        'cycle_hours' => $hours,
        'cycle_minutes' => $minutes,
        'source' => $request->source,
        'previous_process_id' => $request->previous_process_id,
        'start_date' => $realStart->format('Y-m-d H:i'),
        'end_date' => $realEnd->format('Y-m-d H:i'),
    ]);
    $process->save();
    \Log::info('Process updated', ['updated_process' => $process->toArray()]);
    // Add this line for within-RC cascading shift:
    $this->rescheduleDownstreamProcesses($process); 
    $this->globalRescheduleFrom($process->id);

    \Log::info("Rescheduling done for process $id");
    return response()->json(['success' => true]);
}

protected function rescheduleDownstreamProcesses($parentProcess)
{
    // Find all processes that depend on this one (i.e., where previous_process_id == parentProcess->id)
    $downstreams = \App\Models\RouteCardProcess::where('previous_process_id', $parentProcess->id)
        ->orderBy('id') // order as per your needs
        ->get();

    foreach ($downstreams as $proc) {
        // New start is parent's end, recalculate end
        $start = \Carbon\Carbon::parse($parentProcess->end_date);
        $start = $this->adjustToOfficeHours($start); // optional, if you want office hour logic
        $minutes = intval($proc->cycle_hours) * 60 + intval($proc->cycle_minutes);
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($start, $minutes);

        // Update this process
        $proc->start_date = $realStart->format('Y-m-d H:i');
        $proc->end_date = $realEnd->format('Y-m-d H:i');
        $proc->save();

        // Recurse for further downstream processes
        $this->rescheduleDownstreamProcesses($proc);
    }
}
/**
 * Recursively collect all downstream (child) process IDs for a given process.
 * @param int $processId
 * @param array $collected
 * @return array
 */
protected function getDownstreamProcessIds($processId, &$collected = [])
{
    $children = \App\Models\RouteCardProcess::where('previous_process_id', $processId)->pluck('id')->toArray();
    foreach ($children as $childId) {
        if (!in_array($childId, $collected)) {
            $collected[] = $childId;
            $this->getDownstreamProcessIds($childId, $collected);
        }
    }
    return $collected;
}

    protected function rescheduleEntireRouteCard($rcId)
    {
        $rc = RouteCard::with(['processes' => function ($q) {
            $q->orderBy('id'); // Or order by your process order
        }])->find($rcId);

        if (!$rc) return;

        // For each process in this RC, reschedule based on machine availability after previous_process (or start of RC)
        foreach ($rc->processes as $proc) {

            if ($proc->source === 'outsourced') continue;
            $prevEnd = null;
            if ($proc->previous_process_id) {
                $prevProc = $rc->processes->where('id', $proc->previous_process_id)->first();
                $prevEnd = $prevProc ? Carbon::parse($prevProc->end_date) : null;
            }

            // If first process, use either its current start_date or the end of last job on its machine
            $proposedStart = $prevEnd ?: Carbon::parse($rc->project_start_date ?? $proc->start_date);

            // Machine availability check: this will push process start to after last booked process (any route card!)
            $duration = ((int)$proc->cycle_hours * 60) + (int)$proc->cycle_minutes;
            $machineAvailable = $this->getMachineAvailableTime($proc->machine, $proposedStart, $duration, $proc->id);

            // Office hours adjustment
            $start = max($proposedStart, $machineAvailable);
            $start = $this->adjustToOfficeHours($start);

            [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($start, $duration);

            $proc->start_date = $realStart->format('Y-m-d H:i');
            $proc->end_date = $realEnd->format('Y-m-d H:i');
            $proc->save();
        }
    }
public function globalRescheduleFrom($changedProcessId)
{
    \Log::info("globalRescheduleFrom called for process ID: $changedProcessId");

    // Find the process just updated
    $updatedProcess = \App\Models\RouteCardProcess::find($changedProcessId);
    if (!$updatedProcess) return;

    $rcs = \App\Models\RouteCard::with(['processes' => function ($q) {
        $q->orderBy('id');
    }])->orderBy('project_start_date')->get();

    // 1. Cascade within the current RC from the updated process
    $this->rescheduleDownstreamProcesses($updatedProcess);

    // 2. Find the index of this RC in the list
    $currentRcIndex = $rcs->search(function($rc) use ($updatedProcess) {
        return $rc->id == $updatedProcess->route_card_id;
    });

    // 3. Get the end time of the *last* process in the current RC
    $currentRc = $rcs[$currentRcIndex];
    $lastProcess = $currentRc->processes->sortBy('start_date')->last();
    $lastEnd = $lastProcess ? \Carbon\Carbon::parse($lastProcess->end_date) : null;

    // 4. For all subsequent RCs, set their first process's start after the latest end of same machine/operator
    for ($i = $currentRcIndex + 1; $i < $rcs->count(); $i++) {
        $rc = $rcs[$i];
        $firstProcess = $rc->processes->sortBy('start_date')->first();
        if (!$firstProcess) continue;

        $machine = $firstProcess->machine;
        $operator = $firstProcess->operator;
        $projectStart = \Carbon\Carbon::parse($rc->project_start_date);

        // Find latest end time for this machine/operator from all *previous* RCs
        $latestMachineOperatorEnd = null;
        for ($j = 0; $j < $i; $j++) {
            foreach ($rcs[$j]->processes as $p) {
                if ($p->machine == $machine && $p->operator == $operator) {
                    $end = \Carbon\Carbon::parse($p->end_date);
                    if (!$latestMachineOperatorEnd || $end->gt($latestMachineOperatorEnd)) {
                        $latestMachineOperatorEnd = $end->copy();
                    }
                }
            }
        }

        // Earliest possible start: latest of machine/operator or project start
        $earliestStart = $projectStart->copy();
        $constraints = ["projectStart: " . $projectStart->format('Y-m-d H:i')];
        if ($latestMachineOperatorEnd) {
            $constraints[] = "lastEnd for machine/operator ($machine/$operator): " . $latestMachineOperatorEnd->format('Y-m-d H:i');
            if ($latestMachineOperatorEnd->gt($projectStart)) {
                $earliestStart = $latestMachineOperatorEnd->copy();
            }
        }

        // LOG
        \Log::info("RC {$rc->route_no} - First process {$firstProcess->id}: Determined earliestStart as {$earliestStart->format('Y-m-d H:i')}", [
            'constraints_considered' => $constraints,
            'machine' => $machine,
            'operator' => $operator,
            'process' => $firstProcess->process
        ]);

        $duration = ((int)$firstProcess->cycle_hours * 60) + (int)$firstProcess->cycle_minutes;
        [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($earliestStart, $duration);

        if (
            $firstProcess->start_date != $realStart->format('Y-m-d H:i') ||
            $firstProcess->end_date != $realEnd->format('Y-m-d H:i')
        ) {
            \Log::info("Process {$firstProcess->id} in RC {$rc->route_no} rescheduled", [
                'old_start' => $firstProcess->getOriginal('start_date'),
                'old_end' => $firstProcess->getOriginal('end_date'),
                'new_start' => $realStart->format('Y-m-d H:i'),
                'new_end' => $realEnd->format('Y-m-d H:i')
            ]);
            $firstProcess->start_date = $realStart->format('Y-m-d H:i');
            $firstProcess->end_date = $realEnd->format('Y-m-d H:i');
            $firstProcess->save();
        }

        // Update lastEnd for downstream logic (even though not needed now)
        $lastEnd = $realEnd->copy();

        // Cascade for all downstream processes in this RC
        $this->rescheduleDownstreamProcesses($firstProcess);
    }

    \Log::info("Rescheduling done for process $changedProcessId");
}

/**
 * Get the next available time for an operator across all route card processes.
 */
protected function getOperatorAvailableTime($operator, $proposedStart, $durationMinutes, $skipProcessId = null)
{
    $conflicts = \App\Models\RouteCardProcess::where('operator', $operator)
        ->when($skipProcessId, function ($q) use ($skipProcessId) {
            $q->where('id', '!=', $skipProcessId);
        })
        ->where(function ($q) use ($proposedStart, $durationMinutes) {
            $start = \Carbon\Carbon::parse($proposedStart);
            $end = $start->copy()->addMinutes($durationMinutes);
            $q->where(function ($w) use ($start, $end) {
                $w->where('start_date', '<', $end)
                  ->where('end_date', '>', $start);
            });
        })
        ->orderBy('end_date', 'desc')
        ->first();

    if ($conflicts) {
        return \Carbon\Carbon::parse($conflicts->end_date);
    }
    return \Carbon\Carbon::parse($proposedStart);
}

    public function markPlanned($id)
    {
        $card = RouteCard::findOrFail($id);
        $card->status = 'Planned';
        $card->save();

        Log::info("RouteCard {$card->route_no} marked as Planned");
        return response()->json(['success' => true]);
    }

    public function plannedList()
    {
        $planned = RouteCard::where('status', 'Planned')->orderByDesc('route_date')->get();
        return response()->json($planned);
    }
    public function markReadyProduction($id)
    {
        $card = RouteCard::findOrFail($id);
        $card->status = 'Ready to Production';
        $card->save();

        Log::info("RouteCard {$card->route_no} marked as Ready to Production");
        return response()->json(['success' => true]);
    }

    public function readyToProductionList()
    {
        $cards = RouteCard::with(['processes' => function ($q) {
            $q->orderBy('id');
        }])->where('status', 'Ready to Production')->orderByDesc('route_date')->get();

        // Optionally: Attach operator names
        $operatorMap = EmployeeDetails::pluck('name', 'id')->toArray();
        foreach ($cards as $rc) {
            foreach ($rc->processes as $p) {
                $p->operator_name = $p->operator && isset($operatorMap[$p->operator]) ? $operatorMap[$p->operator] : '-';
            }
        }
        return response()->json($cards);
    }

    // Mark as In Production
    public function markInProduction($id)
    {
        $rc = RouteCard::findOrFail($id);
        $rc->status = 'In Production';
        $rc->save();

        \Log::info("RouteCard {$rc->route_no} marked as In Production");
        return response()->json(['success' => true]);
    }
}
