<?php
declare(strict_types=1);

ini_set('display_errors', '0');

require_once __DIR__ . '/../includes/console_shell.php';
require_login();

$pageLink = 'masters/employee-shifts.php';
$acl = require_acl('can_view', $pageLink);
$errors = [];
$flash = $_SESSION['flash_employee_shifts'] ?? null;
unset($_SESSION['flash_employee_shifts']);

$mode = (string)($_GET['mode'] ?? '');
$editId = max(0, (int)($_GET['edit'] ?? 0));
$isFormView = $mode === 'add' || $editId > 0;
$search = trim((string)($_GET['q'] ?? ''));
$editItem = null;

function shift_stmt(mysqli $con, string $sql): mysqli_stmt
{
    $stmt = $con->prepare($sql);
    if (!$stmt) {
        throw new RuntimeException('Unable to prepare database operation.');
    }
    return $stmt;
}

function shift_run(mysqli_stmt $stmt): void
{
    if (!$stmt->execute()) {
        throw new RuntimeException('Unable to complete database operation.');
    }
}

function shift_text(string $value, int $max = 255): string
{
    $value = trim((string)(preg_replace('/\s+/u', ' ', $value) ?? $value));
    return mb_substr($value, 0, $max);
}

function shift_flag($value): int
{
    return (string)$value === '1' ? 1 : 0;
}

function shift_column_exists(mysqli $con, string $table, string $column): bool
{
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $column)) return false;
    $stmt = shift_stmt($con, 'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND COLUMN_NAME=? LIMIT 1');
    $stmt->bind_param('ss', $table, $column);
    shift_run($stmt);
    $exists = (bool)$stmt->get_result()->fetch_assoc();
    $stmt->close();
    return $exists;
}

function shift_time_valid(string $value): bool
{
    $date = DateTimeImmutable::createFromFormat('!H:i', $value);
    return $date !== false && $date->format('H:i') === $value;
}

function shift_hhmm_valid(string $value): bool
{
    return $value === '' || preg_match('/^(?:\d{1,3}):[0-5]\d$/', $value) === 1;
}

function shift_db_time(string $value): string
{
    return $value === '' ? '0' : date('h:i A', strtotime($value));
}

function shift_input_time(mixed $value): string
{
    $value = trim((string)$value);
    return $value === '' || $value === '0' ? '' : date('H:i', strtotime($value));
}

function shift_total_hours(string $in, string $out, bool $fullDay = false): string
{
    $start = DateTimeImmutable::createFromFormat('!H:i', $in);
    $end = DateTimeImmutable::createFromFormat('!H:i', $out);
    if (!$start || !$end) {
        return '';
    }
    if ($end < $start || ($fullDay && $end == $start)) {
        $end = $end->modify('+1 day');
    }
    $minutes = (int)(($end->getTimestamp() - $start->getTimestamp()) / 60);
    return intdiv($minutes, 60) . ' hrs, ' . ($minutes % 60) . 'min';
}

function shift_duration_minutes(string $in, string $out, bool $fullDay = false): int
{
    if (!shift_time_valid($in) || !shift_time_valid($out)) return 0;
    [$inHour, $inMinute] = array_map('intval', explode(':', $in));
    [$outHour, $outMinute] = array_map('intval', explode(':', $out));
    $start = ($inHour * 60) + $inMinute;
    $end = ($outHour * 60) + $outMinute;
    $minutes = $end - $start;
    if ($minutes < 0 || ($minutes === 0 && $fullDay)) $minutes += 1440;
    return $minutes;
}

function shift_group_key(string $name): string
{
    $name = mb_strtolower(trim($name));
    $name = trim((string)(preg_replace('/[^a-z0-9]+/u', ' ', $name) ?? $name));
    if (preg_match('/\b(?:mid\s*night|overnight|night\s+shift)\b/', $name)) return 'midnight';
    if (preg_match('/\b(?:(?:multi|muti)(?:ple)?\s*day(?:s)?|multi|muti|cross\s*date)\b/', $name)) return 'multi';
    if (preg_match('/\b(?:day|daytime|normal|regular)(?:\s+shift)?\b/', $name)) return 'day';
    return '';
}

function shift_resolve_group(string $in, string $out, string $ownership, int $afterMidnight, int $fullDay): string
{
    if (!shift_time_valid($in) || !shift_time_valid($out)) return '';
    [$inHour, $inMinute] = array_map('intval', explode(':', $in));
    [$outHour, $outMinute] = array_map('intval', explode(':', $out));
    $inMinutes = ($inHour * 60) + $inMinute;
    $outMinutes = ($outHour * 60) + $outMinute;

    $midnightCondition = $inMinutes >= 0 && $inMinutes <= 300 && $outMinutes <= 300;
    if ($midnightCondition) {
        if ($ownership === 'previous') return 'midnight';
        if ($ownership !== 'login' || !in_array($afterMidnight, [0, 1], true)) return '';
        return $afterMidnight === 1 ? 'multi' : 'day';
    }
    if ($outMinutes > $inMinutes) {
        if (!in_array($afterMidnight, [0, 1], true)) return '';
        return $afterMidnight === 1 ? 'multi' : 'day';
    }
    if ($outMinutes < $inMinutes) return 'multi';
    return '';
}

function shift_group_id(mysqli $con, string $key): int
{
    $stmt = shift_stmt($con, 'SELECT id, name FROM jos_app_empshiftgroup WHERE active_status=1 ORDER BY id ASC');
    shift_run($stmt);
    $result = $stmt->get_result();
    $id = 0;
    while ($row = $result->fetch_assoc()) {
        if (shift_group_key((string)$row['name']) === $key) {
            $id = (int)$row['id'];
            break;
        }
    }
    $stmt->close();
    return $id;
}

function shift_locations(mysqli $con, int $shiftId): array
{
    $items = [];
    // orglocation is the legacy storage column; progressive setup stores the
    // selected Payroll Location id in it so existing shift consumers remain compatible.
    $stmt = shift_stmt($con, 'SELECT l.orglocation, l.status, o.name FROM jos_app_empshift_locationlog l INNER JOIN jos_app_payrolllocation o ON o.id=l.orglocation WHERE l.shiftid=? ORDER BY o.name ASC, l.id ASC');
    $stmt->bind_param('i', $shiftId);
    shift_run($stmt);
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        $items[] = $row;
    }
    $stmt->close();
    return $items;
}

function shift_selected_locations(mysqli $con, int $shiftId): array
{
    return array_map(static fn(array $row): int => (int)$row['orglocation'], shift_locations($con, $shiftId));
}

function shift_lm_rules_post(): array
{
    $mins = (array)($_POST['lm_min'] ?? []);
    $maxs = (array)($_POST['lm_max'] ?? []);
    $calculations = (array)($_POST['lm_calculation'] ?? []);
    $counts = (array)($_POST['lm_count'] ?? []);
    $hours = (array)($_POST['lm_hrs'] ?? []);
    $days = (array)($_POST['lm_days'] ?? []);
    $rupees = (array)($_POST['lm_rs'] ?? []);
    $rules = [];
    $total = min(100, max(count($mins), count($maxs), count($calculations), count($counts), count($hours), count($days), count($rupees)));
    for ($i = 0; $i < $total; $i++) {
        $rules[] = [
            'min' => trim((string)($mins[$i] ?? '')),
            'max' => trim((string)($maxs[$i] ?? '')),
            'calculation' => (int)($calculations[$i] ?? 0),
            'count' => trim((string)($counts[$i] ?? '')),
            'hrs' => trim((string)($hours[$i] ?? '')),
            'days' => trim((string)($days[$i] ?? '')),
            'rs' => trim((string)($rupees[$i] ?? '')),
        ];
    }
    return $rules;
}

function shift_decimal_valid(string $value): bool
{
    return preg_match('/^\d+(?:\.\d{1,4})?$/', $value) === 1;
}

function shift_lm_minutes_value(string $minutes): string
{
    $value = rtrim(rtrim(number_format((float)$minutes, 4, '.', ''), '0'), '.');
    return $value === '' ? '0' : $value;
}

function shift_save_lm_policy(mysqli $con, array $posted, int $modifiedBy): void
{
    foreach ($posted['lm_orgids'] as $orgId) {
        $stmt = shift_stmt($con, 'SELECT id FROM jos_app_lmconfig WHERE orgid=? AND month=? AND year=? ORDER BY id DESC LIMIT 1');
        $stmt->bind_param('iii', $orgId, $posted['lm_month'], $posted['lm_year']);
        shift_run($stmt);
        $existing = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        $policyId = (int)($existing['id'] ?? 0);

        if ($policyId > 0) {
            $stmt = shift_stmt($con, 'UPDATE jos_app_lmconfig SET policyname=?, monthgrace=?, calculate=? WHERE id=?');
            $stmt->bind_param('ssii', $posted['lm_policyname'], $posted['lm_monthgrace'], $posted['lm_calculate'], $policyId);
        } else {
            $stmt = shift_stmt($con, 'INSERT INTO jos_app_lmconfig (policyname, month, year, orgid, monthgrace, calculate) VALUES (?, ?, ?, ?, ?, ?)');
            $stmt->bind_param('siiisi', $posted['lm_policyname'], $posted['lm_month'], $posted['lm_year'], $orgId, $posted['lm_monthgrace'], $posted['lm_calculate']);
        }
        shift_run($stmt);
        if ($policyId <= 0) $policyId = (int)$con->insert_id;
        $stmt->close();

        $stmt = shift_stmt($con, 'DELETE FROM jos_app_lmconfig_grid WHERE config_id=?');
        $stmt->bind_param('i', $policyId);
        shift_run($stmt);
        $stmt->close();

        $stmt = shift_stmt($con, 'INSERT INTO jos_app_lmconfig_grid (config_id, month, year, orgid, mintime, maxtime, calculation, `count`, hrs, days, rs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
        foreach ($posted['lm_rules'] as $rule) {
            $min = (int)$rule['min'];
            $max = (int)$rule['max'];
            $calculation = (int)$rule['calculation'];
            $count = (int)$rule['count'];
            $hrs = shift_lm_minutes_value((string)$rule['hrs']);
            $days = (string)$rule['days'];
            $rs = (string)$rule['rs'];
            $stmt->bind_param('iiiiiiiisss', $policyId, $posted['lm_month'], $posted['lm_year'], $orgId, $min, $max, $calculation, $count, $hrs, $days, $rs);
            shift_run($stmt);
        }
        $stmt->close();

        $stmt = shift_stmt($con, 'INSERT INTO jos_app_lmconfig_editlog (lmconfigid, `date`, orgid, month, year, policyname, calculate, monthgrace, modify_by) VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?)');
        $stmt->bind_param('iiiisisi', $policyId, $orgId, $posted['lm_month'], $posted['lm_year'], $posted['lm_policyname'], $posted['lm_calculate'], $posted['lm_monthgrace'], $modifiedBy);
        shift_run($stmt);
        $editLogId = (int)$con->insert_id;
        $stmt->close();

        $stmt = shift_stmt($con, 'INSERT INTO jos_app_lmconfig_grid_editlog (config_id, `date`, month, year, mintime, maxtime, calculation, `count`, hrs, calculate, days, rs) VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)');
        foreach ($posted['lm_rules'] as $rule) {
            $min = (int)$rule['min'];
            $max = (int)$rule['max'];
            $calculation = (int)$rule['calculation'];
            $count = (int)$rule['count'];
            $hrs = shift_lm_minutes_value((string)$rule['hrs']);
            $days = (string)$rule['days'];
            $rs = (string)$rule['rs'];
            $stmt->bind_param('iiiiiiisss', $editLogId, $posted['lm_month'], $posted['lm_year'], $min, $max, $calculation, $count, $hrs, $days, $rs);
            shift_run($stmt);
        }
        $stmt->close();
    }
}

function shift_post_data(): array
{
    $locations = array_values(array_unique(array_filter(
        array_map('intval', (array)($_POST['orglocation'] ?? [])),
        static fn(int $id): bool => $id > 0
    )));
    $lmOrgIds = array_values(array_unique(array_filter(
        array_map('intval', (array)($_POST['lm_orgid'] ?? [])),
        static fn(int $id): bool => $id > 0
    )));
    return [
        'id' => max(0, (int)($_POST['id'] ?? 0)),
        'shift' => max(0, (int)($_POST['shift'] ?? 0)),
        'work_date_policy' => trim((string)($_POST['work_date_policy'] ?? '')),
        'after_midnight' => trim((string)($_POST['after_midnight'] ?? '')),
        'full_day' => shift_flag($_POST['full_day'] ?? 0),
        'shift_type' => shift_text((string)($_POST['shift_type'] ?? '')),
        'shortname' => shift_text((string)($_POST['shortname'] ?? ''), 50),
        'in_time' => trim((string)($_POST['in_time'] ?? '')),
        'in_grace' => trim((string)($_POST['in_grace'] ?? '')),
        'max_grace' => trim((string)($_POST['max_grace'] ?? '')),
        'out_time' => trim((string)($_POST['out_time'] ?? '')),
        'max_out' => trim((string)($_POST['max_out'] ?? '')),
        'lunch_applicable' => shift_flag($_POST['lunch_applicable'] ?? 0),
        'lunch_time' => trim((string)($_POST['lunch_time'] ?? '')),
        'notapplay_time' => trim((string)($_POST['notapplay_time'] ?? '')),
        'lunch_minusinshift' => shift_flag($_POST['lunch_minusinshift'] ?? 0),
        'lunch_minusinwork' => (int)($_POST['lunch_minusinwork'] ?? 0),
        'unused_lunch' => (int)($_POST['unused_lunch'] ?? 0),
        'fixlunch_time' => shift_flag($_POST['fixlunch_time'] ?? 0),
        'minlt_time' => trim((string)($_POST['minlt_time'] ?? '')),
        'lunch_in' => trim((string)($_POST['lunch_in'] ?? '')),
        'lunch_flow_complete' => shift_flag($_POST['lunch_flow_complete'] ?? 0),
        'lunch_trigger_mode' => trim((string)($_POST['lunch_trigger_mode'] ?? 'minimum')),
        'lunch_cutoff_enabled' => shift_flag($_POST['lunch_cutoff_enabled'] ?? 0),
        'cameraonof' => shift_flag($_POST['cameraonof'] ?? 0),
        'indisplaybefore' => trim((string)($_POST['indisplaybefore'] ?? '')),
        'indisplayafter' => trim((string)($_POST['indisplayafter'] ?? '')),
        'outdisplaybefore' => trim((string)($_POST['outdisplaybefore'] ?? '')),
        'outdisplayafter' => trim((string)($_POST['outdisplayafter'] ?? '')),
        'ot_category' => (int)($_POST['ot_category'] ?? 0),
        'lm_show' => shift_flag($_POST['lm_show'] ?? 0),
        'lm_track_enabled' => in_array((string)($_POST['lm_track_enabled'] ?? ''), ['yes', 'no'], true) ? (string)$_POST['lm_track_enabled'] : '',
        'lm_policy_action' => trim((string)($_POST['lm_policy_action'] ?? 'keep')),
        'lm_rule_method' => trim((string)($_POST['lm_rule_method'] ?? 'count')),
        'lm_rule_unit' => trim((string)($_POST['lm_rule_unit'] ?? 'days')),
        'lm_rule_mode' => trim((string)($_POST['lm_rule_mode'] ?? 'auto')),
        'lm_count_every' => trim((string)($_POST['lm_count_every'] ?? '3')),
        'lm_repeat_occurrences' => trim((string)($_POST['lm_repeat_occurrences'] ?? '')),
        'lm_occurrence_total' => trim((string)($_POST['lm_occurrence_total'] ?? '1')),
        'lm_band_width' => trim((string)($_POST['lm_band_width'] ?? '30')),
        'lm_deduct_value' => trim((string)($_POST['lm_deduct_value'] ?? '0.5')),
        'lm_flow_complete' => shift_flag($_POST['lm_flow_complete'] ?? 0),
        'lm_orgids' => $lmOrgIds,
        'lm_month' => (int)($_POST['lm_month'] ?? date('n')),
        'lm_year' => (int)($_POST['lm_year'] ?? date('Y')),
        'lm_policyname' => shift_text((string)($_POST['lm_policyname'] ?? ''), 255),
        'lm_calculate' => (int)($_POST['lm_calculate'] ?? 0),
        'lm_monthgrace' => trim((string)($_POST['lm_monthgrace'] ?? '0')),
        'lm_confirm_global' => shift_flag($_POST['lm_confirm_global'] ?? 0),
        'lm_rules' => shift_lm_rules_post(),
        'night_category' => (int)($_POST['night_category'] ?? 0),
        'night_shift' => shift_flag($_POST['night_shift'] ?? 0),
        'night_onwff' => shift_flag($_POST['night_onwff'] ?? 0),
        'night_otcount' => shift_flag($_POST['night_otcount'] ?? 0),
        'night_ot' => shift_flag($_POST['night_ot'] ?? 0),
        'night_shiftcount' => shift_flag($_POST['night_shiftcount'] ?? 0),
        'shiftcount_onwff' => shift_flag($_POST['shiftcount_onwff'] ?? 0),
        'minwork_short' => trim((string)($_POST['minwork_short'] ?? '')),
        'min_worktime' => trim((string)($_POST['min_worktime'] ?? '')),
        'max_worktime' => trim((string)($_POST['max_worktime'] ?? '')),
        'early_logouttime' => trim((string)($_POST['early_logouttime'] ?? '')),
        'locations' => $locations,
        'policy_answers_json' => (string)($_POST['policy_answers_json'] ?? ''),
    ];
}

function shift_draft_json(array $data, int $status = 200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store');
    echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

function shift_draft_row(mysqli $con, int $userId, string $draftMode, int $shiftId): ?array
{
    $stmt = shift_stmt($con, 'SELECT id, current_step, lm_question, form_data, draft_version, updated_at FROM jos_app_empshift_draft WHERE userid=? AND mode=? AND shift_id=? AND status=0 LIMIT 1');
    $stmt->bind_param('isi', $userId, $draftMode, $shiftId);
    shift_run($stmt);
    $row = $stmt->get_result()->fetch_assoc() ?: null;
    $stmt->close();
    return $row;
}

function shift_policy_answers_row(mysqli $con, int $shiftId): ?array
{
    $stmt = shift_stmt($con, 'SELECT answers_json, schema_version, setup_complete, current_step, current_module, current_question, answer_source, updated_at FROM jos_app_empshift_policy_answers WHERE shiftid=? LIMIT 1');
    $stmt->bind_param('i', $shiftId);
    shift_run($stmt);
    $row = $stmt->get_result()->fetch_assoc() ?: null;
    $stmt->close();
    return $row;
}

function shift_policy_payload(string $json): ?array
{
    if ($json === '' || strlen($json) > 524288) return null;
    $payload = json_decode($json, true);
    if (!is_array($payload) || !is_array($payload['fields'] ?? null) || !is_array($payload['ui'] ?? null)) return null;
    $payload['schemaVersion'] = max(1, min(100, (int)($payload['schemaVersion'] ?? 1)));
    $payload['rules'] = is_array($payload['rules'] ?? null) ? array_values($payload['rules']) : [];
    return $payload;
}

function shift_save_policy_answers(mysqli $con, int $shiftId, array $payload, int $userId): void
{
    $schemaVersion = max(1, min(100, (int)($payload['schemaVersion'] ?? 1)));
    $currentStep = max(0, min(8, (int)($payload['ui']['current'] ?? 0)));
    $currentModule = shift_text((string)($payload['ui']['currentModule'] ?? 'review'), 30);
    $currentQuestion = shift_text((string)(
        $currentModule === 'late_mark'
            ? ($payload['ui']['lmFlowCurrent'] ?? '')
            : ($currentModule === 'lunch' ? ($payload['ui']['lunchFlowCurrent'] ?? '') : '')
    ), 50);
    $setupComplete = !empty($payload['fields']['lm_flow_complete']) && !empty($payload['fields']['lunch_flow_complete']) ? 1 : 0;
    $answersJson = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    if ($answersJson === false) throw new RuntimeException('Unable to encode the progressive answer snapshot.');

    $stmt = shift_stmt($con, 'INSERT INTO jos_app_empshift_policy_answers (shiftid, schema_version, answers_json, setup_complete, current_step, current_module, current_question, answer_source, created_by, modified_by, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, \'wizard\', ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE schema_version=VALUES(schema_version), answers_json=VALUES(answers_json), setup_complete=VALUES(setup_complete), current_step=VALUES(current_step), current_module=VALUES(current_module), current_question=VALUES(current_question), answer_source=\'wizard\', modified_by=VALUES(modified_by), updated_at=NOW()');
    $stmt->bind_param('iisiissii', $shiftId, $schemaVersion, $answersJson, $setupComplete, $currentStep, $currentModule, $currentQuestion, $userId, $userId);
    shift_run($stmt);
    $stmt->close();
}

$draftUserId = (int)(current_user()['id'] ?? 0);
$draftMode = $editId > 0 ? 'edit' : 'add';
$draftShiftId = $editId > 0 ? $editId : 0;

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['draft_action'])) {
    if (!verify_csrf($_POST['_csrf'] ?? null)) shift_draft_json(['ok' => false, 'message' => 'Security token expired. Refresh the page.'], 419);
    if ($draftUserId <= 0) shift_draft_json(['ok' => false, 'message' => 'Your login session has expired.'], 401);
    $draftAction = (string)$_POST['draft_action'];
    try {
        if ($draftAction === 'delete') {
            $stmt = shift_stmt($con, 'DELETE FROM jos_app_empshift_draft WHERE userid=? AND mode=? AND shift_id=? AND status=0');
            $stmt->bind_param('isi', $draftUserId, $draftMode, $draftShiftId);
            shift_run($stmt); $stmt->close();
            shift_draft_json(['ok' => true, 'deleted' => true]);
        }
        if ($draftAction !== 'save') shift_draft_json(['ok' => false, 'message' => 'Invalid draft operation.'], 400);

        $payloadJson = (string)($_POST['payload'] ?? '');
        if ($payloadJson === '' || strlen($payloadJson) > 524288) shift_draft_json(['ok' => false, 'message' => 'Draft data is missing or too large.'], 422);
        $payload = json_decode($payloadJson, true);
        if (!is_array($payload) || !is_array($payload['fields'] ?? null) || !is_array($payload['ui'] ?? null)) shift_draft_json(['ok' => false, 'message' => 'Draft data is invalid.'], 422);
        $currentStep = max(0, min(8, (int)($payload['ui']['current'] ?? 0)));
        $lmQuestion = shift_text((string)($payload['ui']['lmFlowCurrent'] ?? 'track'), 30);
        if (!in_array($lmQuestion, ['track','grace','deduction','method','unit','mode','rules','cycle','scope','review'], true)) $lmQuestion = 'track';
        $clientVersion = max(0, (int)($_POST['draft_version'] ?? 0));
        $existingDraft = shift_draft_row($con, $draftUserId, $draftMode, $draftShiftId);
        if ($existingDraft && $clientVersion !== (int)$existingDraft['draft_version']) {
            shift_draft_json(['ok' => false, 'conflict' => true, 'message' => 'A newer version of this draft already exists.', 'draft_version' => (int)$existingDraft['draft_version']], 409);
        }
        $nextVersion = $existingDraft ? ((int)$existingDraft['draft_version'] + 1) : 1;
        if ($existingDraft) {
            $draftId = (int)$existingDraft['id'];
            $stmt = shift_stmt($con, 'UPDATE jos_app_empshift_draft SET current_step=?, lm_question=?, form_data=?, draft_version=?, updated_at=NOW() WHERE id=? AND draft_version=?');
            $stmt->bind_param('issiii', $currentStep, $lmQuestion, $payloadJson, $nextVersion, $draftId, $clientVersion);
        } else {
            $stmt = shift_stmt($con, 'INSERT INTO jos_app_empshift_draft (userid, mode, shift_id, current_step, lm_question, form_data, draft_version, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 0, NOW(), NOW())');
            $stmt->bind_param('isiissi', $draftUserId, $draftMode, $draftShiftId, $currentStep, $lmQuestion, $payloadJson, $nextVersion);
        }
        shift_run($stmt); $stmt->close();
        shift_draft_json(['ok' => true, 'draft_version' => $nextVersion, 'saved_at' => date('c')]);
    } catch (Throwable $e) {
        error_log('Employee Shift draft operation failed: ' . $e->getMessage());
        shift_draft_json(['ok' => false, 'message' => 'Unable to save the draft right now.'], 500);
    }
}

