<?php
/**
 * ajax_actions_pu.php  (shop-floor tablet AJAX endpoints)
 * =====================================================================
 * CHANGES FROM ORIGINAL:
 *
 *   0. get_live_uph_pu: planned/target UPH now comes from
 *      pu_calculateBasicUph() (route-based: operation_time /
 *      parts_per_operation, from route_details / set_route_details) —
 *      never from quantity / total_hours. total_hours moves every time a
 *      work order's setup_hours, breakdown adjustment, or split changes
 *      — none of which change how fast the route itself runs, so basing
 *      "planned" on it made the target drift for reasons that have
 *      nothing to do with actual machine performance. See
 *      pu_scheduler_lib.php's BASIC UPH section header for the full
 *      rationale.
 *
 *   1. All duplicated scheduling functions removed — uses
 *      pu_scheduler_lib.php.
 *
 *   2. FIX (#6, string-concat vs arithmetic divergence): every place in
 *      this file that built the composite (virtual) machine_id from a
 *      physical machine id + table id did it via STRING CONCATENATION
 *      (`intval($machineId . $tableId)`), while every other file in the
 *      module used the arithmetic form (`physical*10 + table`). These
 *      only produce the same result while table_id is a single digit —
 *      one accidental double-digit table count away from silently
 *      routing a task onto a completely different (and wrong) composite
 *      machine slot. Now goes through pu_encodeVirtualMachineId(), the
 *      one place this arithmetic lives.
 *
 *   3. log_action_pu's 'break' branch had a related but distinct bug:
 *      $cMachId was computed from the RAW request machine_id (via the
 *      string-concat method above) BEFORE the actual task row was even
 *      looked up — but $machineId itself gets correctly reassigned a few
 *      lines later from the looked-up row's own (correct, composite)
 *      machine_id. The final recalculation call still used the STALE
 *      pre-lookup $cMachId instead of the corrected value. Now uses the
 *      looked-up row's own machine_id directly.
 *
 *   4. end_breakdown_pu previously only inserted a 'completed' log row —
 *      it never cleared is_breakdown_affected on the affected tasks, and
 *      never triggered any reschedule, so stopping a breakdown from the
 *      tablet left the schedule stale until something else happened to
 *      trigger a recompute. Same bug already found and fixed in CNC's
 *      equivalent endpoint. Now clears the flag and reschedules every
 *      table of the physical machine.
 *
 *   5. get_current_task_priority_pu had no machine scoping at all. Since
 *      table_id is only unique WITHIN a physical machine (every PU
 *      station independently has a "table 1", "table 2", ...), the same
 *      work_order+part+table_id combination can legitimately exist on
 *      more than one physical machine at once, and this could return the
 *      wrong one. Now accepts an optional machine_id (physical or
 *      composite — detects which by size) and scopes by it when
 *      provided; unchanged (with a documented caveat) when it isn't,
 *      since the existing caller contract doesn't currently send one.
 *
 *   6. Everything else (count logging / inventory / UPH / clock /
 *      changeover / first-off / breakdown-split) had no NEW bugs
 *      identified beyond what's listed above — this file was already the
 *      most mature of the four (see its own in-code comments referencing
 *      prior fixes). Behaviour preserved, only routed through the shared
 *      library where it touches scheduling.
 * =====================================================================
 */

include("../../../include/startup.php");
require_once __DIR__ . '/pu_scheduler_lib.php';

if (isset($_REQUEST['action'])) {
    while (ob_get_level()) ob_end_clean();
    header('Content-Type: application/json');
    header('Cache-Control: no-cache, must-revalidate');
}

/* =====================================================================
 * Inventory / BOM helpers — unchanged from original, no scheduling bugs
 * identified in this block.
 * ===================================================================== */

function updateInventoryStock($conn, $itemId, $qtyChange, $workOrderId, $set_id) {
    $itemId    = intval($itemId);
    $qtyChange = floatval($qtyChange);
    $res = mysqli_query($conn, "SELECT quantity_in_stock FROM inventory WHERE item_id=$itemId");
    if ($res && mysqli_num_rows($res) > 0) {
        mysqli_query($conn, "UPDATE inventory SET quantity_in_stock=quantity_in_stock+($qtyChange) WHERE item_id=$itemId");
    } else {
        if ($qtyChange < 0) return false;
        mysqli_query($conn, "INSERT INTO inventory(item_id,quantity_in_stock) VALUES($itemId,$qtyChange)");
    }

    if ($qtyChange > 0) {
        $existResult = mysqli_query($conn, "SELECT COUNT(*) AS total FROM production_planning WHERE part_id = $itemId AND status IN (1, 2)");
        $existRow = mysqli_fetch_assoc($existResult);
        if ($existRow && intval($existRow['total']) > 0) {
            $remainingQty = $qtyChange;
            $planResult = mysqli_query($conn, "SELECT id, produce_qty FROM production_planning WHERE part_id = $itemId AND status IN (1, 2) AND produce_qty > 0 ORDER BY req_date ASC");
            if ($planResult && mysqli_num_rows($planResult) > 0) {
                while ($planRow = mysqli_fetch_assoc($planResult)) {
                    if ($remainingQty <= 0) break;
                    $planId = intval($planRow['id']);
                    $currentProdQty = floatval($planRow['produce_qty']);
                    if ($remainingQty >= $currentProdQty) {
                        mysqli_query($conn, "UPDATE production_planning SET produce_qty = 0 WHERE id = $planId");
                        $remainingQty -= $currentProdQty;
                    } else {
                        $newProdQty = $currentProdQty - $remainingQty;
                        mysqli_query($conn, "UPDATE production_planning SET produce_qty = $newProdQty WHERE id = $planId");
                        $remainingQty = 0;
                    }
                }
            }
        }
    }
    return true;
}

function reduceWorkOrderRawMaterial($conn, $itemId, $qtyUsed, $workOrderId, $setId = 0) {
    $itemId = intval($itemId); $qtyUsed = floatval($qtyUsed); $workOrderId = intval($workOrderId); $setId = intval($setId);
    mysqli_query($conn, "UPDATE work_order_raw_matrial SET quantity=quantity-($qtyUsed) WHERE work_order_id=$workOrderId AND product_id=$itemId AND set_id=$setId");
}

function reduceBomChildInventory($conn, $mainProductId, $countQty, $workOrderId, $setId = 0) {
    if ($setId > 0) {
        $setRes = mysqli_query($conn, "SELECT part_id FROM set_mangmnt_mapping WHERE set_id=$setId");
        if (!$setRes || !mysqli_num_rows($setRes)) return;
        $childTotals = [];
        while ($sr = mysqli_fetch_assoc($setRes)) {
            $bomRes = mysqli_query($conn, "SELECT sub_part_id, quantity_required FROM bom WHERE main_product_id={$sr['part_id']}");
            if (!$bomRes) continue;
            while ($b = mysqli_fetch_assoc($bomRes)) {
                $childTotals[$b['sub_part_id']] = ($childTotals[$b['sub_part_id']] ?? 0) + floatval($b['quantity_required']);
            }
        }
        foreach ($childTotals as $cid => $qty) {
            $deduct = $countQty >= 0 ? ceil($qty * $countQty) : -ceil(abs($qty * $countQty));
            updateInventoryStock($conn, $cid, -$deduct, $workOrderId, $setId);
            reduceWorkOrderRawMaterial($conn, $cid, $deduct, $workOrderId, $setId);
        }
    } else {
        $mainProductId = intval($mainProductId);
        $bomRes = mysqli_query($conn, "SELECT sub_part_id, quantity_required FROM bom WHERE main_product_id=$mainProductId");
        if (!$bomRes) return;
        while ($b = mysqli_fetch_assoc($bomRes)) {
            $deduct = -1 * floatval($b['quantity_required']) * $countQty;
            updateInventoryStock($conn, $b['sub_part_id'], $deduct, $workOrderId, $setId);
            reduceWorkOrderRawMaterial($conn, $b['sub_part_id'], -$deduct, $workOrderId, $setId);
        }
    }
}

