<?php

declare(strict_types=1);

require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_permission('queue.manage', ['super_admin','clinic_admin','receptionist','staff','dentist','dental_assistant']);
require DENTAL_ROOT . '/app/layout.php';
require_once DENTAL_ROOT . '/app/queue_flow.php';
require_once DENTAL_ROOT . '/app/transaction_integrity.php';

$pdo = db();
$user = current_user() ?? [];
$actualRole = actual_role();
$taskRole = active_role();
$userId = (int)($user['id'] ?? 0);
$selectedDentistId = 0;
if ($taskRole === 'dentist') {
    if (active_role_can_operate('dentist')) {
        $selectedDentistId = $userId;
    } else {
        $requestedDentist = (int)($_GET['dentist_id'] ?? $_SESSION['task_dentist_id'] ?? 0);
        $availableDentists = active_dentists((int)($user['branch_id'] ?? 0) ?: null);
        $ids = array_map(static fn(array $row): int => (int)$row['id'], $availableDentists);
        if (!in_array($requestedDentist, $ids, true)) $requestedDentist = (int)($ids[0] ?? 0);
        $selectedDentistId = $requestedDentist;
        if ($selectedDentistId > 0) $_SESSION['task_dentist_id'] = $selectedDentistId;
    }
}
$assistantDentistIds = ($taskRole === 'dental_assistant' && active_role_can_operate('dental_assistant')) ? task_dentist_scope_ids() : [];
$canIntake = task_context_action_allowed('queue.intake') && has_role(['super_admin','clinic_admin','receptionist','staff']);
$canFrontdeskFlow = task_context_action_allowed('queue.frontdesk') && has_role(['super_admin','clinic_admin','receptionist','staff']);
$canClinicalFlow = (task_context_action_allowed('queue.clinical') || task_context_action_allowed('queue.clinical_support')) && ((in_array($taskRole,['super_admin','clinic_admin'],true)&&has_role(['super_admin','clinic_admin'])) || ($taskRole==='dentist'&&active_role_can_operate('dentist')) || ($taskRole==='dental_assistant'&&active_role_can_operate('dental_assistant')));
$branchId = signed_user_branch_id();
if ($branchId < 1 && superuser_primary_workspace()) {
    $branchId = (int) ($pdo->query('SELECT id FROM `' . table('branches') . '` WHERE is_active=1 ORDER BY id LIMIT 1')->fetchColumn() ?: 0);
}
$error = '';

function queue_add_history(PDO $pdo, int $queueId, ?string $oldStatus, string $newStatus, ?string $notes = null): void
{
    $stmt = $pdo->prepare(
        'INSERT INTO `' . table('queue_status_history') . '` '
        . '(queue_entry_id,previous_status,new_status,notes,changed_by,created_at) VALUES (?,?,?,?,?,NOW())'
    );
    $stmt->execute([$queueId, $oldStatus, $newStatus, $notes, current_user()['id'] ?? null]);
}

function appointment_add_history(PDO $pdo, int $appointmentId, ?string $oldStatus, string $newStatus, ?string $reason = null): void
{
    $stmt = $pdo->prepare(
        'INSERT INTO `' . table('appointment_status_history') . '` '
        . '(appointment_id,previous_status,new_status,reason,changed_by,created_at) VALUES (?,?,?,?,?,NOW())'
    );
    $stmt->execute([$appointmentId, $oldStatus, $newStatus, $reason, current_user()['id'] ?? null]);
}


function queue_patient_number(PDO $pdo, int $branchId = 0): string
{
    if (function_exists('dental_next_patient_number')) {
        return dental_next_patient_number($pdo, $branchId);
    }
    for ($attempt=0;$attempt<20;$attempt++) {
        $number='P'.date('Y').str_pad((string)random_int(1,9999),4,'0',STR_PAD_LEFT);
        $stmt=$pdo->prepare('SELECT COUNT(*) FROM `' . table('patients') . '` WHERE patient_no=?');
        $stmt->execute([$number]);
        if ((int)$stmt->fetchColumn()===0) return $number;
    }
    return 'P'.date('Y').strtoupper(bin2hex(random_bytes(3)));
}

function queue_upsert_intake(PDO $pdo, int $patientId, int $queueId, ?int $appointmentId, string $intakeType, array $data): void
{
    if (!clinical_sequence_schema_ready()) return;
    $stmt=$pdo->prepare(
        'INSERT INTO `' . table('patient_intakes') . '` '
        . '(patient_id,appointment_id,queue_entry_id,intake_type,chief_complaint,history_present_condition,allergies_confirmed,allergy_changes,medications_confirmed,medication_changes,medical_changes,dental_history_changes,pain_score,consent_acknowledged,status,completed_by,completed_at,created_at,updated_at) '
        . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,"complete",?,NOW(),NOW(),NOW()) '
        . 'ON DUPLICATE KEY UPDATE appointment_id=VALUES(appointment_id),intake_type=VALUES(intake_type),chief_complaint=VALUES(chief_complaint),history_present_condition=VALUES(history_present_condition),allergies_confirmed=VALUES(allergies_confirmed),allergy_changes=VALUES(allergy_changes),medications_confirmed=VALUES(medications_confirmed),medication_changes=VALUES(medication_changes),medical_changes=VALUES(medical_changes),dental_history_changes=VALUES(dental_history_changes),pain_score=VALUES(pain_score),consent_acknowledged=VALUES(consent_acknowledged),status="complete",completed_by=VALUES(completed_by),completed_at=NOW(),updated_at=NOW()'
    );
    $stmt->execute([
        $patientId,$appointmentId,$queueId,$intakeType,
        trim((string)($data['chief_complaint']??''))?:null,
        trim((string)($data['history_present_condition']??''))?:null,
        !empty($data['allergies_confirmed'])?1:0,
        function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($data['allergy_changes'] ?? '') : (trim((string)($data['allergy_changes']??''))?:null),
        !empty($data['medications_confirmed'])?1:0,
        function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($data['medication_changes'] ?? '') : (trim((string)($data['medication_changes']??''))?:null),
        function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($data['medical_changes'] ?? '') : (trim((string)($data['medical_changes']??''))?:null),
        trim((string)($data['dental_history_changes']??''))?:null,
        ($data['pain_score']??'')!==''?max(0,min(10,(int)$data['pain_score'])):null,
        !empty($data['consent_acknowledged'])?1:0,
        current_user()['id']??null,
    ]);
    $pdo->prepare('UPDATE `' . table('queue_entries') . '` SET intake_completed_at=NOW(),updated_at=NOW() WHERE id=?')->execute([$queueId]);
}

function queue_create(PDO $pdo, int $branchId, string $source, array $data): array
{
    $prefix = $source === 'appointment' ? 'A' : 'W';
    $lockName = 'dental_queue_' . $branchId . '_' . date('Ymd') . '_' . $prefix;
    $lock = $pdo->prepare('SELECT GET_LOCK(?, 5)');
    $lock->execute([$lockName]);
    if ((int) $lock->fetchColumn() !== 1) {
        throw new RuntimeException('The queue is busy. Please submit the check-in again.');
    }

    try {
        $seq = $pdo->prepare(
            'SELECT COALESCE(MAX(CAST(SUBSTRING(queue_no,2) AS UNSIGNED)),0) '
            . 'FROM `' . table('queue_entries') . '` '
            . 'WHERE branch_id=? AND DATE(checked_in_at)=CURDATE() AND queue_no LIKE ?'
        );
        $seq->execute([$branchId, $prefix . '%']);
        $number = (int) $seq->fetchColumn() + 1;
        $queueNo = $prefix . str_pad((string) $number, 3, '0', STR_PAD_LEFT);

        $stmt = $pdo->prepare(
            'INSERT INTO `' . table('queue_entries') . '` '
            . '(branch_id,appointment_id,patient_id,source_type,queue_no,priority,service_id,service_name,provider_type,dentist_user_id,dentist_name,inhouse_provider_user_id,chair_id,chair_name,reason,notes,status,checked_in_by,checked_in_at) '
            . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,"waiting",?,NOW())'
        );
        $stmt->execute([
            $branchId,
            $data['appointment_id'] ?? null,
            $data['patient_id'],
            $source,
            $queueNo,
            $data['priority'] ?? 'regular',
            $data['service_id'] ?? null,
            $data['service_name'] ?? null,
            (($data['provider_type'] ?? 'dentist') === 'inhouse') ? 'inhouse' : 'dentist',
            $data['dentist_user_id'] ?? null,
            $data['dentist_name'] ?? null,
            $data['inhouse_provider_user_id'] ?? null,
            $data['chair_id'] ?? null,
            $data['chair_name'] ?? null,
            $data['reason'] ?? null,
            $data['notes'] ?? null,
            current_user()['id'] ?? null,
        ]);
        $queueId = (int) $pdo->lastInsertId();
        queue_add_history($pdo, $queueId, null, 'waiting', ucfirst(str_replace('_', ' ', $source)) . ' checked in');
        return ['id' => $queueId, 'queue_no' => $queueNo];
    } finally {
        $release = $pdo->prepare('SELECT RELEASE_LOCK(?)');
        $release->execute([$lockName]);
    }
}