$latestDraft = null;
if ($isFormView && $draftUserId > 0 && $_SERVER['REQUEST_METHOD'] !== 'POST') {
    try {
        $latestDraft = shift_draft_row($con, $draftUserId, $draftMode, $draftShiftId);
    } catch (Throwable $e) {
        error_log('Employee Shift draft load failed: ' . $e->getMessage());
    }
}

if (mb_strlen($search) > 100) {
    $errors[] = 'Search must be 100 characters or fewer.';
    $search = '';
}

$cameraFeatureEnabled = false;
$hasFixLunchTime = false;
try {
    $hasFixLunchTime = shift_column_exists($con, 'jos_app_empshift', 'fixlunch_time');
    $stmt = shift_stmt($con, 'SELECT cameraonof FROM jos_app_usersetting LIMIT 1');
    shift_run($stmt);
    $cameraFeatureEnabled = (int)($stmt->get_result()->fetch_assoc()['cameraonof'] ?? 0) === 1;
    $stmt->close();
} catch (Throwable $e) {
    $cameraFeatureEnabled = false;
}

$posted = null;
$policyAnswersPayload = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $action = (string)($_POST['action'] ?? '');
    if (!verify_csrf($_POST['_csrf'] ?? null)) {
        $errors[] = 'Security token expired. Please refresh and try again.';
    } elseif ($action !== 'save') {
        $errors[] = 'Invalid request.';
    } else {
        $posted = shift_post_data();
        $policyAnswersPayload = shift_policy_payload($posted['policy_answers_json']);
        if (!$policyAnswersPayload) $errors[] = 'The progressive form answer snapshot is missing or invalid. Please review the form and save again.';
        $id = $posted['id'];
        if ($id > 0 && empty($acl['can_edit'])) {
            $errors[] = 'You do not have permission to edit employee shifts.';
        }
        if ($id === 0 && empty($acl['can_add'])) {
            $errors[] = 'You do not have permission to add employee shifts.';
        }
        if ($posted['shift_type'] === '') $errors[] = 'Shift Name is required.';
        if ($posted['shortname'] === '') $errors[] = 'Shift Short Name is required.';
        if (!shift_time_valid($posted['in_time'])) $errors[] = 'A valid In Time is required.';
        if (!shift_time_valid($posted['out_time'])) $errors[] = 'A valid Out Time is required.';
        if (shift_time_valid($posted['in_time']) && shift_time_valid($posted['out_time'])) {
            [$postInHour, $postInMinute] = array_map('intval', explode(':', $posted['in_time']));
            [$postOutHour, $postOutMinute] = array_map('intval', explode(':', $posted['out_time']));
            $postInMinutes = ($postInHour * 60) + $postInMinute;
            $postOutMinutes = ($postOutHour * 60) + $postOutMinute;
            $postMidnightCondition = $postInMinutes >= 0 && $postInMinutes <= 300 && $postOutMinutes <= 300;
            $needsAfterMidnightAnswer = (!$postMidnightCondition && $postOutMinutes > $postInMinutes)
                || ($postMidnightCondition && $posted['work_date_policy'] === 'login');
            if ($postMidnightCondition && !in_array($posted['work_date_policy'], ['login', 'previous'], true)) {
                $errors[] = 'Please select whether this early-morning shift belongs to the Login Date or Previous Work Date.';
            }
            if ($needsAfterMidnightAnswer && !in_array($posted['after_midnight'], ['0', '1'], true)) {
                $errors[] = 'Please confirm whether a valid logout can occur after midnight.';
            }
            $resolvedGroup = shift_resolve_group(
                $posted['in_time'],
                $posted['out_time'],
                $posted['work_date_policy'],
                (int)$posted['after_midnight'],
                $posted['full_day']
            );
            if ($posted['in_time'] === $posted['out_time'] && !$postMidnightCondition) {
                $errors[] = 'IN and OUT cannot be the same for this shift type.';
            } elseif ($resolvedGroup === '') {
                $errors[] = 'Please confirm how punches after midnight should be assigned to a work date.';
            } else {
                try {
                    $posted['shift'] = shift_group_id($con, $resolvedGroup);
                    if ($posted['shift'] <= 0) {
                        $errors[] = 'The required active shift group is not configured.';
                    }
                } catch (Throwable $e) {
                    $errors[] = 'Unable to derive the shift type.';
                }
            }
        }
        if ($posted['in_grace'] === '' || !ctype_digit($posted['in_grace'])) $errors[] = 'LM Grace must contain minutes only.';
        if ($posted['max_grace'] !== '' && !ctype_digit($posted['max_grace'])) $errors[] = 'Max LM Grace must contain minutes only.';
        if ($posted['max_grace'] !== '' && ctype_digit($posted['max_grace']) && ctype_digit($posted['in_grace']) && (int)$posted['max_grace'] <= (int)$posted['in_grace']) {
            $errors[] = 'Maximum Late Mark Grace must be greater than the normal grace period.';
        }
        if (!in_array($posted['lm_policy_action'], ['keep', 'configure'], true)) $errors[] = 'Invalid late-mark policy action.';
        if (!$posted['lm_flow_complete']) $errors[] = 'Complete and confirm the late-mark questions.';
        if ($posted['lm_policy_action'] === 'configure') {
            if (!in_array($posted['lm_rule_method'], ['count', 'minute'], true)) $errors[] = 'Select a valid late-mark rule method.';
            if (!in_array($posted['lm_rule_unit'], ['days', 'hrs', 'rs'], true)) $errors[] = 'Select a valid late-mark deduction unit.';
            if (!$posted['lm_orgids']) $errors[] = 'Select at least one Payroll Location for the cutting policy.';
            if ($posted['lm_month'] < 1 || $posted['lm_month'] > 12) $errors[] = 'Select a valid late-mark policy month.';
            if ($posted['lm_year'] < 2020 || $posted['lm_year'] > 2100) $errors[] = 'Select a valid late-mark policy year.';
            if ($posted['lm_policyname'] === '') $errors[] = 'Late-mark Policy Name is required.';
            if (!in_array($posted['lm_calculate'], [0, 1, 2], true)) $errors[] = 'Invalid late-mark calculation cycle.';
            if ($posted['lm_monthgrace'] === '' || !ctype_digit($posted['lm_monthgrace'])) $errors[] = 'Policy grace adjustment must contain minutes only.';
            if (!$posted['lm_confirm_global']) $errors[] = 'Confirm that the cutting policy applies to every employee in the selected payroll locations.';
            if (!$posted['lm_rules']) $errors[] = 'Generate at least one late-mark cutting rule.';

            usort($posted['lm_rules'], static fn(array $a, array $b): int => [(int)$a['min'], (int)$a['max'], (int)$a['count']] <=> [(int)$b['min'], (int)$b['max'], (int)$b['count']]);
            $expectedCalculation = $posted['lm_rule_method'] === 'count' ? 4 : 3;
            $rangeGroups = [];
            foreach ($posted['lm_rules'] as $ruleIndex => $rule) {
                $label = 'Late-mark rule ' . ($ruleIndex + 1);
                if (!ctype_digit($rule['min']) || !ctype_digit($rule['max']) || (int)$rule['min'] < 1 || (int)$rule['max'] < (int)$rule['min']) {
                    $errors[] = $label . ' has an invalid minute range.';
                    continue;
                }
                if ((int)$rule['calculation'] !== $expectedCalculation) $errors[] = $label . ' does not match the selected calculation method.';
                if ((int)$rule['calculation'] === 4 && (!ctype_digit($rule['count']) || (int)$rule['count'] < 1)) $errors[] = $label . ' requires a positive late-mark count.';
                $rangeKey = (int)$rule['min'] . ':' . (int)$rule['max'];
                $rangeGroups[$rangeKey] ??= ['min' => (int)$rule['min'], 'max' => (int)$rule['max'], 'counts' => []];
                if ((int)$rule['calculation'] === 4) {
                    $countKey = (int)$rule['count'];
                    if (isset($rangeGroups[$rangeKey]['counts'][$countKey])) $errors[] = $label . ' repeats occurrence Count ' . $countKey . ' for the same minute range.';
                    $rangeGroups[$rangeKey]['counts'][$countKey] = true;
                }
                $hrsValid = shift_decimal_valid($rule['hrs'] === '' ? '0' : $rule['hrs']);
                $daysValid = shift_decimal_valid($rule['days'] === '' ? '0' : $rule['days']);
                $rsValid = shift_decimal_valid($rule['rs'] === '' ? '0' : $rule['rs']);
                $unitValues = ['hrs' => (float)($rule['hrs'] ?: 0), 'days' => (float)($rule['days'] ?: 0), 'rs' => (float)($rule['rs'] ?: 0)];
                if (!$hrsValid || !$daysValid || !$rsValid || ($unitValues[$posted['lm_rule_unit']] ?? 0) <= 0 || count(array_filter($unitValues, static fn(float $value): bool => $value > 0)) !== 1) {
                    $errors[] = $label . ' must contain only the selected ' . ($posted['lm_rule_unit'] === 'rs' ? 'Rs amount' : $posted['lm_rule_unit']) . ' deduction.';
                }
            }
            $uniqueRanges = array_values($rangeGroups);
            usort($uniqueRanges, static fn(array $a, array $b): int => [$a['min'], $a['max']] <=> [$b['min'], $b['max']]);
            $expectedMin = 1;
            foreach ($uniqueRanges as $rangeIndex => $range) {
                if ($range['min'] !== $expectedMin) $errors[] = 'Late-minute range ' . ($rangeIndex + 1) . ' must start at minute ' . $expectedMin . ' after grace; ranges cannot overlap or leave gaps.';
                $expectedMin = $range['max'] + 1;
            }
            $durationMinutes = shift_duration_minutes($posted['in_time'], $posted['out_time'], !empty($posted['full_day']));
            $coverageLimit = max(1, $durationMinutes - (int)$posted['in_grace']);
            if ($posted['max_grace'] !== '') $coverageLimit = min($coverageLimit, max(1, (int)$posted['max_grace'] - (int)$posted['in_grace']));
            $lastCoveredMinute = $uniqueRanges ? (int)end($uniqueRanges)['max'] : 0;
            if ($posted['lm_rules'] && $lastCoveredMinute < $coverageLimit) {
                $errors[] = 'Late-mark slabs must cover minutes 1 through ' . $coverageLimit . ' after grace.';
            }
        }
        if (!$posted['locations']) $errors[] = 'Select at least one Payroll Location.';
        if (!in_array($posted['ot_category'], [0, 1, 2], true)) $errors[] = 'Invalid OT Category.';
        if (!in_array($posted['lunch_minusinwork'], [0, 1, 2], true)) $errors[] = 'Invalid calculated-hours lunch option.';
        if (!in_array($posted['unused_lunch'], [0, 1, 2, 3], true)) $errors[] = 'Invalid unused lunch option.';
        if (!in_array($posted['night_category'], [0, 1, 2], true)) $errors[] = 'Invalid night calculation option.';

        $isMidnightShift = false;
        if (!$cameraFeatureEnabled || !$posted['cameraonof']) {
            $posted['cameraonof'] = 0;
            foreach (['indisplaybefore', 'indisplayafter', 'outdisplaybefore', 'outdisplayafter'] as $field) $posted[$field] = '';
        } else {
            foreach (['indisplaybefore', 'indisplayafter', 'outdisplaybefore', 'outdisplayafter'] as $field) {
                if ($posted[$field] !== '' && !shift_time_valid($posted[$field])) $errors[] = 'Camera display times must use a valid time.';
            }
        }
        if (!$posted['lunch_applicable']) {
            foreach (['lunch_time', 'notapplay_time', 'minlt_time', 'lunch_in'] as $field) $posted[$field] = '';
            $posted['lunch_minusinshift'] = $posted['lunch_minusinwork'] = $posted['unused_lunch'] = 0;
            $posted['fixlunch_time'] = 0;
        } else {
            if (!$posted['lunch_flow_complete']) $errors[] = 'Complete and confirm the lunch-policy questions.';
            if ($posted['lunch_time'] === '' || !ctype_digit($posted['lunch_time']) || (int)$posted['lunch_time'] < 1) $errors[] = 'Allowed lunch must be a positive number of minutes.';
            if (!shift_hhmm_valid($posted['notapplay_time'])) $errors[] = 'Maximum lunch-application duration must use HH:MM.';
            if (!shift_hhmm_valid($posted['minlt_time'])) $errors[] = 'Minimum worked duration must use HH:MM.';
            if ($posted['lunch_in'] !== '' && !shift_time_valid($posted['lunch_in'])) $errors[] = 'Lunch Start Time is invalid.';
            if (!in_array($posted['lunch_trigger_mode'], ['minimum', 'fixed'], true)) $errors[] = 'Select when lunch should become applicable.';
            if ($posted['lunch_trigger_mode'] === 'minimum' && $posted['minlt_time'] === '') $errors[] = 'Enter the minimum worked duration before lunch applies.';
            if ($posted['lunch_trigger_mode'] === 'fixed' && $posted['lunch_in'] === '') $errors[] = 'Enter the fixed lunch start time.';
            if ($posted['minlt_time'] !== '' && $posted['lunch_in'] !== '') $errors[] = 'Choose minimum worked duration or a fixed lunch time, not both.';
            if ($posted['lunch_cutoff_enabled'] && $posted['notapplay_time'] === '') $errors[] = 'Enter the maximum worked duration for lunch application.';
            if (!$posted['lunch_cutoff_enabled']) $posted['notapplay_time'] = '';
            if ($posted['lunch_trigger_mode'] === 'minimum') {
                $posted['lunch_in'] = '';
                $posted['fixlunch_time'] = 0;
            } else {
                $posted['minlt_time'] = '';
            }
        }
        if ($posted['ot_category'] !== 2) {
            $posted['night_category'] = $posted['night_shift'] = $posted['night_onwff'] = 0;
            $posted['night_otcount'] = $posted['night_ot'] = $posted['night_shiftcount'] = $posted['shiftcount_onwff'] = 0;
        }
        foreach (['minwork_short', 'min_worktime', 'max_worktime'] as $field) {
            if (!shift_hhmm_valid($posted[$field])) $errors[] = 'Shift configuration times must use HH:MM.';
        }
        if ($posted['early_logouttime'] !== '' && !ctype_digit($posted['early_logouttime'])) $errors[] = 'Early Logout must contain minutes only.';

        if (!$errors) {
            try {
                $stmt = shift_stmt($con, 'SELECT id, name FROM jos_app_empshiftgroup WHERE id=? AND active_status=1 LIMIT 1');
                $stmt->bind_param('i', $posted['shift']);
                shift_run($stmt);
                $groupRow = $stmt->get_result()->fetch_assoc();
                if (!$groupRow) {
                    $errors[] = 'Selected Shift Type is not active.';
                } else {
                    $isMidnightShift = stripos((string)$groupRow['name'], 'midnight') !== false;
                }
                $stmt->close();

                if (!$isMidnightShift) {
                    $posted['max_out'] = '';
                } elseif ($posted['max_out'] !== '' && !shift_time_valid($posted['max_out'])) {
                    $errors[] = 'Max Out must be a valid time.';
                }

                $stmt = shift_stmt($con, 'SELECT id FROM jos_app_empshift WHERE (shift_type=? OR shortname=?) AND id<>? LIMIT 1');
                $stmt->bind_param('ssi', $posted['shift_type'], $posted['shortname'], $id);
                shift_run($stmt);
                if ($stmt->get_result()->fetch_assoc()) $errors[] = 'Shift Name or Shift Short Name already exists.';
                $stmt->close();

                $stmt = shift_stmt($con, 'SELECT id FROM jos_app_payrolllocation WHERE id=? AND status=1 LIMIT 1');
                foreach ($posted['locations'] as $locationId) {
                    $stmt->bind_param('i', $locationId);
                    shift_run($stmt);
                    if (!$stmt->get_result()->fetch_assoc()) $errors[] = 'One or more payroll locations are invalid or inactive.';
                }
                $stmt->close();

                if ($posted['lm_policy_action'] === 'configure') {
                    $stmt = shift_stmt($con, 'SELECT id FROM jos_app_payrolllocation WHERE id=? LIMIT 1');
                    foreach ($posted['lm_orgids'] as $locationId) {
                        $stmt->bind_param('i', $locationId);
                        shift_run($stmt);
                        if (!$stmt->get_result()->fetch_assoc()) $errors[] = 'One or more payroll locations are invalid.';
                    }
                    $stmt->close();
                }
            } catch (Throwable $e) {
                $errors[] = 'Unable to validate shift data. Please try again.';
            }
        }

        if (!$errors) {
            $con->begin_transaction();
            try {
                $inDb = shift_db_time($posted['in_time']);
                $outDb = shift_db_time($posted['out_time']);
                $maxOutDb = shift_db_time($posted['max_out']);
                $lunchInDb = shift_db_time($posted['lunch_in']);
                $inGraceDb = '0-' . (int)$posted['in_grace'];
                $shiftHours = shift_total_hours($posted['in_time'], $posted['out_time'], !empty($posted['full_day']));
                $camInBefore = shift_db_time($posted['indisplaybefore']);
                $camInAfter = shift_db_time($posted['indisplayafter']);
                $camOutBefore = shift_db_time($posted['outdisplaybefore']);
                $camOutAfter = shift_db_time($posted['outdisplayafter']);

                $values = [$posted['shift'], $posted['shift_type'], $posted['shortname'], $inDb, $inGraceDb, $posted['max_grace'], $outDb, $maxOutDb, $shiftHours,
                    $posted['lunch_applicable'], $posted['lunch_time'], $posted['notapplay_time'], $posted['lunch_minusinshift'], $posted['lunch_minusinwork'], $posted['unused_lunch'], $posted['minlt_time'], $lunchInDb,
                    $posted['cameraonof'], $camInBefore, $camInAfter, $camOutBefore, $camOutAfter, $posted['ot_category'], $posted['lm_show'], $posted['night_category'], $posted['night_shift'], $posted['night_onwff'], $posted['night_otcount'], $posted['night_ot'], $posted['night_shiftcount'], $posted['shiftcount_onwff']];
                $types = 'issssssssissiiississssiiiiiiiii';
                $columns = 'shift=?, shift_type=?, shortname=?, in_time=?, in_grace=?, max_grace=?, out_time=?, max_out=?, shifthrs=?, lunch_applicable=?, lunch_time=?, notapplay_time=?, lunch_minusinshift=?, lunch_minusinwork=?, unused_lunch=?, minlt_time=?, lunch_in=?, cameraonof=?, indisplaybefore=?, indisplayafter=?, outdisplaybefore=?, outdisplayafter=?, ot_category=?, lm_show=?, night_category=?, night_shift=?, night_onwff=?, night_otcount=?, night_ot=?, night_shiftcount=?, shiftcount_onwff=?';
                if ($hasFixLunchTime) {
                    $columns .= ', fixlunch_time=?';
                    $values[] = $posted['fixlunch_time'];
                    $types .= 'i';
                }
                if ($id > 0) {
                    $stmt = shift_stmt($con, 'UPDATE jos_app_empshift SET ' . $columns . ', update_status=0, user_status=0 WHERE id=?');
                    $values[] = $id;
                    $stmt->bind_param($types . 'i', ...$values);
                } else {
                    $stmt = shift_stmt($con, 'INSERT INTO jos_app_empshift SET ' . $columns);
                    $stmt->bind_param($types, ...$values);
                }
                shift_run($stmt);
                $shiftId = $id > 0 ? $id : (int)$con->insert_id;
                $stmt->close();

                $stmt = shift_stmt($con, 'DELETE FROM jos_app_empshift_locationlog WHERE shiftid=?');
                $stmt->bind_param('i', $shiftId); shift_run($stmt); $stmt->close();
                $status = 0;
                $stmt = shift_stmt($con, 'INSERT INTO jos_app_empshift_locationlog (shiftid, orglocation, status) VALUES (?, ?, ?)');
                foreach ($posted['locations'] as $locationId) {
                    $stmt->bind_param('iii', $shiftId, $locationId, $status); shift_run($stmt);
                }
                $stmt->close();

                $stmt = shift_stmt($con, 'DELETE FROM jos_app_halfday_config WHERE shiftid=?');
                $stmt->bind_param('i', $shiftId); shift_run($stmt); $stmt->close();
                $stmt = shift_stmt($con, 'INSERT INTO jos_app_halfday_config (shiftid, min_worktimeshorthr, min_worktimehr, max_worktimehr, early_logouttimemin) VALUES (?, ?, ?, ?, ?)');
                $stmt->bind_param('issss', $shiftId, $posted['minwork_short'], $posted['min_worktime'], $posted['max_worktime'], $posted['early_logouttime']);
                shift_run($stmt); $stmt->close();

                // This is the legacy shift-history business table, not the console audit helper.
                $locationCsv = implode(',', $posted['locations']);
                $modifiedBy = (int)(current_user()['id'] ?? 0);
                $stmt = shift_stmt($con, 'INSERT INTO jos_app_employee_shift_edit_log (`date`, shift, shift_id, shiftname, shift_type, shortname, in_time, in_grace, max_grace, out_time, out_grace, lunch_time, lunch_in, lunch_in_grace, lunch_out, lunch_out_grace, adminingrace, adminlunchoutgrace, update_status, user_status, indipslaybefore, indisplayafter, outdisplaybefore, outdisplayafter, lindisplaybefore, lindisplayafter, loutdisplaybefore, loutdisplayafter, orglocation, modify_by, workhrs, ot_category, min_worktimeshorthr, min_worktimehr, max_worktimehr, early_logouttimemin, lunch_minusinshift, lunch_minusinwork, night_category, night_onwff, night_otcount, night_ot, night_shiftcount) SELECT NOW(), e.shift, e.id, e.shiftname, e.shift_type, e.shortname, e.in_time, e.in_grace, e.max_grace, e.out_time, e.out_grace, e.lunch_time, e.lunch_in, e.lunch_in_grace, e.lunch_out, e.lunch_out_grace, e.adminingrace, e.adminlunchoutgrace, e.update_status, e.user_status, e.indisplaybefore, e.indisplayafter, e.outdisplaybefore, e.outdisplayafter, e.lindisplaybefore, e.lindisplayafter, e.loutdisplaybefore, e.loutdisplayafter, ?, ?, e.workhrs, e.ot_category, h.min_worktimeshorthr, h.min_worktimehr, h.max_worktimehr, h.early_logouttimemin, e.lunch_minusinshift, e.lunch_minusinwork, e.night_category, e.night_onwff, e.night_otcount, e.night_ot, e.night_shiftcount FROM jos_app_empshift e LEFT JOIN jos_app_halfday_config h ON h.shiftid=e.id WHERE e.id=? LIMIT 1');
                $stmt->bind_param('sii', $locationCsv, $modifiedBy, $shiftId);
                shift_run($stmt); $stmt->close();

                if ($posted['lm_policy_action'] === 'configure') {
                    shift_save_lm_policy($con, $posted, $modifiedBy);
                }

                // Preserve the exact progressive interview, independently of the legacy calculation columns.
                shift_save_policy_answers($con, $shiftId, $policyAnswersPayload, $modifiedBy);

                $stmt = shift_stmt($con, 'DELETE FROM jos_app_empshift_draft WHERE userid=? AND mode=? AND shift_id=? AND status=0');
                $stmt->bind_param('isi', $draftUserId, $draftMode, $draftShiftId);
                shift_run($stmt); $stmt->close();

                $con->commit();
                $_SESSION['flash_employee_shifts'] = $id > 0 ? 'Employee Shift updated successfully.' : 'Employee Shift saved successfully.';
                redirect('/masters/employee-shifts.php');
            } catch (Throwable $e) {
                $con->rollback();
                error_log('Progressive Employee Shift save failed: ' . $e->getMessage());
                $errors[] = 'Unable to save Employee Shift. Please try again.';
            }
        }
        $isFormView = true;
        $editId = $id;
    }
}