define('PU_UPH_WINDOW_MINUTES', 60);

/* =====================================================================
 * get_current_task_priority_pu
 * ===================================================================== */
if ($_REQUEST['action'] === 'get_current_task_priority_pu') {
    $woId    = intval($_REQUEST['work_order_id']);
    $tableId = intval($_REQUEST['table_id']);
    $partId  = intval($_REQUEST['part_id']);

    // FIX: table_id is only unique WITHIN a physical machine, so without
    // a machine filter this could match the wrong physical machine's
    // "table N". Scopes by machine_id if the caller provides one
    // (accepting either the physical or composite form); unchanged
    // behaviour if not, since no current caller sends one — flagged
    // rather than silently required, to avoid breaking an unseen caller.
    $machineFilter = '';
    if (isset($_REQUEST['machine_id']) && intval($_REQUEST['machine_id']) > 0) {
        $reqMachineId = intval($_REQUEST['machine_id']);
        // A composite id is always > table_id range and divisible logic
        // applies; a physical id is typically much smaller. Try composite
        // match first (exact), fall back to physical (v_machine_id).
        $machineFilter = " AND (machine_id = $reqMachineId OR v_machine_id = $reqMachineId) ";
    }

    $r = mysqli_fetch_assoc(mysqli_query($conn,
        "SELECT id,priority,machine_id FROM pu_machine_table
         WHERE work_order_id=$woId AND table_id=$tableId AND part_id=$partId $machineFilter LIMIT 1"
    ));
    echo json_encode($r
        ? ['success'=>true,'task_id'=>$r['id'],'priority'=>$r['priority'],'machine_id'=>$r['machine_id']]
        : ['success'=>false,'message'=>'Task not found']);
    exit;
}