function queue_inhouse_assignment(PDO $pdo,int $branchId,array $service): array
{
    if(!inhouse_resource_schema_ready())throw new RuntimeException('Apply Version 0.5.8.4 in System Setup before accepting an in-house walk-in.');
    $date=date('Y-m-d');$time=date('H:i');
    $start=$date.' '.$time.':00';
    $end=date('Y-m-d H:i:s',strtotime($start.' +'.max(5,(int)($service['duration_minutes']??30)).' minutes'));
    $reserveStart=date('Y-m-d H:i:s',strtotime($start.' -'.max(0,(int)($service['preparation_minutes']??0)).' minutes'));
    $reserveEnd=date('Y-m-d H:i:s',strtotime($end.' +'.max(0,(int)($service['cleanup_minutes']??0)).' minutes'));
    $providers=inhouse_service_staff_candidates($branchId,$service);
    $provider=null;$bestLoad=PHP_INT_MAX;
    $loadStmt=$pdo->prepare('SELECT COUNT(*) FROM `' . table('queue_entries') . '` WHERE branch_id=? AND inhouse_provider_user_id=? AND status NOT IN ("completed","cancelled")');
    foreach($providers as $candidate){
        $id=(int)$candidate['id'];
        if(!inhouse_provider_available($id,$branchId,$reserveStart,$reserveEnd))continue;
        $loadStmt->execute([$branchId,$id]);$load=(int)$loadStmt->fetchColumn();
        if($load<$bestLoad){$provider=$candidate;$bestLoad=$load;}
    }
    if(!$provider)throw new RuntimeException('No assigned in-house staff member is available to receive this walk-in.');
    $chair=null;
    if(!empty($service['requires_chair'])){
        $chairs=shared_inhouse_chair_candidates($branchId);$bestChairLoad=PHP_INT_MAX;
        $chairLoad=$pdo->prepare('SELECT COUNT(*) FROM `' . table('queue_entries') . '` WHERE branch_id=? AND chair_id=? AND status IN ("seated","in_treatment")');
        foreach($chairs as $candidate){
            $id=(int)$candidate['id'];
            if(!shared_inhouse_chair_available($id,(string)$candidate['chair_name'],$branchId,$reserveStart,$reserveEnd))continue;
            $chairLoad->execute([$branchId,$id]);$load=(int)$chairLoad->fetchColumn();
            if($load<$bestChairLoad){$chair=$candidate;$bestChairLoad=$load;}
        }
        if(!$chair)throw new RuntimeException('No shared in-house chair is available to receive this walk-in.');
    }
    return ['provider_id'=>(int)$provider['id'],'provider_name'=>(string)$provider['name'],'chair_id'=>(int)($chair['id']??0),'chair_name'=>(string)($chair['chair_name']??'')];
}

function queue_update_appointment_status(PDO $pdo, ?int $appointmentId, string $queueStatus, ?string $reason = null): void
{
    if (!$appointmentId) {
        return;
    }
    $map = [
        'waiting' => 'checked_in',
        'called' => 'checked_in',
        'seated' => 'checked_in',
        'in_treatment' => 'in_treatment',
        'waiting_billing' => 'awaiting_billing',
        'cashier_called' => 'awaiting_billing',
        'completed' => 'completed',
        'cancelled' => 'left_before_treatment',
    ];
    if (!isset($map[$queueStatus])) {
        return;
    }
    $stmt = $pdo->prepare('SELECT status,start_at FROM `' . table('appointments') . '` WHERE id=? FOR UPDATE');
    $stmt->execute([$appointmentId]);
    $appointment = $stmt->fetch();
    $old = $appointment['status'] ?? false;
    $new = $map[$queueStatus];
    if ($old === false || $old === $new) {
        return;
    }
    if ($new === 'left_before_treatment') {
        // Queue is the source of truth for an arrived patient. Older data may
        // still show the linked appointment as confirmed even though the queue
        // row is already waiting/called/seated. Allow the operator to close it
        // from Queue instead of leaving a stale warning and schedule tile.
        if (!in_array((string)$old, ['confirmed','pending_confirmation','checked_in','called','seated'], true)) {
            throw new RuntimeException('The appointment cannot be closed as left before treatment from its current state.');
        }
    } else {
        integrity_assert_appointment_transition((string)$old,$new,(string)($appointment['start_at']??''));
    }
    $update=$pdo->prepare('UPDATE `' . table('appointments') . '` SET status=?,updated_at=NOW() WHERE id=? AND status=?');
    $update->execute([$new,$appointmentId,(string)$old]);
    if($update->rowCount()!==1)throw new RuntimeException('The linked appointment changed while the queue transition was being completed.');
    appointment_add_history($pdo, $appointmentId, (string) $old, $new, $reason ?: 'Updated from clinic queue');
    if (function_exists('queue_sync_terminal_appointment_segments')) {
        queue_sync_terminal_appointment_segments($pdo, $appointmentId, $new);
    }
}