$shiftTypes = [];
$stmt = shift_stmt($con, 'SELECT id, name FROM jos_app_empshiftgroup WHERE active_status=1 ORDER BY id ASC');
shift_run($stmt); $result = $stmt->get_result();
while ($row = $result->fetch_assoc()) $shiftTypes[] = $row;
$stmt->close();

$payrollLocationOptions = [];
$stmt = shift_stmt($con, 'SELECT id, name FROM jos_app_payrolllocation WHERE status=1 ORDER BY name ASC, id ASC');
shift_run($stmt); $result = $stmt->get_result();
while ($row = $result->fetch_assoc()) $payrollLocationOptions[] = $row;
$stmt->close();

$activeLmPolicies = [];
try {
    $stmt = shift_stmt($con, 'SELECT id, orgid, month, year, policyname, calculate, monthgrace FROM jos_app_lmconfig ORDER BY orgid ASC, year DESC, month DESC, id DESC');
    shift_run($stmt); $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        $orgId = (int)$row['orgid'];
        if (!isset($activeLmPolicies[$orgId])) $activeLmPolicies[$orgId] = $row;
    }
    $stmt->close();
} catch (Throwable $e) {
    $activeLmPolicies = [];
}

if ($editId > 0) {
    try {
        $stmt = shift_stmt($con, 'SELECT e.*, h.min_worktimeshorthr, h.min_worktimehr, h.max_worktimehr, h.early_logouttimemin FROM jos_app_empshift e LEFT JOIN jos_app_halfday_config h ON h.shiftid=e.id WHERE e.id=? LIMIT 1');
        $stmt->bind_param('i', $editId); shift_run($stmt);
        $editItem = $stmt->get_result()->fetch_assoc() ?: null; $stmt->close();
        if (!$editItem && !$errors) {
            $_SESSION['flash_employee_shifts'] = 'Employee Shift record was not found.';
            redirect('/masters/employee-shifts.php');
        }
        if ($editItem && !$posted) {
            try {
                $policyAnswersRow = shift_policy_answers_row($con, $editId);
                $policyAnswersPayload = $policyAnswersRow ? shift_policy_payload((string)$policyAnswersRow['answers_json']) : null;
            } catch (Throwable $e) {
                error_log('Progressive Employee Shift answer snapshot load failed: ' . $e->getMessage());
                $policyAnswersPayload = null;
            }
        }
    } catch (Throwable $e) {
        $errors[] = 'Unable to load Employee Shift.';
    }
}

$shifts = [];
if (!$isFormView) {
    try {
        if ($search !== '') {
            $like = '%' . $search . '%';
            $stmt = shift_stmt($con, 'SELECT e.*, g.name AS group_name, h.min_worktimeshorthr, h.min_worktimehr, h.max_worktimehr, h.early_logouttimemin FROM jos_app_empshift e LEFT JOIN jos_app_empshiftgroup g ON g.id=e.shift LEFT JOIN jos_app_halfday_config h ON h.shiftid=e.id WHERE e.shift_type LIKE ? OR e.shortname LIKE ? ORDER BY e.id ASC');
            $stmt->bind_param('ss', $like, $like);
        } else {
            $stmt = shift_stmt($con, 'SELECT e.*, g.name AS group_name, h.min_worktimeshorthr, h.min_worktimehr, h.max_worktimehr, h.early_logouttimemin FROM jos_app_empshift e LEFT JOIN jos_app_empshiftgroup g ON g.id=e.shift LEFT JOIN jos_app_halfday_config h ON h.shiftid=e.id ORDER BY e.id ASC');
        }
        shift_run($stmt); $result = $stmt->get_result();
        while ($row = $result->fetch_assoc()) {
            $row['locations'] = shift_locations($con, (int)$row['id']);
            $shifts[] = $row;
        }
        $stmt->close();
    } catch (Throwable $e) {
        $errors[] = 'Unable to load Employee Shifts.';
    }
}

$form = $posted ?? ($editItem ?: [
    'id' => 0, 'shift' => 0, 'work_date_policy' => '', 'after_midnight' => '', 'full_day' => 0, 'shift_type' => '', 'shortname' => '', 'in_time' => '', 'in_grace' => '0', 'max_grace' => '', 'out_time' => '', 'max_out' => '',
    'lunch_applicable' => 0, 'lunch_time' => '', 'notapplay_time' => '', 'lunch_minusinshift' => 0, 'lunch_minusinwork' => 0, 'unused_lunch' => 0, 'fixlunch_time' => 0, 'minlt_time' => '', 'lunch_in' => '', 'lunch_flow_complete' => 0, 'lunch_trigger_mode' => 'minimum', 'lunch_cutoff_enabled' => 0,
    'cameraonof' => 0, 'indisplaybefore' => '', 'indisplayafter' => '', 'outdisplaybefore' => '', 'outdisplayafter' => '', 'ot_category' => 0, 'lm_show' => 0, 'lm_track_enabled' => '',
    'lm_policy_action' => 'keep', 'lm_rule_method' => 'count', 'lm_rule_unit' => 'days', 'lm_rule_mode' => 'auto', 'lm_count_every' => '3', 'lm_repeat_occurrences' => '', 'lm_occurrence_total' => '1', 'lm_band_width' => '30', 'lm_deduct_value' => '', 'lm_flow_complete' => 0, 'lm_orgids' => [], 'lm_month' => (int)date('n'), 'lm_year' => (int)date('Y'), 'lm_policyname' => 'Standard Late Mark Policy', 'lm_calculate' => 0, 'lm_monthgrace' => '0', 'lm_confirm_global' => 0, 'lm_rules' => [],
    'night_category' => 0, 'night_shift' => 0, 'night_onwff' => 0, 'night_otcount' => 0, 'night_ot' => 0, 'night_shiftcount' => 0, 'shiftcount_onwff' => 0,
    'minwork_short' => '', 'min_worktime' => '', 'max_worktime' => '', 'early_logouttime' => '', 'locations' => []
]);
if (!$posted && $editItem) {
    $form['in_time'] = shift_input_time($editItem['in_time'] ?? '');
    $form['out_time'] = shift_input_time($editItem['out_time'] ?? '');
    $form['max_out'] = shift_input_time($editItem['max_out'] ?? '');
    $form['lunch_in'] = shift_input_time($editItem['lunch_in'] ?? '');
    foreach (['indisplaybefore', 'indisplayafter', 'outdisplaybefore', 'outdisplayafter'] as $field) $form[$field] = shift_input_time($editItem[$field] ?? '');
    $storedInGrace = (string)($editItem['in_grace'] ?? '');
    $form['in_grace'] = strpos($storedInGrace, '0-') === 0 ? substr($storedInGrace, 2) : $storedInGrace;
    $form['minwork_short'] = (string)($editItem['min_worktimeshorthr'] ?? '');
    $form['min_worktime'] = (string)($editItem['min_worktimehr'] ?? '');
    $form['max_worktime'] = (string)($editItem['max_worktimehr'] ?? '');
    $form['early_logouttime'] = (string)($editItem['early_logouttimemin'] ?? '');
    $form['locations'] = shift_selected_locations($con, (int)$editItem['id']);
    $groupKey = '';
    foreach ($shiftTypes as $type) {
        if ((int)$type['id'] === (int)$form['shift']) {
            $groupKey = shift_group_key((string)$type['name']);
            break;
        }
    }
    $form['work_date_policy'] = $groupKey === 'midnight' ? 'previous' : 'login';
    $form['after_midnight'] = $groupKey === 'multi' ? 1 : ($groupKey === 'day' ? 0 : '');
    $form['full_day'] = 0;
    $form['fixlunch_time'] = (int)($editItem['fixlunch_time'] ?? 0);
    $form['lunch_flow_complete'] = 1;
    $form['lunch_trigger_mode'] = !empty($form['lunch_in']) ? 'fixed' : 'minimum';
    $form['lunch_cutoff_enabled'] = trim((string)($form['notapplay_time'] ?? '')) !== '' && (string)$form['notapplay_time'] !== '0';
}
$form += [
    'lm_policy_action' => 'keep', 'lm_orgids' => [], 'lm_month' => (int)date('n'), 'lm_year' => (int)date('Y'),
    'lm_policyname' => 'Standard Late Mark Policy', 'lm_calculate' => 0, 'lm_monthgrace' => '0',
    'lm_confirm_global' => 0, 'lm_rules' => [], 'lm_rule_method' => 'count', 'lm_rule_unit' => 'days',
    'lm_rule_mode' => 'auto', 'lm_count_every' => '3', 'lm_repeat_occurrences' => '', 'lm_occurrence_total' => '1', 'lm_band_width' => '30', 'lm_deduct_value' => '', 'lm_flow_complete' => 0,
    'fixlunch_time' => 0, 'lunch_flow_complete' => 0, 'lunch_trigger_mode' => 'minimum', 'lunch_cutoff_enabled' => 0, 'lm_track_enabled' => '',
];
if (!$posted && $policyAnswersPayload) {
    $savedFields = (array)($policyAnswersPayload['fields'] ?? []);
    foreach ($savedFields as $name => $savedValue) {
        if (is_array($savedValue) || !array_key_exists($name, $form)) continue;
        $form[$name] = $savedValue;
    }
    if (isset($savedFields['orglocation[]']) && is_array($savedFields['orglocation[]'])) {
        $form['locations'] = array_values(array_unique(array_map('intval', $savedFields['orglocation[]'])));
    }
    if (isset($savedFields['lm_orgid[]']) && is_array($savedFields['lm_orgid[]'])) {
        $form['lm_orgids'] = array_values(array_unique(array_map('intval', $savedFields['lm_orgid[]'])));
    }
    $form['lm_rules'] = array_values((array)($policyAnswersPayload['rules'] ?? []));
}
$formLocations = array_map('intval', (array)($form['locations'] ?? []));
$formLmOrgIds = array_map('intval', (array)($form['lm_orgids'] ?? []));
$formLmRules = array_values((array)($form['lm_rules'] ?? []));
$isEditing = (int)($form['id'] ?? $editId) > 0;
$progressiveGroupIds = ['day' => 0, 'midnight' => 0, 'multi' => 0];
foreach ($shiftTypes as $type) {
    $key = shift_group_key((string)$type['name']);
    if ($key !== '') $progressiveGroupIds[$key] = (int)$type['id'];
}

console_header('Employee Shifts', $pageLink);
?>
<div class="mb-4 flex flex-wrap items-center gap-3">
    <h1 class="text-xl font-bold text-slate-900">Employee Shift Master</h1>
    <span class="hidden border-l border-slate-200 pl-3 text-xs font-medium text-slate-500 lg:inline">Timing, late mark, lunch, OT, terminal and half-day rules</span>
    <?php if ($isFormView): ?><a href="/masters/employee-shifts.php" class="ml-auto inline-flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2 text-xs font-bold text-brand-600 hover:bg-brand-50"><i class="fa-solid fa-arrow-left"></i>Back to List</a><?php endif; ?>
</div>

<?php if ($flash): ?><div class="mb-6 rounded-xl border border-emerald-100 bg-emerald-50 px-5 py-4 text-sm font-semibold text-emerald-700"><?= h((string)$flash) ?></div><?php endif; ?>
<?php if ($errors): ?><div class="mb-6 rounded-xl border border-red-100 bg-red-50 px-5 py-4 text-red-700"><h2 class="font-bold">Please fix the following</h2><ul class="mt-2 list-disc list-inside text-sm"><?php foreach ($errors as $error): ?><li><?= h($error) ?></li><?php endforeach; ?></ul></div><?php endif; ?>