/* =====================================================================
 * insert_schedule_break_pu
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'insert_schedule_break_pu') {
    $machineId   = intval($_REQUEST['machine_id']); // physical
    $tableId     = intval($_REQUEST['table_id']);
    $duration    = floatval($_REQUEST['duration']);
    $reason      = mysqli_real_escape_string($conn, $_REQUEST['reason']);
    $afterTaskId = intval($_REQUEST['after_task_id'] ?? 0);
    $afterPri    = intval($_REQUEST['after_priority'] ?? 0);

    // FIX: was intval($machineId.$tableId) (string concatenation) —
    // replaced with the same arithmetic every other file in this module
    // uses, via the shared helper.
    $cMachineId = pu_encodeVirtualMachineId($machineId, $tableId);

    if ($machineId <= 0 || $tableId <= 0 || $duration < 0.5 || $duration > 10) {
        echo json_encode(['status'=>'error','message'=>'Invalid inputs']); exit;
    }
    mysqli_autocommit($conn, false);
    $err = '';
    $breakPri = $afterPri + 1;
    if (!mysqli_query($conn, "UPDATE pu_machine_table SET priority=priority+1 WHERE v_machine_id=$machineId AND table_id=$tableId AND priority>=$breakPri"))
        $err = mysqli_error($conn);
    if (!$err) {
        $tr = mysqli_fetch_assoc(mysqli_query($conn, "SELECT end_date FROM pu_machine_table WHERE id=$afterTaskId"));
        if ($tr) {
            $sd = $tr['end_date']; $ed = date('Y-m-d H:i:s', strtotime($sd)+$duration*3600);
            if (!mysqli_query($conn, "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,start_date,end_date,total_hours,is_break,break_reason,priority,work_order_id,part_id,quantity,product_name) VALUES($machineId,$cMachineId,$tableId,'$sd','$ed',$duration,1,'$reason',$breakPri,0,0,0,'BREAK: $reason')"))
                $err = mysqli_error($conn);
        } else $err = 'Current task not found';
    }
    if ($err) { mysqli_rollback($conn); echo json_encode(['status'=>'error','message'=>$err]); }
    else      { mysqli_commit($conn); pu_fullReschedule($conn, $cMachineId); echo json_encode(['status'=>'success','message'=>'Break scheduled']); }
    mysqli_autocommit($conn, true); exit;
}

/* =====================================================================
 * log_action_pu
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'log_action_pu') {
    $woId       = intval($_REQUEST['work_order_id']);
    $tableId    = intval($_REQUEST['table_id']   ?? 0);
    $partId     = intval($_REQUEST['part_id']    ?? 0);
    $machineId  = intval($_REQUEST['machine_id'] ?? 0); // physical
    $engId      = intval($_SESSION['engineer_id'] ?? 0);
    $actionType = $conn->real_escape_string($_REQUEST['action_type']);
    $puId       = intval($_REQUEST['pu_machine_table_id'] ?? 0); // manuf_id
    $now        = date('Y-m-d H:i:s');

    if (!$woId || !$machineId || !$engId || !$actionType) {
        echo json_encode(['success'=>false,'message'=>'Missing parameters']); exit;
    }

    if ($actionType === 'break') {
        $breakDur = isset($_REQUEST['break_duration']) ? floatval($_REQUEST['break_duration']) : (20/60);
        if ($breakDur < (5/60) || $breakDur > 10) $breakDur = (20/60);
        $breakReason = mysqli_real_escape_string($conn, $_REQUEST['break_reason'] ?? 'Scheduled Break');
        mysqli_autocommit($conn, false);
        $err = '';

        $priRow = mysqli_fetch_assoc(mysqli_query($conn,
            "SELECT id,priority,machine_id,v_machine_id FROM pu_machine_table
             WHERE work_order_id=$woId AND table_id=$tableId AND manuf_id=$puId AND part_id=$partId LIMIT 1"
        ));
        // FIX: the composite machine id used for the final reschedule call
        // now comes from THIS lookup (the actual, correct row) rather than
        // a value computed from the raw request before the lookup ran.
        $rescheduleMachineId = null;

        if ($priRow) {
            $afterId    = $priRow['id'];
            $afterPri   = $priRow['priority'];
            $machineId  = $priRow['machine_id']; // composite — reassigned intentionally
            $vMachineId = intval($priRow['v_machine_id']);
            $rescheduleMachineId = intval($priRow['machine_id']);

            $bPri = $afterPri + 1;
            if (!mysqli_query($conn, "UPDATE pu_machine_table SET priority=priority+1 WHERE machine_id=$machineId AND table_id=$tableId AND priority>=$bPri"))
                $err = mysqli_error($conn);
            if (!$err) {
                $tr = mysqli_fetch_assoc(mysqli_query($conn, "SELECT end_date FROM pu_machine_table WHERE id=$afterId"));
                if ($tr) {
                    $sd = $tr['end_date']; $ed = date('Y-m-d H:i:s', strtotime($sd)+$breakDur*3600);
                    if (!mysqli_query($conn, "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,start_date,end_date,original_start_date,original_end_date,total_hours,original_total_hours,remaing_hours,original_remaing_hours,is_break,break_reason,priority,work_order_id,part_id,quantity,product_name,finished) VALUES($vMachineId,$machineId,$tableId,'$sd','$ed','$sd','$ed',$breakDur,$breakDur,$breakDur,$breakDur,1,'$breakReason',$bPri,0,0,0,'BREAK: $breakReason',0)"))
                        $err = mysqli_error($conn);
                } else $err = 'Task not found';
            }
        } else $err = 'Task not found for scheduling break';

        mysqli_query($conn, "INSERT INTO pu_machine_activity_log(work_order_id,part_id,machine_id,table_id,pu_machine_table_id,engineer_id,action_type,created_at) VALUES($woId,$partId,$machineId,$tableId,$puId,$engId,'$actionType','$now')");

        if ($err) {
            mysqli_rollback($conn);
            echo json_encode(['success'=>false,'message'=>$err]);
        } else {
            mysqli_commit($conn);
            if ($rescheduleMachineId) pu_fullReschedule($conn, $rescheduleMachineId);
            echo json_encode(['success'=>true,'message'=>'Break scheduled']);
        }
        mysqli_autocommit($conn, true); exit;
    }

    if (in_array($actionType, ['scheduler_break_start','scheduler_break_stop'])) {
        $ok = mysqli_query($conn, "INSERT INTO pu_machine_activity_log(work_order_id,part_id,machine_id,table_id,pu_machine_table_id,engineer_id,action_type,created_at) VALUES($woId,$partId,$machineId,$tableId,$puId,$engId,'$actionType','$now')");
        echo json_encode(['success'=>(bool)$ok]); exit;
    }

    if ($actionType === 'finish') {
        mysqli_query($conn, "UPDATE pu_machine_table SET finished=1, changeover_pending=1 WHERE work_order_id=$woId AND part_id=$partId AND manuf_id=$puId AND table_id=$tableId");
    }

    $ok = mysqli_query($conn, "INSERT INTO pu_machine_activity_log(work_order_id,part_id,machine_id,table_id,pu_machine_table_id,engineer_id,action_type,created_at) VALUES($woId,$partId,$machineId,$tableId,$puId,$engId,'$actionType','$now')");
    echo json_encode(['success'=>(bool)$ok, 'message'=>$ok?'':mysqli_error($conn)]);
    exit;
}

/* =====================================================================
 * log_breakdown_pu — already correctly scoped by manuf_id, unchanged
 * except for using the shared library where it touches scheduling.
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'log_breakdown_pu') {
    $engId      = intval($_SESSION['engineer_id'] ?? 0);
    $actionType = $conn->real_escape_string($_REQUEST['action_type']);
    $bdReason   = isset($_REQUEST['breakdown_reason']) ? intval($_REQUEST['breakdown_reason']) : null;
    $plainMachineId = intval($_REQUEST['machine_id'] ?? 0); // physical
    $now = date('Y-m-d H:i:s');
    $puManufIds = json_decode($_REQUEST['pu_table_ids'], true);

    if (!$engId || !$bdReason || empty($puManufIds)) {
        echo json_encode(['success'=>false,'message'=>'Missing parameters']); exit;
    }

    if ($actionType === 'breakdown') {
        $bdDur = max(0.5, min(24, floatval($_REQUEST['breakdown_duration'] ?? 1)));
        mysqli_autocommit($conn, false);
        $err = ''; $tables = []; $totalOk = 0;

        foreach ($puManufIds as $entry) {
            $manufId = intval($entry['pu_machine_table_id']);
            $tid     = intval($entry['table_id']);
            $eMachId = intval($entry['machine_id']); // composite
            if ($manufId <= 0) continue;
            $tr = mysqli_fetch_assoc(mysqli_query($conn,
                "SELECT * FROM pu_machine_table WHERE manuf_id=$manufId AND machine_id=$eMachId AND is_break=0 AND finished=0 LIMIT 1"
            ));
            if ($tr) $tables[] = ['table_id'=>$tid,'machine_id'=>$eMachId,'task'=>$tr];
        }
        if (empty($tables)) { mysqli_rollback($conn); mysqli_autocommit($conn,true); echo json_encode(['success'=>false,'message'=>'No active tasks']); exit; }

        foreach ($tables as $te) {
            if ($err) break;
            $ctid = $te['table_id']; $cmid = $te['machine_id']; $task = $te['task'];
            $currentId = intval($task['id']);
            $manufId   = intval($task['manuf_id']);
            $pri = intval($task['priority']);
            $qty = floatval($task['quantity']); $th = floatval($task['total_hours']); $sh = floatval($task['setup_hours']);
            $endDate = $task['end_date'];

            $okRow = mysqli_fetch_assoc(mysqli_query($conn,
                "SELECT COALESCE(SUM(quantity),0) AS ok FROM pu_part_count_log
                 WHERE work_order_id={$task['work_order_id']} AND pu_machine_table_id=$manufId AND part_id={$task['part_id']} AND type='ok'"
            ));
            $okCount = floatval($okRow['ok'] ?? 0);
            $totalOk += $okCount;

            $bds = $endDate; $bde = date('Y-m-d H:i:s', strtotime($bds)+$bdDur*3600);

            if ($okCount > 0 && $okCount < $qty) {
                $runHoursPerUnit = $qty > 0 ? ($th - $sh) / $qty : 0;
                $remQty = $qty - $okCount;
                $compH  = ($okCount * $runHoursPerUnit) + $sh;
                $remH   = ($remQty  * $runHoursPerUnit) + $sh;
                $newManufId = null;

                if ($manufId > 0) {
                    $manufResult = mysqli_query($conn, "SELECT * FROM manufacture_machine_table WHERE id = $manufId LIMIT 1");
                    if ($manufResult && mysqli_num_rows($manufResult) > 0) {
                        $originalManuf = mysqli_fetch_assoc($manufResult);
                        $manufQuantity = floatval($originalManuf['quantity']);
                        $manufTotalHours = floatval($originalManuf['total_hours']);
                        $manufHoursPerUnit = $manufQuantity > 0 ? ($manufTotalHours / $manufQuantity) : 0;
                        $manufCompletedHours = $okCount * $manufHoursPerUnit;
                        $manufRemainingHours = $remQty  * $manufHoursPerUnit;

                        if (!mysqli_query($conn, "UPDATE manufacture_machine_table SET quantity = $okCount, total_hours = $manufCompletedHours WHERE id = $manufId")) {
                            $err = "Failed to update original manufacture record: " . mysqli_error($conn);
                        }
                        if (!$err) {
                            $manufColumns = []; $manufValues = [];
                            foreach ($originalManuf as $col => $val) {
                                if ($col === 'id') continue;
                                $manufColumns[] = "`$col`";
                                if ($col === 'quantity')      $manufValues[] = $remQty;
                                elseif ($col === 'total_hours') $manufValues[] = $manufRemainingHours;
                                elseif (is_null($val))        $manufValues[] = 'NULL';
                                else $manufValues[] = "'" . mysqli_real_escape_string($conn, $val) . "'";
                            }
                            $insertManufQuery = "INSERT INTO manufacture_machine_table (" . implode(', ', $manufColumns) . ") VALUES (" . implode(', ', $manufValues) . ")";
                            if (!mysqli_query($conn, $insertManufQuery)) $err = "Failed to create new manufacture record: " . mysqli_error($conn);
                            else $newManufId = $conn->insert_id;
                        }
                    }
                }

                if (!$err && !mysqli_query($conn, "UPDATE pu_machine_table SET quantity=$okCount,total_hours=$compH,total_hours_update=$compH,finished=1 WHERE id=$currentId")) { $err = mysqli_error($conn); }
                if (!$err && !mysqli_query($conn, "UPDATE pu_machine_table SET priority=priority+2 WHERE machine_id=$cmid AND table_id=$ctid AND priority>$pri")) { $err = mysqli_error($conn); }
                if (!$err && !mysqli_query($conn, "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,start_date,end_date,total_hours,remaing_hours,is_break,break_reason,priority,work_order_id,part_id,quantity,product_name) VALUES(".intval($task['v_machine_id']).",$cmid,$ctid,'$bds','$bde',$bdDur,$bdDur,1,'BREAKDOWN',".($pri+1).",0,0,0,'BREAKDOWN')")) { $err = mysqli_error($conn); }

                if (!$err) {
                    $sids  = mysqli_real_escape_string($conn, $task['shift_ids']); $cyc = floatval($task['cycle']);
                    $woId2 = intval($task['work_order_id']); $pId = intval($task['part_id']);
                    $pname = mysqli_real_escape_string($conn, $task['product_name']); $setId = intval($task['set_id']);
                    $vMachineId = intval($task['v_machine_id']);
                    $manufIdForInsert = $newManufId !== null ? intval($newManufId) : ($manufId > 0 ? $manufId : 'NULL');
                    if (!mysqli_query($conn, "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,manuf_id,work_order_id,part_id,quantity,setup_hours,total_hours,total_hours_update,priority,start_date,end_date,shift_ids,cycle,is_break,break_reason,finished,remaing_hours,breakdown_hours,is_breakdown_affected,product_name,set_id) VALUES($vMachineId,$cmid,$ctid,$manufIdForInsert,$woId2,$pId,$remQty,$sh,$remH,$remH,".($pri+2).",'$bde','$bde','$sids',$cyc,0,'',0,$remH,$bdDur,1,'$pname',$setId)")) { $err = mysqli_error($conn); }
                }
                if ($err) break;
            } else {
                if (!mysqli_query($conn, "UPDATE pu_machine_table SET priority=priority+1 WHERE machine_id=$cmid AND table_id=$ctid AND priority>$pri")) { $err = mysqli_error($conn); break; }
                if (!mysqli_query($conn, "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,start_date,end_date,total_hours,remaing_hours,is_break,break_reason,priority,work_order_id,part_id,quantity,product_name) VALUES(".intval($task['v_machine_id']).",$cmid,$ctid,'$bds','$bde',$bdDur,$bdDur,1,'BREAKDOWN',".($pri+1).",0,0,0,'BREAKDOWN')")) { $err = mysqli_error($conn); break; }
            }
        }

        if (!$err) {
            $manufIdArr = [];
            foreach ($puManufIds as $e) $manufIdArr[] = intval($e['pu_machine_table_id']);
            $manufIdsStr = implode(',', $manufIdArr);
            if (!mysqli_query($conn, "INSERT INTO pu_machine_breakdown_log(machine_id,engineer_id,pu_machine_table_ids,breakdown_reason,breakdown_duration,ok_count_at_breakdown,status,created_at) VALUES($plainMachineId,$engId,'$manufIdsStr',$bdReason,$bdDur,$totalOk,'active','$now')"))
                $err = mysqli_error($conn);
        }
        if ($err) { mysqli_rollback($conn); echo json_encode(['success'=>false,'message'=>$err]); }
        else      { mysqli_commit($conn); echo json_encode(['success'=>true]); }
        mysqli_autocommit($conn, true);
    }
    exit;
}

/* =====================================================================
 * log_bulk_action_pu — unchanged, no scheduling bugs identified.
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'log_bulk_action_pu') {
    $actionType = mysqli_real_escape_string($conn, $_REQUEST['action_type']);
    $workOrders = json_decode($_REQUEST['work_orders'], true);
    $engId      = intval($_SESSION['user_id']);
    $now        = date('Y-m-d H:i:s');
    if (!is_array($workOrders) || empty($workOrders)) { echo json_encode(['success'=>false,'message'=>'No work orders']); exit; }

    $puIds = [];
    foreach ($workOrders as $o) {
        $v = intval($o['puMachineTableId'] ?? 0);
        if ($v) $puIds[] = $v;
    }

    $stateMap = [];
    if ($puIds) {
        $puList = implode(',', $puIds);
        $sRes = mysqli_query($conn,
            "SELECT l1.pu_machine_table_id, l1.action_type
             FROM pu_machine_activity_log l1
             INNER JOIN (SELECT pu_machine_table_id, MAX(created_at) AS max_at FROM pu_machine_activity_log WHERE pu_machine_table_id IN ($puList) GROUP BY pu_machine_table_id) l2
               ON l1.pu_machine_table_id=l2.pu_machine_table_id AND l1.created_at=l2.max_at"
        );
        while ($r = mysqli_fetch_assoc($sRes)) $stateMap[$r['pu_machine_table_id']] = $r['action_type'];
    }

    $changed = $skipped = $errors = 0;
    foreach ($workOrders as $o) {
        $woId = intval($o['workOrderId'] ?? 0);
        $pId  = mysqli_real_escape_string($conn, $o['partId'] ?? '');
        $mId  = intval($o['machineId'] ?? 0);
        $tId  = intval($o['tableId'] ?? 0);
        $puId = intval($o['puMachineTableId'] ?? 0);
        if (!$woId||!$mId||!$engId||!$actionType) { $errors++; continue; }
        $cur = $stateMap[$puId] ?? null;
        if (($actionType==='start'&&$cur==='start') || ($actionType==='pause'&&$cur==='pause')) { $skipped++; continue; }
        if (mysqli_query($conn, "INSERT INTO pu_machine_activity_log(work_order_id,part_id,machine_id,table_id,pu_machine_table_id,engineer_id,action_type,created_at) VALUES($woId,'$pId',$mId,$tId,$puId,$engId,'$actionType','$now')")) {
            if ($actionType==='finish') mysqli_query($conn, "UPDATE pu_machine_table SET finished=1 WHERE work_order_id=$woId AND part_id='$pId'");
            $changed++;
        } else $errors++;
    }
    echo json_encode(['success'=>true,'changed'=>$changed,'skipped'=>$skipped,'errors'=>$errors]);
    exit;
}

/* 
elseif ($_REQUEST['action'] === 'end_breakdown_pu') {
    $mId   = intval($_REQUEST['machine_id']); // physical
    $engId = intval($_SESSION['engineer_id'] ?? 0);
    $now   = date('Y-m-d H:i:s');

    $res = mysqli_query($conn, "SELECT * FROM pu_machine_breakdown_log WHERE machine_id=$mId AND status='active' ORDER BY id DESC LIMIT 1");
    if (!$res || mysqli_num_rows($res) == 0) {
        echo json_encode(['success'=>false,'message'=>'No active breakdown found']); exit;
    }
    $row = mysqli_fetch_assoc($res);

    mysqli_autocommit($conn, false);
    $err = '';

    $puIdsEsc = mysqli_real_escape_string($conn, $row['pu_machine_table_ids']);
    if (!mysqli_query($conn, "INSERT INTO pu_machine_breakdown_log
        (machine_id, engineer_id, pu_machine_table_ids, breakdown_reason, breakdown_duration, ok_count_at_breakdown, status, created_at)
        VALUES ($mId, $engId, '$puIdsEsc', {$row['breakdown_reason']}, {$row['breakdown_duration']}, {$row['ok_count_at_breakdown']}, 'completed', '$now')"
    )) {
        $err = mysqli_error($conn);
    }
    if (!$err && !mysqli_query($conn, "UPDATE pu_machine_table SET is_breakdown_affected = 0 WHERE v_machine_id = $mId AND is_breakdown_affected = 1")) {
        $err = mysqli_error($conn);
    }

    if ($err) {
        mysqli_rollback($conn);
        echo json_encode(['success'=>false,'message'=>$err]);
    } else {
        mysqli_commit($conn);
        pu_fullReschedulePhysicalMachine($conn, $mId);
        echo json_encode(['success'=>true]);
    }
    mysqli_autocommit($conn, true);
    exit;
}*/
elseif ($_REQUEST['action'] === 'end_breakdown_pu') {
    $mId   = intval($_REQUEST['machine_id']); // physical
    $engId = intval($_SESSION['engineer_id'] ?? 0);
    $now   = date('Y-m-d H:i:s');

    $res = mysqli_query($conn, "SELECT * FROM pu_machine_breakdown_log WHERE machine_id=$mId AND status='active' ORDER BY id DESC LIMIT 1");
    if (!$res || mysqli_num_rows($res) == 0) {
        echo json_encode(['success'=>false,'message'=>'No active breakdown found']); exit;
    }
    $row = mysqli_fetch_assoc($res);

    // Actual elapsed time = now - when the breakdown was logged as started.
    // This replaces the operator-entered ESTIMATE with what really happened.
    $startTs      = strtotime($row['created_at']);
    $nowTs        = strtotime($now);
    $actualHours  = round(max($nowTs - $startTs, 60) / 3600, 4); // floor of 1 minute, avoid 0/negative

    mysqli_autocommit($conn, false);
    $err = '';

    $puIdsEsc = mysqli_real_escape_string($conn, $row['pu_machine_table_ids']);
    if (!mysqli_query($conn, "INSERT INTO pu_machine_breakdown_log
        (machine_id, engineer_id, pu_machine_table_ids, breakdown_reason, breakdown_duration, ok_count_at_breakdown, status, created_at)
        VALUES ($mId, $engId, '$puIdsEsc', {$row['breakdown_reason']}, $actualHours, {$row['ok_count_at_breakdown']}, 'completed', '$now')"
    )) {
        $err = mysqli_error($conn);
    }

    // FIX: correct the placeholder BREAKDOWN block(s) in pu_machine_table
    // to reflect the ACTUAL elapsed time instead of the estimate they were
    // created with, before the reschedule below runs off of them.
    if (!$err) {
        $blocksRes = mysqli_query($conn,
            "SELECT id, start_date FROM pu_machine_table
             WHERE v_machine_id=$mId AND is_break=1 AND break_reason='BREAKDOWN' AND finished=0"
        );
        while ($block = mysqli_fetch_assoc($blocksRes)) {
            $blockId  = intval($block['id']);
            $newEnd   = date('Y-m-d H:i:s', strtotime($block['start_date']) + $actualHours * 3600);
            if (!mysqli_query($conn,
                "UPDATE pu_machine_table
                 SET total_hours=$actualHours, remaing_hours=$actualHours, end_date='$newEnd'
                 WHERE id=$blockId"
            )) { $err = mysqli_error($conn); break; }
        }
    }

    if (!$err && !mysqli_query($conn, "UPDATE pu_machine_table SET is_breakdown_affected = 0 WHERE v_machine_id = $mId AND is_breakdown_affected = 1")) {
        $err = mysqli_error($conn);
    }

    if ($err) {
        mysqli_rollback($conn);
        echo json_encode(['success'=>false,'message'=>$err]);
    } else {
        mysqli_commit($conn);
        pu_fullReschedulePhysicalMachine($conn, $mId);
        echo json_encode(['success'=>true, 'actual_duration_hours'=>$actualHours]);
    }
    mysqli_autocommit($conn, true);
    exit;
}