$operationRequest = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        verify_csrf();
        $action = (string) ($_POST['action'] ?? '');
        $operationRequest = operation_integrity_begin(
            $pdo,
            'queue',
            $action,
            $_POST,
            'queue_entry',
            (int)($_POST['queue_id'] ?? 0) ?: null
        );

        if ($action === 'checkin_appointment') {
            if(!schema_column_exists(table('queue_entries'),'chair_id'))throw new RuntimeException('Apply the v0.5.8.4 database update from System Setup before checking patients into the queue.');
            if (!$canIntake) throw new RuntimeException('Appointment check-in is not part of the active task view.');
            if ((string)($_POST['intake_mode'] ?? '') !== 'appointment') throw new RuntimeException('Use the Appointment check-in action for scheduled patients.');
            $appointmentId = (int) ($_POST['appointment_id'] ?? 0);
            if ($appointmentId < 1) throw new RuntimeException('Select an appointment to check in.');
            $pdo->beginTransaction();
            $stmt = $pdo->prepare(
                'SELECT a.*,p.first_name,p.last_name FROM `' . table('appointments') . '` a '
                . 'JOIN `' . table('patients') . '` p ON p.id=a.patient_id '
                . 'WHERE a.id=? AND a.branch_id=? AND DATE(a.start_at)=CURDATE() '
                . 'AND a.status="confirmed" LIMIT 1 FOR UPDATE'
            );
            $stmt->execute([$appointmentId, $branchId]);
            $appointment = $stmt->fetch();
            if (!$appointment) {
                throw new RuntimeException('Only a confirmed appointment scheduled for today can be checked in.');
            }
            $duplicate = $pdo->prepare(
                'SELECT queue_no FROM `' . table('queue_entries') . '` '
                . 'WHERE appointment_id=? AND DATE(checked_in_at)=CURDATE() AND status NOT IN ("completed","cancelled") LIMIT 1 FOR UPDATE'
            );
            $duplicate->execute([$appointmentId]);
            if ($existing = $duplicate->fetchColumn()) {
                throw new RuntimeException('This appointment is already active in the queue as ' . $existing . '.');
            }
            $created = queue_create($pdo, $branchId, 'appointment', [
                'appointment_id' => $appointmentId,
                'patient_id' => (int) $appointment['patient_id'],
                'priority' => (string) ($_POST['priority'] ?? 'regular'),
                'service_id' => !empty($appointment['service_id']) ? (int)$appointment['service_id'] : null,
                'service_name' => $appointment['service_name'],
                'provider_type' => (($appointment['provider_type'] ?? 'dentist') === 'inhouse') ? 'inhouse' : 'dentist',
                'dentist_user_id' => $appointment['dentist_user_id'],
                'dentist_name' => $appointment['dentist_name'],
                'inhouse_provider_user_id' => $appointment['inhouse_provider_user_id'] ?? null,
                'chair_id' => $appointment['chair_id'] ?? null,
                'chair_name' => $appointment['chair_name'],
                'reason' => $appointment['notes'],
                'notes' => trim((string) ($_POST['notes'] ?? '')) ?: null,
            ]);
            $oldStatus = (string) $appointment['status'];
            integrity_assert_appointment_transition($oldStatus,'checked_in',(string)$appointment['start_at']);
            $appointmentUpdate=$pdo->prepare('UPDATE `' . table('appointments') . '` SET status="checked_in",updated_at=NOW() WHERE id=? AND status="confirmed"');$appointmentUpdate->execute([$appointmentId]);if($appointmentUpdate->rowCount()!==1)throw new RuntimeException('The appointment status changed while check-in was being completed. Please reopen the queue and try again.');
            appointment_add_history($pdo, $appointmentId, $oldStatus, 'checked_in', 'Patient checked in at reception');
            queue_upsert_intake($pdo,(int)$appointment['patient_id'],(int)$created['id'],$appointmentId,($appointment['appointment_origin']??'')==='clinical_followup'?'follow_up':'returning_patient',[
                'chief_complaint'=>$_POST['chief_complaint']??$appointment['reason_for_visit']??$appointment['notes']??'',
                'history_present_condition'=>$_POST['history_present_condition']??'',
                'allergies_confirmed'=>isset($_POST['allergies_confirmed']),
                'allergy_changes'=>$_POST['allergy_changes']??'',
                'medications_confirmed'=>isset($_POST['medications_confirmed']),
                'medication_changes'=>$_POST['medication_changes']??'',
                'medical_changes'=>$_POST['medical_changes']??'',
                'dental_history_changes'=>$_POST['dental_history_changes']??'',
                'pain_score'=>$_POST['pain_score']??'',
                'consent_acknowledged'=>isset($_POST['consent_acknowledged']),
            ]);
            workflow_event((int)$appointment['patient_id'],'checked_in','Scheduled patient checked in',['appointment_id'=>$appointmentId,'queue_entry_id'=>(int)$created['id']],$created['queue_no'].' · '.(string)$appointment['service_name']);
            audit('appointment_patient_checked_in', 'queue_entry', $created['id'], ['queue_no' => $created['queue_no'], 'appointment_id' => $appointmentId]);
            $pdo->commit();
            flash('success', $appointment['first_name'] . ' ' . $appointment['last_name'] . ' checked in as ticket ' . $created['queue_no'] . '. Print or hand the queue ticket to the patient; the waiting-room display will call this number.');
        } elseif ($action === 'add_walkin') {
            if(!schema_column_exists(table('queue_entries'),'chair_id'))throw new RuntimeException('Apply the v0.5.8.4 database update from System Setup before registering walk-ins.');
            if (!$canIntake) throw new RuntimeException('Walk-in registration is not part of the active task view.');
            if ((string)($_POST['intake_mode'] ?? '') !== 'walk_in') throw new RuntimeException('Use the Walk-in intake action for unscheduled patients.');
            if ((int)($_POST['appointment_id'] ?? 0) > 0) throw new RuntimeException('Walk-in intake cannot be linked to an appointment. Use Check in appointment instead.');
            $patientId = (int) ($_POST['patient_id'] ?? 0);
            $pdo->beginTransaction();
            if ($patientId < 1) {
                $firstName = trim((string) ($_POST['new_first_name'] ?? ''));
                $lastName = trim((string) ($_POST['new_last_name'] ?? ''));
                if ($firstName === '' || $lastName === '') {
                    throw new RuntimeException('Select an existing patient or enter the walk-in patient’s first and last name.');
                }
                $birthDate = trim((string)($_POST['new_birth_date'] ?? ''));
                $mobile = trim((string)($_POST['new_mobile'] ?? ''));
                if ($birthDate !== '' || $mobile !== '') {
                    $duplicateSql = 'SELECT id,patient_no,first_name,last_name,birth_date,mobile FROM `' . table('patients') . '` '
                        . 'WHERE deleted_at IS NULL AND LOWER(first_name)=LOWER(?) AND LOWER(last_name)=LOWER(?) AND (';
                    $duplicateArgs = [$firstName,$lastName];
                    $matchParts = [];
                    if ($birthDate !== '') { $matchParts[] = 'birth_date=?'; $duplicateArgs[] = $birthDate; }
                    if ($mobile !== '') { $matchParts[] = 'mobile=?'; $duplicateArgs[] = $mobile; }
                    $duplicateSql .= implode(' OR ', $matchParts) . ') LIMIT 1';
                    $duplicate = $pdo->prepare($duplicateSql);
                    $duplicate->execute($duplicateArgs);
                    if ($possible = $duplicate->fetch()) {
                        throw new RuntimeException('Possible duplicate patient: ' . $possible['patient_no'] . ' · ' . $possible['first_name'] . ' ' . $possible['last_name'] . '. Search and select the existing patient before checking in the walk-in.');
                    }
                }
                $patientNo = queue_patient_number($pdo, $branchId);
                $newPatient = $pdo->prepare(
                    'INSERT INTO `' . table('patients') . '` '
                    . '(branch_id,patient_no,first_name,middle_name,last_name,birth_date,sex,mobile,email,address,emergency_contact,medical_alerts,status,created_by,created_at) '
                    . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,"active",?,NOW())'
                );
                $alertParts=array_filter([
                    function_exists('dentalcare_medical_alert_line') ? dentalcare_medical_alert_line('Allergies', $_POST['new_allergies'] ?? '') : (trim((string)($_POST['new_allergies']??''))!==''?'Allergies: '.trim((string)$_POST['new_allergies']):null),
                    function_exists('dentalcare_medical_alert_line') ? dentalcare_medical_alert_line('Conditions', $_POST['new_conditions'] ?? '') : (trim((string)($_POST['new_conditions']??''))!==''?'Conditions: '.trim((string)$_POST['new_conditions']):null),
                ]);
                $newPatient->execute([
                    $branchId,$patientNo,$firstName,trim((string)($_POST['new_middle_name']??''))?:null,$lastName,
                    ($_POST['new_birth_date'] ?? '') ?: null,trim((string)($_POST['new_sex']??''))?:null,
                    trim((string) ($_POST['new_mobile'] ?? '')) ?: null,trim((string)($_POST['new_email']??''))?:null,
                    trim((string)($_POST['new_address']??''))?:null,trim((string)($_POST['new_emergency_contact']??''))?:null,
                    $alertParts ? implode("
", $alertParts) : null,current_user()['id'] ?? null,
                ]);
                $patientId = (int) $pdo->lastInsertId();
                if((function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_allergies']??'') : trim((string)($_POST['new_allergies']??'')))!==null||(function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_medications']??'') : trim((string)($_POST['new_medications']??'')))!==null||(function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_conditions']??'') : trim((string)($_POST['new_conditions']??'')))!==null){
                    integrity_insert_reported_medical_history($pdo,$patientId,[
                        'allergies'=>function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_allergies'] ?? '') : ($_POST['new_allergies']??''),
                        'medications'=>function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_medications'] ?? '') : ($_POST['new_medications']??''),
                        'conditions'=>function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($_POST['new_conditions'] ?? '') : ($_POST['new_conditions']??''),
                    ],'patient_reported',(int)(current_user()['id']??0),'Initial walk-in intake. Pending dentist review.');
                }
                workflow_event($patientId,'registered','Walk-in patient registered',[], 'Patient number '.$patientNo);
                audit('walkin_patient_quick_registered', 'patient', $patientId, ['patient_no' => $patientNo]);
            }

            $patientStmt = $pdo->prepare('SELECT id,branch_id,patient_no,first_name,last_name FROM `' . table('patients') . '` WHERE id=? AND deleted_at IS NULL FOR UPDATE');
            $patientStmt->execute([$patientId]);
            $patient = $patientStmt->fetch();
            if (!$patient) {
                throw new RuntimeException('The selected patient record is not available.');
            }
            if((int)($patient['branch_id']??0)>0&&(int)$patient['branch_id']!==$branchId)throw new RuntimeException('The selected patient belongs to another clinic branch.');
            $selectedPatientNo=trim((string)($_POST['selected_patient_no']??''));
            if($selectedPatientNo!==''&&!hash_equals((string)$patient['patient_no'],$selectedPatientNo))throw new RuntimeException('The selected patient details changed before submission. Search and select the patient again.');
            $activePatient=$pdo->prepare('SELECT queue_no FROM `' . table('queue_entries') . '` WHERE patient_id=? AND status NOT IN ("completed","cancelled") ORDER BY id DESC LIMIT 1 FOR UPDATE');
            $activePatient->execute([$patientId]);
            if($activeQueueNo=$activePatient->fetchColumn())throw new RuntimeException('This patient already has an active queue visit as '.$activeQueueNo.'.');

            $serviceId = (int) ($_POST['service_id'] ?? 0);
            $service = service_definition($serviceId, $branchId);
            if (!$service) {
                throw new RuntimeException('Select an active clinic service.');
            }
            $serviceName = trim((string) $service['name']);
            $providerType = service_provider_type($service);
            $doctorId = 0;
            $doctorName = null;
            $inhouseProviderId = null;
            $inhouseProviderName = null;
            $chairId = null;
            $chairName = null;
            if ($providerType === 'dentist') {
                $doctorId = (int) ($_POST['dentist_user_id'] ?? 0);
                if ($doctorId < 1) {
                    throw new RuntimeException('Select the dentist who will receive this walk-in patient.');
                }
                $doctorStmt = $pdo->prepare(
                    'SELECT u.name FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id '
                    . 'WHERE u.id=? AND r.code="dentist" AND u.status="active" '
                    . 'AND (u.branch_id=? OR u.branch_id IS NULL OR EXISTS(SELECT 1 FROM `' . table('dentist_branch_assignments') . '` dba WHERE dba.dentist_user_id=u.id AND dba.branch_id=? AND dba.status="active")) LIMIT 1'
                );
                $doctorStmt->execute([$doctorId,$branchId,$branchId]);
                $doctorName = $doctorStmt->fetchColumn() ?: null;
                if (!$doctorName) {
                    throw new RuntimeException('The selected dentist is not active at this branch.');
                }
                $binding = doctor_chair_binding($doctorId,$branchId);
                if (empty($binding['valid'])) {
                    throw new RuntimeException((string)($binding['issue'] ?? 'Assign a unique chair to this dentist before accepting the walk-in.'));
                }
                $chairId = (int)($binding['chair_id']??0) ?: null;
                $chairName = (string)$binding['chair_name'];
            } else {
                $assignment = queue_inhouse_assignment($pdo,$branchId,$service);
                $inhouseProviderId = (int)$assignment['provider_id'];
                $inhouseProviderName = (string)$assignment['provider_name'];
                $chairId = (int)$assignment['chair_id'] ?: null;
                $chairName = (string)$assignment['chair_name'] ?: null;
            }

            $created = queue_create($pdo, $branchId, 'walk_in', [
                'patient_id' => $patientId,
                'priority' => (string) ($_POST['priority'] ?? 'regular'),
                'service_id' => (int)$service['id'],
                'service_name' => $serviceName,
                'provider_type' => $providerType,
                'dentist_user_id' => $doctorId ?: null,
                'dentist_name' => $doctorName,
                'inhouse_provider_user_id' => $inhouseProviderId,
                'chair_id' => $chairId,
                'chair_name' => $chairName,
                'reason' => trim((string) ($_POST['reason'] ?? '')) ?: null,
                'notes' => trim((string) ($_POST['notes'] ?? '')) ?: null,
            ]);
            queue_upsert_intake($pdo,$patientId,(int)$created['id'],null,'walk_in',[
                'chief_complaint'=>$_POST['reason']??'',
                'history_present_condition'=>$_POST['history_present_condition']??'',
                'allergies_confirmed'=>isset($_POST['allergies_confirmed']),
                'allergy_changes'=>$_POST['allergy_changes']??$_POST['new_allergies']??'',
                'medications_confirmed'=>isset($_POST['medications_confirmed']),
                'medication_changes'=>$_POST['medication_changes']??$_POST['new_medications']??'',
                'medical_changes'=>$_POST['medical_changes']??$_POST['new_conditions']??'',
                'dental_history_changes'=>$_POST['dental_history_changes']??'',
                'pain_score'=>$_POST['pain_score']??'',
                'consent_acknowledged'=>isset($_POST['consent_acknowledged']),
            ]);
            workflow_event($patientId,'checked_in','Walk-in added to clinic queue',['queue_entry_id'=>(int)$created['id']],$created['queue_no'].' · '.$serviceName);
            audit('walkin_patient_checked_in', 'queue_entry', $created['id'], ['queue_no' => $created['queue_no'], 'patient_id' => $patientId]);
            $pdo->commit();
            flash('success', $patient['first_name'] . ' ' . $patient['last_name'] . ' added as walk-in ticket ' . $created['queue_no'] . ($providerType==='inhouse'?' · Assigned to '.$inhouseProviderName.($chairName?' · '.$chairName:''):'' ) . '. Print or hand the queue ticket to the patient; the waiting-room display will call this number.');
        } elseif ($action === 'change_status') {
            if (!$canFrontdeskFlow && !$canClinicalFlow && !has_role(['super_admin','clinic_admin'])) throw new RuntimeException('Queue status updates are not part of the active task view.');
            $queueId = (int) ($_POST['queue_id'] ?? 0);
            $newStatus = (string) ($_POST['status'] ?? '');
            $allowedByTask = match ($taskRole) {
                'super_admin', 'clinic_admin' => ['waiting','called','seated','in_treatment','waiting_billing','cancelled'],
                'receptionist' => ['waiting','called','seated','cancelled'],
                'staff' => ['waiting','called','seated','waiting_billing','cancelled'],
                'dentist', 'dental_assistant' => ['called','seated','in_treatment','waiting_billing','cancelled'],
                default => [],
            };
            if (!in_array($newStatus, $allowedByTask, true)) {
                throw new RuntimeException('The selected queue status is not available in the active task view.');
            }
            $pdo->beginTransaction();
            $stmt = $pdo->prepare('SELECT * FROM `' . table('queue_entries') . '` WHERE id=? AND branch_id=? FOR UPDATE');
            $stmt->execute([$queueId, $branchId]);
            $queue = $stmt->fetch();
            if (!$queue) {
                throw new RuntimeException('Queue entry not found.');
            }
            if ($taskRole === 'dentist' && $selectedDentistId > 0 && (int)($queue['dentist_user_id'] ?? 0) !== $selectedDentistId) {
                throw new RuntimeException('This patient is assigned to another dentist.');
            }
            if (active_role_is('dentist') && (int)($queue['dentist_user_id'] ?? 0) !== $userId) {
                throw new RuntimeException('This patient is assigned to another dentist.');
            }
            if ($taskRole === 'dental_assistant' && active_role_can_operate('dental_assistant') && (!in_array((int)($queue['dentist_user_id'] ?? 0), $assistantDentistIds, true))) {
                throw new RuntimeException('This patient is not assigned to a dentist supported by this assistant.');
            }
            $oldStatus = (string) $queue['status'];
            $providerType = (($queue['provider_type'] ?? 'dentist') === 'inhouse') ? 'inhouse' : 'dentist';
            $allowedTransitions = queue_allowed_transitions($providerType, $oldStatus);
            if ($oldStatus !== $newStatus && !in_array($newStatus,$allowedTransitions,true)) {
                if ($providerType === 'dentist' && in_array($newStatus,['in_treatment','waiting_billing'],true)) {
                    throw new RuntimeException($newStatus === 'in_treatment'
                        ? 'Use Start treatment so the clinical encounter is opened safely.'
                        : 'Finalize the clinical encounter before sending a dentist-required visit to cashier.');
                }
                throw new RuntimeException('This queue transition is not valid from ' . ucwords(str_replace('_',' ',$oldStatus)) . '.');
            }
            $statusNote = trim((string)($_POST['status_note'] ?? ''));
            if ($oldStatus !== $newStatus && queue_transition_requires_reason($oldStatus, $newStatus) && $statusNote === '') {
                throw new RuntimeException(queue_transition_action_label($oldStatus, $newStatus) . ' requires a reason.');
            }
            $chairLockName = null;
            try {
                if ($oldStatus !== $newStatus && $newStatus === 'seated') {
                    if ($providerType === 'inhouse' && (int)($queue['inhouse_provider_user_id']??0) < 1) {
                        throw new RuntimeException('Assign a responsible in-house staff member before seating this patient.');
                    }
                    $chairLockName = queue_acquire_chair_lock($pdo, $queue);
                    queue_assert_chair_available_for_seating($pdo, $queue);
                }
                if ($oldStatus !== $newStatus) {
                    $update = $pdo->prepare(
                        'UPDATE `' . table('queue_entries') . '` SET status=?,updated_at=NOW(), '
                        . 'called_at=IF(?="called" AND called_at IS NULL,NOW(),called_at), '
                        . 'seated_at=IF(?="seated" AND seated_at IS NULL,NOW(),seated_at), '
                        . 'treatment_started_at=IF(?="in_treatment" AND treatment_started_at IS NULL,NOW(),treatment_started_at), '
                        . 'billing_at=IF(?="waiting_billing" AND billing_at IS NULL,NOW(),billing_at), '
                        . 'cashier_called_at=IF(?="cashier_called" AND cashier_called_at IS NULL,NOW(),cashier_called_at), '
                        . 'completed_at=IF(?="completed" AND completed_at IS NULL,NOW(),completed_at), '
                        . 'cancelled_at=IF(?="cancelled" AND cancelled_at IS NULL,NOW(),cancelled_at) '
                        . 'WHERE id=? AND status=?'
                    );
                    $update->execute([$newStatus, $newStatus, $newStatus, $newStatus, $newStatus, $newStatus, $newStatus, $newStatus, $queueId, $oldStatus]);
                    if ($update->rowCount() !== 1) {
                        throw new RuntimeException('The queue status changed while this action was being recorded. Reload the queue and try again.');
                    }
                    if (in_array($newStatus, ['waiting','called'], true) && $oldStatus === 'seated') {
                        $pdo->prepare('UPDATE `' . table('queue_entries') . '` SET seated_at=NULL WHERE id=?')->execute([$queueId]);
                    }
                    queue_add_history($pdo, $queueId, $oldStatus, $newStatus, $statusNote !== '' ? $statusNote : null);
                    queue_update_appointment_status($pdo, $queue['appointment_id'] ? (int) $queue['appointment_id'] : null, $newStatus, $statusNote !== '' ? $statusNote : null);
                    workflow_event((int)$queue['patient_id'],'queue_'.$newStatus,'Queue status: '.ucwords(str_replace('_',' ',$newStatus)),['appointment_id'=>(int)($queue['appointment_id']??0),'queue_entry_id'=>$queueId],trim((string)($_POST['status_note']??''))?:null);
                    audit('queue_status_changed', 'queue_entry', $queueId, ['status' => $newStatus], ['status' => $oldStatus]);
                }
                $pdo->commit();
            } catch (Throwable $transitionException) {
                if ($pdo->inTransaction()) $pdo->rollBack();
                throw $transitionException;
            } finally {
                queue_release_chair_lock($pdo, $chairLockName);
            }
            flash('success', 'Queue status updated.');
        } else {
            throw new RuntimeException('Unsupported queue action.');
        }

        $createdQueueId=isset($created['id'])?(int)$created['id']:0;
        operation_integrity_complete($pdo, $operationRequest, ['entity_type'=>'queue_entry','entity_id'=>$createdQueueId,'redirect'=>'queue.php']);
        header('Location: queue.php'.($createdQueueId>0?'?focus_queue_id='.$createdQueueId.'#queue-entry-'.$createdQueueId:''));
        exit;
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        operation_integrity_fail($pdo,$operationRequest,$exception);
        $error = $exception->getMessage();
    }
}