<?php if ($isFormView): ?>
<?php $inputClass = 'mt-1 w-full rounded-xl border border-slate-200 bg-white px-3 py-2.5 text-sm text-slate-800 outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100'; ?>
<style>
.answer-btn{border:1px solid #cbd5e1;border-radius:.75rem;background:#fff;padding:.7rem 1rem;font-size:.875rem;font-weight:700;color:#334155}
.answer-card{display:flex;min-height:84px;flex-direction:column;align-items:flex-start;justify-content:center;border:1px solid #e2e8f0;border-radius:.9rem;background:#fff;padding:1rem;text-align:left;color:#0f172a;transition:.15s ease}
.answer-card span{margin-top:.25rem;font-size:.75rem;font-weight:600;color:#64748b}
.answer-btn:hover,.answer-card:hover,.answer-selected{border-color:#6366f1!important;background:#eef2ff!important;box-shadow:0 0 0 2px #e0e7ff}
.answer-card:disabled{cursor:not-allowed;border-color:#e2e8f0!important;background:#f8fafc!important;color:#94a3b8!important;box-shadow:none!important;opacity:.8}
.answer-card:disabled span{color:#ef4444}
.wizard-status-scroll{max-height:290px;overflow-y:auto;padding-right:.25rem;scrollbar-width:thin;scrollbar-color:#cbd5e1 transparent}
.wizard-step-link{position:relative;border:1px solid transparent;transition:background-color .18s ease,border-color .18s ease,color .18s ease,box-shadow .18s ease}
.wizard-step-link .step-icon{display:flex;height:2rem;width:2rem;flex:none;align-items:center;justify-content:center;border-radius:9999px;font-size:.72rem;font-weight:800;transition:inherit}
.wizard-step-link .step-meta{display:block;margin-top:.12rem;font-size:.65rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}
.wizard-step-link.is-current{border-color:#2563eb;background:#2563eb;color:#fff;box-shadow:0 10px 22px rgba(37,99,235,.22)}
.wizard-step-link.is-current .step-icon{background:#fff;color:#2563eb}
.wizard-step-link.is-current .step-meta{color:#dbeafe}
.wizard-step-link.is-complete{border-color:#bbf7d0;background:#f0fdf4;color:#166534}
.wizard-step-link.is-complete .step-icon{background:#22c55e;color:#fff}
.wizard-step-link.is-complete .step-meta{color:#15803d}
.wizard-step-link.is-available{border-color:#dbeafe;background:#eff6ff;color:#1e40af}
.wizard-step-link.is-available .step-icon{background:#dbeafe;color:#2563eb}
.wizard-step-link.is-available .step-meta{color:#3b82f6}
.wizard-step-link.is-upcoming{background:#f8fafc;color:#94a3b8;cursor:not-allowed}
.wizard-step-link.is-upcoming .step-icon{background:#e2e8f0;color:#94a3b8}
.wizard-step-link.is-upcoming .step-meta{color:#94a3b8}
.lm-rule-row{display:grid;grid-template-columns:minmax(70px,.7fr) minmax(70px,.7fr) minmax(100px,1fr) minmax(75px,.7fr) minmax(110px,1fr) 42px;gap:.55rem;align-items:end;border:1px solid #e2e8f0;border-radius:1rem;background:#f8fafc;padding:.75rem}
.lm-rule-row.is-minute{grid-template-columns:minmax(70px,.7fr) minmax(70px,.7fr) minmax(100px,1fr) minmax(110px,1fr) 42px}
.lm-rule-row label{font-size:.68rem;font-weight:800;color:#64748b;text-transform:uppercase;letter-spacing:.04em}
.lm-rule-row input,.lm-rule-row select{margin-top:.3rem;width:100%;border:1px solid #cbd5e1;border-radius:.65rem;background:#fff;padding:.55rem .65rem;font-size:.8rem;color:#0f172a}
.lm-rule-fixed{margin-top:.3rem;width:100%;border:1px solid #cbd5e1;border-radius:.65rem;background:#eef2ff;padding:.55rem .65rem;font-size:.8rem;color:#312e81;text-transform:none;letter-spacing:0}
@media(max-width:900px){.lm-rule-row{grid-template-columns:repeat(2,minmax(0,1fr))}}
</style>
<form method="post" id="shift-form" novalidate>
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
    <input type="hidden" name="action" value="save">
    <input type="hidden" name="id" value="<?= h((string)($form['id'] ?? $editId)) ?>">
    <input type="hidden" name="policy_answers_json" id="policy-answers-json" value="">
    <input type="hidden" name="shift" id="shift-type" value="<?= h((string)$form['shift']) ?>">
    <input type="hidden" name="work_date_policy" id="work-date-policy" value="<?= h((string)($form['work_date_policy'] ?? '')) ?>">
    <input type="hidden" name="after_midnight" id="after-midnight" value="<?= h((string)($form['after_midnight'] ?? '')) ?>">
    <input type="hidden" name="full_day" id="full-day" value="<?= h((string)($form['full_day'] ?? 0)) ?>">

    <div id="draft-resume-modal" class="fixed inset-0 z-[110] <?= $latestDraft ? 'flex' : 'hidden' ?> items-center justify-center bg-slate-950/60 p-4" role="dialog" aria-modal="true" aria-labelledby="draft-resume-title"><div class="w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl"><div class="text-xs font-extrabold uppercase tracking-wider text-brand-600">Saved work found</div><h2 id="draft-resume-title" class="mt-2 text-2xl font-bold text-slate-900">Continue your shift draft?</h2><p class="mt-2 text-sm leading-6 text-slate-600">This draft was last saved <strong id="draft-resume-time"><?= $latestDraft ? h(date('d M Y, h:i A', strtotime((string)$latestDraft['updated_at']))) : '' ?></strong>. Continue from the same question or discard it and begin again.</p><div class="mt-6 flex flex-wrap justify-end gap-3"><button type="button" id="draft-start-new" class="rounded-xl border border-red-200 bg-white px-5 py-2.5 text-sm font-bold text-red-600">Discard and start new</button><button type="button" id="draft-continue" class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Continue saved draft</button></div></div></div>
    <div id="draft-toast" class="fixed bottom-5 right-5 z-[120] hidden max-w-sm rounded-xl border px-4 py-3 text-sm font-bold shadow-xl" role="status" aria-live="polite"></div>

    <div class="grid gap-6 xl:grid-cols-[340px_minmax(0,1fr)]">
        <aside class="h-fit rounded-2xl border border-slate-200 bg-white p-5 shadow-sm xl:sticky xl:top-5">
            <div class="flex items-start justify-between gap-4">
                <div><div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Smart Shift Setup</div><h2 class="mt-1 text-lg font-bold text-slate-900">Setup status</h2></div>
                <div id="wizard-percent" class="rounded-lg bg-brand-50 px-3 py-2 text-lg font-extrabold text-brand-700">11%</div>
            </div>
            <div class="mt-4 h-2.5 overflow-hidden rounded-full bg-slate-100"><div id="wizard-progress" class="h-full rounded-full bg-brand-600 transition-all" style="width:11%"></div></div>
            <div class="mt-2 flex items-center justify-between"><div id="wizard-count" class="text-sm font-extrabold text-slate-800">Step 1 of 9</div><div class="text-xs font-semibold text-slate-500">Follow the highlighted step</div></div>
            <div id="draft-status" class="mt-3 flex items-center gap-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-bold text-slate-600"><span id="draft-status-dot" class="h-2.5 w-2.5 rounded-full bg-slate-300"></span><span id="draft-status-text">Draft autosave ready</span></div>
            <div class="mt-4 rounded-xl bg-slate-900 px-4 py-3 text-white"><div class="text-[10px] font-bold uppercase tracking-wider text-slate-300">System decision</div><div class="mt-1 flex items-end justify-between gap-3"><div id="sidebar-result" class="text-base font-bold">Pending</div><div id="sidebar-duration" class="text-right text-xs text-slate-300">Enter IN and OUT time</div></div></div>
            <div class="wizard-status-scroll mt-4 space-y-2" id="wizard-steps"><?php foreach (['Timing','Shift Type','Identity & Payroll Location','Late Mark','Lunch','Half Day','Overtime','Camera','Review'] as $index=>$label): ?><button type="button" data-jump="<?= $index ?>" class="wizard-step-link flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-sm font-semibold"><span class="step-icon"><?= $index+1 ?></span><span class="min-w-0"><span class="block truncate"><?= h($label) ?></span><span class="step-meta">Upcoming</span></span></button><?php endforeach; ?></div>
        </aside>

        <div class="min-w-0">
            <section class="shift-step rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="0">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 1</div><h2 class="mt-2 text-2xl font-bold text-slate-900">What are the normal shift timings?</h2><p class="mt-2 text-sm text-slate-500">Duration, date crossing, shift name and short name will be suggested automatically.</p>
                <div class="mt-7 grid gap-5 md:grid-cols-2"><label class="text-sm font-semibold text-slate-700">IN Time *<input type="time" name="in_time" id="in-time" required value="<?= h((string)$form['in_time']) ?>" class="<?= $inputClass ?>"></label><label class="text-sm font-semibold text-slate-700">OUT Time *<input type="time" name="out_time" id="out-time" required value="<?= h((string)$form['out_time']) ?>" class="<?= $inputClass ?>"></label></div>
                <div id="timing-preview" class="mt-6 hidden rounded-xl border border-brand-100 bg-brand-50 p-4 text-sm text-brand-800"></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="1">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 2</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Confirm the shift type</h2><p class="mt-2 text-sm text-slate-500">The next question is selected from the approved Shift Type policy mapper.</p>
                <div id="full-day-question" class="hidden"></div>
                <div id="ownership-question" class="mt-7 hidden"><div class="text-xs font-extrabold uppercase tracking-wider text-amber-600">Midnight condition detected</div><div class="mt-2 font-bold text-slate-900">This shift belongs to which work date?</div><p class="mt-1 text-sm text-slate-500">Shown only when IN is between 12:00 AM and 5:00 AM and OUT is up to 5:00 AM.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" data-workdate="login" class="answer-card"><strong>Login Date</strong><span>Day Shift, then validate after-midnight logout.</span></button><button type="button" data-workdate="previous" class="answer-card" <?= $progressiveGroupIds['midnight'] > 0 ? '' : 'disabled' ?>><strong>Previous Work Date</strong><span><?= $progressiveGroupIds['midnight'] > 0 ? 'Midnight Shift; no further shift-type question.' : 'Midnight Shift type is not active' ?></span></button></div></div>
                <div id="same-day-question" class="mt-7 hidden"><div class="text-xs font-extrabold uppercase tracking-wider text-brand-600">Day shift validation</div><div class="mt-2 font-bold text-slate-900">Can the employee continue working and logout after 12:00 AM?</div><p class="mt-1 text-sm text-slate-500">No keeps Day Shift. Yes converts this to Multi-Day Shift.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" data-after-midnight="1" class="answer-card" <?= $progressiveGroupIds['multi'] > 0 ? '' : 'disabled' ?>><strong>Yes</strong><span><?= $progressiveGroupIds['multi'] > 0 ? 'Multi-Day Shift' : 'Multi-Day Shift type is not active' ?></span></button><button type="button" data-after-midnight="0" class="answer-card" <?= $progressiveGroupIds['day'] > 0 ? '' : 'disabled' ?>><strong>No</strong><span><?= $progressiveGroupIds['day'] > 0 ? 'Day Shift' : 'Day Shift type is not active' ?></span></button></div></div>
                <div id="type-result" class="mt-6 hidden rounded-xl border border-emerald-200 bg-emerald-50 p-5"><div class="text-xs font-extrabold uppercase tracking-wider text-emerald-700">System decision</div><div id="type-result-name" class="mt-1 text-xl font-bold text-emerald-900"></div><div id="type-result-help" class="mt-1 text-sm text-emerald-700"></div></div>
                <div id="type-config-error" class="mt-4 hidden rounded-xl border border-red-200 bg-red-50 p-4 text-sm font-semibold text-red-700"></div>
                <label id="max-out-wrap" class="mt-6 hidden text-sm font-semibold text-slate-700">Final valid OUT cutoff<input type="time" name="max_out" value="<?= h((string)$form['max_out']) ?>" class="<?= $inputClass ?>"><span class="mt-1 block text-xs font-normal text-slate-500">Used by the existing Midnight Shift logic.</span></label>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="2">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 3</div><h2 class="mt-2 text-2xl font-bold text-slate-900">What should this shift be called, and where can it be used?</h2>
                <div class="mt-7 grid gap-5 md:grid-cols-2"><label class="text-sm font-semibold text-slate-700">Shift Name *<input name="shift_type" id="shift-name" required maxlength="255" value="<?= h((string)$form['shift_type']) ?>" class="<?= $inputClass ?>"></label><label class="text-sm font-semibold text-slate-700">Shift Short Name *<input name="shortname" id="short-name" required maxlength="50" value="<?= h((string)$form['shortname']) ?>" class="<?= $inputClass ?>"></label></div>
                <div class="mt-7"><div class="text-sm font-semibold text-slate-700">Payroll Location *</div><p class="mt-1 text-xs text-slate-500">Choose where employees may use this shift.</p><div class="mt-3 grid gap-2 md:grid-cols-2 lg:grid-cols-3"><?php foreach ($payrollLocationOptions as $location): ?><label class="flex items-center gap-3 rounded-xl border border-slate-200 px-4 py-3 text-sm"><input type="checkbox" name="orglocation[]" value="<?= h((string)$location['id']) ?>" <?= in_array((int)$location['id'],$formLocations,true)?'checked':'' ?> class="rounded border-slate-300 text-brand-600"><span><?= h(trim((string)$location['name'])) ?></span></label><?php endforeach; ?></div></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="3">
                <div class="flex flex-wrap items-start justify-between gap-4"><div><div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 4 · Late Mark Brain</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Build the late-arrival policy</h2><p class="mt-2 text-sm text-slate-500">One simple business question at a time. The system converts answers into the existing LM fields and slabs.</p></div><div class="min-w-[150px] rounded-xl bg-brand-50 px-4 py-3"><div id="lm-flow-count" class="text-xs font-bold text-brand-700">Question 1</div><div class="mt-2 h-1.5 overflow-hidden rounded-full bg-brand-100"><div id="lm-flow-progress" class="h-full rounded-full bg-brand-600 transition-all" style="width:10%"></div></div></div></div>
                <input type="hidden" name="lm_track_enabled" id="lm-track-enabled" value="<?= h((string)$form['lm_track_enabled']) ?>"><input type="hidden" name="lm_policy_action" id="lm-policy-action" value="<?= h((string)$form['lm_policy_action']) ?>"><input type="hidden" name="lm_rule_method" id="lm-rule-method" value="<?= h((string)$form['lm_rule_method']) ?>"><input type="hidden" name="lm_rule_unit" id="lm-rule-unit" value="<?= h((string)$form['lm_rule_unit']) ?>"><input type="hidden" name="lm_rule_mode" id="lm-rule-mode" value="<?= h((string)$form['lm_rule_mode']) ?>"><input type="hidden" name="lm_calculate" id="lm-calculate" value="<?= h((string)$form['lm_calculate']) ?>"><input type="hidden" name="lm_flow_complete" id="lm-flow-complete" value="<?= h((string)$form['lm_flow_complete']) ?>">
                <select name="lm_show" id="lm-show" class="hidden"><option value="0" <?= empty($form['lm_show'])?'selected':'' ?>>Yes</option><option value="1" <?= !empty($form['lm_show'])?'selected':'' ?>>No</option></select>
                <div id="lm-answered" class="mt-6 grid gap-2 md:grid-cols-2"></div>

                <div class="lm-stage mt-7" data-lm-stage="track"><div class="text-lg font-bold text-slate-900">Do you want to track employees who report late?</div><p class="mt-1 text-sm text-slate-500">Use this when the company wants attendance to flag arrivals after the allowed shift grace.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lm-answer="track" data-value="yes"><strong>Yes</strong><span>Track late arrivals and build the policy.</span></button><button type="button" class="answer-card" data-lm-answer="track" data-value="no"><strong>No</strong><span>Skip the module and keep late mark disabled for this shift.</span></button></div></div>

                <div class="lm-stage mt-7 hidden" data-lm-stage="grace"><div class="text-lg font-bold text-slate-900">How many minutes after shift start should employees be allowed?</div><p class="mt-1 text-sm text-slate-500">Example: with 5 minutes grace, 9:04 is on time and 9:06 is late.</p><label class="mt-4 block max-w-xs text-sm font-semibold text-slate-700">Grace minutes<input inputmode="numeric" name="in_grace" id="lm-grace" required value="<?= h((string)$form['in_grace']) ?>" class="<?= $inputClass ?>"></label><input type="hidden" name="max_grace" value=""><div id="lm-simulation" class="mt-4 rounded-xl border border-brand-100 bg-brand-50 p-4 text-sm text-brand-900"></div><button type="button" data-lm-next="deduction" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Save grace and continue</button></div>

                <div class="lm-stage mt-7 hidden" data-lm-stage="deduction"><div class="text-lg font-bold text-slate-900">Should late marks result in a deduction?</div><p class="mt-1 text-sm text-slate-500">You can record late arrivals without a deduction, or ask the system to apply deduction rules.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lm-answer="deduction" data-value="keep"><strong>No, Record Only</strong><span>Keep late arrivals for attendance reporting without applying a deduction.</span></button><button type="button" class="answer-card" data-lm-answer="deduction" data-value="configure"><strong>Yes, Apply Deduction</strong><span>Build the required count or late-minute slab rules.</span></button></div></div>

                <div id="lm-policy-builder">
                    <div class="lm-stage mt-7 hidden" data-lm-stage="scope"><div class="text-lg font-bold text-slate-900">Where should this completed late-mark policy apply?</div><p class="mt-1 text-sm text-slate-500">Your cutting rules are ready. Now choose the payroll locations that should receive the same policy.</p><div class="mt-4 grid gap-2 md:grid-cols-2 lg:grid-cols-3"><?php foreach ($payrollLocationOptions as $location): $existingPolicy=$activeLmPolicies[(int)$location['id']]??null; ?><label class="rounded-xl border border-slate-200 bg-white p-3 text-sm"><span class="flex items-center gap-3"><input type="checkbox" name="lm_orgid[]" value="<?= h((string)$location['id']) ?>" data-lm-location data-existing="<?= h($existingPolicy ? ((string)$existingPolicy['policyname'].' · '.(string)$existingPolicy['month'].'/'.(string)$existingPolicy['year']) : '') ?>" <?= in_array((int)$location['id'],$formLmOrgIds,true)?'checked':'' ?> class="rounded border-slate-300 text-brand-600"><strong><?= h((string)$location['name']) ?></strong></span><span class="mt-1 block pl-7 text-xs text-slate-500"><?= $existingPolicy?'Latest: '.h((string)$existingPolicy['policyname']).' ('.h((string)$existingPolicy['month']).'/'.h((string)$existingPolicy['year']).')':'No policy found' ?></span></label><?php endforeach; ?></div><div id="lm-existing-summary" class="mt-3 hidden rounded-xl border border-amber-200 bg-amber-50 p-3 text-sm font-semibold text-amber-800"></div><div class="mt-4 grid gap-4 md:grid-cols-3"><label class="text-sm font-semibold text-slate-700">Policy Name<input name="lm_policyname" value="<?= h((string)$form['lm_policyname']) ?>" class="<?= $inputClass ?>"></label><label class="text-sm font-semibold text-slate-700">Effective Month<select name="lm_month" class="<?= $inputClass ?>"><?php foreach (range(1,12) as $month): ?><option value="<?= $month ?>" <?= (int)$form['lm_month']===$month?'selected':'' ?>><?= h(date('F',mktime(0,0,0,$month,1))) ?></option><?php endforeach; ?></select></label><label class="text-sm font-semibold text-slate-700">Effective Year<input type="number" min="2020" max="2100" name="lm_year" value="<?= h((string)$form['lm_year']) ?>" class="<?= $inputClass ?>"></label></div><button type="button" data-lm-next="review" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Review policy</button></div>

                    <div class="lm-stage mt-7 hidden" data-lm-stage="method"><div class="text-lg font-bold text-slate-900">When should this deduction trigger?</div><p class="mt-1 text-sm text-slate-500">Every rule still uses a late-minute range. This decides whether Count is required for that range.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lm-answer="method" data-value="minute"><strong>On the 1st late occurrence</strong><span>Apply the range deduction immediately; no Count value is required.</span></button><button type="button" class="answer-card" data-lm-answer="method" data-value="count"><strong>On a selected occurrence count</strong><span>Example: 1–30 minutes late for the 3rd time deducts 60 minutes.</span></button></div></div>
                    <div class="lm-stage mt-7 hidden" data-lm-stage="unit"><div class="text-lg font-bold text-slate-900">Deduction should be in which unit?</div><p class="mt-1 text-sm text-slate-500">Your choice controls the single deduction field shown in every slab.</p><div class="mt-4 grid gap-3 md:grid-cols-3"><button type="button" class="answer-card" data-lm-answer="unit" data-value="days"><strong>Days</strong><span>Starts at 0.5 day; use a day fraction.</span></button><button type="button" class="answer-card" data-lm-answer="unit" data-value="hrs"><strong>Hours (in minutes)</strong><span>Starts at 60 minutes and saves directly to legacy Hrs (MM).</span></button><button type="button" class="answer-card" data-lm-answer="unit" data-value="rs"><strong>Amount (Rs)</strong><span>Starts at Rs 100 as a fixed payroll deduction.</span></button></div></div>
                    <div class="lm-stage mt-7 hidden" data-lm-stage="mode"><div class="text-lg font-bold text-slate-900">Should the same rule repeat automatically?</div><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lm-answer="mode" data-value="auto"><strong>Yes, generate automatically</strong><span>The system builds complete ranges using one pattern.</span></button><button type="button" class="answer-card" data-lm-answer="mode" data-value="custom"><strong>No, create custom rules</strong><span>Add the exact rows your company follows.</span></button></div></div>

                    <div class="lm-stage mt-7 hidden" data-lm-stage="rules">
                        <div id="lm-auto-rule-inputs">
                            <div class="text-lg font-bold text-slate-900" id="lm-rule-question">Define the first late-minute range</div>
                            <div id="lm-repeat-question" class="mt-4">
                                <div class="font-bold text-slate-900">Do you want to keep this minute slab for multiple occurrences?</div>
                                <p class="mt-1 text-sm text-slate-500">Choose Yes when the same minute range needs separate deductions for occurrences such as the 4th, 5th and 6th.</p>
                                <div class="mt-3 grid gap-3 md:grid-cols-2">
                                    <button type="button" class="answer-card" data-lm-repeat="no"><strong>No, one occurrence only</strong><span>Create this minute range only for the selected occurrence.</span></button>
                                    <button type="button" class="answer-card" data-lm-repeat="yes"><strong>Yes, multiple occurrences</strong><span>Repeat the same minute range for consecutive occurrence counts.</span></button>
                                </div>
                            </div>
                            <input type="hidden" name="lm_repeat_occurrences" id="lm-repeat-occurrences" value="<?= h((string)$form['lm_repeat_occurrences']) ?>">
                            <div id="lm-range-controls" class="mt-4 hidden grid gap-4 md:grid-cols-3">
                                <label id="lm-band-width-wrap" class="text-sm font-semibold text-slate-700">Current late-minute range<input name="lm_band_width" id="lm-band-width" type="number" min="1" value="<?= h((string)$form['lm_band_width']) ?>" class="<?= $inputClass ?>"><span class="mt-1 block text-xs font-normal text-slate-500">Only this one range is generated now.</span></label>
                                <label id="lm-count-every-wrap" class="text-sm font-semibold text-slate-700">First occurrence count<input name="lm_count_every" id="lm-count-every" type="number" min="1" value="<?= h((string)$form['lm_count_every']) ?>" class="<?= $inputClass ?>"></label>
                                <label id="lm-occurrence-total-wrap" class="text-sm font-semibold text-slate-700">How many occurrences?<input name="lm_occurrence_total" id="lm-occurrence-total" type="number" min="2" value="<?= h((string)$form['lm_occurrence_total']) ?>" class="<?= $inputClass ?>"><span class="mt-1 block text-xs font-normal text-slate-500">Example: first Count 4 and quantity 3 creates Counts 4, 5 and 6.</span></label>
                            </div>
                            <button type="button" id="lm-generate-rules" class="mt-4 hidden rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Generate this range</button>
                        </div>
                        <div id="lm-next-range-question" class="mt-5 hidden rounded-xl border border-amber-200 bg-amber-50 p-4">
                            <div class="font-bold text-slate-900">Do you want to create the next late-minute range?</div>
                            <p id="lm-coverage-status" class="mt-1 text-sm text-slate-600"></p>
                            <div class="mt-3 flex flex-wrap gap-3"><button type="button" id="lm-next-range-yes" class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Yes, create next range</button><button type="button" id="lm-next-range-no" class="rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-bold text-slate-700">No</button></div>
                        </div>
                        <div class="mt-5 rounded-xl border border-brand-100 bg-brand-50 p-4"><div class="font-bold text-brand-950">Slabs open in a separate window</div><p id="lm-rule-summary" class="mt-1 text-sm text-brand-800">Answer the occurrence question to begin.</p><button type="button" id="lm-edit-rules" class="mt-3 hidden rounded-lg border border-brand-200 bg-white px-4 py-2 text-sm font-bold text-brand-700">View or edit generated slabs</button><button type="button" id="lm-open-custom-rules" class="mt-3 hidden rounded-lg bg-brand-600 px-4 py-2 text-sm font-bold text-white">Open slab editor</button></div>
                    </div>

                    <div id="lm-rules-editor-modal" class="fixed inset-0 z-[105] hidden items-center justify-center bg-slate-950/60 p-4" role="dialog" aria-modal="true" aria-labelledby="lm-rules-editor-title"><div class="flex max-h-[90vh] w-full max-w-6xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl"><div class="flex items-start justify-between gap-4 border-b border-slate-200 px-6 py-5"><div><div class="text-xs font-extrabold uppercase tracking-wider text-brand-600">Late-mark cutting policy</div><h3 id="lm-rules-editor-title" class="mt-1 text-xl font-bold text-slate-900">Generated slab editor</h3><p class="mt-1 text-sm text-slate-500">Each row combines late minutes, occurrence Count when selected, and one required deduction value.</p></div><button type="button" id="lm-rules-modal-close" class="flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-slate-600" aria-label="Close slab editor"><i class="fa-solid fa-xmark"></i></button></div><div class="overflow-auto px-6 py-5"><div id="lm-rules-modal-error" class="mb-4 hidden rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm font-bold text-red-700"></div><div class="mb-4 flex justify-end"><button type="button" id="lm-add-rule" class="rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-bold text-slate-700">Add rule</button></div><div id="lm-rule-list" class="space-y-2"></div><div id="lm-rule-empty" class="rounded-xl border border-dashed border-slate-300 bg-slate-50 p-6 text-center text-sm text-slate-500">No rules created yet. Use Add rule to create the first slab.</div></div><div class="flex flex-wrap justify-end gap-3 border-t border-slate-200 bg-slate-50 px-6 py-4"><button type="button" id="lm-rules-modal-close-secondary" class="rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-bold text-slate-700">Close</button><button type="button" id="lm-rules-continue" class="hidden rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Confirm this range</button></div></div></div>

                    <div class="lm-stage mt-7 hidden" data-lm-stage="cycle"><div class="text-lg font-bold text-slate-900">When should the late-mark calculation reset?</div><p class="mt-1 text-sm text-slate-500">Choose one option to continue. These choices map directly to the existing Calculate field.</p><div class="mt-4 grid gap-3 md:grid-cols-3"><button type="button" class="answer-card" data-lm-answer="cycle" data-value="0"><strong>Daily</strong><span>Calculate each work date separately.</span></button><button type="button" class="answer-card" data-lm-answer="cycle" data-value="1"><strong>Weekly-off cycle</strong><span>Reset around the configured weekly-off boundary.</span></button><button type="button" class="answer-card" data-lm-answer="cycle" data-value="2"><strong>Monthly</strong><span>Aggregate for the payroll month.</span></button></div><label class="mt-4 block max-w-sm text-sm font-semibold text-slate-700">Overall policy grace adjustment (minutes)<input inputmode="numeric" name="lm_monthgrace" value="<?= h((string)$form['lm_monthgrace']) ?>" class="<?= $inputClass ?>"><span class="mt-1 block text-xs font-normal text-slate-500">Normally 0; preserves legacy monthgrace.</span></label></div>
                </div>

                <div class="lm-stage mt-7 hidden" data-lm-stage="review"><div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-5"><div class="text-xs font-extrabold uppercase tracking-wider text-emerald-700">System result</div><div id="lm-policy-result" class="mt-2 text-xl font-bold text-emerald-950"></div><div id="lm-policy-result-help" class="mt-2 text-sm text-emerald-800"></div></div><label id="lm-global-confirm-wrap" class="mt-4 hidden items-start gap-3 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm font-semibold text-amber-900"><input type="checkbox" name="lm_confirm_global" value="1" <?= !empty($form['lm_confirm_global'])?'checked':'' ?> class="mt-1 rounded border-amber-300 text-amber-600"><span>I understand this creates or updates the policy for every employee in the selected payroll locations from the effective month.</span></label><div class="mt-4 flex flex-wrap gap-3"><button type="button" id="lm-finish" class="rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-bold text-white">Confirm late-mark setup</button><button type="button" id="lm-view-slabs" class="hidden rounded-xl border border-brand-200 bg-white px-5 py-2.5 text-sm font-bold text-brand-700">View generated slabs</button></div></div>

                <div id="lm-slabs-modal" class="fixed inset-0 z-[100] hidden items-center justify-center bg-slate-950/60 p-4" role="dialog" aria-modal="true" aria-labelledby="lm-slabs-title"><div class="flex max-h-[88vh] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl"><div class="flex items-start justify-between gap-4 border-b border-slate-200 px-6 py-5"><div><div class="text-xs font-extrabold uppercase tracking-wider text-brand-600">Late-mark policy confirmed</div><h3 id="lm-slabs-title" class="mt-1 text-xl font-bold text-slate-900">Generated cutting slabs</h3><p id="lm-slabs-summary" class="mt-1 text-sm text-slate-500"></p></div><button type="button" id="lm-modal-close" class="flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-slate-600" aria-label="Close slab preview"><i class="fa-solid fa-xmark"></i></button></div><div class="overflow-auto px-6 py-5"><div id="lm-slabs-modal-body"></div></div><div class="flex flex-wrap justify-end gap-3 border-t border-slate-200 bg-slate-50 px-6 py-4"><button type="button" id="lm-modal-close-secondary" class="rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-bold text-slate-700">Stay on review</button><button type="button" id="lm-modal-continue" class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Continue to Lunch <i class="fa-solid fa-arrow-right ml-2"></i></button></div></div></div>

                <div id="lm-stage-example" class="mt-5 hidden rounded-xl border border-sky-200 bg-sky-50 p-4 text-sm leading-6 text-sky-950"></div>
                <div class="mt-6 flex items-center justify-between border-t border-slate-100 pt-4"><button type="button" id="lm-flow-back" class="invisible rounded-lg border border-slate-200 px-4 py-2 text-sm font-bold text-slate-600"><i class="fa-solid fa-arrow-left mr-2"></i>Previous late-mark question</button><button type="button" id="lm-edit-answers" class="hidden text-sm font-bold text-brand-600">Edit policy answers</button></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="4">
                <input type="hidden" name="lunch_applicable" id="lunch-toggle" value="<?= h((string)$form['lunch_applicable']) ?>">
                <input type="hidden" name="lunch_minusinshift" id="lunch-minus-shift" value="<?= h((string)$form['lunch_minusinshift']) ?>">
                <input type="hidden" name="lunch_minusinwork" id="lunch-minus-work" value="<?= h((string)$form['lunch_minusinwork']) ?>">
                <input type="hidden" name="unused_lunch" id="unused-lunch" value="<?= h((string)$form['unused_lunch']) ?>">
                <input type="hidden" name="fixlunch_time" id="fix-lunch-time" value="<?= h((string)$form['fixlunch_time']) ?>">
                <input type="hidden" name="lunch_flow_complete" id="lunch-flow-complete" value="<?= h((string)$form['lunch_flow_complete']) ?>">
                <input type="hidden" name="lunch_trigger_mode" id="lunch-trigger-mode" value="<?= h((string)$form['lunch_trigger_mode']) ?>">
                <input type="hidden" name="lunch_cutoff_enabled" id="lunch-cutoff-enabled" value="<?= h((string)$form['lunch_cutoff_enabled']) ?>">
                <div class="flex flex-wrap items-start justify-between gap-4"><div><div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 5 · Lunch</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Set up lunch for this shift</h2><p class="mt-2 text-sm text-slate-500">Answer a few simple questions about your company’s lunch rule.</p></div><div class="min-w-[180px] rounded-xl bg-brand-50 px-4 py-3"><div id="lunch-flow-count" class="text-xs font-bold text-brand-700">Lunch question 1</div><div class="mt-2 h-1.5 overflow-hidden rounded-full bg-brand-100"><div id="lunch-flow-progress" class="h-full rounded-full bg-brand-600" style="width:12%"></div></div></div></div>
                <div id="lunch-answered" class="mt-5 grid gap-2 md:grid-cols-2"></div>

                <div class="lunch-stage mt-7" data-lunch-stage="track"><div class="text-lg font-bold text-slate-900">Do employees get a lunch break in this shift?</div><p class="mt-1 text-sm text-slate-500">Choose No if this shift has no lunch break.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="track" data-value="1"><strong>Yes</strong><span>This shift includes a lunch break.</span></button><button type="button" class="answer-card" data-lunch-answer="track" data-value="0"><strong>No</strong><span>This shift does not include lunch.</span></button></div></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="duration"><div class="text-lg font-bold text-slate-900">How long is the lunch break?</div><p class="mt-1 text-sm text-slate-500">Example: enter 30 for a 30-minute lunch.</p><label class="mt-4 block max-w-sm text-sm font-semibold text-slate-700">Lunch time in minutes<input id="lunch-minutes" inputmode="numeric" name="lunch_time" value="<?= h((string)$form['lunch_time']) ?>" class="<?= $inputClass ?>"></label><button type="button" data-lunch-next="trigger" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Continue</button></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="trigger"><div class="text-lg font-bold text-slate-900">When can an employee take lunch?</div><p class="mt-1 text-sm text-slate-500">Choose the rule your company follows.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="trigger" data-value="minimum"><strong>After working some hours</strong><span>Example: lunch is allowed after 4 hours of work.</span></button><button type="button" class="answer-card" data-lunch-answer="trigger" data-value="fixed"><strong>At a fixed time</strong><span>Example: lunch starts at 1:00 PM.</span></button></div></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="minimum"><div class="text-lg font-bold text-slate-900">How long must the employee work before lunch?</div><p class="mt-1 text-sm text-slate-500">Example: enter 04:00 if lunch is allowed after 4 hours.</p><label class="mt-4 block max-w-sm text-sm font-semibold text-slate-700">Work time before lunch (HH:MM)<input id="min-lunch" name="minlt_time" placeholder="04:00" value="<?= h((string)$form['minlt_time']) ?>" class="<?= $inputClass ?>"></label><button type="button" data-lunch-next="required" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Continue</button></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="fixed"><div class="text-lg font-bold text-slate-900">What time does lunch start?</div><label class="mt-4 block max-w-sm text-sm font-semibold text-slate-700">Lunch starts at<input id="lunch-start" type="time" name="lunch_in" value="<?= h((string)$form['lunch_in']) ?>" class="<?= $inputClass ?>"></label><?php if ($hasFixLunchTime): ?><div class="mt-5 text-lg font-bold text-slate-900">Which break should be treated as lunch?</div><div class="mt-3 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="fixedwindow" data-value="1"><strong>Only the break at the lunch time</strong><span>Other breaks will not be treated as lunch.</span></button><button type="button" class="answer-card" data-lunch-answer="fixedwindow" data-value="0"><strong>Use the employee’s lunch OUT and IN</strong><span>Count the break shown by the employee’s punches.</span></button></div><?php else: ?><button type="button" data-lunch-next="required" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Continue</button><?php endif; ?></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="required"><div class="text-lg font-bold text-slate-900">Is lunch included in the shift hours?</div><p class="mt-1 text-sm text-slate-500">Example: this is a 9-hour shift with a 30-minute lunch.</p><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="required" data-value="0"><strong>Yes, lunch is included</strong><span>The employee works 8 hours 30 minutes and takes 30 minutes for lunch.</span></button><button type="button" class="answer-card" data-lunch-answer="required" data-value="1"><strong>No, lunch is extra</strong><span>The employee must complete all 9 working hours in addition to lunch.</span></button></div></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="calculated">
                    <div class="text-lg font-bold text-slate-900">How should the lunch break be counted?</div>
                    <p class="mt-1 text-sm text-slate-500">See what happens in the same simple example, then choose your company’s rule.</p>
                    <div id="lunch-calculated-sample" class="mt-4 rounded-xl border border-sky-200 bg-sky-50 p-4 text-sm leading-6 text-sky-950"></div>
                    <div class="mt-4 grid gap-4 lg:grid-cols-3">
                        <button type="button" class="answer-card !min-h-[250px] !justify-start" data-lunch-answer="calculated" data-value="0">
                            <strong>Count the allowed lunch as paid time</strong>
                            <span class="!mt-2 !text-slate-600">The allowed lunch time is counted. Only extra break time reduces the employee’s hours.</span>
                            <span id="lunch-pair-formula" class="!mt-4 w-full rounded-lg bg-white/80 px-3 py-2 !font-bold !text-slate-900"></span>
                            <span class="!mt-3 !text-brand-700">Example: for a 40-minute break with 30 minutes allowed, only 10 minutes are deducted.</span>
                        </button>
                        <button type="button" class="answer-card !min-h-[250px] !justify-start" data-lunch-answer="calculated" data-value="1">
                            <strong>Treat the full lunch break as unpaid</strong>
                            <span class="!mt-2 !text-slate-600">All the time between lunch OUT and lunch IN is removed from worked hours.</span>
                            <span id="lunch-raw-formula" class="!mt-4 w-full rounded-lg bg-white/80 px-3 py-2 !font-bold !text-slate-900"></span>
                            <span class="!mt-3 !text-amber-700">Example: if the break is 40 minutes, all 40 minutes are deducted.</span>
                        </button>
                        <button type="button" class="answer-card !min-h-[250px] !justify-start" data-lunch-answer="calculated" data-value="2">
                            <strong>Deduct one more fixed lunch</strong>
                            <span class="!mt-2 !text-slate-600">The actual lunch break is removed first, and the allowed lunch time is deducted once more.</span>
                            <span id="lunch-full-formula" class="!mt-4 w-full rounded-lg bg-white/80 px-3 py-2 !font-bold !text-slate-900"></span>
                            <span class="!mt-3 !text-red-700">Choose this only if your company intentionally makes this extra deduction.</span>
                        </button>
                    </div>
                </div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="unused"><div class="text-lg font-bold text-slate-900">If an employee takes a shorter lunch, what should happen to the unused minutes?</div><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="unused" data-value="0"><strong>Do nothing extra</strong><span>Keep the normal result.</span></button><button type="button" class="answer-card" data-lunch-answer="unused" data-value="1"><strong>Add the unused minutes</strong><span>Count the unused lunch minutes as worked time.</span></button><button type="button" class="answer-card" data-lunch-answer="unused" data-value="2"><strong>Keep the earlier rule</strong><span>Use the same calculation already followed for existing shifts.</span></button><button type="button" class="answer-card" data-lunch-answer="unused" data-value="3"><strong>Deduct the full lunch</strong><span>Deduct the full allowed lunch even when the employee takes less time.</span></button></div></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="cutoff"><div class="text-lg font-bold text-slate-900">Should lunch be ignored when an employee works beyond a certain number of hours?</div><div class="mt-4 grid gap-3 md:grid-cols-2"><button type="button" class="answer-card" data-lunch-answer="cutoff" data-value="0"><strong>No</strong><span>Use the lunch rule whenever the employee qualifies for lunch.</span></button><button type="button" class="answer-card" data-lunch-answer="cutoff" data-value="1"><strong>Yes</strong><span>Set the number of hours after which lunch should not be counted.</span></button></div></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="cutofftime"><div class="text-lg font-bold text-slate-900">After how many worked hours should lunch be ignored?</div><p class="mt-1 text-sm text-slate-500">Example: enter 10:00 to stop applying lunch after 10 worked hours.</p><label class="mt-4 block max-w-sm text-sm font-semibold text-slate-700">Worked hours (HH:MM)<input id="lunch-cutoff-time" name="notapplay_time" placeholder="10:00" value="<?= h((string)$form['notapplay_time']) ?>" class="<?= $inputClass ?>"></label><button type="button" data-lunch-next="review" class="mt-4 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white">Review lunch setup</button></div>
                <div class="lunch-stage mt-7 hidden" data-lunch-stage="review"><div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-5"><div class="text-xs font-extrabold uppercase tracking-wider text-emerald-700">Your lunch setup</div><div id="lunch-result" class="mt-2 text-xl font-bold text-emerald-950"></div><div id="lunch-result-help" class="mt-2 text-sm leading-6 text-emerald-800"></div></div><div class="mt-4 flex gap-3"><button type="button" id="lunch-finish" class="rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-bold text-white">Save lunch and continue</button><button type="button" id="lunch-edit-answers" class="hidden rounded-xl border border-brand-200 bg-white px-5 py-2.5 text-sm font-bold text-brand-700">Edit answers</button></div></div>
                <div class="mt-6 border-t border-slate-100 pt-4"><button type="button" id="lunch-flow-back" class="invisible rounded-lg border border-slate-200 px-4 py-2 text-sm font-bold text-slate-600"><i class="fa-solid fa-arrow-left mr-2"></i>Previous question</button></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="5">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 6</div><h2 class="mt-2 text-2xl font-bold text-slate-900">When should short day or half day apply?</h2><p class="mt-2 text-sm text-slate-500">The same legacy thresholds, collected in a clearer order.</p>
                <div class="mt-7 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><?php foreach (['minwork_short'=>'Minimum work for short day','min_worktime'=>'Minimum work for half day','max_worktime'=>'Maximum work for half day'] as $field=>$label): ?><label class="text-sm font-semibold text-slate-700"><?= h($label) ?><input name="<?= h($field) ?>" placeholder="HH:MM" value="<?= h((string)$form[$field]) ?>" class="<?= $inputClass ?>"></label><?php endforeach; ?><label class="text-sm font-semibold text-slate-700">Allowed early logout (minutes)<input inputmode="numeric" name="early_logouttime" value="<?= h((string)$form['early_logouttime']) ?>" class="<?= $inputClass ?>"></label></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="6">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 7</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Can work beyond this shift become overtime?</h2>
                <label class="mt-6 block max-w-sm text-sm font-semibold text-slate-700">Overtime policy<select name="ot_category" id="ot-category" class="<?= $inputClass ?>"><option value="0" <?= (int)$form['ot_category']===0?'selected':'' ?>>No overtime</option><option value="1" <?= (int)$form['ot_category']===1?'selected':'' ?>>Day OT</option><option value="2" <?= (int)$form['ot_category']===2?'selected':'' ?>>Night OT</option></select></label>
                <div id="night-fields" class="mt-6 rounded-xl bg-slate-50 p-5"><label class="block max-w-sm text-sm font-semibold text-slate-700">Calculate Night On<select name="night_category" class="<?= $inputClass ?>"><option value="0" <?= (int)$form['night_category']===0?'selected':'' ?>>With All Shifts</option><option value="1" <?= (int)$form['night_category']===1?'selected':'' ?>>Only 1st Shift</option><option value="2" <?= (int)$form['night_category']===2?'selected':'' ?>>Only 2nd Shift</option></select></label><div class="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-3"><?php foreach (['night_shift'=>'Consider total hours as Night OT','night_onwff'=>'Night OT on Weekly Off','night_otcount'=>'Calculate Night OT Count','night_ot'=>'Calculate Night OT Hours','night_shiftcount'=>'Calculate Night Shift Count','shiftcount_onwff'=>'Shift Count on Weekly Off'] as $field=>$label): ?><label class="flex items-center gap-3 text-sm font-semibold text-slate-700"><input type="checkbox" name="<?= h($field) ?>" value="1" <?= !empty($form[$field])?'checked':'' ?> class="rounded border-slate-300 text-brand-600"><?= h($label) ?></label><?php endforeach; ?></div></div>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="7">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Question 8</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Should camera punch windows apply?</h2>
                <?php if ($cameraFeatureEnabled): ?><label class="mt-6 block max-w-sm text-sm font-semibold text-slate-700">Enable camera windows?<select name="cameraonof" id="camera-toggle" class="<?= $inputClass ?>"><option value="0" <?= empty($form['cameraonof'])?'selected':'' ?>>No</option><option value="1" <?= !empty($form['cameraonof'])?'selected':'' ?>>Yes</option></select></label><div id="camera-fields" class="mt-6 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><?php foreach (['indisplaybefore'=>'IN Display Before','indisplayafter'=>'IN Display After','outdisplaybefore'=>'OUT Display Before','outdisplayafter'=>'OUT Display After'] as $field=>$label): ?><label class="text-sm font-semibold text-slate-700"><?= h($label) ?><input type="time" name="<?= h($field) ?>" value="<?= h((string)$form[$field]) ?>" class="<?= $inputClass ?>"></label><?php endforeach; ?></div><?php else: ?><input type="hidden" name="cameraonof" value="0"><div class="mt-6 rounded-xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-600">The company-level camera feature is disabled, so no camera questions are required.</div><?php endif; ?>
            </section>

            <section class="shift-step hidden rounded-2xl border border-slate-100 bg-white p-6 shadow-sm md:p-8" data-step="8">
                <div class="text-xs font-extrabold uppercase tracking-[0.18em] text-brand-600">Final review</div><h2 class="mt-2 text-2xl font-bold text-slate-900">Review the policy before saving</h2><p class="mt-2 text-sm text-slate-500">The existing transactional save and employee-shift history log will be used.</p><div id="review-grid" class="mt-7 grid gap-4 md:grid-cols-2"></div><div id="review-warning" class="mt-5 hidden rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm font-semibold text-amber-800"></div>
            </section>

            <div class="mt-5 flex flex-wrap items-center justify-between gap-3"><button type="button" id="wizard-back" class="rounded-xl border border-slate-200 bg-white px-5 py-3 text-sm font-bold text-slate-600 hover:bg-slate-50"><i class="fa-solid fa-arrow-left mr-2"></i>Back</button><div id="step-error" class="hidden text-sm font-semibold text-red-600"></div><div class="ml-auto flex gap-3"><a href="/masters/employee-shifts.php" class="rounded-xl border border-slate-200 bg-white px-5 py-3 text-sm font-bold text-slate-600">Cancel</a><button type="button" id="wizard-next" class="rounded-xl bg-brand-600 px-6 py-3 text-sm font-bold text-white hover:bg-brand-700"><span id="wizard-next-label">Continue</span><i class="fa-solid fa-arrow-right ml-2"></i></button><button type="submit" id="wizard-save" class="hidden rounded-xl bg-emerald-600 px-6 py-3 text-sm font-bold text-white hover:bg-emerald-700"><?= $isEditing?'Update':'Save' ?> Employee Shift</button></div></div>
        </div>
    </div>
</form>
<?php if (false): // Retained reference markup from employee-shifts.php. ?>
<form method="post" class="space-y-6" id="legacy-shift-form-reference" novalidate>
    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="save"><input type="hidden" name="id" value="<?= h((string)($form['id'] ?? $editId)) ?>">
    <?php $inputClass = 'mt-1 w-full rounded-xl border border-slate-200 bg-white px-3 py-2.5 text-sm text-slate-800 outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100'; ?>
    <section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><h2 class="font-bold text-slate-900">Shift Details</h2><div class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-4">
        <label class="text-sm font-semibold text-slate-700">Shift Type *<select name="shift" id="shift-type" required class="<?= $inputClass ?>"><option value="">Select</option><?php foreach ($shiftTypes as $type): ?><option value="<?= h((string)$type['id']) ?>" data-midnight="<?= stripos((string)$type['name'], 'midnight') !== false ? '1' : '0' ?>" <?= (int)$form['shift']===(int)$type['id']?'selected':'' ?>><?= h($type['name']) ?></option><?php endforeach; ?></select></label>
        <label class="text-sm font-semibold text-slate-700">Shift Name *<input name="shift_type" required maxlength="255" value="<?= h((string)$form['shift_type']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Shift Short Name *<input name="shortname" required maxlength="50" value="<?= h((string)$form['shortname']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Total Shift Hours<input id="total-hours" readonly value="<?= h(shift_total_hours((string)$form['in_time'], (string)$form['out_time'])) ?>" class="<?= $inputClass ?> bg-slate-50"></label>
    </div><div class="mt-5"><div class="text-sm font-semibold text-slate-700 mb-2">Payroll Location *</div><div class="grid gap-2 md:grid-cols-2 lg:grid-cols-3"><?php foreach ($payrollLocationOptions as $location): ?><label class="flex items-center gap-3 rounded-xl border border-slate-200 px-4 py-3 text-sm"><input type="checkbox" name="orglocation[]" value="<?= h((string)$location['id']) ?>" <?= in_array((int)$location['id'],$formLocations,true)?'checked':'' ?> class="rounded border-slate-300 text-brand-600"><span><?= h(trim((string)$location['name'])) ?></span></label><?php endforeach; ?></div></div></section>

    <section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><h2 class="font-bold text-slate-900">Attendance Timing</h2><div class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-5">
        <label class="text-sm font-semibold text-slate-700">In Time *<input type="time" name="in_time" id="in-time" required value="<?= h((string)$form['in_time']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">LM Grace (MM) *<input inputmode="numeric" name="in_grace" required value="<?= h((string)$form['in_grace']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Max LM Grace (MM)<input inputmode="numeric" name="max_grace" value="<?= h((string)$form['max_grace']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Out Time *<input type="time" name="out_time" id="out-time" required value="<?= h((string)$form['out_time']) ?>" class="<?= $inputClass ?>"></label>
        <label id="max-out-wrap" class="text-sm font-semibold text-slate-700">Max Out<input type="time" name="max_out" value="<?= h((string)$form['max_out']) ?>" class="<?= $inputClass ?>"></label>
    </div></section>

    <?php if ($cameraFeatureEnabled): ?><section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><div class="flex items-center justify-between gap-4"><h2 class="font-bold text-slate-900">Camera Display Windows</h2><label class="text-sm font-semibold">Enable <select name="cameraonof" id="camera-toggle" class="ml-2 rounded-lg border border-slate-200 px-3 py-2"><option value="0" <?= empty($form['cameraonof'])?'selected':'' ?>>No</option><option value="1" <?= !empty($form['cameraonof'])?'selected':'' ?>>Yes</option></select></label></div><div id="camera-fields" class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><?php foreach (['indisplaybefore'=>'In Display Before','indisplayafter'=>'In Display After','outdisplaybefore'=>'Out Display Before','outdisplayafter'=>'Out Display After'] as $field=>$label): ?><label class="text-sm font-semibold text-slate-700"><?= h($label) ?><input type="time" name="<?= h($field) ?>" value="<?= h((string)$form[$field]) ?>" class="<?= $inputClass ?>"></label><?php endforeach; ?></div></section><?php endif; ?>

    <section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><div class="flex items-center justify-between"><h2 class="font-bold text-slate-900">Lunch Configuration</h2><label class="text-sm font-semibold">Applicable <select name="lunch_applicable" id="lunch-toggle" class="ml-2 rounded-lg border border-slate-200 px-3 py-2"><option value="0" <?= empty($form['lunch_applicable'])?'selected':'' ?>>No</option><option value="1" <?= !empty($form['lunch_applicable'])?'selected':'' ?>>Yes</option></select></label></div><div id="lunch-fields" class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-4">
        <label class="text-sm font-semibold text-slate-700">Lunch Time (MM)<input inputmode="numeric" name="lunch_time" value="<?= h((string)$form['lunch_time']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Don’t Apply After (HH:MM)<input name="notapplay_time" placeholder="HH:MM" value="<?= h((string)$form['notapplay_time']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Minus From Shift Hours<select name="lunch_minusinshift" class="<?= $inputClass ?>"><option value="0" <?= empty($form['lunch_minusinshift'])?'selected':'' ?>>Yes</option><option value="1" <?= !empty($form['lunch_minusinshift'])?'selected':'' ?>>No</option></select></label>
        <label class="text-sm font-semibold text-slate-700">Minus From Working Hours<select name="lunch_minusinwork" class="<?= $inputClass ?>"><option value="0" <?= empty($form['lunch_minusinwork'])?'selected':'' ?>>Yes</option><option value="1" <?= !empty($form['lunch_minusinwork'])?'selected':'' ?>>No</option></select></label>
        <label class="text-sm font-semibold text-slate-700">Unused Lunch Rule<select name="unused_lunch" class="<?= $inputClass ?>"><option value="0" <?= (int)$form['unused_lunch']===0?'selected':'' ?>>No</option><option value="1" <?= (int)$form['unused_lunch']===1?'selected':'' ?>>Add to Total Hours</option><option value="2" <?= (int)$form['unused_lunch']===2?'selected':'' ?>>Minus From In/Out Pair</option></select></label>
        <label class="text-sm font-semibold text-slate-700">Minimum Hours to Apply Lunch<input id="min-lunch" name="minlt_time" placeholder="HH:MM" value="<?= h((string)$form['minlt_time']) ?>" class="<?= $inputClass ?>"></label>
        <label class="text-sm font-semibold text-slate-700">Lunch Start Time<input id="lunch-start" type="time" name="lunch_in" value="<?= h((string)$form['lunch_in']) ?>" class="<?= $inputClass ?>"></label>
    </div></section>

    <section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><h2 class="font-bold text-slate-900">OT and Late Mark</h2><div class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-3"><label class="text-sm font-semibold text-slate-700">OT Category<select name="ot_category" id="ot-category" class="<?= $inputClass ?>"><option value="0" <?= (int)$form['ot_category']===0?'selected':'' ?>>None</option><option value="1" <?= (int)$form['ot_category']===1?'selected':'' ?>>Day OT</option><option value="2" <?= (int)$form['ot_category']===2?'selected':'' ?>>Night OT</option></select></label><label class="text-sm font-semibold text-slate-700">LM Show<select name="lm_show" class="<?= $inputClass ?>"><option value="0" <?= empty($form['lm_show'])?'selected':'' ?>>Yes</option><option value="1" <?= !empty($form['lm_show'])?'selected':'' ?>>No</option></select></label></div>
    <div id="night-fields" class="mt-5 rounded-xl bg-slate-50 p-5"><label class="block max-w-xs text-sm font-semibold text-slate-700">Calculate Night On<select name="night_category" class="<?= $inputClass ?>"><option value="0" <?= (int)$form['night_category']===0?'selected':'' ?>>With All Shifts</option><option value="1" <?= (int)$form['night_category']===1?'selected':'' ?>>Only 1st Shift</option><option value="2" <?= (int)$form['night_category']===2?'selected':'' ?>>Only 2nd Shift</option></select></label><div class="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-3"><?php foreach (['night_shift'=>'Consider total hours as Night OT','night_onwff'=>'Night OT on Weekly Off','night_otcount'=>'Calculate Night OT Count','night_ot'=>'Calculate Night OT Hours','night_shiftcount'=>'Calculate Night Shift Count','shiftcount_onwff'=>'Shift Count on Weekly Off'] as $field=>$label): ?><label class="flex items-center gap-3 text-sm font-semibold text-slate-700"><input type="checkbox" name="<?= h($field) ?>" value="1" <?= !empty($form[$field])?'checked':'' ?> class="rounded border-slate-300 text-brand-600"><?= h($label) ?></label><?php endforeach; ?></div></div></section>

    <section class="rounded-2xl border border-slate-100 bg-white p-6 shadow-sm"><h2 class="font-bold text-slate-900">Shift Configuration</h2><div class="mt-5 grid gap-5 md:grid-cols-2 lg:grid-cols-4"><?php foreach (['minwork_short'=>'Min Work for Short Day','min_worktime'=>'Min Work for Half Day','max_worktime'=>'Max Work for Half Day'] as $field=>$label): ?><label class="text-sm font-semibold text-slate-700"><?= h($label) ?><input name="<?= h($field) ?>" placeholder="HH:MM" value="<?= h((string)$form[$field]) ?>" class="<?= $inputClass ?>"></label><?php endforeach; ?><label class="text-sm font-semibold text-slate-700">Early Logout (Minutes)<input inputmode="numeric" name="early_logouttime" value="<?= h((string)$form['early_logouttime']) ?>" class="<?= $inputClass ?>"></label></div></section>
    <div class="flex justify-end gap-3"><a href="/masters/employee-shifts.php" class="rounded-xl border border-slate-200 bg-white px-5 py-3 text-sm font-bold text-slate-600">Cancel</a><button class="rounded-xl bg-brand-600 px-6 py-3 text-sm font-bold text-white hover:bg-brand-700"><?= $isEditing?'Update':'Save' ?> Employee Shift</button></div>
</form>
<?php endif; ?>
<?php else: ?>
<div class="rounded-2xl border border-slate-100 bg-white shadow-sm overflow-hidden">
    <div class="border-b border-slate-100 p-5 flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
        <div><h2 class="text-lg font-bold text-slate-900">Employee Shift List</h2><p class="mt-1 text-xs font-medium text-slate-500">Search and maintain configured employee shifts.</p></div>
        <?php if (!empty($acl['can_add'])): ?><a href="?mode=add" class="inline-flex items-center justify-center gap-2 rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-bold text-white shadow-sm hover:bg-brand-700"><i class="fa-solid fa-plus"></i>Add</a><?php endif; ?>
    </div>
    <div class="border-b border-slate-100 p-5"><form method="get" class="flex flex-col sm:flex-row gap-3"><input name="q" value="<?= h($search) ?>" placeholder="Search shift name or short name" class="w-full max-w-md rounded-xl border border-slate-200 px-4 py-2.5 text-sm"><button class="rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-bold text-white">Search</button><?php if ($search!==''): ?><a href="/masters/employee-shifts.php" class="rounded-xl border border-slate-200 px-4 py-2.5 text-center text-sm font-bold">Clear</a><?php endif; ?></form></div>
    <div class="overflow-x-auto"><table class="min-w-full text-left text-sm"><thead class="bg-slate-50 text-xs uppercase tracking-wide text-slate-500"><tr><th class="px-5 py-4">Shift</th><th class="px-5 py-4">Timing</th><th class="px-5 py-4">Rules</th><th class="px-5 py-4">Payroll Locations</th><th class="px-5 py-4 text-right">Action</th></tr></thead><tbody class="divide-y divide-slate-100"><?php if (!$shifts): ?><tr><td colspan="5" class="px-5 py-12 text-center text-slate-500">No employee shifts found.</td></tr><?php endif; ?><?php foreach ($shifts as $row): ?><tr class="align-top"><td class="px-5 py-4"><div class="font-bold text-slate-900"><?= h((string)$row['shift_type']) ?></div><div class="mt-1 text-xs text-slate-500"><?= h((string)($row['group_name']??'')) ?> · <?= h((string)$row['shortname']) ?></div><div class="mt-1 text-xs text-brand-600"><?= h((string)($row['shifthrs']??'')) ?></div></td><td class="px-5 py-4 text-slate-700"><div><?= h((string)$row['in_time']) ?> — <?= h((string)$row['out_time']) ?></div><div class="mt-1 text-xs text-slate-500">Grace <?= h((string)$row['in_grace']) ?> min</div></td><td class="px-5 py-4 text-xs text-slate-600"><div>Lunch: <?= !empty($row['lunch_applicable'])?'Yes':'No' ?></div><div>OT: <?= (int)$row['ot_category']===2?'Night OT':((int)$row['ot_category']===1?'Day OT':'None') ?></div><div>Half day: <?= h((string)($row['min_worktimehr']??'—')) ?></div></td><td class="px-5 py-4"><div class="space-y-1"><?php foreach ($row['locations'] as $location): ?><div class="flex items-center gap-2 text-xs"><span class="h-2 w-2 rounded-full <?= (int)$location['status']===1?'bg-emerald-500':'bg-red-400' ?>"></span><?= h((string)$location['name']) ?></div><?php endforeach; ?></div></td><td class="px-5 py-4 text-right"><?php if (!empty($acl['can_edit'])): ?><a href="?edit=<?= h((string)$row['id']) ?>" class="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-slate-200 text-brand-600 hover:bg-brand-50" title="Edit"><i class="fa-solid fa-pen"></i></a><?php endif; ?></td></tr><?php endforeach; ?></tbody></table></div>
</div>
<?php endif; ?>

<script>
(() => {
  const form = document.getElementById('shift-form');
  if (!form) return;
  const byId = id => document.getElementById(id);
  const steps = [...document.querySelectorAll('.shift-step')];
  const links = [...document.querySelectorAll('.wizard-step-link')];
  const groupIds = <?= json_encode($progressiveGroupIds, JSON_UNESCAPED_SLASHES) ?>;
  const initialDraft = <?= json_encode($latestDraft ? ['draftVersion'=>(int)$latestDraft['draft_version'],'updatedAt'=>(string)$latestDraft['updated_at'],'payload'=>json_decode((string)$latestDraft['form_data'], true)] : null, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
  const initialPolicyAnswers = <?= json_encode($policyAnswersPayload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
  let lmRules = <?= json_encode($formLmRules, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
  let current = 0;
  let reached = <?= $isEditing || $errors ? '8' : '0' ?>;
  let derivedKey = '';

  const visible = (id, on) => { const el=byId(id); if (el) el.classList.toggle('hidden', !on); };
  const value = name => form.elements[name]?.value ?? '';
  const setValue = (id, next) => { const el=byId(id); if (el) el.value=String(next); };
  const lmAnswered = new Set(value('lm_flow_complete')==='1' ? ['track','deduction','method','unit','mode','cycle'] : []);
  const minutes = time => { if (!time) return null; const [h,m]=time.split(':').map(Number); return h*60+m; };
  const clock = time => { if (!time) return '—'; const [h,m]=time.split(':').map(Number); const suffix=h>=12?'PM':'AM'; return `${h%12||12}:${String(m).padStart(2,'0')} ${suffix}`; };
  const duration = () => {
    const a=minutes(value('in_time')), b=minutes(value('out_time'));
    if (a===null || b===null) return null;
    let total=b-a;
    if (total<0 || (total===0 && value('full_day')==='1')) total+=1440;
    return total;
  };
  const durationLabel = () => { const total=duration(); return total===null?'—':`${Math.floor(total/60)}h ${total%60}m`; };
  const selectedText = name => { const el=form.elements[name]; return el?.options?.[el.selectedIndex]?.text || '—'; };

  const chooseButtons = () => {
    document.querySelectorAll('[data-after-midnight]').forEach(btn => btn.classList.toggle('answer-selected', btn.dataset.afterMidnight===value('after_midnight')));
    document.querySelectorAll('[data-workdate]').forEach(btn => btn.classList.toggle('answer-selected', btn.dataset.workdate===value('work_date_policy')));
    document.querySelectorAll('[data-full-day]').forEach(btn => btn.classList.toggle('answer-selected', btn.dataset.fullDay===value('full_day')));
    document.querySelectorAll('[data-lm-action]').forEach(btn => btn.classList.toggle('answer-selected', btn.dataset.lmAction===value('lm_policy_action')));
  };

  const lmCoverageLimit = () => {
    const grace=Math.max(0,Number(value('in_grace'))||0), total=duration()||0, maxGrace=Number(value('max_grace'))||0;
    let limit=Math.max(1,total-grace);
    if(maxGrace>grace) limit=Math.min(limit,maxGrace-grace);
    return Math.max(1,limit);
  };

  const readLmRules = () => {
    lmRules=[...document.querySelectorAll('.lm-rule-row')].map(row=>({
      min:row.querySelector('[name="lm_min[]"]')?.value||'', max:row.querySelector('[name="lm_max[]"]')?.value||'',
      calculation:row.querySelector('[name="lm_calculation[]"]')?.value||'0', count:row.querySelector('[name="lm_count[]"]')?.value||'0',
      hrs:row.querySelector('[name="lm_hrs[]"]')?.value??'0', days:row.querySelector('[name="lm_days[]"]')?.value??'0', rs:row.querySelector('[name="lm_rs[]"]')?.value??'0'
    }));
  };

  const renderLmRules = () => {
    const list=byId('lm-rule-list'); if(!list)return;
    const unit=value('lm_rule_unit'), unitMeta=unit==='days'?['Days','0.01']:unit==='hrs'?['Minutes','1']:['Amount (Rs)','1'], isCount=value('lm_rule_method')==='count', calculation=isCount?4:3;
    list.innerHTML=lmRules.map((rule,index)=>`<div class="lm-rule-row ${isCount?'':'is-minute'}" data-lm-rule-row><label>From<input type="number" min="1" name="lm_min[]" value="${rule.min}"></label><label>To<input type="number" min="1" name="lm_max[]" value="${rule.max}"></label><label>Trigger<div class="lm-rule-fixed">${isCount?'Occurrence count':'1st occurrence'}</div><input type="hidden" name="lm_calculation[]" value="${calculation}"></label>${isCount?`<label>Count<input type="number" min="1" name="lm_count[]" value="${Number(rule.count)>0?rule.count:1}"></label>`:`<input type="hidden" name="lm_count[]" value="0">`}<label>${unitMeta[0]}<input type="number" min="${unit==='days'?'0.01':'1'}" step="${unitMeta[1]}" name="lm_${unit}[]" value="${rule[unit]??''}" placeholder="Required"></label>${unit!=='hrs'?`<input type="hidden" name="lm_hrs[]" value="0">`:''}${unit!=='days'?`<input type="hidden" name="lm_days[]" value="0">`:''}${unit!=='rs'?`<input type="hidden" name="lm_rs[]" value="0">`:''}<button type="button" data-delete-lm-rule="${index}" class="mb-0.5 flex h-9 w-9 items-center justify-center rounded-lg bg-red-50 text-red-600" title="Delete rule"><i class="fa-solid fa-trash"></i></button></div>`).join('');
    visible('lm-rule-empty',lmRules.length===0);
    visible('lm-edit-rules',lmRules.length>0);
    if(byId('lm-rule-summary')) byId('lm-rule-summary').textContent=lmRules.length?`${lmRules.length} slab(s) generated. Open the slab window to review or edit them.`:'Generate the policy slabs to review and edit them.';
    document.querySelectorAll('[data-delete-lm-rule]').forEach(btn=>btn.addEventListener('click',()=>{readLmRules();lmRules.splice(Number(btn.dataset.deleteLmRule),1);renderLmRules();}));
    updateLmRangeProgress();
  };

  const lmUniqueRanges = () => [...new Map(lmRules.map(rule=>[`${Number(rule.min)}:${Number(rule.max)}`,{min:Number(rule.min),max:Number(rule.max)}])).values()].sort((a,b)=>a.min-b.min||a.max-b.max);
  const lmCoverageEnd = () => lmUniqueRanges().reduce((end,range)=>range.min===end+1?Math.max(end,range.max):end,0);
  const lmDeductionsComplete = () => {const unit=value('lm_rule_unit');return lmRules.length>0&&lmRules.every(rule=>Number(rule[unit])>0);};
  function updateLmRangeProgress(){
    const method=value('lm_rule_method'), repeat=value('lm_repeat_occurrences'), hasRules=lmRules.length>0, covered=lmCoverageEnd(), limit=lmCoverageLimit(), complete=covered>=limit;
    document.querySelectorAll('[data-lm-repeat]').forEach(btn=>btn.classList.toggle('answer-selected',btn.dataset.lmRepeat===repeat));
    visible('lm-repeat-question',method==='count'&&!hasRules);
    visible('lm-range-controls',!hasRules&&(method!=='count'||['yes','no'].includes(repeat)));
    visible('lm-occurrence-total-wrap',method==='count'&&repeat==='yes'&&!hasRules);
    visible('lm-count-every-wrap',method==='count'&&!hasRules);
    visible('lm-generate-rules',!hasRules&&(method!=='count'||['yes','no'].includes(repeat)));
    visible('lm-next-range-question',hasRules&&!complete);
    if(byId('lm-coverage-status'))byId('lm-coverage-status').textContent=`Confirmed through minute ${covered}. This shift requires coverage through minute ${limit}.`;
    if(byId('lm-rule-summary'))byId('lm-rule-summary').textContent=hasRules?`${lmRules.length} slab(s) created; minute coverage ${covered} of ${limit}.`:(method==='count'&&!repeat?'Answer the occurrence question to begin.':'Generate the first minute range.');
  }

  const lmModalError = message => {const box=byId('lm-rules-modal-error');if(box){box.textContent=message;box.classList.toggle('hidden',!message);}};
  const openLmRulesEditor = () => {const modal=byId('lm-rules-editor-modal');if(modal){lmModalError('');modal.classList.remove('hidden');modal.classList.add('flex');}};
  const closeLmRulesEditor = () => {const modal=byId('lm-rules-editor-modal');if(modal){readLmRules();modal.classList.add('hidden');modal.classList.remove('flex');renderLmRules();}};

  const closeLmSlabsModal = () => {const modal=byId('lm-slabs-modal');if(modal){modal.classList.add('hidden');modal.classList.remove('flex');}};
  const openLmSlabsModal = () => {
    readLmRules();
    const modal=byId('lm-slabs-modal'), body=byId('lm-slabs-modal-body'), unit=value('lm_rule_unit');
    if(!modal||!body)return;
    const unitLabel=unit==='days'?'Days':unit==='hrs'?'Hours (entered in minutes)':'Amount (Rs)';
    if(byId('lm-slabs-summary')) byId('lm-slabs-summary').textContent=`${lmRules.length} slab(s) · ${unitLabel} · ${value('lm_calculate')==='0'?'Daily':value('lm_calculate')==='1'?'Weekly-off cycle':'Monthly'} reset`;
    body.innerHTML=lmRules.length?`<div class="overflow-hidden rounded-xl border border-slate-200"><table class="w-full min-w-[620px] text-left text-sm"><thead class="bg-slate-100 text-xs uppercase tracking-wider text-slate-600"><tr><th class="px-4 py-3">Late minutes</th><th class="px-4 py-3">Rule</th><th class="px-4 py-3">Deduction</th></tr></thead><tbody class="divide-y divide-slate-100">${lmRules.map(rule=>{const deduction=unit==='days'?`${Number(rule.days||0)} day(s)`:unit==='hrs'?`${Number(rule.hrs||0)} minute(s)`:`Rs ${Number(rule.rs||0).toFixed(2)}`;const trigger=String(rule.calculation)==='4'?`Every ${Number(rule.count||0)} late mark(s)`:'Late-minute range';return `<tr><td class="px-4 py-3 font-bold text-slate-900">${Number(rule.min)}–${Number(rule.max)}</td><td class="px-4 py-3 text-slate-600">${trigger}</td><td class="px-4 py-3 font-bold text-brand-700">${deduction}</td></tr>`;}).join('')}</tbody></table></div>`:'<div class="rounded-xl border border-slate-200 bg-slate-50 p-6 text-center text-slate-600">No cutting slabs were generated because this policy records late marks only.</div>';
    modal.classList.remove('hidden');modal.classList.add('flex');
  };

  const updateLmPolicyUi = () => {
    visible('lm-policy-builder',value('lm_policy_action')==='configure');
    chooseButtons();
    const grace=Math.max(0,Number(value('in_grace'))||0), start=minutes(value('in_time'));
    if(byId('lm-simulation')){
      if(start===null) byId('lm-simulation').textContent='Enter the shift time and grace period to preview the cutoff.';
      else {const cutoff=(start+grace)%1440, cutoffTime=`${String(Math.floor(cutoff/60)).padStart(2,'0')}:${String(cutoff%60).padStart(2,'0')}`;byId('lm-simulation').innerHTML=`Shift starts at <strong>${clock(value('in_time'))}</strong>. Through <strong>${clock(cutoffTime)}</strong> is on time; cutting slabs start from minute 1 after grace.`;}
    }
    const method=byId('lm-rule-method')?.value||'count';
    visible('lm-auto-rule-inputs',value('lm_rule_mode')!=='custom');
    byId('lm-add-rule')?.classList.toggle('hidden',value('lm_rule_mode')!=='custom');
    visible('lm-open-custom-rules',value('lm_rule_mode')==='custom');
    byId('lm-rules-continue')?.classList.toggle('hidden',!document.querySelector('.lm-rule-row'));
    if(byId('lm-rule-question')) byId('lm-rule-question').textContent=lmRules.length?'Complete the late-minute ranges':method==='count'?'Define the first late-minute range and occurrence':'Define the first late-minute range';
    const replacing=[...document.querySelectorAll('[data-lm-location]:checked')].filter(el=>el.dataset.existing).map(el=>el.closest('label')?.querySelector('strong')?.textContent||'');
    const summary=byId('lm-existing-summary');
    if(summary){summary.textContent=replacing.length?`An existing policy was found for: ${replacing.join(', ')}. The same effective month will be updated; a new month creates a new policy version.`:'';summary.classList.toggle('hidden',replacing.length===0);}
    document.querySelectorAll('[data-lm-answer]').forEach(btn=>{
      const key=btn.dataset.lmAnswer;
      const selected=key==='track'?byId('lm-track-enabled')?.value:key==='deduction'?value('lm_policy_action'):key==='method'?value('lm_rule_method'):key==='unit'?value('lm_rule_unit'):key==='mode'?value('lm_rule_mode'):key==='cycle'?value('lm_calculate'):'';
      btn.classList.toggle('answer-selected',lmAnswered.has(key) && btn.dataset.value===selected);
    });
    updateLmRangeProgress();
  };

  const generateLmRules = () => {
    const method=byId('lm-rule-method').value, unit=byId('lm-rule-unit').value, limit=lmCoverageLimit();
    if(method==='count'&&!['yes','no'].includes(value('lm_repeat_occurrences')))return error('Choose whether this minute slab should be kept for multiple occurrences.');
    const width=Math.max(1,Number(byId('lm-band-width').value)||30), start=lmCoverageEnd()+1, end=Math.min(limit,start+width-1), firstCount=method==='count'?Math.max(1,Number(byId('lm-count-every').value)||1):0;
    const quantity=method==='count'&&value('lm_repeat_occurrences')==='yes'?Math.max(2,Number(byId('lm-occurrence-total').value)||0):1;
    if(method==='count'&&value('lm_repeat_occurrences')==='yes'&&Number(byId('lm-occurrence-total').value)<2)return error('Enter at least 2 occurrences.');
    for(let offset=0;offset<quantity;offset++)lmRules.push({min:start,max:end,calculation:method==='count'?4:3,count:method==='count'?firstCount+offset:0,hrs:unit==='hrs'?'':0,days:unit==='days'?'':0,rs:unit==='rs'?'':0});
    renderLmRules();
    updateLmPolicyUi();
    openLmRulesEditor();
  };

  let lmFlowCurrent=value('lm_flow_complete')==='1'?'review':'track';
  let lmFlowHistory=[];
  const lmFlowOrder=['track','grace','deduction','unit','method','mode','rules','cycle','scope','review'];
  const lmTimeAfter = extra => {const start=minutes(value('in_time'));if(start===null)return 'the calculated time';const point=(start+extra+1440)%1440;return clock(`${String(Math.floor(point/60)).padStart(2,'0')}:${String(point%60).padStart(2,'0')}`);};
  const lmSelectedLocations = () => [...document.querySelectorAll('[data-lm-location]:checked')].map(el=>el.closest('label')?.querySelector('strong')?.textContent||'').filter(Boolean);
  const lmExample = stage => {
    const grace=Math.max(0,Number(value('in_grace'))||0), start=clock(value('in_time')), lateAt=lmTimeAfter(grace+1);
    if(stage==='track') return `<strong>Example for this ${start} shift:</strong> choose Yes if an employee arriving after the allowed grace should receive a late mark. Choose No to skip all late-mark policy questions.`;
    if(stage==='grace') return `<strong>Example for this ${start} shift:</strong> with ${grace} minutes grace, arrival through ${lmTimeAfter(grace)} is on time and ${lateAt} is the first late minute.`;
    if(stage==='deduction') return `<strong>Example:</strong> record the ${lateAt} arrival as late without deduction, or apply a rule such as 3 late marks = 0.5 day deduction.`;
    if(stage==='scope') return `<strong>Impact example:</strong> selecting Pune and Mumbai creates the same policy in both locations. Employees still use the grace of their own assigned shift.`;
    if(stage==='method') return `<strong>Example:</strong> an employee is 1–30 minutes late for the 3rd time in the selected cycle, so the slab deducts 60 minutes. Choose 1st occurrence when no repeat count is required.`;
    if(stage==='unit') return `<strong>Preset:</strong> Days starts at 0.5, Hours starts at 60 minutes, and Amount starts at Rs 100.`;
    if(stage==='mode') return `<strong>Automatic:</strong> the system covers all ${lmCoverageLimit()} possible minutes after grace. <strong>Custom:</strong> you control every range and deduction.`;
    if(stage==='rules'){const covered=lmCoverageEnd();return lmRules.length?`<strong>Coverage:</strong> minute 1 through ${covered} is created. Fill every deduction, confirm this range, and then decide whether to create the next range up to minute ${lmCoverageLimit()}.`:`<strong>For this ${durationLabel()} shift:</strong> build one minute range at a time until minute ${lmCoverageLimit()} after grace is covered.`;}
    if(stage==='cycle') return `<strong>Example:</strong> Daily evaluates each date, Weekly-off cycle resets at the configured weekly-off boundary, and Monthly aggregates the payroll month.`;
    if(stage==='review') return byId('lm-track-enabled')?.value==='no'?`<strong>Final effect:</strong> late-mark tracking is disabled for this shift and no cutting-policy rows will be changed.`:value('lm_policy_action')==='configure'?`<strong>Final effect:</strong> ${grace}-minute shift grace + ${lmRules.length} generated slab(s) for ${lmSelectedLocations().join(', ')||'the selected locations'}.`:`<strong>Final effect:</strong> save ${grace}-minute shift grace and leave all shared payroll-location cutting policies unchanged.`;
    return '';
  };

  const renderLmAnswered = () => {
    const items=[];
    if(lmFlowCurrent!=='track' || value('lm_flow_complete')==='1') items.push(['Track Late Mark',byId('lm-track-enabled')?.value==='no'?'No':'Yes']);
    if(byId('lm-track-enabled')?.value!=='no'){
      if(lmFlowOrder.indexOf(lmFlowCurrent)>1 || value('lm_flow_complete')==='1') items.push(['Grace',`${value('in_grace')||0} minutes`]);
      if(lmFlowOrder.indexOf(lmFlowCurrent)>2 || value('lm_flow_complete')==='1') items.push(['Deduction',value('lm_policy_action')==='configure'?'Apply Deduction':'Record Only']);
      if(value('lm_policy_action')==='configure' && (lmFlowOrder.indexOf(lmFlowCurrent)>5 || value('lm_flow_complete')==='1')) items.push(['Trigger',`${value('lm_rule_method')==='count'?'Selected occurrence count':'1st late occurrence'} · ${value('lm_rule_unit')==='days'?'Days':value('lm_rule_unit')==='hrs'?'Minutes':'Amount (Rs)'}`]);
      if(value('lm_policy_action')==='configure' && (['scope','review'].includes(lmFlowCurrent) || value('lm_flow_complete')==='1')) items.push(['Reset',value('lm_calculate')==='0'?'Daily':value('lm_calculate')==='1'?'Weekly-off cycle':'Monthly']);
    }
    byId('lm-answered').innerHTML=items.map(([label,result])=>`<div class="rounded-xl border border-brand-100 bg-brand-50 px-3 py-2"><div class="text-[10px] font-extrabold uppercase tracking-wider text-brand-600">${label}</div><div class="mt-0.5 text-sm font-bold text-slate-800">${result}</div></div>`).join('');
  };

  const showLmStage = (stage,remember=true) => {
    if(remember && stage!==lmFlowCurrent) lmFlowHistory.push(lmFlowCurrent);
    lmFlowCurrent=stage;
    document.querySelectorAll('.lm-stage').forEach(el=>el.classList.toggle('hidden',el.dataset.lmStage!==stage));
    const configure=value('lm_policy_action')==='configure';
    visible('lm-policy-builder',configure);
    const tracking=byId('lm-track-enabled')?.value!=='no';
    const relevant=!tracking?['track','review']:(configure?lmFlowOrder:['track','grace','deduction','review']);
    const position=Math.max(0,relevant.indexOf(stage)), percent=stage==='review'?100:Math.round(((position+1)/relevant.length)*100);
    byId('lm-flow-count').textContent=stage==='review'?'Policy ready':`Late-mark question ${position+1} of ${relevant.length-1}`;
    byId('lm-flow-progress').style.width=`${percent}%`;
    byId('lm-flow-back').classList.toggle('invisible',lmFlowHistory.length===0 || value('lm_flow_complete')==='1');
    visible('lm-edit-answers',value('lm_flow_complete')==='1');
    const example=lmExample(stage);byId('lm-stage-example').innerHTML=example;visible('lm-stage-example',!!example);
    if(stage==='review'){
      const configured=configure, method=value('lm_rule_method')==='count'?'occurrence-count':'1st-occurrence';
      const disabled=byId('lm-track-enabled')?.value==='no';
      byId('lm-policy-result').textContent=disabled?'Late Mark Disabled':configured?`${value('in_grace')} minute grace with ${method} deduction`:`${value('in_grace')} minute grace; record late marks only`;
      byId('lm-policy-result-help').textContent=disabled?'All remaining late-mark questions were skipped. Existing shared cutting policies are not changed by this form.':configured?`${lmRules.length} compatible slabs will be saved for ${lmSelectedLocations().length} payroll location(s).`:'No new deduction rules will be created.';
      byId('lm-global-confirm-wrap').classList.toggle('hidden',!configured);byId('lm-global-confirm-wrap').classList.toggle('flex',configured);
      visible('lm-view-slabs',value('lm_flow_complete')==='1' && configured && lmRules.length>0);
    }
    renderLmAnswered();updateLmPolicyUi();
  };

  const lunchFlowOrder=['track','duration','trigger','minimum','fixed','required','calculated','unused','cutoff','cutofftime','review'];
  let lunchFlowCurrent=value('lunch_flow_complete')==='1'?'review':'track';
  let lunchFlowHistory=[];
  const lunchLabel = () => {
    if(value('lunch_applicable')!=='1')return 'No lunch for this shift';
    const trigger=value('lunch_trigger_mode')==='fixed'?`starting at ${clock(value('lunch_in'))}`:`after ${value('minlt_time')||'—'} of work`;
    const work={0:'allowed lunch is paid',1:'full break is unpaid',2:'one extra fixed lunch is deducted'}[value('lunch_minusinwork')]||'allowed lunch is paid';
    return `${value('lunch_time')||0} minutes · ${trigger} · ${work}`;
  };
  const lunchMinutesLabel = total => `${Math.floor(Math.max(0,total)/60)}h ${String(Math.max(0,total)%60).padStart(2,'0')}m`;
  const lunchClockAt = total => clock(`${String(Math.floor(((total%1440)+1440)%1440/60)).padStart(2,'0')}:${String(((total%1440)+1440)%1440%60).padStart(2,'0')}`);
  const renderLunchCalculatedExample = () => {
    const total=Math.max(1,duration()||540), allowed=Math.max(1,Number(value('lunch_time'))||30), sampleExtra=10;
    const breakMinutes=Math.min(Math.max(allowed+sampleExtra,allowed),Math.max(allowed,total-60));
    const available=Math.max(0,total-breakMinutes), firstWork=Math.min(available,240,Math.max(0,Math.floor(available/2))), secondWork=Math.max(0,available-firstWork);
    const start=minutes(value('in_time'))??540, firstOut=start+firstWork, secondIn=firstOut+breakMinutes, finalOut=start+total;
    const raw=firstWork+secondWork, pairAware=raw+Math.min(allowed,breakMinutes), full=Math.max(0,raw-allowed), required=value('lunch_minusinshift')==='0'?Math.max(0,total-allowed):total;
    if(byId('lunch-calculated-sample'))byId('lunch-calculated-sample').innerHTML=`<strong>Example:</strong> Employee works from ${lunchClockAt(start)} to ${lunchClockAt(firstOut)}, takes lunch from ${lunchClockAt(firstOut)} to ${lunchClockAt(secondIn)}, and then works until ${lunchClockAt(finalOut)}.<br>The lunch break is <strong>${lunchMinutesLabel(breakMinutes)}</strong>, while the allowed lunch is <strong>${lunchMinutesLabel(allowed)}</strong>.`;
    if(byId('lunch-pair-formula'))byId('lunch-pair-formula').textContent=`Final counted hours: ${lunchMinutesLabel(pairAware)}. Only ${lunchMinutesLabel(Math.max(0,breakMinutes-allowed))} extra break is deducted.`;
    if(byId('lunch-raw-formula'))byId('lunch-raw-formula').textContent=`Final counted hours: ${lunchMinutesLabel(raw)}. The full ${lunchMinutesLabel(breakMinutes)} break is deducted.`;
    if(byId('lunch-full-formula'))byId('lunch-full-formula').textContent=`Final counted hours: ${lunchMinutesLabel(full)}. The break and one more ${lunchMinutesLabel(allowed)} are deducted.`;
  };
  const renderLunchAnswered = () => {
    const items=[];
    if(lunchFlowCurrent!=='track'||value('lunch_flow_complete')==='1')items.push(['Lunch',value('lunch_applicable')==='1'?'Yes':'No']);
    if(value('lunch_applicable')==='1'){
      if(lunchFlowOrder.indexOf(lunchFlowCurrent)>1||value('lunch_flow_complete')==='1')items.push(['Lunch time',`${value('lunch_time')||0} minutes`]);
      if(lunchFlowOrder.indexOf(lunchFlowCurrent)>4||value('lunch_flow_complete')==='1')items.push(['Lunch starts',value('lunch_trigger_mode')==='fixed'?clock(value('lunch_in')):`After ${value('minlt_time')||'—'} of work`]);
      if(lunchFlowOrder.indexOf(lunchFlowCurrent)>7||value('lunch_flow_complete')==='1')items.push(['How lunch is counted',{0:'Allowed lunch is paid',1:'Full break is unpaid',2:'Extra fixed lunch is deducted'}[value('lunch_minusinwork')]||'Allowed lunch is paid']);
    }
    byId('lunch-answered').innerHTML=items.map(([label,result])=>`<div class="rounded-xl border border-brand-100 bg-brand-50 px-3 py-2"><div class="text-[10px] font-extrabold uppercase tracking-wider text-brand-600">${label}</div><div class="mt-0.5 text-sm font-bold text-slate-800">${result}</div></div>`).join('');
  };
  const showLunchStage = (stage,remember=true) => {
    if(remember&&stage!==lunchFlowCurrent)lunchFlowHistory.push(lunchFlowCurrent);
    lunchFlowCurrent=stage;
    document.querySelectorAll('.lunch-stage').forEach(el=>el.classList.toggle('hidden',el.dataset.lunchStage!==stage));
    const relevant=value('lunch_applicable')==='1'?lunchFlowOrder:['track','review'];
    const position=Math.max(0,relevant.indexOf(stage));
    byId('lunch-flow-count').textContent=stage==='review'?'Lunch setup ready':`Lunch question ${position+1}`;
    byId('lunch-flow-progress').style.width=`${stage==='review'?100:Math.max(10,Math.round(((position+1)/relevant.length)*100))}%`;
    byId('lunch-flow-back').classList.toggle('invisible',lunchFlowHistory.length===0||value('lunch_flow_complete')==='1');
    document.querySelectorAll('[data-lunch-answer]').forEach(btn=>{
      const key=btn.dataset.lunchAnswer,selected=key==='track'?value('lunch_applicable'):key==='trigger'?value('lunch_trigger_mode'):key==='fixedwindow'?value('fixlunch_time'):key==='required'?value('lunch_minusinshift'):key==='calculated'?value('lunch_minusinwork'):key==='unused'?value('unused_lunch'):key==='cutoff'?value('lunch_cutoff_enabled'):'';
      btn.classList.toggle('answer-selected',String(btn.dataset.value)===String(selected));
    });
    if(stage==='review'){
      byId('lunch-result').textContent=lunchLabel();
      byId('lunch-result-help').textContent=value('lunch_applicable')==='1'?`${value('lunch_minusinshift')==='0'?'Lunch is included in the shift hours.':'Lunch is extra and does not reduce the required work hours.'} ${value('lunch_cutoff_enabled')==='1'?`Lunch will not be counted after ${value('notapplay_time')} of work.`:'There is no maximum-hours limit for lunch.'}`:'This shift will be saved without lunch.';
      byId('lunch-edit-answers').classList.toggle('hidden',value('lunch_flow_complete')!=='1');
    }
    if(stage==='calculated')renderLunchCalculatedExample();
    renderLunchAnswered();
  };

  let draftVersion=Number(initialDraft?.draftVersion||0), draftTimer=null, draftSaving=false, draftSavePromise=null, draftDirty=false, draftReady=!initialDraft, draftConflict=false, toastTimer=null;
  const setDraftStatus = (text,state='idle') => {
    const colors={idle:'bg-slate-300',saving:'bg-blue-500 animate-pulse',saved:'bg-emerald-500',warning:'bg-amber-500',error:'bg-red-500'};
    if(byId('draft-status-text'))byId('draft-status-text').textContent=text;
    if(byId('draft-status-dot'))byId('draft-status-dot').className=`h-2.5 w-2.5 rounded-full ${colors[state]||colors.idle}`;
  };
  const draftToast = (message,state='saved',linger=2800) => {
    const toast=byId('draft-toast');if(!toast)return;
    const styles={saved:'border-emerald-200 bg-emerald-50 text-emerald-800',saving:'border-blue-200 bg-blue-50 text-blue-800',warning:'border-amber-200 bg-amber-50 text-amber-900',error:'border-red-200 bg-red-50 text-red-800'};
    toast.className=`fixed bottom-5 right-5 z-[120] max-w-sm rounded-xl border px-4 py-3 text-sm font-bold shadow-xl ${styles[state]||styles.saved}`;toast.textContent=message;
    clearTimeout(toastTimer);if(linger>0)toastTimer=setTimeout(()=>toast.classList.add('hidden'),linger);
  };
  const collectDraftPayload = () => {
    readLmRules();
    const fields={};
    [...form.elements].forEach(el=>{
      if(!el.name||['_csrf','action','draft_action','payload','draft_version','policy_answers_json'].includes(el.name)||/^lm_(?:min|max|calculation|count|hrs|days|rs)\[\]$/.test(el.name))return;
      if(el.name.endsWith('[]')){if(!Array.isArray(fields[el.name]))fields[el.name]=[];if((el.type==='checkbox'||el.type==='radio')?el.checked:true)fields[el.name].push(el.value);return;}
      if(el.type==='checkbox'||el.type==='radio'){fields[el.name]=el.checked?el.value:'0';return;}
      fields[el.name]=el.value;
    });
    const modules=['timing','shift_type','identity','late_mark','lunch','half_day','overtime','camera','review'];
    return {schemaVersion:4,fields,rules:lmRules,ui:{current,reached,currentModule:modules[current]||'review',lmFlowCurrent,lmFlowHistory:[...lmFlowHistory],lmAnswered:[...lmAnswered],lunchFlowCurrent,lunchFlowHistory:[...lunchFlowHistory],derivedKey}};
  };
  const applyDraftPayload = payload => {
    if(!payload||!payload.fields||!payload.ui)return false;
    const fields=payload.fields;
    if(Number(payload.schemaVersion||1)<2 && fields.lm_rule_unit==='hrs'){
      if(Number(fields.lm_deduct_value)>0)fields.lm_deduct_value=String(Number(fields.lm_deduct_value)*60);
      if(Array.isArray(payload.rules))payload.rules=payload.rules.map(rule=>({...rule,hrs:Number(rule.hrs||0)*60}));
    }
    [...form.elements].forEach(el=>{
      if(!el.name||!Object.prototype.hasOwnProperty.call(fields,el.name))return;
      const saved=fields[el.name];
      if(el.name.endsWith('[]')){if(el.type==='checkbox'||el.type==='radio')el.checked=Array.isArray(saved)&&saved.map(String).includes(String(el.value));return;}
      if(el.type==='checkbox'||el.type==='radio'){el.checked=String(saved)===String(el.value)&&String(saved)!=='0';return;}
      el.value=String(saved??'');
    });
    lmRules=Array.isArray(payload.rules)?payload.rules:[];
    current=Math.max(0,Math.min(8,Number(payload.ui.current)||0));reached=Math.max(current,Math.min(8,Number(payload.ui.reached)||current));
    lmFlowCurrent=lmFlowOrder.includes(payload.ui.lmFlowCurrent)?payload.ui.lmFlowCurrent:'track';lmFlowHistory=Array.isArray(payload.ui.lmFlowHistory)?payload.ui.lmFlowHistory.filter(item=>lmFlowOrder.includes(item)):[];
    lunchFlowCurrent=lunchFlowOrder.includes(payload.ui.lunchFlowCurrent)?payload.ui.lunchFlowCurrent:(value('lunch_flow_complete')==='1'?'review':'track');lunchFlowHistory=Array.isArray(payload.ui.lunchFlowHistory)?payload.ui.lunchFlowHistory.filter(item=>lunchFlowOrder.includes(item)):[];
    lmAnswered.clear();(Array.isArray(payload.ui.lmAnswered)?payload.ui.lmAnswered:[]).forEach(item=>lmAnswered.add(item));derivedKey=String(payload.ui.derivedKey||'');
    renderLmRules();derive(false);conditionalFields();showLmStage(lmFlowCurrent,false);showLunchStage(lunchFlowCurrent,false);render();return true;
  };
  const saveDraft = async (keepalive=false) => {
    if(!draftReady||draftConflict)return false;
    clearTimeout(draftTimer);
    if(draftSaving){draftDirty=true;return draftSavePromise||false;}
    draftSaving=true;draftDirty=false;setDraftStatus('Saving draft…','saving');
    const data=new FormData();data.set('draft_action','save');data.set('_csrf',form.elements['_csrf'].value);data.set('draft_version',String(draftVersion));data.set('payload',JSON.stringify(collectDraftPayload()));
    draftSavePromise=(async()=>{
      try{
        const response=await fetch(window.location.href,{method:'POST',body:data,credentials:'same-origin',keepalive});const result=await response.json();
        if(response.status===409||result.conflict){draftConflict=true;setDraftStatus('Newer draft found — reload required','error');draftToast('This draft was changed in another tab. Reload before continuing.','error',0);return false;}
        if(!response.ok||!result.ok)throw new Error(result.message||'Draft save failed');
        draftVersion=Number(result.draft_version||draftVersion);const savedAt=result.saved_at?new Date(result.saved_at):new Date();setDraftStatus(`Saved ${savedAt.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}`,'saved');draftToast('Draft saved','saved');return true;
      }catch(error){setDraftStatus(navigator.onLine?'Draft save failed — retrying':'Offline — changes waiting to save',navigator.onLine?'error':'warning');draftToast(navigator.onLine?'Draft save failed. We will retry.':'You are offline. Changes are waiting to save.',navigator.onLine?'error':'warning');draftDirty=true;return false;}
    })();
    const saved=await draftSavePromise;
    draftSaving=false;draftSavePromise=null;
    if(draftDirty&&!draftConflict&&!keepalive){await new Promise(resolve=>setTimeout(resolve,saved?0:1500));return saveDraft();}
    return saved;
  };
  const scheduleDraftSave = (delay=900) => {if(!draftReady||draftConflict)return;draftDirty=true;setDraftStatus('Unsaved changes','warning');clearTimeout(draftTimer);draftTimer=setTimeout(()=>saveDraft(),delay);};
  const flushDraft = async () => {if(!draftReady||draftConflict)return false;draftDirty=true;return saveDraft();};
  const deleteDraft = async () => {const data=new FormData();data.set('draft_action','delete');data.set('_csrf',form.elements['_csrf'].value);const response=await fetch(window.location.href,{method:'POST',body:data,credentials:'same-origin'});if(!response.ok)throw new Error('Unable to discard draft');draftVersion=0;};

  const derive = (suggestNames=false) => {
    const inTime=value('in_time'), outTime=value('out_time'), a=minutes(inTime), b=minutes(outTime);
    const ready=a!==null && b!==null;
    visible('timing-preview', ready);
    if (ready) byId('timing-preview').innerHTML=`<strong>${clock(inTime)} to ${clock(outTime)}</strong><span class="mx-2">·</span>${durationLabel()}${b<a?' · crosses midnight':''}`;
    byId('sidebar-duration').textContent=ready?`${clock(inTime)} – ${clock(outTime)} · ${durationLabel()}`:'Enter IN and OUT time';

    const equal=ready && a===b;
    const midnightCondition=ready && a>=0 && a<=300 && b<=300;
    const dayCondition=ready && b>a && !midnightCondition;
    const multiDayCondition=ready && b<a && !midnightCondition;
    visible('full-day-question', false);
    visible('ownership-question', midnightCondition);
    const needsOtQuestion=dayCondition || (midnightCondition && value('work_date_policy')==='login');
    visible('same-day-question', needsOtQuestion);
    derivedKey='';
    if (midnightCondition && value('work_date_policy')==='previous') derivedKey='midnight';
    else if (needsOtQuestion && value('after_midnight')==='0') derivedKey='day';
    else if (needsOtQuestion && value('after_midnight')==='1') derivedKey='multi';
    else if (multiDayCondition) derivedKey='multi';

    const names={day:'Day Shift',midnight:'Midnight Shift',multi:'Multi-Day Shift'};
    const resolvedGroupId=derivedKey ? Number(groupIds[derivedKey]||0) : 0;
    setValue('shift-type', resolvedGroupId);
    visible('type-result', !!derivedKey);
    visible('type-config-error', !!derivedKey && resolvedGroupId<=0);
    visible('max-out-wrap', derivedKey==='midnight');
    if (derivedKey) {
      byId('type-result-name').textContent=names[derivedKey];
      byId('type-result-help').textContent=derivedKey==='day'?'Attendance belongs to the employee login date.':derivedKey==='midnight'?'This early-morning shift belongs to the previous work date.':'The shift continues across dates.';
      if(resolvedGroupId<=0) byId('type-config-error').textContent=`${names[derivedKey]} is not active in Shift Types. Choose an available answer or activate that Shift Type before continuing.`;
      byId('sidebar-result').textContent=names[derivedKey];
      if (suggestNames && !byId('shift-name').value) byId('shift-name').value=`${names[derivedKey]} ${clock(inTime)}`;
      if (suggestNames && !byId('short-name').value) byId('short-name').value=`${clock(inTime)} - ${clock(outTime)}`;
    } else byId('sidebar-result').textContent='Pending';
    chooseButtons();
  };

  const conditionalFields = () => {
    visible('night-fields', value('ot_category')==='2');
    visible('camera-fields', value('cameraonof')==='1');
    updateLmPolicyUi();
  };

  const error = message => { const el=byId('step-error'); el.textContent=message; el.classList.toggle('hidden', !message); };
  const validHHMM = text => text==='' || /^\d{1,3}:[0-5]\d$/.test(text);
  const validate = step => {
    error('');
    if (step===0 && (!value('in_time') || !value('out_time'))) return error('Enter both IN and OUT time.'), false;
    if (step===1) {
      const a=minutes(value('in_time')),b=minutes(value('out_time'));
      const midnightCondition=a!==null&&b!==null&&a>=0&&a<=300&&b<=300;
      if (a===b && !midnightCondition) return error('IN and OUT cannot be the same. Change one of the times.'), false;
      if (!derivedKey && midnightCondition && !['login','previous'].includes(value('work_date_policy'))) return error('Choose Login Date or Previous Work Date for this early-morning shift.'), false;
      if (!derivedKey) return error('Answer whether the employee can continue working and logout after 12:00 AM.'), false;
      if (Number(value('shift'))<=0) return error(`${derivedKey==='day'?'Day Shift':derivedKey==='midnight'?'Midnight Shift':'Multi-Day Shift'} is not active in Shift Types. Choose an available answer.`), false;
    }
    if (step===2 && (!value('shift_type').trim() || !value('shortname').trim())) return error('Enter the shift name and short name.'), false;
    if (step===2 && !form.querySelector('input[name="orglocation[]"]:checked')) return error('Select at least one payroll location.'), false;
    if (step===3 && !/^\d+$/.test(value('in_grace'))) return error('Late Mark Grace must contain minutes only.'), false;
    if (step===3 && value('max_grace') && !/^\d+$/.test(value('max_grace'))) return error('Maximum Late Mark Grace must contain minutes only.'), false;
    if (step===3 && value('max_grace') && Number(value('max_grace'))<=Number(value('in_grace'))) return error('Maximum late threshold must be greater than the normal grace period.'), false;
    if (step===3 && value('lm_flow_complete')!=='1') return error('Complete and confirm the late-mark questions before continuing.'), false;
    if (step===3 && value('lm_policy_action')==='configure') {
      if(!form.querySelector('[data-lm-location]:checked')) return error('Select at least one payroll location.'),false;
      if(!value('lm_policyname').trim()) return error('Enter a late-mark policy name.'),false;
      if(!document.querySelector('.lm-rule-row')) return error('Generate at least one compatible cutting slab.'),false;
      readLmRules();
      if(!lmDeductionsComplete()) return error('Complete every slab deduction before continuing.'),false;
      if(lmCoverageEnd()<lmCoverageLimit()) return error(`Complete late-minute ranges through minute ${lmCoverageLimit()} before continuing.`),false;
      if(!form.querySelector('[name="lm_confirm_global"]:checked')) return error('Confirm the payroll-location impact before continuing.'),false;
    }
    if (step===4 && value('lunch_flow_complete')!=='1') return error('Complete and confirm the lunch-policy questions before continuing.'), false;
    if (step===4 && value('lunch_applicable')==='1' && (!/^\d+$/.test(value('lunch_time')) || Number(value('lunch_time'))<1)) return error('Enter a positive number of allowed lunch minutes.'), false;
    if (step===4 && value('lunch_applicable')==='1' && value('lunch_trigger_mode')==='minimum' && (!value('minlt_time') || !validHHMM(value('minlt_time')))) return error('Minimum worked duration must use HH:MM.'), false;
    if (step===4 && value('lunch_applicable')==='1' && value('lunch_trigger_mode')==='fixed' && !value('lunch_in')) return error('Enter the fixed lunch start time.'), false;
    if (step===4 && value('lunch_cutoff_enabled')==='1' && (!value('notapplay_time') || !validHHMM(value('notapplay_time')))) return error('Maximum worked duration must use HH:MM.'), false;
    if (step===5 && !['minwork_short','min_worktime','max_worktime'].every(name=>validHHMM(value(name)))) return error('Half-day thresholds must use HH:MM.'), false;
    return true;
  };

  const reviewCard = (label, content) => `<div class="rounded-xl border border-slate-200 p-4"><div class="text-xs font-extrabold uppercase tracking-wider text-slate-500">${label}</div><div class="mt-1 font-bold text-slate-900">${content||'—'}</div></div>`;
  const buildReview = () => {
    const locations=[...form.querySelectorAll('input[name="orglocation[]"]:checked')].map(el=>el.parentElement.innerText.trim()).join(', ');
    const lunch=lunchLabel();
    const half=[value('minwork_short'),value('min_worktime'),value('max_worktime')].filter(Boolean).join(' / ')||'Not configured';
    const lmPolicy=value('lm_policy_action')==='configure'?`Configure ${document.querySelectorAll('[data-lm-location]:checked').length} location(s) · ${document.querySelectorAll('.lm-rule-row').length} slab(s)`:'Keep active location policies';
    byId('review-grid').innerHTML=[reviewCard('Shift',`${value('shift_type')} · ${byId('sidebar-result').textContent}`),reviewCard('Timing',`${clock(value('in_time'))} – ${clock(value('out_time'))} · ${durationLabel()}`),reviewCard('Work-date ownership',derivedKey==='day'?'Same date':(derivedKey==='midnight'?'Shift-start / previous work date':'Cross-date policy')),reviewCard('Payroll Locations',locations),reviewCard('Late mark',`${value('in_grace')} minute grace · ${lmPolicy}`),reviewCard('Lunch',lunch),reviewCard('Half day',half),reviewCard('Overtime',selectedText('ot_category')),reviewCard('Camera',value('cameraonof')==='1'?'Enabled':'Disabled')].join('');
    const warnings=[];
    if (!value('minwork_short') && !value('min_worktime') && !value('max_worktime')) warnings.push('Half-day thresholds are blank.');
    if (value('lunch_applicable')==='1' && !value('lunch_time')) warnings.push('Lunch is enabled with zero/blank lunch minutes.');
    const box=byId('review-warning'); box.textContent=warnings.join(' '); box.classList.toggle('hidden', warnings.length===0);
  };

  const render = () => {
    steps.forEach((step,index)=>step.classList.toggle('hidden', index!==current));
    links.forEach((link,index)=>{
      const icon=link.querySelector('.step-icon');
      const meta=link.querySelector('.step-meta');
      link.disabled=index>reached;
      link.classList.remove('is-current','is-complete','is-available','is-upcoming');
      link.removeAttribute('aria-current');
      if(index===current){
        link.classList.add('is-current'); icon.textContent=String(index+1); meta.textContent='Current step'; link.setAttribute('aria-current','step');
      }else if(index<current){
        link.classList.add('is-complete'); icon.innerHTML='<i class="fa-solid fa-check" aria-hidden="true"></i>'; meta.textContent='Completed';
      }else if(index<=reached){
        link.classList.add('is-available'); icon.textContent=String(index+1); meta.textContent='Available';
      }else{
        link.classList.add('is-upcoming'); icon.innerHTML='<i class="fa-solid fa-lock" aria-hidden="true"></i>'; meta.textContent='Locked';
      }
    });
    byId('wizard-back').classList.toggle('invisible',current===0);
    const interviewPending=(current===3 && value('lm_flow_complete')!=='1') || (current===4 && value('lunch_flow_complete')!=='1');
    byId('wizard-next').classList.toggle('hidden',current===steps.length-1 || interviewPending);
    byId('wizard-next-label').textContent=current===3?'Continue to Lunch':current===4?'Continue to Half Day':'Continue';
    byId('wizard-save').classList.toggle('hidden',current!==steps.length-1);
    const percent=Math.round(((current+1)/steps.length)*100);
    byId('wizard-progress').style.width=`${percent}%`;
    byId('wizard-percent').textContent=`${percent}%`;
    byId('wizard-count').textContent=`Step ${current+1} of ${steps.length}`;
    if (current===3) showLmStage(lmFlowCurrent,false);
    if (current===4) showLunchStage(lunchFlowCurrent,false);
    if (current===8) buildReview();
    error(''); window.scrollTo({top:0,behavior:'smooth'});
  };

  byId('wizard-next').addEventListener('click',async()=>{derive(true); if(!validate(current)) return; reached=Math.max(reached,current+1); current=Math.min(steps.length-1,current+1); render();await flushDraft();});
  byId('wizard-back').addEventListener('click',async()=>{current=Math.max(0,current-1); render();await flushDraft();});
  links.forEach((link,index)=>link.addEventListener('click',async()=>{if(index<=reached){current=index; render();await flushDraft();}}));
  document.querySelectorAll('[data-after-midnight]').forEach(btn=>btn.addEventListener('click',()=>{if(btn.disabled)return;setValue('after-midnight',btn.dataset.afterMidnight);derive(true);}));
  document.querySelectorAll('[data-workdate]').forEach(btn=>btn.addEventListener('click',()=>{if(btn.disabled)return;setValue('work-date-policy',btn.dataset.workdate);setValue('after-midnight','');derive(true);}));
  document.querySelectorAll('[data-full-day]').forEach(btn=>btn.addEventListener('click',()=>{setValue('full-day',btn.dataset.fullDay); if(btn.dataset.fullDay==='0'){setValue('work-date-policy',''); error('Change the IN or OUT time to continue.');} derive(true);}));
  document.querySelectorAll('[data-lm-answer]').forEach(btn=>btn.addEventListener('click',()=>{
    setValue('lm-flow-complete',0);error('');
    const key=btn.dataset.lmAnswer,next=btn.dataset.value;
    lmAnswered.add(key);
    if(key==='track'){
      setValue('lm-track-enabled',next);
      if(next==='no'){
        setValue('lm-show',1);byId('lm-grace').value='0';setValue('lm-policy-action','keep');lmRules=[];renderLmRules();showLmStage('review');
      }else{
        setValue('lm-show',0);showLmStage('grace');
      }
    }
    else if(key==='deduction'){setValue('lm-policy-action',next);if(next==='keep'){lmRules=[];renderLmRules();showLmStage('review');}else showLmStage('unit');}
    else if(key==='unit'){setValue('lm-rule-unit',next);if(byId('lm-deduct-value'))byId('lm-deduct-value').value=next==='rs'?'100':next==='hrs'?'60':'0.5';showLmStage('method');}
    else if(key==='method'){setValue('lm-rule-method',next);setValue('lm-repeat-occurrences',next==='minute'?'no':'');lmRules=[];renderLmRules();showLmStage('mode');}
    else if(key==='mode'){setValue('lm-rule-mode',next);if(next==='custom'){lmRules=[];renderLmRules();}showLmStage('rules');if(next==='custom')openLmRulesEditor();}
    else if(key==='cycle'){setValue('lm-calculate',next);showLmStage('scope');}
    updateLmPolicyUi();
  }));
  document.querySelectorAll('[data-lm-next]').forEach(btn=>btn.addEventListener('click',()=>{
    error('');const target=btn.dataset.lmNext;
    if(target==='deduction' && !/^\d+$/.test(value('in_grace'))) return error('Enter grace minutes using numbers only.');
    if(target==='review' && (!form.querySelector('[data-lm-location]:checked') || !value('lm_policyname').trim())) return error('Select a payroll location and enter a policy name.');
    showLmStage(target);
  }));
  document.querySelectorAll('[data-lm-location]').forEach(el=>el.addEventListener('change',updateLmPolicyUi));
  ['lm-grace'].forEach(id=>byId(id)?.addEventListener('input',()=>{setValue('lm-flow-complete',0);updateLmPolicyUi();if(byId('lm-stage-example'))byId('lm-stage-example').innerHTML=lmExample(lmFlowCurrent);}));
  document.querySelectorAll('[data-lm-repeat]').forEach(btn=>btn.addEventListener('click',()=>{setValue('lm-repeat-occurrences',btn.dataset.lmRepeat);if(btn.dataset.lmRepeat==='no')setValue('lm-occurrence-total',1);else if(Number(value('lm_occurrence_total'))<2)setValue('lm-occurrence-total',2);lmRules=[];renderLmRules();error('');updateLmPolicyUi();}));
  byId('lm-generate-rules')?.addEventListener('click',generateLmRules);
  byId('lm-next-range-yes')?.addEventListener('click',()=>{error('');generateLmRules();});
  byId('lm-next-range-no')?.addEventListener('click',()=>error(`The cutting slabs currently cover minute 1 through ${lmCoverageEnd()}. Complete the shift coverage through minute ${lmCoverageLimit()} before continuing.`));
  byId('lm-edit-rules')?.addEventListener('click',openLmRulesEditor);
  byId('lm-open-custom-rules')?.addEventListener('click',openLmRulesEditor);
  byId('lm-rules-modal-close')?.addEventListener('click',closeLmRulesEditor);
  byId('lm-rules-modal-close-secondary')?.addEventListener('click',closeLmRulesEditor);
  byId('lm-add-rule')?.addEventListener('click',()=>{readLmRules();const start=lmCoverageEnd()+1,unit=value('lm_rule_unit'),isCount=value('lm_rule_method')==='count';lmRules.push({min:start,max:Math.max(start,lmCoverageLimit()),calculation:isCount?4:3,count:isCount?Math.max(1,Number(byId('lm-count-every')?.value)||3):0,hrs:unit==='hrs'?'':0,days:unit==='days'?'':0,rs:unit==='rs'?'':0});renderLmRules();updateLmPolicyUi();});
  byId('lm-rules-continue')?.addEventListener('click',()=>{
    readLmRules();
    if(!lmRules.length)return lmModalError('Add at least one cutting rule.');
    if(!lmDeductionsComplete())return lmModalError(`Enter a positive ${value('lm_rule_unit')==='rs'?'Rs amount':value('lm_rule_unit')==='hrs'?'minute deduction':'day deduction'} in every generated slab before confirming.`);
    const invalid=lmRules.some(rule=>Number(rule.min)<1||Number(rule.max)<Number(rule.min)||(value('lm_rule_method')==='count'&&Number(rule.count)<1));
    if(invalid)return lmModalError('Correct the minute range and occurrence Count values before confirming.');
    closeLmRulesEditor();
    if(lmCoverageEnd()<lmCoverageLimit()){updateLmRangeProgress();if(byId('lm-stage-example'))byId('lm-stage-example').innerHTML=lmExample('rules');return;}
    showLmStage('cycle');
  });
  byId('lm-flow-back')?.addEventListener('click',()=>{const previous=lmFlowHistory.pop();if(previous)showLmStage(previous,false);});
  byId('lm-edit-answers')?.addEventListener('click',()=>{
    setValue('lm-flow-complete',0);
    const tracking=byId('lm-track-enabled')?.value!=='no', configure=value('lm_policy_action')==='configure';
    const flow=!tracking?['track']:(configure?lmFlowOrder.slice(0,-1):['track','grace','deduction']);
    const target=flow[flow.length-1];lmFlowHistory=flow.slice(0,-1);showLmStage(target,false);render();
  });
  byId('lm-finish')?.addEventListener('click',()=>{if(value('lm_policy_action')==='configure'&&!form.querySelector('[name="lm_confirm_global"]:checked'))return error('Confirm the payroll-location impact first.');setValue('lm-flow-complete',1);error('');showLmStage('review',false);byId('lm-finish').textContent='Late-mark setup confirmed';render();openLmSlabsModal();});
  byId('lm-view-slabs')?.addEventListener('click',openLmSlabsModal);
  byId('lm-modal-close')?.addEventListener('click',closeLmSlabsModal);
  byId('lm-modal-close-secondary')?.addEventListener('click',closeLmSlabsModal);
  byId('lm-modal-continue')?.addEventListener('click',()=>{closeLmSlabsModal();byId('wizard-next')?.click();});
  document.querySelectorAll('[data-lunch-answer]').forEach(btn=>btn.addEventListener('click',()=>{
    setValue('lunch-flow-complete',0);error('');
    const key=btn.dataset.lunchAnswer,next=btn.dataset.value;
    if(key==='track'){
      setValue('lunch-toggle',next);
      if(next==='0'){
        ['lunch-minutes','min-lunch','lunch-start','lunch-cutoff-time'].forEach(id=>{if(byId(id))byId(id).value='';});
        setValue('lunch-minus-shift',0);setValue('lunch-minus-work',0);setValue('unused-lunch',0);setValue('fix-lunch-time',0);setValue('lunch-cutoff-enabled',0);showLunchStage('review');
      }else showLunchStage('duration');
    }else if(key==='trigger'){
      setValue('lunch-trigger-mode',next);
      if(next==='minimum'){if(byId('lunch-start'))byId('lunch-start').value='';setValue('fix-lunch-time',0);showLunchStage('minimum');}
      else {if(byId('min-lunch'))byId('min-lunch').value='';showLunchStage('fixed');}
    }else if(key==='fixedwindow'){
      if(!value('lunch_in'))return error('Enter the fixed lunch start time first.');
      setValue('fix-lunch-time',next);showLunchStage('required');
    }else if(key==='required'){setValue('lunch-minus-shift',next);showLunchStage('calculated');}
    else if(key==='calculated'){
      setValue('lunch-minus-work',next);
      if(next==='0')showLunchStage('unused');else{setValue('unused-lunch',0);showLunchStage('cutoff');}
    }else if(key==='unused'){setValue('unused-lunch',next);showLunchStage('cutoff');}
    else if(key==='cutoff'){
      setValue('lunch-cutoff-enabled',next);
      if(next==='1')showLunchStage('cutofftime');else{if(byId('lunch-cutoff-time'))byId('lunch-cutoff-time').value='';showLunchStage('review');}
    }
  }));
  document.querySelectorAll('[data-lunch-next]').forEach(btn=>btn.addEventListener('click',()=>{
    error('');const target=btn.dataset.lunchNext;
    if(target==='trigger'&&(!/^\d+$/.test(value('lunch_time'))||Number(value('lunch_time'))<1))return error('Enter a positive number of allowed lunch minutes.');
    if(target==='required'&&lunchFlowCurrent==='minimum'&&(!value('minlt_time')||!validHHMM(value('minlt_time'))))return error('Enter minimum worked duration as HH:MM.');
    if(target==='required'&&lunchFlowCurrent==='fixed'&&!value('lunch_in'))return error('Enter the fixed lunch start time.');
    if(target==='review'&&(!value('notapplay_time')||!validHHMM(value('notapplay_time'))))return error('Enter maximum worked duration as HH:MM.');
    showLunchStage(target);
  }));
  byId('lunch-flow-back')?.addEventListener('click',()=>{const previous=lunchFlowHistory.pop();if(previous)showLunchStage(previous,false);});
  byId('lunch-edit-answers')?.addEventListener('click',()=>{setValue('lunch-flow-complete',0);lunchFlowHistory=[];showLunchStage('track',false);render();});
  byId('lunch-finish')?.addEventListener('click',()=>{setValue('lunch-flow-complete',1);if(!validate(4)){setValue('lunch-flow-complete',0);return;}showLunchStage('review',false);byId('lunch-finish').textContent='Lunch setup confirmed';render();draftToast('Lunch saved. Moving to Half Day…','saved',2500);byId('wizard-next')?.click();});
  ['in-time','out-time'].forEach(id=>byId(id)?.addEventListener('change',()=>{setValue('full-day',0);setValue('after-midnight','');setValue('work-date-policy','');reached=Math.min(reached,1);derive(true);}));
  ['lunch-toggle','ot-category','camera-toggle'].forEach(id=>byId(id)?.addEventListener('change',conditionalFields));
  byId('min-lunch')?.addEventListener('input',e=>{if(e.target.value&&byId('lunch-start'))byId('lunch-start').value='';});
  byId('lunch-start')?.addEventListener('input',e=>{if(e.target.value&&byId('min-lunch'))byId('min-lunch').value='';});
  form.addEventListener('input',scheduleDraftSave);
  form.addEventListener('change',scheduleDraftSave);
  form.addEventListener('click',event=>{const button=event.target.closest('button');if(button&&!['draft-continue','draft-start-new','lm-modal-close','lm-modal-close-secondary','lm-view-slabs'].includes(button.id))setTimeout(()=>scheduleDraftSave(0),0);});
  window.addEventListener('online',()=>{if(draftDirty)scheduleDraftSave();});
  window.addEventListener('pagehide',()=>{if(draftReady&&draftDirty&&!draftConflict)saveDraft(true);});
  byId('draft-continue')?.addEventListener('click',()=>{if(applyDraftPayload(initialDraft?.payload)){byId('draft-resume-modal').classList.add('hidden');byId('draft-resume-modal').classList.remove('flex');draftReady=true;setDraftStatus('Draft restored','saved');draftToast('Draft restored. Continue from where you stopped.','saved',3800);}});
  byId('draft-start-new')?.addEventListener('click',async()=>{try{await deleteDraft();byId('draft-resume-modal').classList.add('hidden');byId('draft-resume-modal').classList.remove('flex');draftReady=true;setDraftStatus('New draft ready','idle');draftToast('Previous draft discarded. Starting a new shift.','warning',3500);scheduleDraftSave();}catch(error){draftToast('Unable to discard the saved draft. Please try again.','error',0);}});
  form.addEventListener('submit',event=>{derive(false);let blocked=false;for(const step of [1,2,3,4,5]){if(!validate(step)){event.preventDefault();current=step;render();blocked=true;break;}}if(!blocked){setValue('policy-answers-json',JSON.stringify(collectDraftPayload()));draftReady=false;clearTimeout(draftTimer);setDraftStatus('Saving final shift…','saving');}});
  const restoredPolicy=!initialDraft&&initialPolicyAnswers?applyDraftPayload(initialPolicyAnswers):false;
  if(!restoredPolicy){renderLmRules(); derive(false); conditionalFields(); showLmStage(lmFlowCurrent,false); showLunchStage(lunchFlowCurrent,false); render();}
  setDraftStatus(initialDraft?'Saved draft available':'Draft autosave ready',initialDraft?'saved':'idle');
})();
</script>
<?php console_footer(); ?>