/* =====================================================================
 * log_count_pu — unchanged, no scheduling bugs identified in this block.
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'log_count_pu') {
    $tableId   = intval($_REQUEST['table_id']);
    $woId      = intval($_REQUEST['work_order_id']);
    $machineId = intval($_SESSION['machine_id'] ?? 0);
    $engId     = intval($_SESSION['engineer_id'] ?? 0);
    $type      = $conn->real_escape_string($_REQUEST['type']);
    $qty       = intval($_REQUEST['quantity'] ?? 1);

    $nok_q_scrap_part_ids_part_ids = isset($_REQUEST['nok_q_scrap_part_ids_part_ids']) ? $conn->real_escape_string($_REQUEST['nok_q_scrap_part_ids_part_ids']) : '';
    $nok_q_ok_part_ids_raw         = isset($_REQUEST['nok_q_ok_part_ids'])             ? $conn->real_escape_string($_REQUEST['nok_q_ok_part_ids'])             : '';

    $reasonId = isset($_REQUEST['reason_id']) ? intval($_REQUEST['reason_id']) : null;
    $partId   = intval($_REQUEST['part_id'] ?? 0);
    $puId     = intval($_REQUEST['pu_machine_table_id'] ?? 0);
    $now      = date('Y-m-d H:i:s');
    $setId    = intval($_REQUEST['setId'] ?? 0);

    if (!$woId || !$machineId || !$engId || !$type) {
        echo json_encode(['success'=>false,'message'=>'Missing parameters']); exit;
    }

    if ($type === 'ok') {
        if ($setId > 0) {
            $mapRes = $conn->query("SELECT part_id,ratio FROM set_mangmnt_mapping WHERE set_id=$setId");
            while ($map = $mapRes->fetch_assoc()) {
                updateInventoryStock($conn, intval($map['part_id']), floatval($map['ratio']) * $qty, $woId, $setId);
            }
            reduceBomChildInventory($conn, $partId, $qty, $woId, $setId);

            if ($qty > 0) {
                if (!$conn->query("INSERT INTO pu_part_count_log(work_order_id,machine_id,table_id,part_id,partIds,engineer_id,setId,type,quantity,final_quantity,pu_machine_table_id,created_at) VALUES($woId,$machineId,$tableId,$partId,$partId,$engId,$setId,'$type',$qty,$qty,$puId,'$now')")) {
                    echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit;
                }
            } elseif ($qty < 0) {
                $cr = $conn->query("SELECT id,quantity,final_quantity FROM pu_part_count_log WHERE work_order_id=$woId AND machine_id=$machineId AND engineer_id=$engId AND part_id=$partId AND pu_machine_table_id=$puId AND type='$type' ORDER BY id DESC LIMIT 1");
                if ($cr->num_rows == 0) { echo json_encode(['success'=>false,'message'=>'Cannot decrement non-existent set record']); exit; }
                $r = $cr->fetch_assoc(); $nq = $r['quantity'] + $qty;
                if ($nq < 0) { echo json_encode(['success'=>false,'message'=>'Negative count']); exit; }
                if ($nq == 0) {
                    if (!$conn->query("DELETE FROM pu_part_count_log WHERE id={$r['id']}")) { echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit; }
                } else {
                    $nf = $r['final_quantity'] + $qty;
                    if (!$conn->query("UPDATE pu_part_count_log SET quantity=$nq,final_quantity=$nf WHERE id={$r['id']}")) { echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit; }
                }
            }
            echo json_encode(['success'=>true]); exit;
        }

        if ($qty > 0) {
            if (!$conn->query("INSERT INTO pu_part_count_log(work_order_id,machine_id,table_id,part_id,partIds,engineer_id,type,quantity,final_quantity,pu_machine_table_id,created_at) VALUES($woId,$machineId,$tableId,$partId,$partId,$engId,'$type',$qty,$qty,$puId,'$now')")) {
                echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit;
            }
            updateInventoryStock($conn, $partId, $qty, $woId, $setId);
            reduceBomChildInventory($conn, $partId, $qty, $woId, $setId);
        } elseif ($qty < 0) {
            $cr = $conn->query("SELECT id,quantity,final_quantity FROM pu_part_count_log WHERE work_order_id=$woId AND machine_id=$machineId AND engineer_id=$engId AND part_id=$partId AND pu_machine_table_id=$puId AND type='$type' ORDER BY id DESC LIMIT 1");
            if ($cr->num_rows == 0) { echo json_encode(['success'=>false,'message'=>'Cannot decrement']); exit; }
            $r = $cr->fetch_assoc(); $nq = $r['quantity']+$qty;
            if ($nq < 0) { echo json_encode(['success'=>false,'message'=>'Negative count']); exit; }
            updateInventoryStock($conn, $partId, $qty, $woId, $setId);
            reduceBomChildInventory($conn, $partId, $qty, $woId, $setId);
            if ($nq == 0) {
                if (!$conn->query("DELETE FROM pu_part_count_log WHERE id={$r['id']}")) { echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit; }
            } else {
                $nf = $r['final_quantity']+$qty;
                if (!$conn->query("UPDATE pu_part_count_log SET quantity=$nq,final_quantity=$nf WHERE id={$r['id']}")) { echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit; }
            }
        }
        echo json_encode(['success'=>true]); exit;
    }

    // NOK / QUARANTINE: increment
    if ($qty > 0) {
        if (!$nok_q_scrap_part_ids_part_ids) { echo json_encode(['success'=>false,'message'=>'No parts selected']); exit; }
        if (!$reasonId)                       { echo json_encode(['success'=>false,'message'=>'No reason selected']); exit; }

        mysqli_autocommit($conn, false);
        $success = true;

        if (!$conn->query("INSERT INTO pu_part_count_log(work_order_id,machine_id,table_id,part_id,engineer_id,type,setId,partIds,reason_id,quantity,final_quantity,pu_machine_table_id,created_at) VALUES($woId,$machineId,$tableId,$partId,$engId,'$type','$setId','$nok_q_scrap_part_ids_part_ids',$reasonId,$qty,$qty,$puId,'$now')")) {
            mysqli_rollback($conn); mysqli_autocommit($conn,true);
            echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]); exit;
        }
        $logId = mysqli_insert_id($conn);

        $selParts = array_map('intval', array_filter(explode(',', $nok_q_scrap_part_ids_part_ids), 'strlen'));
        $unsParts = array_map('intval', array_filter(explode(',', $nok_q_ok_part_ids_raw), 'strlen'));

        foreach ($selParts as $spId) {
            if ($spId <= 0) continue;
            if (!$conn->query("INSERT INTO pu_selected_ok_parts(part_id,quantity,pu_machine_table_id,pu_part_count_log_id,created_at) VALUES($spId,1,$puId,$logId,'$now')")) {
                $success = false; break;
            }
        }
        if ($success) {
            foreach ($unsParts as $upId) {
                if ($upId <= 0) continue;
                if (!$conn->query("INSERT INTO pu_unselected_ok_parts(part_id,quantity,pu_machine_table_id,pu_part_count_log_id,created_at) VALUES($upId,1,$puId,$logId,'$now')")) {
                    $success = false; break;
                }
                updateInventoryStock($conn, $upId, 1, $woId, $setId);
            }
        }
        if ($success) {
            reduceBomChildInventory($conn, $partId, $qty, $woId, $setId);
            mysqli_commit($conn); mysqli_autocommit($conn, true);
            echo json_encode(['success'=>true]);
        } else {
            mysqli_rollback($conn); mysqli_autocommit($conn, true);
            echo json_encode(['success'=>false,'message'=>'Failed to record details']);
        }
        exit;
    }

    // NOK / QUARANTINE: undo
    if ($qty < 0) {
        mysqli_autocommit($conn, false);
        $success = true;

        $r = mysqli_fetch_assoc(mysqli_query($conn,
            "SELECT id,quantity FROM pu_part_count_log
             WHERE work_order_id=$woId AND machine_id=$machineId AND engineer_id=$engId AND part_id=$partId AND pu_machine_table_id=$puId AND type='$type'
             ORDER BY id DESC LIMIT 1"
        ));
        if (!$r) { mysqli_autocommit($conn, true); echo json_encode(['success'=>false,'message'=>'Nothing to undo']); exit; }
        $xId = $r['id']; $nq = $r['quantity'] + $qty;
        if ($nq < 0) { mysqli_autocommit($conn, true); echo json_encode(['success'=>false,'message'=>'Negative count']); exit; }

        if ($nq == 0) {
            $allUnselectedRes = mysqli_query($conn, "SELECT part_id, quantity FROM pu_unselected_ok_parts WHERE pu_part_count_log_id=$xId");
            $unselectedReversals = [];
            while ($uRow = mysqli_fetch_assoc($allUnselectedRes)) {
                $invPartId = intval($uRow['part_id']);
                $unselectedReversals[$invPartId] = ($unselectedReversals[$invPartId] ?? 0) + floatval($uRow['quantity']);
            }
            foreach ($unselectedReversals as $invPartId => $totalQty) updateInventoryStock($conn, $invPartId, -$totalQty, $woId, $setId);

            if (!$conn->query("DELETE FROM pu_selected_ok_parts   WHERE pu_part_count_log_id=$xId")) $success = false;
            if ($success && !$conn->query("DELETE FROM pu_unselected_ok_parts WHERE pu_part_count_log_id=$xId")) $success = false;
            if ($success && !$conn->query("DELETE FROM pu_part_count_log WHERE id=$xId")) $success = false;
        } else {
            $lastUnselectedRes = mysqli_query($conn, "SELECT id, part_id, quantity FROM pu_unselected_ok_parts WHERE pu_part_count_log_id=$xId ORDER BY id DESC LIMIT 1");
            if ($lastUnselectedRes && mysqli_num_rows($lastUnselectedRes) > 0) {
                $lastRow = mysqli_fetch_assoc($lastUnselectedRes);
                updateInventoryStock($conn, intval($lastRow['part_id']), -floatval($lastRow['quantity']), $woId, $setId);
            }
            if (!$conn->query("DELETE FROM pu_selected_ok_parts WHERE pu_part_count_log_id=$xId ORDER BY id DESC LIMIT 1")) $success = false;
            if ($success) $conn->query("DELETE FROM pu_unselected_ok_parts WHERE pu_part_count_log_id=$xId ORDER BY id DESC LIMIT 1");
            if ($success && !$conn->query("UPDATE pu_part_count_log SET quantity=$nq WHERE id=$xId")) $success = false;
        }

        if ($success) {
            reduceBomChildInventory($conn, $partId, $qty, $woId, $setId);
            mysqli_commit($conn); mysqli_autocommit($conn, true);
            echo json_encode(['success'=>true]);
        } else {
            mysqli_rollback($conn); mysqli_autocommit($conn, true);
            echo json_encode(['success'=>false,'message'=>mysqli_error($conn)]);
        }
        exit;
    }
    exit;
}

/* =====================================================================
 * schedule_break_all_tables_pu
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'schedule_break_all_tables_pu') {
    $tables   = json_decode($_POST['tables'], true);
    $duration = floatval($_POST['duration']) / 60; // minutes -> hours
    $reason   = mysqli_real_escape_string($conn, $_POST['reason']);

    if (empty($tables) || $duration < 0.083 || $duration > 10) {
        echo json_encode(['success' => false, 'message' => 'Invalid parameters']); exit;
    }

    mysqli_autocommit($conn, false); $err = '';
    foreach ($tables as $tbl) {
        $tid  = intval($tbl['table_id']); $woId = intval($tbl['work_order_id']);
        $pid  = intval($tbl['part_id']);  $mid  = intval($tbl['machine_id']); // physical

        // FIX: was intval($mid.$tid) (string concatenation) — replaced
        // with the shared arithmetic helper.
        $cmid = pu_encodeVirtualMachineId($mid, $tid);

        pu_fullReschedule($conn, $cmid);
        $pr = mysqli_fetch_assoc(mysqli_query($conn,
            "SELECT id,priority,v_machine_id FROM pu_machine_table
             WHERE work_order_id=$woId AND table_id=$tid AND part_id=$pid AND is_break=0 AND finished=0
             ORDER BY priority ASC LIMIT 1"
        ));
        if (!$pr) { $err = "Task not found for table $tid"; break; }
        $vMachineId = intval($pr['v_machine_id']);
        $bPri = $pr['priority'] + 1;
        mysqli_query($conn, "UPDATE pu_machine_table SET priority=priority+1 WHERE machine_id=$cmid AND table_id=$tid AND priority>=$bPri");
        $tr = mysqli_fetch_assoc(mysqli_query($conn, "SELECT end_date FROM pu_machine_table WHERE id={$pr['id']}"));
        if (!$tr) { $err = "Task dates not found for table $tid"; break; }
        $sd = $tr['end_date']; $ed = date('Y-m-d H:i:s', strtotime($sd) + $duration * 3600);
        if (!mysqli_query($conn,
            "INSERT INTO pu_machine_table(v_machine_id,machine_id,table_id,start_date,end_date,total_hours,remaing_hours,is_break,break_reason,priority,work_order_id,part_id,quantity,product_name,finished)
             VALUES($vMachineId,$cmid,$tid,'$sd','$ed',$duration,$duration,1,'$reason',$bPri,0,0,0,'BREAK: $reason',0)"
        )) { $err = mysqli_error($conn); break; }
        pu_fullReschedule($conn, $cmid);
    }
    if ($err) { mysqli_rollback($conn); echo json_encode(['success'=>false,'message'=>$err]); }
    else      { mysqli_commit($conn); echo json_encode(['success'=>true,'message'=>'Break scheduled for all tables']); }
    mysqli_autocommit($conn, true); exit;
}

/* =====================================================================
 * check_active_changeover_pu / start_changeover_pu / end_changeover_pu
 * — all use the PHYSICAL machine id consistently within this file (this
 * was already correct here; the mismatch was in dashboard_pu.php's own
 * JOIN, fixed in that file).
 * ===================================================================== */