// Existing walk-in patients are searched on demand through api/patient_search.php.
$dentists = active_dentists($branchId ?: null);
$services = clinic_services_catalog($branchId ?: null);

$walkinPrefill = null;
$walkinPrefillId = $canIntake ? (int) ($_GET['patient_id'] ?? 0) : 0;
if ($walkinPrefillId > 0) {
    try {
        $prefillStmt = $pdo->prepare(
            'SELECT p.id,p.branch_id,p.patient_no,p.first_name,p.middle_name,p.last_name,p.birth_date,p.sex,p.mobile,p.email,p.address,p.emergency_contact,p.medical_alerts,'
            . 'b.name branch_name,'
            . '(SELECT a.dentist_name FROM `' . table('appointments') . '` a WHERE a.patient_id=p.id AND a.dentist_name<>"" ORDER BY a.start_at DESC LIMIT 1) primary_dentist_name,'
            . '(SELECT COALESCE(SUM(GREATEST(i.amount-i.paid_amount,0)),0) FROM `' . table('invoices') . '` i WHERE i.patient_id=p.id AND i.status NOT IN ("voided","refunded","written_off")) outstanding_balance '
            . 'FROM `' . table('patients') . '` p LEFT JOIN `' . table('branches') . '` b ON b.id=p.branch_id '
            . 'WHERE p.id=? AND p.deleted_at IS NULL LIMIT 1'
        );
        $prefillStmt->execute([$walkinPrefillId]);
        $walkinPrefill = $prefillStmt->fetch() ?: null;
        if ($walkinPrefill && (int)($walkinPrefill['branch_id'] ?? 0) > 0 && (int)$walkinPrefill['branch_id'] !== $branchId) {
            $walkinPrefill = null;
            if ($error === '') $error = 'The selected patient belongs to another clinic branch. Use the correct branch before walk-in check-in.';
        }
    } catch (Throwable) {
        $walkinPrefill = null;
    }
}
$walkinPrefillName = $walkinPrefill ? trim((string)$walkinPrefill['last_name'] . ', ' . (string)$walkinPrefill['first_name'] . (empty($walkinPrefill['middle_name']) ? '' : ' ' . (string)$walkinPrefill['middle_name'])) : '';
$walkinPrefillLabel = $walkinPrefill ? ((string)$walkinPrefill['patient_no'] . ' · ' . $walkinPrefillName) : '';
$walkinPrefillAge = '';
if ($walkinPrefill && !empty($walkinPrefill['birth_date'])) {
    try { $walkinPrefillAge = (string) (new DateTimeImmutable((string)$walkinPrefill['birth_date']))->diff(new DateTimeImmutable('today'))->y; } catch (Throwable) { $walkinPrefillAge = ''; }
}

$appointmentsStmt = $pdo->prepare(
    'SELECT a.id,a.start_at,a.service_name,a.provider_type,a.dentist_name,a.inhouse_provider_name,a.chair_name,p.first_name,p.last_name,p.patient_no '
    . 'FROM `' . table('appointments') . '` a JOIN `' . table('patients') . '` p ON p.id=a.patient_id '
    . 'WHERE a.branch_id=? AND DATE(a.start_at)=CURDATE() '
    . 'AND a.status="confirmed" '
    . 'AND NOT EXISTS (SELECT 1 FROM `' . table('queue_entries') . '` q WHERE q.appointment_id=a.id AND DATE(q.checked_in_at)=CURDATE() AND q.status NOT IN ("completed","cancelled")) '
    . 'ORDER BY a.start_at'
);
if ($canIntake) { $appointmentsStmt->execute([$branchId]); $todaysAppointments = $appointmentsStmt->fetchAll(); } else { $todaysAppointments = []; }
$requestedAppointmentId = (int) ($_GET['appointment_id'] ?? 0);

