<?php
/**
 * ajax_actions_for_cnc.php  (office / Gantt-admin AJAX endpoints)
 * =====================================================================
 * CHANGES FROM ORIGINAL:
 *
 *   1. All duplicated scheduling functions removed — uses
 *      cnc_scheduler_lib.php. scheduleNewTaskInlineoriginal() is
 *      deleted entirely; it was byte-for-byte identical to
 *      scheduleNewTaskInline() and was never called anywhere.
 *
 *   2. select_order_for_cnc: dual-table insert now calls
 *      cnc_rebuildDualTableMirror() after scheduling instead of
 *      hand-rolling align/insert/update inline. Also fixes the
 *      6-argument call into scheduleNewTaskInline() that PHP was
 *      silently truncating to 5 (the aligned start time was computed
 *      and then never actually used) — cnc_scheduleNewTaskInline()
 *      properly accepts a $startFrom override now.
 *
 *   3. update_cnc_task_priority (DRAG AND DROP): this is the main
 *      fix, in two layers.
 *
 *      Layer 1 (previously): the entire dual-table re-creation block
 *      was commented out, so dragging a dual-table job left a stale
 *      mirror break on its old opposite table forever (permanent
 *      capacity leak) while creating no mirror on its new opposite
 *      table (the "linked" UI banner showed but nothing actually
 *      blocked that table's schedule).
 *
 *      Layer 2 (found after re-enabling it): the re-enabled logic
 *      still corrupted the OPPOSITE table's genuinely unrelated real
 *      jobs on drag, and still created a pointless duplicate "alignment
 *      break" when dropping onto an empty machine. Root cause: the
 *      alignment/mirror comparison was running AFTER the dragged task
 *      already occupied part of its table's timeline (so an empty
 *      opposite table looked like a fake gap that needed filling), and
 *      the mirror's insertion point on the opposite table was reusing
 *      the dragged task's own priority NUMBER from an entirely
 *      different, unrelated priority sequence (each table has its own
 *      independent numbering).
 *
 *      cnc_scheduler_lib.php's dual-table subsystem was rewritten to
 *      compute each table's true backlog BEFORE any placement (always
 *      excluding the task's own row and any of its own leftover
 *      break rows), take the later of the two as the common start,
 *      and only fill the side that's genuinely ahead — never both,
 *      never neither table's own unrelated priority sequence. See
 *      that file's dual-table section header for the full writeup.
 *
 *      swapPriorityWithDualTask() was dead code (defined, never
 *      called) — removed. Not needed under the corrected algorithm.
 *
 *   4. add_cnc_break: fixed the bug where $priority (meant to place
 *      the break ahead of the selected "highest priority" task) was
 *      computed but never used — the insert always used
 *      $new_priority (= MAX+1), silently appending the break to the
 *      back of the queue regardless of what the user selected. Now
 *      uses $priority for the insert, and — like every other break
 *      endpoint — calls cnc_fullRescheduleMachine() afterward (the
 *      original never did, so the break's own start/end could go
 *      stale until an unrelated action happened to trigger a
 *      recalculation).
 *
 *   5. delete_cnc_task: if the deleted task was dual-table, its mirror
 *      break is now explicitly cleaned up (previously only handled in
 *      one of the two delete_cnc_task copies that existed across the
 *      original files — this file only had one, and it *did* handle
 *      it, but it's now routed through the shared
 *      cnc_deleteDualTableMirror() helper instead of inline SQL).
 *
 *   6. split_cnc_work_order: now calls the shared cnc_splitCncTask(),
 *      which propagates is_dual_table onto the new row (previously
 *      never set) and resets original_uph on both halves (a UPH
 *      baseline computed against the pre-split quantity is meaningless
 *      post-split).
 *
 *   7. start_machine_breakdown / stop_machine_breakdown: now mirror
 *      into cnc_machine_breakdown_log, since that table is the single
 *      source of truth the shop-floor tablet checks
 *      (cnc_isMachineInBreakdown()). Previously a breakdown started
 *      here was invisible to the tablet.
 *
 * FIELDS NO LONGER WRITTEN (unreferenced, see CHANGELOG.md):
 *   dual_table_break_id, linked_task_id, is_shadow_break, shadow_of
 * =====================================================================
 */
include("../../../include/startup.php");
require_once __DIR__ . '/cnc_scheduler_lib.php';

header('Content-Type: application/json');
error_reporting(E_ALL);
ini_set('display_errors', 1);