elseif ($_REQUEST['action'] === 'check_active_changeover_pu') {
    $machineId = intval($_REQUEST['machine_id']);
    $tableId   = (isset($_REQUEST['table_id']) && $_REQUEST['table_id'] !== 'null') ? intval($_REQUEST['table_id']) : null;
    $sql = "SELECT c.id,c.table_id,c.start_time,c.employee_id FROM pu_machine_changeover c WHERE c.machine_id=$machineId AND c.status='running'";
    if ($tableId) $sql .= " AND c.table_id=$tableId";
    $sql .= " ORDER BY c.id DESC";
    $res = mysqli_query($conn, $sql);
    if ($res && mysqli_num_rows($res) > 0) {
        $cos = [];
        while ($r = mysqli_fetch_assoc($res)) $cos[] = ['id'=>$r['id'],'table_id'=>$r['table_id'],'start_time'=>$r['start_time'],'employee_id'=>$r['employee_id']];
        echo json_encode(['success'=>true,'has_active_changeover'=>true,'changeover_data'=> $tableId ? $cos[0] : $cos]);
    } else echo json_encode(['success'=>true,'has_active_changeover'=>false]);
    exit;
}

elseif ($_REQUEST['action'] === 'start_changeover_pu') {
    $machineId = intval($_REQUEST['machine_id']); $tableId = intval($_REQUEST['table_id']);
    $clockNo   = mysqli_real_escape_string($conn, $_REQUEST['clock_no'] ?? ''); $now = date('Y-m-d H:i:s');
    if (!$clockNo) { echo json_encode(['success'=>false,'message'=>'Clock number required']); exit; }
    $uRow = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM admin WHERE clock_no='$clockNo' LIMIT 1"));
    if (!$uRow) { echo json_encode(['success'=>false,'message'=>'Invalid Clock Number']); exit; }
    $empId = intval($uRow['id']);
    $woRow = mysqli_fetch_assoc(mysqli_query($conn,
        "SELECT v.manuf_id AS manuf_id FROM pu_machine_table v LEFT JOIN pu_machine_changeover c ON c.work_order_table_id=v.manuf_id AND c.machine_id=$machineId AND c.status='running'
         WHERE v.v_machine_id=$machineId AND v.table_id=$tableId AND v.is_break=0
           AND (v.finished=0 OR (v.finished=1 AND v.changeover_pending=1))
           AND c.id IS NULL ORDER BY v.priority ASC LIMIT 1"
    ));
    if (!$woRow) { echo json_encode(['success'=>false,'message'=>'No work order found for this table']); exit; }
    $ok = mysqli_query($conn, "INSERT INTO pu_machine_changeover(machine_id,table_id,employee_id,work_order_table_id,start_time,status) VALUES($machineId,$tableId,$empId,{$woRow['manuf_id']},'$now','running')");
    echo json_encode(['success'=>(bool)$ok,'message'=>$ok?'':mysqli_error($conn)]);
    exit;
}