$queueScopeSql = '';
$queueScopeArgs = [];
if ($taskRole === 'dentist' && $selectedDentistId > 0) {
    $queueScopeSql = ' AND provider_type="dentist" AND dentist_user_id=?';
    $queueScopeArgs[] = $selectedDentistId;
} elseif ($taskRole === 'dental_assistant' && active_role_can_operate('dental_assistant')) {
    if ($assistantDentistIds) {
        $marks = implode(',', array_fill(0, count($assistantDentistIds), '?'));
        $queueScopeSql = ' AND provider_type="dentist" AND dentist_user_id IN (' . $marks . ')';
        $queueScopeArgs = $assistantDentistIds;
    } else {
        $queueScopeSql = ' AND 1=0';
    }
}

$expectedScopeSql = '';
$expectedScopeArgs = [];
if ($taskRole === 'dentist' && $selectedDentistId > 0) {
    $expectedScopeSql = ' AND a.provider_type="dentist" AND a.dentist_user_id=?';
    $expectedScopeArgs[] = $selectedDentistId;
} elseif ($taskRole === 'dental_assistant' && active_role_can_operate('dental_assistant')) {
    if ($assistantDentistIds) {
        $marks = implode(',', array_fill(0, count($assistantDentistIds), '?'));
        $expectedScopeSql = ' AND a.provider_type="dentist" AND a.dentist_user_id IN (' . $marks . ')';
        $expectedScopeArgs = $assistantDentistIds;
    } else {
        $expectedScopeSql = ' AND 1=0';
    }
}

$expectedListSql = 'SELECT a.id,a.patient_id,a.start_at,a.end_at,a.status appointment_status,a.service_name,'
    . 'a.provider_type,a.dentist_user_id,a.dentist_name,a.inhouse_provider_name,a.chair_name,a.confirmation_status,'
    . 'p.patient_no,p.first_name,p.last_name,p.mobile,p.medical_alerts,'
    . 'q.id queue_entry_id,q.queue_no,q.status queue_status,q.checked_in_at '
    . 'FROM `' . table('appointments') . '` a '
    . 'JOIN `' . table('patients') . '` p ON p.id=a.patient_id '
    . 'LEFT JOIN (SELECT appointment_id,MAX(id) queue_entry_id FROM `' . table('queue_entries') . '` '
    . 'WHERE appointment_id IS NOT NULL AND DATE(checked_in_at)=CURDATE() GROUP BY appointment_id) qlatest ON qlatest.appointment_id=a.id '
    . 'LEFT JOIN `' . table('queue_entries') . '` q ON q.id=qlatest.queue_entry_id '
    . 'WHERE a.branch_id=? AND DATE(a.start_at)=CURDATE() AND a.status<>"cancelled"'
    . $expectedScopeSql . ' ORDER BY a.start_at,p.last_name,p.first_name';
$expectedListStmt = $pdo->prepare($expectedListSql);
$expectedListStmt->execute(array_merge([$branchId], $expectedScopeArgs));
$expectedAppointments = $expectedListStmt->fetchAll();
$expectedSummary = ['awaiting'=>0,'late'=>0,'arrived'=>0,'finished'=>0];
$nowTimestamp = time();
foreach ($expectedAppointments as &$appointmentRow) {
    $appointmentStatus = strtolower((string)($appointmentRow['appointment_status'] ?? ''));
    $queueStatus = strtolower((string)($appointmentRow['queue_status'] ?? ''));
    $startTimestamp = strtotime((string)$appointmentRow['start_at']) ?: $nowTimestamp;
    if (!empty($appointmentRow['queue_entry_id'])) {
        if (in_array($queueStatus, ['completed','cancelled'], true)) {
            $appointmentRow['arrival_group'] = 'finished';
            $appointmentRow['arrival_state'] = 'completed';
            $appointmentRow['arrival_label'] = $queueStatus === 'cancelled' ? 'Left / removed from queue' : 'Completed';
            $expectedSummary['finished']++;
        } else {
            $appointmentRow['arrival_group'] = 'arrived';
            $appointmentRow['arrival_state'] = 'arrived';
            $appointmentRow['arrival_label'] = $appointmentRow['queue_no'] ? ((string)$appointmentRow['queue_no'] . ' · ' . ucwords(str_replace('_',' ',$queueStatus))) : 'In queue';
            $expectedSummary['arrived']++;
        }
    } elseif (in_array($appointmentStatus, ['completed','left_before_treatment'], true)) {
        $appointmentRow['arrival_group'] = 'finished';
        $appointmentRow['arrival_state'] = 'completed';
        $appointmentRow['arrival_label'] = $appointmentStatus === 'left_before_treatment' ? 'Left before treatment' : 'Completed';
        $expectedSummary['finished']++;
    } elseif ($appointmentStatus === 'no_show') {
        $appointmentRow['arrival_group'] = 'finished';
        $appointmentRow['arrival_state'] = 'no_show';
        $appointmentRow['arrival_label'] = 'No-show';
        $expectedSummary['finished']++;
    } else {
        $appointmentRow['arrival_group'] = 'awaiting';
        if ($startTimestamp + 900 < $nowTimestamp) {
            $appointmentRow['arrival_state'] = 'late';
            $appointmentRow['arrival_label'] = 'Late / not checked in';
            $expectedSummary['late']++;
        } elseif ($startTimestamp <= $nowTimestamp + 1800) {
            $appointmentRow['arrival_state'] = 'due';
            $appointmentRow['arrival_label'] = 'Due soon';
        } else {
            $appointmentRow['arrival_state'] = 'upcoming';
            $appointmentRow['arrival_label'] = 'Expected';
        }
        $expectedSummary['awaiting']++;
    }
}
unset($appointmentRow);
$expected = count($expectedAppointments);

$countStmt = $pdo->prepare(
    'SELECT '
    . 'SUM(CASE WHEN DATE(checked_in_at)=CURDATE() AND source_type="appointment" THEN 1 ELSE 0 END) appointment_count,'
    . 'SUM(CASE WHEN DATE(checked_in_at)=CURDATE() AND source_type="walk_in" THEN 1 ELSE 0 END) walkin_count,'
    . 'SUM(CASE WHEN status IN ("waiting","called","seated","in_treatment") THEN 1 ELSE 0 END) clinical_active,'
    . 'SUM(CASE WHEN status IN ("waiting_billing","cashier_called") THEN 1 ELSE 0 END) cashier_count,'
    . 'SUM(CASE WHEN status NOT IN ("completed","cancelled") AND checked_in_at<CURDATE() THEN 1 ELSE 0 END) carryover_count '
    . 'FROM `' . table('queue_entries') . '` WHERE branch_id=? AND (DATE(checked_in_at)=CURDATE() OR status NOT IN ("completed","cancelled"))' . $queueScopeSql
);
$countStmt->execute(array_merge([$branchId], $queueScopeArgs));
$counts = $countStmt->fetch() ?: [];

$queueSql = 'SELECT q.*,CONCAT(p.first_name," ",p.last_name) patient,p.patient_no,p.medical_alerts,ip.name inhouse_provider_name,(SELECT id FROM `' . table('clinical_encounters') . '` c WHERE c.queue_entry_id=q.id ORDER BY c.id DESC LIMIT 1) encounter_id '
    . 'FROM `' . table('queue_entries') . '` q JOIN `' . table('patients') . '` p ON p.id=q.patient_id LEFT JOIN `' . table('users') . '` ip ON ip.id=q.inhouse_provider_user_id '
    . 'WHERE q.branch_id=? AND q.status NOT IN ("completed","cancelled")';
$queueArgs = [$branchId];
if ($taskRole === 'dentist' && $selectedDentistId > 0) {
    $queueSql .= ' AND q.dentist_user_id=?'; $queueArgs[] = $selectedDentistId;
} elseif ($taskRole === 'dental_assistant') {
    if ($assistantDentistIds) {
        $marks = implode(',', array_fill(0, count($assistantDentistIds), '?'));
        $queueSql .= ' AND q.dentist_user_id IN (' . $marks . ')';
        $queueArgs = array_merge($queueArgs, $assistantDentistIds);
    } elseif (task_preview_mode()) {
        $queueSql .= ' AND q.status IN ("waiting","called","seated","in_treatment")';
    } else {
        $queueSql .= ' AND 1=0';
    }
}
$queueSql .= ' ORDER BY (q.checked_in_at<CURDATE()) DESC,FIELD(q.status,"waiting","called","seated","in_treatment","waiting_billing","cashier_called"),FIELD(q.priority,"emergency","urgent","regular"),q.checked_in_at';
$queueStmt = $pdo->prepare($queueSql);
$queueStmt->execute($queueArgs);
$queueRows = $queueStmt->fetchAll();
$byStatus = [];
foreach ($queueRows as $row) {
    $byStatus[$row['status']][] = $row;
}

$columns = [
    'waiting' => ['Waiting', ['waiting']],
    'called' => ['Called', ['called']],
    'seated' => ['Seated', ['seated']],
    'in_treatment' => ['In Treatment', ['in_treatment']],
    'cashier' => ['Cashier', ['waiting_billing', 'cashier_called']],
];
$allStatusLabels = [
    'waiting' => 'Waiting',
    'called' => 'Call patient',
    'seated' => 'Seated',
    'in_treatment' => 'In treatment',
    'waiting_billing' => 'Send to cashier',
    'cashier_called' => 'Called by cashier',
    'completed' => 'Completed',
    'cancelled' => 'Cancelled / left',
];
$taskStatusCodes = match ($taskRole) {
    'super_admin', 'clinic_admin' => ['waiting','called','seated','in_treatment','waiting_billing','cancelled'],
    'receptionist' => ['waiting','called','seated','cancelled'],
    'staff' => ['waiting','called','seated','waiting_billing','cancelled'],
    'dentist', 'dental_assistant' => ['called','seated','in_treatment','waiting_billing','cancelled'],
    default => [],
};
$statusOptions = array_intersect_key($allStatusLabels, array_flip($taskStatusCodes));
$queueDisplayUrl = queue_display_relative_url($branchId);