/* =====================================================================
 * select_order_for_cnc — insert new work order(s) onto the Gantt
 * ===================================================================== */
if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'select_order_for_cnc') {
    $json_data = $_REQUEST['work_orders'];
    $work_orders = json_decode($json_data, true);

    $inserted = false;
    $processed_machines = [];
    $shiftsByDay = cnc_getShiftsByDay($conn);

    foreach ($work_orders as $item) {
        $id            = (int) $item['id'];
        $work_order_id = (int) $item['work_order_id'];
        $part_id       = (int) $item['part_id'];
        $machine_id    = (int) $item['machine_id'];
        $table_id      = (int) $item['table_id'];
        $setup_hours   = 0;
        $processed_machines[$machine_id] = true;

        $result = $conn->query("SELECT part_id, id, end_date, quantity, product_name, total_hours, set_id
                                 FROM manufacture_machine_table WHERE id = $id");
        if (!$result || $result->num_rows == 0) continue;

        $row = $result->fetch_assoc();
        $quantity    = (int) $row['quantity'];
        $product_name = $row['product_name'];
        $total_hoursss = (float) $row['total_hours'];
        $set_id      = $row['set_id'];
        $manuf_id    = $row['id'];

        if ($set_id > 0) {
            $query_route = "SELECT operation_time, set_time, parts_per_operation FROM set_route_details WHERE set_id = $set_id";
        } else {
            $query_route = "SELECT operation_time, set_time, parts_per_operation FROM route_details WHERE item_id = $part_id";
        }
        $result_route = $conn->query($query_route);

        if ($result_route && $result_route->num_rows > 0) {
            $row_route = $result_route->fetch_assoc();
            [$hours, $minutes, $seconds] = explode(':', $row_route['set_time']);
            $setup_hours = $hours + ($minutes / 60) + ($seconds / 3600);

            if (!empty($row_route['operation_time'])) {
                [$h, $m, $s] = explode(":", $row_route['operation_time']);
                $operation_time = ($h * 60) + $m + ($s / 60);
            } else {
                $operation_time = 1;
            }
            $parts_per_operation = (!empty($row_route['parts_per_operation']) && $row_route['parts_per_operation'] > 0)
                ? (float) $row_route['parts_per_operation'] : 1;
            $cycle = (int) round($operation_time / $parts_per_operation);
        } else {
            $cycle = 1;
            $setup_hours = 0;
        }

        $total_hours = $total_hoursss + $setup_hours;
        $isDualTable = cnc_shouldOccupyBothTables($conn, $part_id, $set_id);

        $result_priority = mysqli_query($conn, "SELECT MAX(priority) AS priority FROM cnc_machine_table WHERE machine_id = $machine_id AND table_id = $table_id");
        $rowp = mysqli_fetch_assoc($result_priority);
        $new_priority = ($rowp['priority'] ?? 0) + 1;

        $scheduleData = cnc_scheduleNewTaskInline($conn, $total_hours, $shiftsByDay);
        $startStr = $scheduleData['start_date'];
        $endStr   = $scheduleData['end_date'];
        $firstShiftUsed = mysqli_real_escape_string($conn, (string) $scheduleData['shift_ids']);
        $remainingHoursStr = mysqli_real_escape_string($conn, $scheduleData['remaining_hours']);

        $insertQuery = "
            INSERT INTO cnc_machine_table
            (machine_id, table_id, manuf_id, work_order_id, part_id, end_date, start_date, quantity, shift_ids,
             product_name, total_hours, total_hours_update, cycle, set_id, priority, remaing_hours,
             original_end_date, original_start_date, setup_hours, original_total_hours, is_dual_table)
            VALUES (
                $machine_id, $table_id, $manuf_id, $work_order_id, $part_id, '$endStr', '$startStr', $quantity,
                '$firstShiftUsed', '" . mysqli_real_escape_string($conn, $product_name) . "', $total_hours,
                $total_hoursss, $cycle, $set_id, $new_priority, '$remainingHoursStr',
                '$endStr', '$startStr', $setup_hours, $total_hours, " . ($isDualTable ? 1 : 0) . "
            )";

        if ($conn->query($insertQuery)) {
            $mainTaskId = $conn->insert_id;
            $inserted = true;

            // NOTE: no explicit mirror-build call needed here anymore --
            // cnc_fullRescheduleMachine() (called once per machine below,
            // after ALL items in this request have been inserted) now
            // automatically resyncs every dual-table job's filler/mirror
            // from current backlog. Building mirrors here, one at a time
            // interleaved with insertion, could actually be LESS accurate
            // than doing it once at the end: if this request inserts
            // several dual-table jobs onto the same machine, an inline
            // build for job #1 wouldn't yet know about job #2's backlog.
            // The final resync processes every dual job on the machine
            // together, in priority order, after the full picture exists.

            $conn->query("UPDATE manufacture_machine_table SET in_scheduler_check = 1 WHERE id = $id");
        }
    }

    foreach (array_keys($processed_machines) as $machine_id) {
        cnc_fullRescheduleMachine($conn, $machine_id);
    }

    echo json_encode($inserted
        ? ['status' => 'success']
        : ['status' => 'failed', 'message' => 'No rows inserted']);
    exit;
}

/* =====================================================================
 * update_cnc_task_priority — DRAG AND DROP
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'update_cnc_task_priority') {

    $taskId                = intval($_REQUEST['task_id']);
    $newMachineId          = intval($_REQUEST['new_machine_id']);
    $newTableId            = intval($_REQUEST['new_table_id']);
    $leftNeighborPriority  = $_REQUEST['left_neighbor_priority'] ?? null;

    $draggedTask = mysqli_fetch_assoc(mysqli_query($conn, "SELECT * FROM cnc_machine_table WHERE id = $taskId"));
    if (!$draggedTask) {
        echo json_encode(['status' => 'error', 'message' => 'Task not found']);
        exit;
    }

    $oldMachineId = intval($draggedTask['machine_id']);
    $oldTableId   = intval($draggedTask['table_id']);
    $today        = date('Y-m-d');

    // Renumber the old table's remaining rows.
    $p = 1;
    $res = mysqli_query($conn, "
        SELECT id FROM cnc_machine_table
        WHERE machine_id = $oldMachineId AND table_id = $oldTableId
          AND end_date >= '$today' AND id != $taskId
        ORDER BY priority
    ");
    while ($r = mysqli_fetch_assoc($res)) {
        mysqli_query($conn, "UPDATE cnc_machine_table SET priority = $p WHERE id = {$r['id']}");
        $p++;
    }

    $insertPriority = ($leftNeighborPriority !== null) ? ((int)$leftNeighborPriority + 1) : 1;

    mysqli_query($conn, "
        UPDATE cnc_machine_table SET priority = priority + 1
        WHERE machine_id = $newMachineId AND table_id = $newTableId AND priority >= $insertPriority
    ");

    mysqli_query($conn, "
        UPDATE cnc_machine_table SET machine_id = $newMachineId, table_id = $newTableId, priority = $insertPriority
        WHERE id = $taskId
    ");

    // FIX: dual-table resync is now handled automatically, unconditionally,
    // inside cnc_fullRescheduleMachine() itself (see cnc_scheduler_lib.php)
    // -- it rebuilds every dual-table job's filler/mirror on the machine
    // from current backlog, every time. This closes a gap the previous
    // version of this endpoint had: it only protected the DRAGGED task if
    // that task itself happened to be dual. Dragging an ORDINARY task to a
    // position before or after an EXISTING dual-table job changes that
    // dual job's true backlog exactly as much as moving the dual job
    // itself would -- and the old, task-specific branching here never
    // caught that case at all, since $isDualTable only reflected the
    // dragged task. No special-casing needed here anymore; every drag
    // (dual or not) gets full dual-table consistency for free.
    if ($oldMachineId != $newMachineId) {
        cnc_fullRescheduleMachine($conn, $oldMachineId);
    }
    cnc_fullRescheduleMachine($conn, $newMachineId);

    echo json_encode(['status' => 'success', 'message' => 'Task updated successfully']);
    exit;
}

/* =====================================================================
 * delete_cnc_task
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'delete_cnc_task') {
    $task_id       = intval($_REQUEST['task_id']);
    $work_order_id = mysqli_real_escape_string($conn, $_REQUEST['work_order_id']);
    $part_id       = mysqli_real_escape_string($conn, $_REQUEST['part_id']);

    $taskData = mysqli_fetch_assoc(mysqli_query($conn, "SELECT machine_id, table_id, is_dual_table FROM cnc_machine_table WHERE id = $task_id LIMIT 1"));
    if (!$taskData) {
        echo json_encode(['success' => false, 'message' => 'Task not found']);
        exit;
    }

    $machine_id  = intval($taskData['machine_id']);
    $isDualTable = (bool) $taskData['is_dual_table'];

    if ($isDualTable) {
        cnc_deleteDualTableMirror($conn, $task_id);
    }

    if (mysqli_query($conn, "DELETE FROM cnc_machine_table WHERE id = $task_id")) {
        $conn->query("UPDATE manufacture_machine_table SET in_scheduler_check = 0 WHERE work_order_id = '$work_order_id' AND part_id = '$part_id'");
        cnc_fullRescheduleMachine($conn, $machine_id);
        echo json_encode(['success' => true, 'message' => 'Work order deleted successfully']);
    } else {
        echo json_encode(['success' => false, 'message' => 'Failed to delete work order: ' . mysqli_error($conn)]);
    }
    exit;
}

/* =====================================================================
 * add_cnc_break — Gantt admin "Add Break" button
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'add_cnc_break') {
    $machineId = intval($_REQUEST['machine_id']);
    $tableId   = intval($_REQUEST['table_id']);
    $duration  = floatval($_REQUEST['duration']);
    $reason    = mysqli_real_escape_string($conn, $_REQUEST['reason']);
    $highestPriorityTaskId = isset($_REQUEST['highest_priority_task_id']) ? intval($_REQUEST['highest_priority_task_id']) : 0;
    $highestPriority       = isset($_REQUEST['highest_priority']) ? intval($_REQUEST['highest_priority']) : 0;

    if ($machineId <= 0 || $tableId <= 0 || $duration < 0.5 || $duration > 10) {
        echo json_encode(['status' => 'error', 'message' => 'Invalid input values']);
        exit;
    }

    mysqli_autocommit($conn, false);
    $error = false;

    // Where the break should actually land: just ahead of whichever task the
    // caller flagged as "currently highest priority" (i.e. currently running).
    // FIX: this value was previously computed and then discarded — the insert
    // always used MAX(priority)+1, silently appending to the back of the
    // queue. It is now the value actually used for the insert.
    $priority = $highestPriority ? max(1, $highestPriority) : 1;

    $query = "SELECT MAX(end_date) as last_end_date FROM cnc_machine_table WHERE machine_id = $machineId AND table_id = $tableId";
    $result = mysqli_query($conn, $query);

    if (!$result) {
        $error = true;
    } else {
        $row = mysqli_fetch_assoc($result);
        $startDate = $row['last_end_date'] ? $row['last_end_date'] : date('Y-m-d H:i:s');
        $endDate   = date('Y-m-d H:i:s', strtotime($startDate) + ($duration * 3600));

        // Make room at the intended priority slot.
        if (!mysqli_query($conn, "
            UPDATE cnc_machine_table SET priority = priority + 1
            WHERE machine_id = $machineId AND table_id = $tableId AND priority >= $priority
        ")) {
            $error = true;
        }

        if (!$error) {
            $insertQuery = "INSERT INTO cnc_machine_table
                (machine_id, table_id, start_date, end_date, original_end_date, original_start_date,
                 total_hours, original_total_hours, remaing_hours,
                 is_break, break_reason, priority)
                VALUES ($machineId, $tableId, '$startDate', '$endDate', '$endDate', '$startDate',
                        $duration, $duration, $duration, 1, '$reason', $priority)";

            if (!mysqli_query($conn, $insertQuery)) {
                $error = true;
            }
        }
    }

    if ($error) {
        mysqli_rollback($conn);
        echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
    } else {
        mysqli_commit($conn);
        mysqli_autocommit($conn, true);
        // FIX: previously this endpoint never triggered a reschedule at all,
        // so the break's own (non-shift-aware) end_date could sit wrong in
        // the DB until an unrelated action happened to fix it.
        cnc_fullRescheduleMachine($conn, $machineId);
        echo json_encode(['status' => 'success']);
        exit;
    }
    mysqli_autocommit($conn, true);
    exit;
}

/* =====================================================================
 * get_cnc_task_details
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'get_cnc_task_details') {

    $task_id       = mysqli_real_escape_string($conn, $_REQUEST['task_id']);
    $work_order_id = mysqli_real_escape_string($conn, $_REQUEST['work_order_id']);
    $part_id       = mysqli_real_escape_string($conn, $_REQUEST['part_id']);
    $machine_id    = mysqli_real_escape_string($conn, $_REQUEST['machine_id']);

    $query = "SELECT
        mmt.machine_id AS task, mmt.id as task_id, wo.work_order_no, mmt.part_id, mmt.table_id,
        mmt.setup_hours, mmt.priority, mmt.total_hours AS hours_to_schedule, mmt.total_hours,
        mmt.work_order_id, mmt.product_name, mmt.quantity, mmt.set_id, won.work_order_name,
        mmt.shift_ids AS shift_name, DATE(mmt.start_date) AS start_date, DATE(mmt.end_date) AS end_date,
        TIME(mmt.start_date) AS start_time, mmt.is_break, mmt.break_reason,
        mmt.is_breakdown_affected, mmt.breakdown_hours,
        mb.id as breakdown_id, mb.breakdown_reason as machine_breakdown_reason,
        m.end_date as manufacture_end_date, m.description as manufacture_description,
        im.part_number as parent_part_number,
        GROUP_CONCAT(DISTINCT wocr.work_order_no ORDER BY wocr.work_order_no SEPARATOR ', ') as customer_order_nos
    FROM cnc_machine_table mmt
    LEFT JOIN work_order wo ON mmt.work_order_id = wo.id
    LEFT JOIN work_order_name won ON mmt.work_order_id = won.work_order_id AND mmt.part_id = won.part_id
    LEFT JOIN machine_breakdown mb ON mmt.machine_id = mb.machine_id AND mb.is_active = 1
    LEFT JOIN (
        SELECT m1.part_id, m1.work_order_id, m1.end_date, m1.description, m1.parent_ids
        FROM manufacture_machine_table m1
        INNER JOIN (
            SELECT part_id, work_order_id, MAX(end_date) as max_end_date
            FROM manufacture_machine_table GROUP BY part_id, work_order_id
        ) m2 ON m1.part_id = m2.part_id AND m1.work_order_id = m2.work_order_id AND m1.end_date = m2.max_end_date
    ) m ON mmt.part_id = m.part_id AND mmt.work_order_id = m.work_order_id
    LEFT JOIN item_management im ON m.parent_ids = im.id
    LEFT JOIN work_order_customer_ref_mapping wocr ON mmt.work_order_id = wocr.work_order_id
    WHERE mmt.id = '$task_id'
    LIMIT 1";

    $result = mysqli_query($conn, $query);

    if ($result && mysqli_num_rows($result) > 0) {
        $data = mysqli_fetch_assoc($result);

        if ($data['set_id'] != 0) {
            $route_query = "SELECT route_desc FROM set_route_details WHERE set_id = '{$data['set_id']}' LIMIT 1";
        } else {
            $route_query = "SELECT route_desc FROM route_details WHERE item_id = '{$data['part_id']}' LIMIT 1";
        }
        $route_result = mysqli_query($conn, $route_query);
        $data['route_desc'] = ($route_result && mysqli_num_rows($route_result) > 0)
            ? mysqli_fetch_assoc($route_result)['route_desc'] : '-';

        $data['is_late'] = (!empty($data['manufacture_end_date']) && $data['end_date'] > $data['manufacture_end_date']);

        $ppRes = mysqli_query($conn, "
            SELECT customer_reff, GROUP_CONCAT(DISTINCT RefSalesCode ORDER BY RefSalesCode SEPARATOR ', ') AS RefSalesCode
            FROM production_planning WHERE work_order_id = " . intval($work_order_id)
        );
        $ppRow = mysqli_fetch_assoc($ppRes);
        $customer_reff = $ppRow['customer_reff'] ?? '';
        $data['customer_code'] = utility::getTableFieldById("client_management", "customer_code", "id", $customer_reff);
        $data['RefSalesCode']  = $ppRow['RefSalesCode'] ?? '';

        echo json_encode(['success' => true, 'data' => $data]);
    } else {
        echo json_encode(['success' => false, 'message' => 'Task not found']);
    }
    exit;
}

/* =====================================================================
 * get_gantt_data
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'get_gantt_data') {

    $currentDateStr = date('Y-m-d');

    $query = "SELECT
        mmt.machine_id AS task, mmt.id as task_id, mmt.table_id, wo.work_order_no, mmt.part_id,
        mmt.setup_hours, mmt.priority, mmt.total_hours AS hours_to_schedule, mmt.total_hours,
        mmt.work_order_id, mmt.product_name, mmt.quantity, mmt.set_id, won.work_order_name,
        mmt.shift_ids AS shift_name, DATE(mmt.start_date) AS start_date, DATE(mmt.end_date) AS end_date,
        TIME(mmt.start_date) AS start_time, mmt.is_break, mmt.break_reason,
        mmt.is_breakdown_affected, mmt.breakdown_hours,
        mb.id as breakdown_id, mb.breakdown_reason as machine_breakdown_reason,
        m.end_date as manufacture_end_date, m.description as manufacture_description, m.parent_ids,
        im.part_number as parent_part_number,
        GROUP_CONCAT(DISTINCT wocr.work_order_no ORDER BY wocr.work_order_no SEPARATOR ', ') as customer_order_nos
    FROM cnc_machine_table mmt
    LEFT JOIN work_order wo ON mmt.work_order_id = wo.id
    LEFT JOIN work_order_name won ON mmt.work_order_id = won.work_order_id AND mmt.part_id = won.part_id
    LEFT JOIN machine_breakdown mb ON mmt.machine_id = mb.machine_id AND mb.is_active = 1
    LEFT JOIN (
        SELECT m1.part_id, m1.work_order_id, m1.end_date, m1.description, m1.parent_ids
        FROM manufacture_machine_table m1
        INNER JOIN (
            SELECT part_id, work_order_id, MAX(end_date) as max_end_date
            FROM manufacture_machine_table GROUP BY part_id, work_order_id
        ) m2 ON m1.part_id = m2.part_id AND m1.work_order_id = m2.work_order_id AND m1.end_date = m2.max_end_date
    ) m ON mmt.part_id = m.part_id AND mmt.work_order_id = m.work_order_id
    LEFT JOIN item_management im ON m.parent_ids = im.id
    LEFT JOIN work_order_customer_ref_mapping wocr ON mmt.work_order_id = wocr.work_order_id
    WHERE mmt.end_date >= '$currentDateStr'
    GROUP BY mmt.id, mmt.machine_id, mmt.table_id, wo.work_order_no, mmt.part_id,
        mmt.setup_hours, mmt.priority, mmt.total_hours, mmt.work_order_id,
        mmt.product_name, mmt.quantity, mmt.set_id, won.work_order_name,
        mmt.shift_ids, mmt.start_date, mmt.end_date, mmt.is_break,
        mmt.break_reason, mmt.is_breakdown_affected, mmt.breakdown_hours,
        mb.id, mb.breakdown_reason, m.end_date, m.description, im.part_number
    ORDER BY mmt.priority, mmt.machine_id, mmt.table_id";

    $result = mysqli_query($conn, $query);
    if (!$result) {
        echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
        exit;
    }

    $activeBreakdowns = [];
    $bdResult = mysqli_query($conn, "SELECT machine_id, breakdown_reason FROM machine_breakdown WHERE is_active = 1");
    while ($bd = mysqli_fetch_assoc($bdResult)) {
        $activeBreakdowns[$bd['machine_id']] = $bd['breakdown_reason'];
    }

    $tasks = [];
    while ($row = mysqli_fetch_assoc($result)) {
        $row['is_late'] = (!empty($row['manufacture_end_date']) && $row['end_date'] > $row['manufacture_end_date']);
        $tasks[] = $row;
    }

    $groupedTasks = [];
    foreach ($tasks as $task) {
        $machineId = $task['task'];
        $tableId   = $task['table_id'];
        $key       = $machineId . '-' . $tableId;
        $hasBreakdown = isset($activeBreakdowns[$machineId]);

        $groupedTasks[$key][] = [
            'task'                    => $machineId,
            'is_late'                 => $task['is_late'],
            'task_id'                 => $task['task_id'],
            'table_id'                => $task['table_id'],
            'allocated_hours'         => $task['hours_to_schedule'],
            'work_order_id'           => $task['work_order_id'],
            'part_id'                 => $task['part_id'],
            'setup_hours'             => $task['setup_hours'],
            'work_order_no'           => $task['work_order_no'],
            'product_name'            => $task['product_name'],
            'quantity'                => $task['quantity'],
            'work_order_name'         => $task['work_order_name'],
            'set_id'                  => $task['set_id'],
            'parent_ids'              => $task['parent_ids'],
            'parent_part_number'      => $task['parent_part_number'],
            'is_break'                => $task['is_break'],
            'break_reason'            => $task['break_reason'],
            'shift_name'              => $task['shift_name'],
            'priority'                => $task['priority'],
            'is_breakdown_affected'   => $task['is_breakdown_affected'],
            'breakdown_hours'         => $task['breakdown_hours'],
            'has_breakdown'           => $hasBreakdown,
            'breakdown_reason'        => $hasBreakdown ? $activeBreakdowns[$machineId] : '',
            'manufacture_end_date'    => $task['manufacture_end_date'],
            'manufacture_description' => $task['manufacture_description'],
            'customer_order_nos'      => $task['customer_order_nos'],
        ];
    }

    echo json_encode(['status' => 'success', 'grouped_tasks' => $groupedTasks]);
    exit;
}

/* =====================================================================
 * update_cnc_task
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'update_cnc_task') {
    $task_id     = intval($_POST['task_id']);
    $priority    = intval($_POST['priority']);
    $machine_id  = intval($_POST['machine_id']);
    $setup_hours = mysqli_real_escape_string($conn, $_POST['setup_hours']);
    $total_hours = mysqli_real_escape_string($conn, $_POST['total_hours']);
    $setting_instruction = mysqli_real_escape_string($conn, $_POST['setting_instruction'] ?? '');
    $start_date  = mysqli_real_escape_string($conn, $_POST['start_date']);
    $quantity    = mysqli_real_escape_string($conn, $_POST['quantity']);

    $update_query = "UPDATE cnc_machine_table SET
        priority = '$priority', machine_id = '$machine_id', setup_hours = '$setup_hours',
        total_hours = '$total_hours', setting_instruction = '$setting_instruction',
        start_date = '$start_date', quantity = '$quantity', updated_at = NOW()
        WHERE id = '$task_id'";

    if (mysqli_query($conn, $update_query)) {
        $result = mysqli_query($conn, "SELECT id, priority FROM cnc_machine_table WHERE machine_id = '$machine_id' AND finished = 0 ORDER BY priority");
        $new_priority = 1;
        while ($row = mysqli_fetch_assoc($result)) {
            mysqli_query($conn, "UPDATE cnc_machine_table SET priority = $new_priority WHERE id = {$row['id']}");
            $new_priority++;
        }
        cnc_fullRescheduleMachine($conn, $machine_id);
        echo json_encode(['success' => true, 'message' => 'Work order updated successfully']);
    } else {
        echo json_encode(['success' => false, 'message' => 'Failed to update work order: ' . mysqli_error($conn)]);
    }
    exit;
}

/* =====================================================================
 * start_machine_breakdown / stop_machine_breakdown
 * Mirrors into cnc_machine_breakdown_log so the tablet's
 * cnc_isMachineInBreakdown() check sees it immediately.
 * ===================================================================== */
else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'start_machine_breakdown') {
    $machine_id = intval($_REQUEST['machine_id']);
    $breakdown_reason_id = mysqli_real_escape_string($conn, $_REQUEST['breakdown_reason']);
    $now = date('Y-m-d H:i:s');

    $reason_result = mysqli_query($conn, "SELECT code FROM keycode_table WHERE id = '$breakdown_reason_id'");
    $reason_text = ($reason_result && mysqli_num_rows($reason_result) > 0)
        ? mysqli_fetch_assoc($reason_result)['code'] : 'Unknown Reason';

    mysqli_query($conn, "UPDATE machine_breakdown SET is_active = 0, breakdown_end = '$now',
        breakdown_hours = TIMESTAMPDIFF(MINUTE, breakdown_start, '$now') / 60, updated_at = '$now'
        WHERE machine_id = $machine_id AND is_active = 1");

    mysqli_query($conn, "INSERT INTO machine_breakdown (machine_id, breakdown_start, breakdown_reason, is_active, created_at, updated_at)
        VALUES ($machine_id, '$now', '$reason_text', 1, '$now', '$now')");

    cnc_markBreakdownAffectedWorkOrders($conn, $machine_id, $now);

    // Mirror into the tablet's source-of-truth table.
    $affectedIds = [];
    $res = mysqli_query($conn, "SELECT manuf_id FROM cnc_machine_table WHERE machine_id = $machine_id AND is_breakdown_affected = 1");
    while ($r = mysqli_fetch_assoc($res)) $affectedIds[] = intval($r['manuf_id']);
    $idList = implode(',', $affectedIds);

    mysqli_query($conn, "
        INSERT INTO cnc_machine_breakdown_log
        (machine_id, engineer_id, cnc_machine_table_ids, breakdown_reason, breakdown_duration, ok_count_at_breakdown, status, created_at)
        VALUES ($machine_id, 0, '$idList', '" . mysqli_real_escape_string($conn, $reason_text) . "', 0, 0, 'active', '$now')
    ");

    echo json_encode(['status' => 'success']);
    exit;

} else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'stop_machine_breakdown') {
    $machine_id = intval($_POST['machine_id']);
    $now = date('Y-m-d H:i:s');

    mysqli_query($conn, "UPDATE machine_breakdown SET is_active = 0, breakdown_end = '$now',
        breakdown_hours = TIMESTAMPDIFF(MINUTE, breakdown_start, '$now') / 60, updated_at = '$now'
        WHERE machine_id = $machine_id AND is_active = 1");

    mysqli_query($conn, "UPDATE cnc_machine_table SET is_breakdown_affected = 0 WHERE machine_id = $machine_id AND is_breakdown_affected = 1");

    // Mirror the "stop" into the tablet's source-of-truth table.
    $lastLog = mysqli_fetch_assoc(mysqli_query($conn, "
        SELECT * FROM cnc_machine_breakdown_log WHERE machine_id = $machine_id AND status = 'active' ORDER BY id DESC LIMIT 1
    "));
    if ($lastLog) {
        $idsEsc = mysqli_real_escape_string($conn, $lastLog['cnc_machine_table_ids']);
        mysqli_query($conn, "
            INSERT INTO cnc_machine_breakdown_log
            (machine_id, engineer_id, cnc_machine_table_ids, breakdown_reason, breakdown_duration, ok_count_at_breakdown, status, created_at)
            VALUES ($machine_id, 0, '$idsEsc', '" . mysqli_real_escape_string($conn, $lastLog['breakdown_reason']) . "', {$lastLog['breakdown_duration']}, {$lastLog['ok_count_at_breakdown']}, 'completed', '$now')
        ");
    }

    cnc_fullRescheduleMachine($conn, $machine_id);
    echo json_encode(['status' => 'success']);
    exit;

} else if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'get_machine_breakdown_status') {
    $res = mysqli_query($conn, "
        SELECT mb.*, sd.resource_code AS machine_name
        FROM machine_breakdown mb LEFT JOIN station_detail_table sd ON mb.machine_id = sd.id
        ORDER BY mb.created_at DESC LIMIT 10
    ");
    $data = [];
    while ($row = mysqli_fetch_assoc($res)) $data[] = $row;
    echo json_encode(['status' => 'success', 'data' => $data]);
    exit;
}

/* =====================================================================
 * split_cnc_work_order — now backed by the shared cnc_splitCncTask()
 * ===================================================================== */
if (isset($_GET['action']) && $_GET['action'] == 'split_cnc_work_order') {
    $taskId   = intval($_POST['task_id']);
    $splitQty = floatval($_POST['split_quantity']);

    if ($taskId <= 0 || $splitQty <= 0) {
        echo json_encode(['success' => false, 'message' => 'Invalid task ID or split quantity']);
        exit;
    }

    $taskRes = mysqli_query($conn, "SELECT quantity, machine_id FROM cnc_machine_table WHERE id = $taskId LIMIT 1");
    if (!$taskRes || mysqli_num_rows($taskRes) == 0) {
        echo json_encode(['success' => false, 'message' => 'Task not found']);
        exit;
    }
    $taskRow    = mysqli_fetch_assoc($taskRes);
    $currentQty = floatval($taskRow['quantity']);
    $machineId  = intval($taskRow['machine_id']);

    if ($splitQty >= $currentQty || $splitQty < 1) {
        echo json_encode(['success' => false, 'message' => 'Split quantity must be between 1 and ' . ($currentQty - 1)]);
        exit;
    }

    $keepQty = $currentQty - $splitQty;

    mysqli_begin_transaction($conn);
    try {
        $splitResult = cnc_splitCncTask($conn, $taskId, $keepQty, $splitQty);
        if (!$splitResult) {
            throw new Exception('Split failed');
        }
        mysqli_commit($conn);

        cnc_fullRescheduleMachine($conn, $machineId);

        echo json_encode([
            'success'            => true,
            'message'            => 'Work order split successfully',
            'original_quantity'  => $keepQty,
            'original_hours'     => round($splitResult['original_hours'], 2),
            'split_quantity'     => $splitQty,
            'split_hours'        => round($splitResult['split_hours'], 2),
            'new_manuf_id'       => $splitResult['new_manuf_id'],
        ]);
    } catch (Exception $e) {
        mysqli_rollback($conn);
        echo json_encode(['success' => false, 'message' => $e->getMessage()]);
    }
    exit;
}
?>