elseif ($_REQUEST['action'] === 'end_changeover_pu') {
    $machineId = intval($_REQUEST['machine_id']); $tableId = intval($_REQUEST['table_id']); $now = date('Y-m-d H:i:s');
    $r = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id,start_time,work_order_table_id FROM pu_machine_changeover WHERE machine_id=$machineId AND table_id=$tableId AND status='running' ORDER BY id DESC LIMIT 1"));
    if ($r) {
        $dur = time() - strtotime($r['start_time']);
        mysqli_query($conn, "UPDATE pu_machine_changeover SET end_time='$now',duration_seconds=$dur,status='completed' WHERE id={$r['id']}");
        if (!empty($r['work_order_table_id'])) {
            $manufId = intval($r['work_order_table_id']);
            mysqli_query($conn, "UPDATE pu_machine_table SET changeover_pending=0 WHERE manuf_id=$manufId");
        }
        echo json_encode(['success'=>true]);
    } else echo json_encode(['success'=>false]);
    exit;
}

elseif ($_REQUEST['action'] === 'first_off_signoff_pu') {
    $woId    = intval($_REQUEST['work_order_id']); $partId  = intval($_REQUEST['part_id']);
    $mId     = intval($_REQUEST['machine_id']);
    $puId    = intval($_REQUEST['pu_machine_table_id']); $now = date('Y-m-d H:i:s');
    $comment = mysqli_real_escape_string($conn, $_REQUEST['qc_comment'] ?? '');
    $clockNo = mysqli_real_escape_string($conn, $_REQUEST['qc_clock_no'] ?? '');
    if (!$clockNo) { echo json_encode(['success'=>false,'message'=>'Clock number required']); exit; }
    $uRow = mysqli_fetch_assoc(mysqli_query($conn, "SELECT id FROM admin WHERE clock_no='$clockNo' LIMIT 1"));
    if (!$uRow) { echo json_encode(['success'=>false,'message'=>'Invalid Clock Number']); exit; }
    $qcEmpId = intval($uRow['id']);
    $ok = mysqli_query($conn, "INSERT INTO pu_first_off_signoff(work_order_id,part_id,pu_machine_table_id,machine_id,qc_employee_id,qc_comment,signed_at) VALUES($woId,$partId,$puId,$mId,$qcEmpId,'$comment','$now')");
    echo json_encode(['success'=>(bool)$ok,'message'=>$ok?'':mysqli_error($conn)]);
    exit;
}