$queueTitle = ($taskRole === 'dentist' && active_role_can_operate('dentist')) ? 'My Patient Queue' : (($taskRole === 'dentist') ? 'Dentist Queue' : ($taskRole === 'dental_assistant' ? 'Chairside Queue' : 'Clinic Queue'));
render_header($queueTitle);
?>
<?php if ($taskRole === 'dentist' && !active_role_can_operate('dentist') && !empty($availableDentists)): ?>
<section class="card task-target-selector"><form method="get" class="toolbar"><div><strong>Dentist queue</strong><span class="muted">Choose the dentist whose assigned queue you need to review.</span></div><label>Dentist<select name="dentist_id" onchange="this.form.submit()"><?php foreach($availableDentists as $doctor): ?><option value="<?= (int)$doctor['id'] ?>" <?= $selectedDentistId===(int)$doctor['id']?'selected':'' ?>><?= e((string)$doctor['name']) ?></option><?php endforeach; ?></select></label></form></section>
<?php endif; ?>
<?php if ($error !== ''): ?><div class="flash error"><?= e($error) ?></div><?php endif; ?>
<?php if (function_exists('dentalcare_render_reception_launchpad')) dentalcare_render_reception_launchpad($canIntake); ?>
<?php if (function_exists('dentalcare_render_reception_enforcement_panel')) dentalcare_render_reception_enforcement_panel($canIntake); ?>
<section class="stats queue-stats" data-live-queue-region="queue-stats">
    <?php
    stat_card('Expected appointments', $expected, 'Scheduled today');
    stat_card('Appointment check-ins', (int) ($counts['appointment_count'] ?? 0), 'A-series tickets');
    stat_card('Walk-ins', (int) ($counts['walkin_count'] ?? 0), 'W-series tickets');
    stat_card('Waiting for cashier', (int) ($counts['cashier_count'] ?? 0), 'Patient names shown to cashier');
    stat_card('Carryover queue', (int) ($counts['carryover_count'] ?? 0), 'Unclosed from a previous day');
    ?>
</section>
<section class="card expected-day-card" data-expected-day data-live-queue-region="expected-patients">
    <div class="expected-day-head">
        <div>
            <span class="eyebrow">Today · <?= e(date('l, F j')) ?></span>
            <h2>Expected Patients</h2>
            <p>Today’s booked patients remain here before arrival, while they are in the queue, and after completion or no-show marking.</p>
        </div>
        <div class="expected-day-summary" aria-label="Expected patient summary">
            <span><strong><?= (int)$expectedSummary['awaiting'] ?></strong> awaiting</span>
            <span class="late"><strong><?= (int)$expectedSummary['late'] ?></strong> late</span>
            <span><strong><?= (int)$expectedSummary['arrived'] ?></strong> arrived</span>
            <span><strong><?= (int)$expectedSummary['finished'] ?></strong> finished</span>
        </div>
    </div>
    <div class="expected-day-tools">
        <label class="expected-search">Search today’s list<input type="search" data-expected-search placeholder="Patient, number, service, or dentist" autocomplete="off"></label>
        <div class="expected-filter-group" role="group" aria-label="Filter expected patients">
            <button type="button" class="active" data-expected-filter="all">All <span><?= count($expectedAppointments) ?></span></button>
            <button type="button" data-expected-filter="awaiting">Awaiting <span><?= (int)$expectedSummary['awaiting'] ?></span></button>
            <button type="button" data-expected-filter="arrived">In queue <span><?= (int)$expectedSummary['arrived'] ?></span></button>
            <button type="button" data-expected-filter="finished">Finished <span><?= (int)$expectedSummary['finished'] ?></span></button>
        </div>
    </div>
    <div class="expected-day-list" data-expected-list data-page-size="25" data-pagination-threshold="25">
        <?php foreach ($expectedAppointments as $appointment):
            $searchText = strtolower(implode(' ', array_filter([
                $appointment['first_name'] ?? '', $appointment['last_name'] ?? '', $appointment['patient_no'] ?? '',
                $appointment['mobile'] ?? '', $appointment['service_name'] ?? '', $appointment['dentist_name'] ?? '',
                $appointment['chair_name'] ?? '', $appointment['arrival_label'] ?? '', $appointment['queue_no'] ?? '',
            ])));
            $canStartCheckIn = $canIntake
                && empty($appointment['queue_entry_id'])
                && !in_array((string)$appointment['appointment_status'], ['completed','no_show','cancelled','left_before_treatment','checked_in','in_treatment','awaiting_billing'], true);
        ?>
        <article class="expected-patient-row <?= e((string)$appointment['arrival_state']) ?>" data-expected-row data-group="<?= e((string)$appointment['arrival_group']) ?>" data-search="<?= e($searchText) ?>">
            <time datetime="<?= e((string)$appointment['start_at']) ?>"><strong><?= e(date('g:i',strtotime((string)$appointment['start_at']))) ?></strong><span><?= e(date('A',strtotime((string)$appointment['start_at']))) ?></span></time>
            <div class="expected-patient-main">
                <div class="expected-patient-title">
                    <strong><?= e((string)$appointment['last_name'] . ', ' . (string)$appointment['first_name']) ?></strong>
                    <span class="expected-state <?= e((string)$appointment['arrival_state']) ?>"><?= e((string)$appointment['arrival_label']) ?></span>
                    <?php if (!empty($appointment['medical_alerts'])): ?><span class="queue-alert">Dental safety alert</span><?php endif; ?>
                </div>
                <div class="expected-patient-meta">
                    <span><?= e((string)$appointment['patient_no']) ?></span>
                    <?php if (!empty($appointment['mobile'])): ?><span><?= e((string)$appointment['mobile']) ?></span><?php endif; ?>
                    <span><?= e((string)($appointment['service_name'] ?: 'Dental appointment')) ?></span>
                    <span><?= e((($appointment['provider_type']??'dentist')==='inhouse') ? ((string)($appointment['inhouse_provider_name']?:'In-house clinic team')) : ((string)($appointment['dentist_name']?:'Dentist unassigned'))) ?></span>
                    <?php if (!empty($appointment['chair_name'])): ?><span>Chair <?= e((string)$appointment['chair_name']) ?></span><?php endif; ?>
                </div>
            </div>
            <div class="expected-patient-actions">
                <?php if ($canStartCheckIn): ?><button class="btn primary" type="button" data-expected-checkin="<?= (int)$appointment['id'] ?>">Start check-in</button><?php endif; ?>
                <?php if (!empty($appointment['queue_entry_id'])): ?><a class="btn ghost" href="#queue-entry-<?= (int)$appointment['queue_entry_id'] ?>">Find in queue</a><a class="btn ghost" target="_blank" href="print_record.php?type=queue_ticket&amp;id=<?= (int)$appointment['queue_entry_id'] ?>">Print ticket</a><?php endif; ?>
                <?php if (task_context_page_allowed('patients')): ?><a href="patients.php?action=view&amp;id=<?= (int)$appointment['patient_id'] ?>">Open patient</a><?php endif; ?>
                <?php if (task_context_page_allowed('appointments')): ?><a href="appointments.php?view=today">Appointments</a><?php endif; ?>
            </div>
        </article>
        <?php endforeach; ?>
        <?php if (!$expectedAppointments): ?><div class="expected-day-empty">No booked patients are expected today. Walk-ins will appear in the live queue below.</div><?php endif; ?>
        <div class="expected-day-empty" data-expected-no-results hidden>No expected patient matches this search and filter.</div>
    </div>
</section>

<?php if ((int)($counts['carryover_count'] ?? 0) > 0): ?>
<section class="alert warning" id="carryover-queue-warning"><strong>Carryover patients are still open from a previous day.</strong> They now appear in the live queue below. Cancel/left-before-treatment, return to waiting, or finish the proper cashier/treatment step to clear the urgent dashboard warning.</section>
<?php endif; ?>
<div class="toolbar queue-operations-bar">
    <div><strong>Live patient flow</strong><span class="muted">Move arrived patients through waiting, treatment, cashier, and previous-day carryover without leaving this page.</span></div>
    <div class="toolbar-actions">
        <a class="btn ghost" href="<?= e($queueDisplayUrl) ?>" target="_blank" rel="noopener">Patient screen</a>
        <button class="btn ghost" type="button" data-copy-text="<?= e($queueDisplayUrl) ?>" data-copy-label="Copy screen link">Copy screen link</button>
        <?php if ($canIntake): ?><button class="btn ghost" data-open="walkin-form">Add walk-in</button>
        <button class="btn primary" data-open="appointment-checkin">Check in appointment</button><?php endif; ?>
    </div>
</div>

<div class="queue-grid queue-grid-enhanced" data-live-queue-region="clinical-queue">
<?php foreach ($columns as $columnKey => [$columnLabel, $statuses]): ?>
    <section class="queue-column" data-paginate-list data-page-size="10" data-pagination-threshold="10">
        <div class="queue-column-head"><h3><?= e($columnLabel) ?></h3><span><?php $n=0;foreach($statuses as $s)$n+=count($byStatus[$s]??[]);echo $n;?></span></div>
        <?php $hasRows=false; foreach ($statuses as $status): foreach ($byStatus[$status] ?? [] as $row): $hasRows=true; $checkedInTs=strtotime((string)$row['checked_in_at']); $isCarryover=$checkedInTs && date('Y-m-d',$checkedInTs)!==date('Y-m-d'); $waitMinutes=max(0,(int)floor((time()-($checkedInTs ?: time()))/60)); $waitLabel=$waitMinutes>=1440 ? floor($waitMinutes/1440).'d '.floor(($waitMinutes%1440)/60).'h' : ($waitMinutes>=60 ? floor($waitMinutes/60).'h '.($waitMinutes%60).'m' : $waitMinutes.' min'); ?>
            <article class="queue-card <?= e((string) $row['priority']) ?><?= $isCarryover ? ' carryover' : '' ?>" id="queue-entry-<?= (int)$row['id'] ?>">
                <div class="queue-card-top"><span class="queue-no"><?= e((string) $row['queue_no']) ?></span><span class="queue-source <?= e((string) $row['source_type']) ?>"><?= $row['source_type']==='appointment'?'Appointment':'Walk-in' ?></span></div>
                <strong class="queue-patient-name"><?= e((string) $row['patient']) ?></strong>
                <small><?= e((string) $row['patient_no']) ?> · Checked in <?= e($isCarryover ? date('M j, g:i A', $checkedInTs ?: time()) : date('g:i A', $checkedInTs ?: time())) ?></small>
                <?php if ($row['service_name']): ?><div class="queue-service"><?= e((string) $row['service_name']) ?></div><?php endif; ?>
                <div class="queue-meta">
                    <?php if (($row['provider_type']??'dentist')==='inhouse'): ?><span>Provider: <?= e((string)($row['inhouse_provider_name']?:'Unassigned in-house staff')) ?></span><?php elseif ($row['dentist_name']): ?><span>Dentist: <?= e((string) $row['dentist_name']) ?></span><?php else: ?><span>Dentist: Unassigned</span><?php endif; ?>
                    <?php if ($row['chair_name']): ?><span>Chair: <?= e((string) $row['chair_name']) ?></span><?php endif; ?>
                    <span>Waiting: <?= e($waitLabel) ?></span>
                </div>
                <?php if ($isCarryover): ?><span class="queue-alert">Carryover from <?= e(date('M j', $checkedInTs ?: time())) ?></span><?php endif; ?>
                <?php if ($row['medical_alerts']): ?><span class="queue-alert">Dental safety alert</span><?php endif; ?>
                <?php if ($row['priority'] !== 'regular'): ?><span class="urgency <?= e((string) $row['priority']) ?>"><?= e((string) $row['priority']) ?></span><?php endif; ?>
                <div class="queue-ticket-actions"><a class="btn ghost compact" target="_blank" href="print_record.php?type=queue_ticket&amp;id=<?= (int)$row['id'] ?>">Print patient ticket</a><small>Patient watches <?= e((string)$row['queue_no']) ?> on the queue screen.</small></div>
                <?php if (($row['provider_type']??'dentist')==='inhouse' && in_array((string)$row['status'],['waiting','called','seated','in_treatment'],true) && task_link_allowed('inhouse_checkups.php')): ?>
                    <a class="btn primary" href="inhouse_checkups.php#checkup-<?= (int)$row['id'] ?>">Open dental-support service</a>
                <?php endif; ?>
                <?php
                    $dentistLed=(($row['provider_type']??'dentist')==='dentist');
                    $safetyGate=($dentistLed&&in_array((string)$row['status'],['seated','in_treatment'],true))?dental_safety_treatment_gate($row):null;
                    $safetyReady=$safetyGate===null||!empty($safetyGate['ready']);
                    $canAssignedDentistStart = ($dentistLed && active_role_is('dentist') && (int)($row['dentist_user_id']??0)===$userId && in_array((string)$row['status'],['seated','in_treatment'],true));
                    $canSupervisedStart = ($dentistLed && has_role(['super_admin','clinic_admin']) && (int)($row['dentist_user_id']??0)>0 && in_array((string)$row['status'],['seated','in_treatment'],true));
                    $canOpenFirstSeating=$dentistLed&&(string)$row['status']==='seated'&&has_role(['super_admin','clinic_admin','dentist','dental_assistant']);
                ?>
                <?php if($safetyGate!==null):?><div class="queue-safety-gate <?= $safetyReady?'ready':'pending' ?>"><span class="badge <?= $safetyReady?'success':'warning' ?>"><?=e((string)($safetyGate['label']??'First seating'))?></span><?php if(!$safetyReady&&!empty($safetyGate['message'])):?><small><?=e((string)$safetyGate['message'])?></small><?php endif;?></div><?php endif;?>
                <?php if($canOpenFirstSeating&&!$safetyReady):?><a class="btn primary" href="<?=e(dental_safety_assessment_url((int)$row['id']))?>">Complete first seating</a><?php endif;?>
                <?php if ($canAssignedDentistStart || $canSupervisedStart): ?>
                    <?php if($row['status']==='in_treatment' && !empty($row['encounter_id'])):?><a class="btn primary" href="clinical_workspace.php?encounter_id=<?=(int)$row['encounter_id']?>">Continue treatment</a><?php elseif($safetyReady):?><a class="btn primary" href="doctor_treatment.php<?= $canSupervisedStart&&!$canAssignedDentistStart ? '?dentist_id='.(int)$row['dentist_user_id'] : '' ?>#queue-entry-<?=(int)$row['id']?>"><?= $canSupervisedStart&&!$canAssignedDentistStart ? 'Open Dentist start gate' : 'Open start gate' ?></a><?php endif;?>
                <?php endif; ?>
                <?php if (in_array($taskRole,['super_admin','clinic_admin'],true) && in_array($row['status'], ['waiting_billing','cashier_called'], true)): ?>
                    <a class="btn primary queue-billing-link" href="billing.php?queue_entry_id=<?= (int) $row['id'] ?>">Open cashier</a>
                <?php endif; ?>
                <?php
                    $rowProvider=(($row['provider_type']??'dentist')==='inhouse')?'inhouse':'dentist';
                    $rowCodes=array_values(array_intersect($taskStatusCodes,queue_allowed_transitions($rowProvider,(string)$row['status'])));
                ?>
                <?php if ($rowCodes): ?><div class="queue-transition-actions" aria-label="Available queue actions">
                    <?php foreach ($rowCodes as $nextStatus): $needsReason=queue_transition_requires_reason((string)$row['status'],$nextStatus); $actionLabel=queue_transition_action_label((string)$row['status'],$nextStatus); ?>
                        <?php if($needsReason): ?><button class="btn <?= $nextStatus==='cancelled'?'danger ghost':'ghost' ?>" type="button" data-queue-transition data-queue-id="<?=(int)$row['id']?>" data-from-status="<?=e((string)$row['status'])?>" data-next-status="<?=e($nextStatus)?>" data-patient="<?=e((string)$row['patient'])?>" data-action-label="<?=e($actionLabel)?>"><?=e($actionLabel)?></button>
                        <?php else: ?><form method="post" class="inline-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="change_status"><input type="hidden" name="queue_id" value="<?=(int)$row['id']?>"><input type="hidden" name="status" value="<?=e($nextStatus)?>"><button class="btn <?=in_array($nextStatus,['called','seated','cashier_called'],true)?'primary':'ghost'?>" type="submit"><?=e($actionLabel)?></button></form><?php endif; ?>
                    <?php endforeach; ?>
                </div><?php endif; ?>
            </article>
        <?php endforeach; endforeach; ?>
        <?php if (!$hasRows): ?><div class="queue-empty">No patients</div><?php endif; ?>
    </section>
<?php endforeach; ?>
</div>

<dialog id="queue-transition-dialog">
    <form method="post" class="form-grid">
        <button type="button" class="dialog-close" data-close>×</button>
        <h2 class="wide" id="queue-transition-title">Update queue status</h2>
        <input type="hidden" name="csrf" value="<?=e(csrf_token())?>">
        <input type="hidden" name="action" value="change_status">
        <input type="hidden" name="queue_id" id="queue-transition-id">
        <input type="hidden" name="status" id="queue-transition-status">
        <p class="wide muted" id="queue-transition-copy"></p>
        <label class="wide">Reason<textarea name="status_note" id="queue-transition-note" required placeholder="Explain why this patient is moving backward or being cancelled."></textarea></label>
        <button class="btn primary" type="submit" id="queue-transition-submit">Save status</button>
    </form>
</dialog>

<?php if ($canIntake): ?>
<dialog id="appointment-checkin" class="wide-dialog" data-dialog-start-top>
    <form method="post" class="form-grid">
        <button type="button" class="dialog-close" data-close>×</button>
        <h2 class="wide">Check in scheduled appointment</h2>
        <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
        <input type="hidden" name="action" value="checkin_appointment">
        <input type="hidden" name="intake_mode" value="appointment">
        <div class="notice info wide"><strong>Appointment mode.</strong> This path only checks in a confirmed appointment scheduled for today. Use Walk-in when there is no appointment.</div>
        <label class="wide">Today’s appointment
            <select name="appointment_id" required>
                <option value="">Select appointment</option>
                <?php foreach ($todaysAppointments as $appointment): ?>
                    <option value="<?= (int) $appointment['id'] ?>" <?= $requestedAppointmentId===(int)$appointment['id']?'selected':'' ?>><?= e(date('g:i A',strtotime((string)$appointment['start_at'])).' · '.$appointment['last_name'].', '.$appointment['first_name'].' · '.$appointment['service_name'].' · '.((($appointment['provider_type']??'dentist')==='inhouse')?($appointment['inhouse_provider_name']?:'In-house clinic team'):($appointment['dentist_name']?:'Dentist unassigned')))  ?></option>
                <?php endforeach; ?>
            </select>
        </label>
        <?php if (!$todaysAppointments): ?><div class="wide flash error">There are no unchecked scheduled appointments for today.</div><?php endif; ?>
        <label>Priority<select name="priority"><option value="regular">Regular</option><option value="urgent">Urgent</option><option value="emergency">Emergency</option></select></label>
        <label>Pain score<input type="number" name="pain_score" min="0" max="10" placeholder="0–10"></label>
        <label class="wide">Chief complaint<textarea name="chief_complaint" required placeholder="What is the patient experiencing today?"></textarea></label>
        <label class="wide">History of present condition<textarea name="history_present_condition" placeholder="When it started, severity, frequency, triggers, and treatment already tried"></textarea></label>
        <label class="check-label"><input type="checkbox" name="allergies_confirmed" value="1"><span>Allergies reviewed with patient</span></label>
        <label class="check-label"><input type="checkbox" name="medications_confirmed" value="1"><span>Current medications reviewed</span></label>
        <label class="wide">Allergy changes<input name="allergy_changes" placeholder="None, or describe changes"></label>
        <label class="wide">Medication changes<input name="medication_changes" placeholder="None, or describe changes"></label>
        <label class="wide">Dental treatment safety changes<textarea name="medical_changes" placeholder="New allergy, medicine, pregnancy, bleeding risk, heart condition, hospitalization, anesthesia reaction, or other change that may affect dental care"></textarea></label>
        <label class="wide">Dental history changes<textarea name="dental_history_changes" placeholder="Recent dental treatment, trauma, sensitivity, or changes"></textarea></label>
        <label class="wide check-label"><input type="checkbox" name="consent_acknowledged" value="1" required><span>Patient or guardian confirmed the intake information and consented to evaluation.</span></label>
        <label class="wide">Reception note<input name="notes" maxlength="255" placeholder="Optional internal note"></label>
        <button class="btn primary wide" type="submit" <?= !$todaysAppointments?'disabled':'' ?>>Complete intake and check in</button>
    </form>