elseif ($_REQUEST['action'] === 'get_live_uph_pu') {

    $woId      = intval($_REQUEST['work_order_id'] ?? 0);
    $partId    = intval($_REQUEST['part_id'] ?? 0);
    $puId      = intval($_REQUEST['pu_machine_table_id'] ?? 0);
    $tableId   = intval($_REQUEST['table_id'] ?? 0);
    $machineId = intval($_SESSION['machine_id'] ?? 0);

    // Rolling window (default 60 minutes)
    $winMin = max(5, min(480, intval($_REQUEST['window_minutes'] ?? PU_UPH_WINDOW_MINUTES)));

    if (!$woId || !$partId || !$puId || !$machineId) {
        echo json_encode([
            'success' => false,
            'message' => 'Missing params'
        ]);
        exit;
    }

    // Last X minutes
    $winStart = date('Y-m-d H:i:s', strtotime("-{$winMin} minutes"));

    // OK quantity produced within the rolling window
    $okRow = mysqli_fetch_assoc(mysqli_query($conn, "
        SELECT COALESCE(SUM(quantity),0) AS c
        FROM pu_part_count_log
        WHERE work_order_id = $woId
          AND pu_machine_table_id = $puId
          AND part_id = $partId
          AND type = 'ok'
          AND created_at >= '$winStart'
    "));

    $okUnits = intval($okRow['c']);

    // Live UPH = OK Qty in last X minutes converted to hourly rate
    $prodHours = $winMin / 60;
    $liveUph = ($prodHours > 0) ? round($okUnits / $prodHours, 2) : 0;

    // Planned UPH from routing
    $plannedUph = 0;

    $pl = mysqli_fetch_assoc(mysqli_query($conn, "
        SELECT part_id, set_id
        FROM pu_machine_table
        WHERE manuf_id = $puId
        LIMIT 1
    "));

    if ($pl) {
        $plannedUph = pu_calculateBasicUph(
            $conn,
            $pl['part_id'],
            intval($pl['set_id'] ?? 0)
        );
    }

    // Save latest calculated UPH
    mysqli_query($conn, "
        UPDATE pu_part_count_log
        SET uph = $liveUph
        WHERE work_order_id = $woId
          AND pu_machine_table_id = $puId
          AND part_id = $partId
    ");

    echo json_encode([
        'success'         => true,
        'live_uph'        => $liveUph,
        'planned_uph'     => $plannedUph,
        'ok_units'        => $okUnits,
        'productive_min'  => $winMin,
        'window_minutes'  => $winMin,
        'is_green'        => ($plannedUph > 0 && $liveUph >= $plannedUph),
        'table_id'        => $tableId
    ]);
    exit;
}

elseif (isset($_REQUEST['action']) && $_REQUEST['action'] === 'update_clock_pu') {
    $machineId         = intval($_REQUEST['machine_id']         ?? 0);
    $productionSeconds = intval($_REQUEST['production_seconds'] ?? 0);
    $isRunning         = (intval($_REQUEST['is_running']        ?? 0)) ? 1 : 0;
    $lastStartRaw      = $_REQUEST['last_start_time'] ?? '';
    $lastStartTime     = ($lastStartRaw !== '') ? date('Y-m-d H:i:s', strtotime($lastStartRaw)) : null;

    if (!$machineId) { echo json_encode(['success' => false, 'message' => 'Missing machine_id']); exit; }

    $prodDate          = ((int)date('H') < 6) ? date('Y-m-d', strtotime('-1 day')) : date('Y-m-d');
    $productionSeconds = max(0, min($productionSeconds, 86400));
    $lastStartSql      = ($lastStartTime !== null) ? "'" . mysqli_real_escape_string($conn, $lastStartTime) . "'" : "NULL";

    $ok = mysqli_query($conn,
        "INSERT INTO pu_machine_clock_daily (machine_id, production_date, production_seconds, last_start_time, is_running)
         VALUES ($machineId, '$prodDate', $productionSeconds, $lastStartSql, $isRunning)
         ON DUPLICATE KEY UPDATE production_seconds = VALUES(production_seconds), last_start_time = VALUES(last_start_time), is_running = VALUES(is_running)"
    );
    echo json_encode(['success' => (bool)$ok]);
    exit;
}

elseif (isset($_REQUEST['action']) && $_REQUEST['action'] === 'logout_pu') {
    unset($_SESSION['engineer_id']);
    unset($_SESSION['machine_id']);
    echo json_encode(['success' => true]);
    exit;
}
?>