</dialog>

<dialog id="walkin-form" class="wide-dialog" data-dialog-start-top>
    <form method="post" class="form-grid">
        <button type="button" class="dialog-close" data-close>×</button>
        <h2 class="wide">Add walk-in patient</h2>
        <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
        <input type="hidden" name="action" value="add_walkin">
        <input type="hidden" name="intake_mode" value="walk_in">
        <div class="notice success wide"><strong>Walk-in mode.</strong> No appointment is required or expected. Search an existing patient, or quick-register a new walk-in below.</div>
        <label class="wide">Existing patient
            <div class="remote-patient-picker" data-patient-picker data-optional="true" data-endpoint="api/patient_search.php">
                <input type="search" data-patient-query data-dialog-initial-focus placeholder="Patient no., any part of full name, guardian, mobile, or email" autocomplete="off" value="<?= e($walkinPrefillLabel) ?>">
                <input type="hidden" name="patient_id" data-patient-id value="<?= e((string)($walkinPrefill['id'] ?? '')) ?>">
                <input type="hidden" name="selected_patient_no" data-selected-patient-no value="<?= e((string)($walkinPrefill['patient_no'] ?? '')) ?>">
                <div class="remote-patient-selection" data-patient-selection <?= $walkinPrefill ? '' : 'hidden' ?>><?= $walkinPrefill ? 'Selected: ' . e($walkinPrefillLabel) : '' ?></div>
                <div class="remote-patient-results" data-patient-results hidden></div>
            </div>
            <small>Leave this empty only when quick-registering a new walk-in below.</small>
        </label>
        <section class="wide walkin-existing-card" data-walkin-existing-details <?= $walkinPrefill ? '' : 'hidden' ?> aria-live="polite">
            <div class="card-head"><div><strong data-existing-name><?= $walkinPrefill ? e($walkinPrefillName) : 'Selected patient' ?></strong><small data-existing-number><?= $walkinPrefill ? 'Patient no. ' . e((string)$walkinPrefill['patient_no']) : '' ?></small></div><a class="btn ghost compact" data-existing-profile href="<?= $walkinPrefill ? 'patients.php?action=view&amp;id=' . (int)$walkinPrefill['id'] : 'patients.php' ?>">Open patient profile</a></div>
            <dl class="walkin-existing-grid">
                <div><dt>Birth date / age</dt><dd data-existing-birth><?= $walkinPrefill ? e(trim((string)($walkinPrefill['birth_date'] ?? '') . ($walkinPrefillAge !== '' ? ' · ' . $walkinPrefillAge . ' years old' : '')) ?: '—') : '—' ?></dd></div><div><dt>Sex</dt><dd data-existing-sex><?= $walkinPrefill ? e((string)($walkinPrefill['sex'] ?: '—')) : '—' ?></dd></div>
                <div><dt>Mobile</dt><dd data-existing-mobile><?= $walkinPrefill ? e((string)($walkinPrefill['mobile'] ?: '—')) : '—' ?></dd></div><div><dt>Email</dt><dd data-existing-email><?= $walkinPrefill ? e((string)($walkinPrefill['email'] ?: '—')) : '—' ?></dd></div>
                <div><dt>Branch</dt><dd data-existing-branch><?= $walkinPrefill ? e((string)($walkinPrefill['branch_name'] ?: '—')) : '—' ?></dd></div><div><dt>Preferred Dentist</dt><dd data-existing-dentist><?= $walkinPrefill ? e((string)($walkinPrefill['primary_dentist_name'] ?: '—')) : '—' ?></dd></div>
                <div><dt>Guardian</dt><dd data-existing-guardian>—</dd></div><div><dt>Insurance</dt><dd data-existing-insurance>—</dd></div>
                <div><dt>Outstanding balance</dt><dd data-existing-balance><?= $walkinPrefill ? '₱' . number_format((float)($walkinPrefill['outstanding_balance'] ?? 0), 2) : '₱0.00' ?></dd></div><div><dt>Emergency contact</dt><dd data-existing-emergency><?= $walkinPrefill ? e((string)($walkinPrefill['emergency_contact'] ?: '—')) : '—' ?></dd></div>
                <div class="wide"><dt>Address</dt><dd data-existing-address><?= $walkinPrefill ? e((string)($walkinPrefill['address'] ?: '—')) : '—' ?></dd></div><div class="wide"><dt>Dental safety alerts</dt><dd data-existing-alerts><?= $walkinPrefill ? e((string)($walkinPrefill['medical_alerts'] ?: 'None recorded')) : 'None recorded' ?></dd></div>
            </dl>
            <p class="muted">Identity details are read-only during walk-in check-in. Use <strong>Open patient profile</strong> for an authorized correction.</p>
        </section>
        <div class="wide queue-form-divider"><span>New patient quick registration</span></div>
        <div class="wide form-grid<?= $walkinPrefill ? ' walkin-new-fields-disabled' : '' ?>" data-new-walkin-fields>
            <label>First name<input name="new_first_name" maxlength="100" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Middle name<input name="new_middle_name" maxlength="100" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Last name<input name="new_last_name" maxlength="100" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Birth date<input type="date" name="new_birth_date" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Sex<select name="new_sex" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>><option value="">Not specified</option><option>Female</option><option>Male</option><option>Other</option></select></label>
            <label>Mobile<input name="new_mobile" maxlength="40" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Email<input type="email" name="new_email" maxlength="190" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label>Emergency contact<input name="new_emergency_contact" maxlength="190" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></label>
            <label class="wide">Address<textarea name="new_address" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></textarea></label>
            <label class="wide">Known allergies<textarea name="new_allergies" placeholder="Enter None when confirmed" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></textarea></label>
            <label class="wide">Current medications<textarea name="new_medications" placeholder="Enter None when confirmed" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></textarea></label>
            <label class="wide">Health conditions relevant to dental care<textarea name="new_conditions" placeholder="Examples: pregnancy, hypertension, diabetes, anticoagulants, heart condition, asthma, or prior anesthesia reaction" <?= $walkinPrefill ? 'disabled aria-disabled="true"' : '' ?>></textarea></label>
        </div>
        <label>Service
            <select name="service_id" required data-queue-service>
                <option value="">Select service</option>
                <?php foreach ($services as $service): ?><option value="<?= (int)$service['id'] ?>" data-provider-type="<?= e(service_provider_type($service)) ?>"><?= e((string)$service['name']) ?><?= service_provider_type($service)==='inhouse'?' · In-house':'' ?></option><?php endforeach; ?>
            </select>
        </label>
        <label data-queue-dentist>Dentist
            <select name="dentist_user_id"><option value="">Select dentist</option><?php foreach($dentists as $dentist):?><option value="<?= (int)$dentist['id'] ?>"><?= e((string)$dentist['name']) ?></option><?php endforeach;?></select>
        </label>
        <div class="wide muted" data-queue-provider-note>Select a service. Dentist-required services use the dentist’s bound chair automatically; in-house services do not require a dentist.</div>
        <label>Priority<select name="priority"><option value="regular">Regular</option><option value="urgent">Urgent</option><option value="emergency">Emergency</option></select></label>
        <label>Pain score<input type="number" name="pain_score" min="0" max="10" placeholder="0–10"></label>
        <label class="wide">Reason for visit / chief complaint<textarea name="reason" required placeholder="Main complaint or requested dental service"></textarea></label>
        <label class="wide">History of present condition<textarea name="history_present_condition" placeholder="When it started, severity, frequency, triggers, and treatment already tried"></textarea></label>
        <label class="check-label"><input type="checkbox" name="allergies_confirmed" value="1"><span>Allergies reviewed</span></label>
        <label class="check-label"><input type="checkbox" name="medications_confirmed" value="1"><span>Medications reviewed</span></label>
        <label class="wide">Dental-safety changes for an existing patient<textarea name="medical_changes" placeholder="New allergy, medicine, health change, or clearance that may affect dental treatment"></textarea></label>
        <label class="wide">Dental history changes<textarea name="dental_history_changes"></textarea></label>
        <label class="wide check-label"><input type="checkbox" name="consent_acknowledged" value="1" required><span>Patient or guardian confirmed the intake information and consented to evaluation.</span></label>
        <label class="wide">Reception note<textarea name="notes"></textarea></label>
        <button class="btn primary wide" type="submit">Register intake and add to queue</button>
    </form>
</dialog>
<?php endif; ?>
<?php if ($canIntake && $requestedAppointmentId > 0): ?><span hidden data-auto-open-dialog="appointment-checkin"></span><?php endif; ?>
<?php if ($canIntake && isset($_GET['walkin'])): ?><span hidden data-auto-open-dialog="walkin-form"></span><?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded',function(){
  const service=document.querySelector('[data-queue-service]');
  const dentistWrap=document.querySelector('[data-queue-dentist]');
  const note=document.querySelector('[data-queue-provider-note]');
  if(!service||!dentistWrap)return;
  const dentist=dentistWrap.querySelector('select');
  const sync=()=>{
    const option=service.options[service.selectedIndex];
    const inhouse=option&&option.dataset.providerType==='inhouse';
    dentistWrap.hidden=inhouse;
    if(dentist){dentist.required=!inhouse&&service.value!=='';if(inhouse)dentist.value='';}
    if(note)note.textContent=inhouse?'This visit will be handled by the in-house clinic team. No dentist or treatment chair is assigned.':'The selected dentist’s unique branch chair will be assigned automatically.';
  };
  service.addEventListener('change',sync);sync();
});
</script>
<?php render_footer(); ?>
