<?php

declare(strict_types=1);

require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_clinical_sequence_schema();
require DENTAL_ROOT . '/app/layout.php';
require DENTAL_ROOT . '/app/module_helpers.php';

$pdo = db();
$actualRole = actual_role();
$role = active_role();
$user = current_user();
$userId = (int)($user['id'] ?? 0);

$branchId = $role === 'super_admin' ? max(0, (int)($_GET['branch_id'] ?? 0)) : signed_user_branch_id();
$branches = [];
if ($role === 'super_admin') {
    $branches = $pdo->query('SELECT id,name FROM `' . table('branches') . '` WHERE is_active=1 ORDER BY name,id')->fetchAll();
}

$selectedPatientId = max(0, (int)($_GET['patient_id'] ?? 0));
if ($selectedPatientId > 0 && !task_patient_scope_allowed($selectedPatientId)) {
    http_response_code(403);
    exit('Patient access denied.');
}
$q = trim((string)($_GET['q'] ?? ''));
$stageFilter = module_safe_status_filter('stage', [
    'scheduled','arrival','clinical','cashier','follow_up','completed','closed','exception'
]);
$range = module_safe_status_filter('range', ['active','today','upcoming','recent','all'], $selectedPatientId > 0 ? 'all' : 'active');
$page = module_page_number();
$perPage = module_page_size('per_page', 25);

function journey_stage_label(string $stage, array $row): string
{
    if ($stage === 'exception') {
        if (($row['queue_status'] ?? '') === 'billing_correction') return 'Billing correction required';
        return (string)(($row['integrity_issue'] ?? '') ?: 'Workflow review required');
    }
    if ($stage === 'scheduled') {
        return in_array((string)($row['appointment_status'] ?? ''), ['requested','pending_confirmation'], true)
            ? 'Appointment needs confirmation'
            : 'Appointment scheduled';
    }
    if ($stage === 'arrival') {
        return match ((string)($row['queue_status'] ?? '')) {
            'called' => 'Patient called',
            'seated' => 'Patient seated',
            default => 'Checked in and waiting',
        };
    }
    if ($stage === 'clinical') {
        return match ((string)($row['queue_status'] ?? '')) {
            'seated' => 'Ready in chair',
            'in_treatment' => 'Treatment in progress',
            default => (($row['encounter_status'] ?? '') === 'finalized' ? 'Clinical care completed' : 'Clinical care'),
        };
    }
    if ($stage === 'cashier') {
        return match ((string)($row['queue_status'] ?? '')) {
            'cashier_called' => 'At cashier',
            'billing_correction' => 'Billing correction required',
            default => ((float)($row['invoice_balance'] ?? 0) > 0.005 ? 'Payment due' : 'Waiting for cashier'),
        };
    }
    if ($stage === 'follow_up') {
        return ($row['follow_up_status'] ?? '') === 'scheduled' ? 'Follow-up scheduled' : 'Follow-up due';
    }
    if ($stage === 'completed') return 'Visit completed';
    if ($stage === 'closed') {
        return match ((string)($row['appointment_status'] ?? $row['queue_status'] ?? '')) {
            'no_show' => 'No-show',
            'left_before_treatment' => 'Left before treatment',
            default => 'Visit cancelled or closed',
        };
    }
    return 'Patient registered';
}

function journey_stage_icon(string $stage): string
{
    return match ($stage) {
        'scheduled' => 'calendar',
        'arrival' => 'queue',
        'clinical' => 'clinical',
        'cashier' => 'receipt',
        'follow_up' => 'repeat',
        'completed' => 'check',
        'closed' => 'clock',
        'exception' => 'shield',
        default => 'patient',
    };
}

function journey_next_action(array $row): array
{
    $stage = (string)($row['stage_code'] ?? '');
    $appointmentId = (int)($row['appointment_id'] ?? 0);
    $queueId = (int)($row['queue_id'] ?? 0);
    $encounterId = (int)($row['encounter_id'] ?? 0);
    $patientId = (int)($row['patient_id'] ?? 0);
    $invoiceId = (int)($row['invoice_id'] ?? 0);

    if ($stage === 'exception') {
        if (($row['queue_status'] ?? '') === 'billing_correction' && $encounterId > 0 && task_context_page_allowed('clinical_workspace')) {
            return ['Open billing correction', 'clinical_workspace.php?encounter_id=' . $encounterId, 'warning'];
        }
        if ($appointmentId > 0 && task_context_page_allowed('appointments')) {
            return ['Review appointment', 'appointments.php?view=history', 'warning'];
        }
        return ['Review patient record', 'patients.php?action=view&id=' . $patientId, 'warning'];
    }
    if ($stage === 'scheduled') {
        $isToday = !empty($row['appointment_start']) && date('Y-m-d', strtotime((string)$row['appointment_start'])) === date('Y-m-d');
        if ($isToday && $appointmentId > 0 && task_context_page_allowed('queue')) {
            return ['Check in patient', 'queue.php?appointment_id=' . $appointmentId, 'primary'];
        }
        return ['Open appointment', 'appointments.php?view=upcoming', 'ghost'];
    }
    if ($stage === 'arrival') return ['Open clinic queue', 'queue.php', 'primary'];
    if ($stage === 'clinical') {
        if ($encounterId > 0 && task_context_page_allowed('clinical_workspace')) {
            return ['Continue clinical care', 'clinical_workspace.php?encounter_id=' . $encounterId, 'primary'];
        }
        return ['Open clinic queue', 'queue.php', 'primary'];
    }
    if ($stage === 'cashier') {
        $url = 'billing.php?patient_id=' . $patientId;
        if ($queueId > 0) $url .= '&queue_entry_id=' . $queueId;
        if ($invoiceId > 0) $url .= '&invoice_id=' . $invoiceId;
        return ['Open Cashier Register', $url, 'primary'];
    }
    if ($stage === 'follow_up') return ['Manage follow-up', 'followups.php?patient_id=' . $patientId, 'primary'];
    if ($stage === 'completed') return ['Open patient record', 'patients.php?action=view&id=' . $patientId, 'ghost'];
    if ($stage === 'closed' && $appointmentId > 0) return ['Review appointment', 'appointments.php?view=history', 'ghost'];
    return ['Open patient record', 'patients.php?action=view&id=' . $patientId, 'ghost'];
}

[$patientScope, $patientScopeArgs] = task_patient_scope_condition('p.id');
$branchQueueSql = $branchId > 0 ? ' AND COALESCE(q.branch_id,p.branch_id)=?' : '';
$branchAppointmentSql = $branchId > 0 ? ' AND COALESCE(a.branch_id,p.branch_id)=?' : '';
$selectedPatientSql = $selectedPatientId > 0 ? ' AND p.id=?' : '';

$financeAggregate = 'LEFT JOIN ('
    . 'SELECT i.queue_entry_id,MAX(i.id) latest_invoice_id,COUNT(*) invoice_count,SUM(i.amount) invoice_total,SUM(i.paid_amount) invoice_paid,'
    . 'SUM(CASE WHEN i.status IN ("unpaid","partial") THEN GREATEST(i.amount-i.paid_amount,0) ELSE 0 END) invoice_balance,'
    . 'CASE WHEN SUM(CASE WHEN i.status IN ("unpaid","partial") THEN GREATEST(i.amount-i.paid_amount,0) ELSE 0 END)>0.005 '
    . 'THEN CASE WHEN SUM(i.paid_amount)>0.005 THEN "partial" ELSE "unpaid" END '
    . 'WHEN COUNT(*)>0 THEN "paid" ELSE NULL END invoice_status,MIN(i.patient_id) min_patient_id,MAX(i.patient_id) max_patient_id '
    . 'FROM `' . table('invoices') . '` i WHERE i.queue_entry_id IS NOT NULL AND i.status NOT IN ("voided","refunded") GROUP BY i.queue_entry_id'
    . ') fin ON fin.queue_entry_id=q.id';

$queueSelect = 'SELECT "queue" visit_source,q.id visit_id,q.visit_id canonical_visit_id,cv.visit_code canonical_visit_code,cv.state_code canonical_state,cv.integrity_status canonical_integrity,p.id patient_id,p.patient_no,'
    . 'TRIM(CONCAT(p.first_name," ",COALESCE(NULLIF(p.middle_name,""),"")," ",p.last_name)) patient_name,p.medical_alerts,'
    . 'COALESCE(q.branch_id,p.branch_id) branch_id,b.name branch_name,a.id appointment_id,a.status appointment_status,a.start_at appointment_start,a.end_at appointment_end,'
    . 'a.series_id,a.series_sequence,a.planned_visit_label,COALESCE(a.service_name,q.service_name,"Clinic visit") service_name,'
    . 'COALESCE(NULLIF(a.dentist_name,""),NULLIF(a.inhouse_provider_name,""),NULLIF(q.dentist_name,""),provider.name,"Unassigned") provider_name,'
    . 'COALESCE(a.provider_type,q.provider_type,"dentist") provider_type,q.id queue_id,q.queue_no,q.status queue_status,q.chair_name queue_chair,q.checked_in_at,'
    . 'c.id encounter_id,c.status encounter_status,c.dentist_user_id encounter_dentist_user_id,c.paused_at,c.pause_reason,'
    . 'fin.latest_invoice_id invoice_id,COALESCE(fin.invoice_count,0) invoice_count,COALESCE(fin.invoice_total,0) invoice_total,COALESCE(fin.invoice_paid,0) invoice_paid,COALESCE(fin.invoice_balance,0) invoice_balance,fin.invoice_status,'
    . 'f.id follow_up_id,f.status follow_up_status,f.recommended_date,'
    . 'COALESCE(q.checked_in_at,a.start_at) visit_at,COALESCE(q.updated_at,q.checked_in_at,a.updated_at,a.created_at) activity_at,'
    . 'CASE '
    . 'WHEN q.appointment_id IS NOT NULL AND a.id IS NULL THEN "Linked appointment is missing" '
    . 'WHEN a.id IS NOT NULL AND a.patient_id<>q.patient_id THEN "Appointment and queue belong to different patients" '
    . 'WHEN c.id IS NOT NULL AND c.patient_id<>q.patient_id THEN "Encounter and queue belong to different patients" '
    . 'WHEN fin.min_patient_id IS NOT NULL AND (fin.min_patient_id<>q.patient_id OR fin.max_patient_id<>q.patient_id) THEN "Invoice and queue belong to different patients" '
    . 'ELSE "" END integrity_issue '
    . 'FROM `' . table('queue_entries') . '` q '
    . 'JOIN `' . table('patients') . '` p ON p.id=q.patient_id '
    . 'LEFT JOIN `' . table('visits') . '` cv ON cv.id=q.visit_id '
    . 'LEFT JOIN `' . table('appointments') . '` a ON a.id=q.appointment_id '
    . 'LEFT JOIN `' . table('branches') . '` b ON b.id=COALESCE(q.branch_id,p.branch_id) '
    . 'LEFT JOIN `' . table('users') . '` provider ON provider.id=COALESCE(q.dentist_user_id,q.inhouse_provider_user_id) '
    . 'LEFT JOIN `' . table('clinical_encounters') . '` c ON c.queue_entry_id=q.id '
    . $financeAggregate . ' '
    . 'LEFT JOIN `' . table('follow_ups') . '` f ON f.id=(SELECT f2.id FROM `' . table('follow_ups') . '` f2 WHERE f2.encounter_id=c.id ORDER BY f2.id DESC LIMIT 1) '
    . 'WHERE p.deleted_at IS NULL' . $patientScope . $branchQueueSql . $selectedPatientSql;

$appointmentSelect = 'SELECT "appointment" visit_source,a.id visit_id,a.visit_id canonical_visit_id,cv.visit_code canonical_visit_code,cv.state_code canonical_state,cv.integrity_status canonical_integrity,p.id patient_id,p.patient_no,'
    . 'TRIM(CONCAT(p.first_name," ",COALESCE(NULLIF(p.middle_name,""),"")," ",p.last_name)) patient_name,p.medical_alerts,'
    . 'COALESCE(a.branch_id,p.branch_id) branch_id,b.name branch_name,a.id appointment_id,a.status appointment_status,a.start_at appointment_start,a.end_at appointment_end,'
    . 'a.series_id,a.series_sequence,a.planned_visit_label,a.service_name,'
    . 'COALESCE(NULLIF(a.dentist_name,""),NULLIF(a.inhouse_provider_name,""),"Unassigned") provider_name,a.provider_type,'
    . 'NULL queue_id,NULL queue_no,NULL queue_status,NULL queue_chair,NULL checked_in_at,NULL encounter_id,NULL encounter_status,NULL encounter_dentist_user_id,NULL paused_at,NULL pause_reason,'
    . 'NULL invoice_id,0 invoice_count,0 invoice_total,0 invoice_paid,0 invoice_balance,NULL invoice_status,'
    . 'f.id follow_up_id,f.status follow_up_status,f.recommended_date,a.start_at visit_at,COALESCE(a.updated_at,a.created_at) activity_at,'
    . 'CASE WHEN a.status IN ("checked_in","in_treatment","awaiting_billing") THEN "Appointment operational status has no linked queue visit" '
    . 'WHEN a.start_at<NOW() AND a.status IN ("requested","pending_confirmation","confirmed") THEN "Past appointment has no linked check-in outcome" '
    . 'ELSE "" END integrity_issue '
    . 'FROM `' . table('appointments') . '` a '
    . 'JOIN `' . table('patients') . '` p ON p.id=a.patient_id '
    . 'LEFT JOIN `' . table('visits') . '` cv ON cv.id=a.visit_id '
    . 'LEFT JOIN `' . table('branches') . '` b ON b.id=COALESCE(a.branch_id,p.branch_id) '
    . 'LEFT JOIN `' . table('follow_ups') . '` f ON f.id=a.follow_up_id '
    . 'WHERE p.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM `' . table('queue_entries') . '` qx WHERE qx.appointment_id=a.id)' . $patientScope . $branchAppointmentSql . $selectedPatientSql;

$baseArgs = [];
$baseArgs = array_merge($baseArgs, $patientScopeArgs);
if ($branchId > 0) $baseArgs[] = $branchId;
if ($selectedPatientId > 0) $baseArgs[] = $selectedPatientId;
$baseArgs = array_merge($baseArgs, $patientScopeArgs);
if ($branchId > 0) $baseArgs[] = $branchId;
if ($selectedPatientId > 0) $baseArgs[] = $selectedPatientId;

$stageSql = 'SELECT v.*,CASE '
    . 'WHEN v.integrity_issue<>"" OR v.queue_status="billing_correction" THEN "exception" '
    . 'WHEN v.appointment_status IN ("cancelled","no_show","left_before_treatment") OR v.queue_status="cancelled" THEN "closed" '
    . 'WHEN v.queue_status="completed" OR v.appointment_status="completed" THEN CASE WHEN v.follow_up_status IN ("pending","scheduled") THEN "follow_up" ELSE "completed" END '
    . 'WHEN v.queue_status IN ("waiting_billing","cashier_called") OR v.invoice_balance>0.005 OR v.encounter_status="finalized" THEN "cashier" '
    . 'WHEN v.queue_status IN ("seated","in_treatment") OR v.encounter_status IN ("draft","pending_review","addendum") THEN "clinical" '
    . 'WHEN v.queue_status IN ("waiting","called") THEN "arrival" '
    . 'WHEN v.appointment_status IN ("requested","pending_confirmation","confirmed") THEN "scheduled" '
    . 'ELSE "completed" END stage_code '
    . 'FROM (' . $queueSelect . ' UNION ALL ' . $appointmentSelect . ') v';

$outerWhere = [];
$outerArgs = [];
if ($q !== '') {
    $like = module_like_term($q);
    $outerWhere[] = '(j.patient_no LIKE ? OR j.patient_name LIKE ? OR j.queue_no LIKE ? OR j.service_name LIKE ? OR j.provider_name LIKE ? OR j.planned_visit_label LIKE ?)';
    array_push($outerArgs, $like, $like, $like, $like, $like, $like);
}
if ($stageFilter !== '') {
    $outerWhere[] = 'j.stage_code=?';
    $outerArgs[] = $stageFilter;
}
if ($range === 'active') {
    $outerWhere[] = '((j.queue_id IS NOT NULL AND COALESCE(j.queue_status,"") NOT IN ("completed","cancelled")) '
        . 'OR (j.queue_id IS NULL AND j.appointment_start>=CURDATE() AND j.appointment_start<DATE_ADD(CURDATE(),INTERVAL 31 DAY)) '
        . 'OR DATE(j.visit_at)=CURDATE())';
} elseif ($range === 'today') {
    $outerWhere[] = 'DATE(j.visit_at)=CURDATE()';
} elseif ($range === 'upcoming') {
    $outerWhere[] = 'j.queue_id IS NULL AND j.appointment_start>=CURDATE() AND j.appointment_start<DATE_ADD(CURDATE(),INTERVAL 31 DAY)';
} elseif ($range === 'recent') {
    $outerWhere[] = 'j.visit_at>=DATE_SUB(CURDATE(),INTERVAL 30 DAY) AND j.visit_at<DATE_ADD(CURDATE(),INTERVAL 1 DAY)';
}
$whereSql = $outerWhere ? ' WHERE ' . implode(' AND ', $outerWhere) : '';

$countStmt = $pdo->prepare('SELECT COUNT(*) FROM (' . $stageSql . ') j' . $whereSql);
$countStmt->execute(array_merge($baseArgs, $outerArgs));
$total = (int)$countStmt->fetchColumn();
$pages = max(1, (int)ceil($total / $perPage));
$page = min($page, $pages);
$offset = ($page - 1) * $perPage;

$orderSql = ' ORDER BY CASE WHEN j.stage_code="exception" THEN 0 WHEN j.stage_code IN ("arrival","clinical","cashier") THEN 1 WHEN j.stage_code="scheduled" THEN 2 ELSE 3 END,'
    . 'CASE WHEN j.stage_code="scheduled" THEN j.visit_at END ASC,j.activity_at DESC,j.visit_id DESC';
$listStmt = $pdo->prepare('SELECT * FROM (' . $stageSql . ') j' . $whereSql . $orderSql . ' LIMIT ' . $perPage . ' OFFSET ' . $offset);
$listStmt->execute(array_merge($baseArgs, $outerArgs));
$rows = $listStmt->fetchAll();

$selectedVisit = null;
$events = [];
$visitToken = trim((string)($_GET['visit'] ?? ''));
if (preg_match('/^(queue|appointment):(\d+)$/', $visitToken, $match)) {
    $source = $match[1];
    $visitId = (int)$match[2];
    $selectedStmt = $pdo->prepare('SELECT * FROM (' . $stageSql . ') j WHERE j.visit_source=? AND j.visit_id=? LIMIT 1');
    $selectedStmt->execute(array_merge($baseArgs, [$source, $visitId]));
    $selectedVisit = $selectedStmt->fetch() ?: null;
    if ($selectedVisit) {
        $eventWhere = ['e.patient_id=?'];
        $eventArgs = [(int)$selectedVisit['patient_id']];
        if (!empty($selectedVisit['canonical_visit_id']) && schema_column_exists(table('visit_workflow_events'),'visit_id')) {
            $eventWhere[] = 'e.visit_id=?';
            $eventArgs[] = (int)$selectedVisit['canonical_visit_id'];
        } elseif (!empty($selectedVisit['queue_id']) && !empty($selectedVisit['appointment_id'])) {
            $eventWhere[] = '(e.queue_entry_id=? OR e.appointment_id=?)';
            $eventArgs[] = (int)$selectedVisit['queue_id'];
            $eventArgs[] = (int)$selectedVisit['appointment_id'];
        } elseif (!empty($selectedVisit['queue_id'])) {
            $eventWhere[] = 'e.queue_entry_id=?';
            $eventArgs[] = (int)$selectedVisit['queue_id'];
        } elseif (!empty($selectedVisit['appointment_id'])) {
            $eventWhere[] = 'e.appointment_id=?';
            $eventArgs[] = (int)$selectedVisit['appointment_id'];
        }
        $eventStmt = $pdo->prepare('SELECT e.*,u.name recorded_by_name FROM `' . table('visit_workflow_events') . '` e LEFT JOIN `' . table('users') . '` u ON u.id=e.recorded_by WHERE ' . implode(' AND ', $eventWhere) . ' ORDER BY e.created_at DESC,e.id DESC LIMIT 100');
        $eventStmt->execute($eventArgs);
        $events = $eventStmt->fetchAll();
    }
}

$pendingRequests = 0;
if (in_array($role, ['super_admin','clinic_admin','receptionist','staff'], true) && public_intake_schema_ready()) {
    $pendingRequests = (int)$pdo->query('SELECT COUNT(*) FROM `' . table('appointment_requests') . '` WHERE status IN ("submitted","needs_verification","reviewing")')->fetchColumn();
}

$stageCounts = array_fill_keys(['exception','scheduled','arrival','clinical','cashier','follow_up','completed','closed'], 0);
try {
    $summaryStmt = $pdo->prepare('SELECT j.stage_code,COUNT(*) total FROM (' . $stageSql . ') j WHERE ((j.queue_id IS NOT NULL AND COALESCE(j.queue_status,"") NOT IN ("completed","cancelled")) OR DATE(j.visit_at)=CURDATE() OR (j.queue_id IS NULL AND j.appointment_start>=CURDATE() AND j.appointment_start<DATE_ADD(CURDATE(),INTERVAL 31 DAY))) GROUP BY j.stage_code');
    $summaryStmt->execute($baseArgs);
    foreach ($summaryStmt->fetchAll() as $summaryRow) $stageCounts[(string)$summaryRow['stage_code']] = (int)$summaryRow['total'];
} catch (Throwable $summaryError) {
    error_log('Visit workflow summary warning: ' . $summaryError->getMessage());
}

render_header('Patient Journey');
?>
<div class="toolbar">
    <?php if($selectedPatientId>0):?>
    <div><strong>Dental visit history</strong><span class="muted">Lifetime appointments, walk-ins, dental treatment, billing handoffs, and follow-ups for this patient only.</span></div>
    <div class="toolbar-actions"><a class="btn primary" href="appointments.php?patient_id=<?=$selectedPatientId?>">Book appointment</a><a class="btn ghost" href="patients.php?action=view&amp;id=<?=$selectedPatientId?>">Open dental record</a><?php if(task_context_page_allowed('queue')):?><a class="btn ghost" href="queue.php?patient_id=<?=$selectedPatientId?>">Register / find walk-in</a><?php endif;?></div>
    <?php else:?>
    <div><strong>Visit command center</strong><span class="muted">Each row is one appointment or walk-in visit. Future appointments, today’s queue, treatment, billing, and follow-up are never mixed with another visit.</span></div>
    <div class="toolbar-actions">
        <?php if ($pendingRequests): ?><a class="btn ghost" href="appointment_requests.php">Online requests · <?= $pendingRequests ?></a><?php endif; ?>
        <?php if (task_context_page_allowed('appointments')): ?><a class="btn ghost" href="appointments.php">Appointments</a><?php endif; ?>
        <?php if (task_context_page_allowed('queue')): ?><a class="btn primary" href="queue.php">Clinic queue</a><?php endif; ?>
    </div>
    <?php endif;?>
</div>

<?php if($selectedPatientId===0 || $total>0):?><div data-live-queue-region="visit-journey-summary"><section class="journey-summary-grid" aria-label="Visit stage summary">
    <?php foreach ([
        'exception'=>['Needs review','shield'],
        'scheduled'=>['Scheduled','calendar'],
        'arrival'=>['Waiting','queue'],
        'clinical'=>['Clinical','clinical'],
        'cashier'=>['Cashier','receipt'],
        'follow_up'=>['Follow-up','repeat'],
    ] as $code=>$definition): ?>
        <a class="journey-summary-card journey-<?=e($code)?><?=((int)($stageCounts[$code]??0)===0)?' zero':''?>" href="<?=e(module_query_url(['stage'=>$code,'page'=>1]))?>">
            <span class="journey-summary-icon"><?=dental_icon($definition[1])?></span>
            <span><strong><?=number_format((int)($stageCounts[$code]??0))?></strong><small><?=e($definition[0])?></small></span>
        </a>
    <?php endforeach; ?>
</section></div><?php endif;?>

<form method="get" class="card management-filter-bar journey-filter-bar">
    <?php if($selectedPatientId>0):?><input type="hidden" name="patient_id" value="<?=$selectedPatientId?>"><?php endif;?>
    <label>Search<input type="search" name="q" value="<?=e($q)?>" placeholder="<?=$selectedPatientId>0?'Search this patient’s visits, services or providers':'Patient, queue, service, provider or visit label'?>"></label>
    <label>Range<select name="range">
        <option value="active" <?=$range==='active'?'selected':''?>>Active + next 30 days</option>
        <option value="today" <?=$range==='today'?'selected':''?>>Today</option>
        <option value="upcoming" <?=$range==='upcoming'?'selected':''?>>Upcoming 30 days</option>
        <option value="recent" <?=$range==='recent'?'selected':''?>>Recent 30 days</option>
        <option value="all" <?=$range==='all'?'selected':''?>>All visits</option>
    </select></label>
    <label>Stage<select name="stage"><option value="">All stages</option><?php foreach(['exception','scheduled','arrival','clinical','cashier','follow_up','completed','closed'] as $stage):?><option value="<?=e($stage)?>" <?=$stageFilter===$stage?'selected':''?>><?=e(journey_stage_label($stage,[]))?></option><?php endforeach;?></select></label>
    <?php if (superuser_primary_workspace()): ?><label>Branch<select name="branch_id"><option value="0">All branches</option><?php foreach($branches as $branch):?><option value="<?=(int)$branch['id']?>" <?=$branchId===(int)$branch['id']?'selected':''?>><?=e((string)$branch['name'])?></option><?php endforeach;?></select></label><?php endif; ?>
    <label>Rows<select name="per_page"><?php foreach([10,25,50,100] as $size):?><option value="<?=$size?>" <?=$perPage===$size?'selected':''?>><?=$size?></option><?php endforeach;?></select></label>
    <button class="btn primary">Apply</button><a class="btn ghost" href="visit_workflow.php<?=$selectedPatientId>0?'?patient_id='.$selectedPatientId:''?>">Reset</a>
</form>

<div data-live-queue-region="visit-journey-flow">
<?php if ($selectedVisit): $selectedStage=(string)$selectedVisit['stage_code']; $selectedAction=journey_next_action($selectedVisit); ?>
<section class="card journey-selected-card">
    <div class="card-head"><div><span class="eyebrow">Selected visit</span><h2><?=e((string)$selectedVisit['patient_name'])?></h2><p><?=e((string)$selectedVisit['patient_no'])?> · <?=e((string)$selectedVisit['service_name'])?> · <?=e(date('M j, Y g:i A',strtotime((string)$selectedVisit['visit_at'])))?></p></div><div class="inline-actions"><span class="journey-state journey-<?=e($selectedStage)?>"><i><?=dental_icon(journey_stage_icon($selectedStage))?></i><?=e(journey_stage_label($selectedStage,$selectedVisit))?></span><a href="<?=e($selectedAction[1])?>" class="btn <?=e($selectedAction[2])?>"><?=e($selectedAction[0])?></a><a href="<?=e(module_query_url(['visit'=>null]))?>">Close</a></div></div>
    <?php if(!empty($selectedVisit['integrity_issue'])):?><div class="alert danger"><strong>Workflow link requires review:</strong> <?=e((string)$selectedVisit['integrity_issue'])?>. No automatic record correction was performed.</div><?php endif;?>
    <div class="visit-chain-grid">
        <article class="visit-chain-step <?=!empty($selectedVisit['appointment_id'])?'complete':'empty'?>"><span>1</span><div><strong>Appointment</strong><small><?=!empty($selectedVisit['appointment_id'])?'#'.(int)$selectedVisit['appointment_id'].' · '.e(module_status((string)$selectedVisit['appointment_status'])):'Walk-in / no appointment'?></small></div></article>
        <article class="visit-chain-step <?=!empty($selectedVisit['queue_id'])?'complete':'empty'?>"><span>2</span><div><strong>Check-in</strong><small><?=!empty($selectedVisit['queue_id'])?'Queue '.e((string)$selectedVisit['queue_no']).' · '.e(module_status((string)$selectedVisit['queue_status'])):'Not checked in'?></small></div></article>
        <article class="visit-chain-step <?=!empty($selectedVisit['encounter_id'])?'complete':'empty'?>"><span>3</span><div><strong>Clinical care</strong><small><?=!empty($selectedVisit['encounter_id'])?'Encounter #'.(int)$selectedVisit['encounter_id'].' · '.e(module_status((string)$selectedVisit['encounter_status'])):'No encounter'?></small></div></article>
        <article class="visit-chain-step <?=!empty($selectedVisit['invoice_count'])?'complete':'empty'?>"><span>4</span><div><strong>Billing</strong><small><?=!empty($selectedVisit['invoice_count'])?number_format((int)$selectedVisit['invoice_count']).' invoice(s) · Balance ₱'.number_format((float)$selectedVisit['invoice_balance'],2):'No invoice'?></small></div></article>
        <article class="visit-chain-step <?=!empty($selectedVisit['follow_up_id'])?'complete':'empty'?>"><span>5</span><div><strong>Follow-up</strong><small><?=!empty($selectedVisit['follow_up_id'])?e(module_status((string)$selectedVisit['follow_up_status'])).' · '.e((string)($selectedVisit['recommended_date']?:'Date not set')):'Not required or not recorded'?></small></div></article>
    </div>
    <div class="journey-detail-grid">
        <div><span>Canonical visit</span><strong><?=e((string)($selectedVisit['canonical_visit_code']?:'Pending link'))?></strong></div>
        <div><span>Canonical state</span><strong><?=e(module_status((string)($selectedVisit['canonical_state']?:$selectedStage)))?></strong></div>
        <div><span>Provider</span><strong><?=e((string)$selectedVisit['provider_name'])?></strong></div>
        <div><span>Chair</span><strong><?=e((string)($selectedVisit['queue_chair']?:'Not assigned'))?></strong></div>
        <div><span>Invoice total</span><strong>₱<?=number_format((float)$selectedVisit['invoice_total'],2)?></strong></div>
        <div><span>Paid</span><strong>₱<?=number_format((float)$selectedVisit['invoice_paid'],2)?></strong></div>
        <?php if(!empty($selectedVisit['series_id'])):?><div><span>Treatment series</span><strong><?=e((string)($selectedVisit['planned_visit_label']?:'Series visit'))?><?=!empty($selectedVisit['series_sequence'])?' · #'.(int)$selectedVisit['series_sequence']:''?></strong></div><?php endif;?>
    </div>
    <h3>Visit timeline</h3>
    <?php if(!$events):?><div class="empty-state">No event-ledger entries are recorded for this visit. The linked appointment, queue, encounter, invoice, and follow-up records above remain authoritative.</div><?php else:?><div class="timeline-list"><?php foreach($events as $event):?><article class="timeline-item"><time><?=e(date('M j, Y g:i A',strtotime((string)$event['created_at'])))?></time><div><strong><?=e((string)$event['stage_label'])?></strong><?php if($event['details']):?><p><?=nl2br(e((string)$event['details']))?></p><?php endif;?><small><?=e((string)($event['recorded_by_name']?:'System'))?></small></div></article><?php endforeach;?></div><?php endif;?>
</section>
<?php endif; ?>

<section class="card server-list-card">
    <div class="card-head"><div><h2><?=$selectedPatientId>0?'Dental visit history':'Visit list'?></h2><p><?=$selectedPatientId>0?'Chronological dental visits for the selected patient.':'One row represents one appointment or walk-in. Linked records are kept within the same visit chain.'?></p></div><?php module_render_list_count($total,'visits');?></div>
    <?php if($selectedPatientId>0&&$total===0):?><div class="setup-empty-state"><span class="eyebrow">No dental visits found</span><h3>This patient has no appointment or walk-in history in the selected range.</h3><p>Book an appointment or register a walk-in when care is needed. No clinic task is active from this record view.</p><div class="button-row"><a class="btn primary" href="appointments.php?patient_id=<?=$selectedPatientId?>">Book appointment</a><a class="btn ghost" href="patients.php?action=view&amp;id=<?=$selectedPatientId?>">Open dental record</a></div></div><?php else:?>
    <?php module_render_server_pagination($total,$page,$perPage,'page','per_page','visits'); ?>
    <div class="table-wrap journey-table-wrap" data-no-pagination><table data-no-pagination="true"><thead><tr><th>Visit</th><th>Patient / service</th><th>Provider / chair</th><th>Linked records</th><th>Financial</th><th>Current stage</th><th>Next action</th></tr></thead><tbody>
    <?php foreach($rows as $row): $stage=(string)$row['stage_code']; $action=journey_next_action($row); $visitParam=$row['visit_source'].':'.$row['visit_id']; ?>
        <tr class="journey-row journey-row-<?=e($stage)?>">
            <td><strong><?=e(date('M j, Y',strtotime((string)$row['visit_at'])))?></strong><small><?=e(date('g:i A',strtotime((string)$row['visit_at'])))?> · <?=e((string)($row['canonical_visit_code']?:($row['visit_source']==='appointment'?'Appointment':'Queue visit')))?></small><?php if($row['queue_no']):?><span class="badge">Queue <?=e((string)$row['queue_no'])?></span><?php endif;?></td>
            <td><strong><?=e((string)$row['patient_name'])?></strong><small><?=e((string)$row['patient_no'])?><?=!empty($row['medical_alerts'])?' · Dental safety alert':''?></small><span><?=e((string)$row['service_name'])?><?=!empty($row['planned_visit_label'])?' · '.e((string)$row['planned_visit_label']):''?></span></td>
            <td><strong><?=e((string)$row['provider_name'])?></strong><small><?=e(module_status((string)$row['provider_type']))?><?=!empty($row['queue_chair'])?' · Chair '.e((string)$row['queue_chair']):' · Chair not assigned'?></small></td>
            <td><div class="journey-link-flags"><span class="<?=$row['appointment_id']?'on':'off'?>" title="Appointment">A</span><span class="<?=$row['queue_id']?'on':'off'?>" title="Queue">Q</span><span class="<?=$row['encounter_id']?'on':'off'?>" title="Encounter">C</span><span class="<?=$row['invoice_count']?'on':'off'?>" title="Invoice">₱</span><span class="<?=$row['follow_up_id']?'on':'off'?>" title="Follow-up">F</span></div><?php if(($row['integrity_issue'] ?? null)):?><small class="danger-text"><?=e((string)$row['integrity_issue'])?></small><?php else:?><small>Visit links consistent</small><?php endif;?></td>
            <td><?php if((int)$row['invoice_count']>0):?><strong>₱<?=number_format((float)$row['invoice_balance'],2)?> due</strong><small>₱<?=number_format((float)$row['invoice_paid'],2)?> paid of ₱<?=number_format((float)$row['invoice_total'],2)?></small><?php else:?><span>—</span><small>No invoice</small><?php endif;?></td>
            <td><span class="journey-state journey-<?=e($stage)?>"><i><?=dental_icon(journey_stage_icon($stage))?></i><?=e(journey_stage_label($stage,$row))?></span></td>
            <td><a class="btn <?=e($action[2])?> compact" href="<?=e($action[1])?>"><?=e($action[0])?></a><a class="journey-details-link" href="<?=e(module_query_url(['visit'=>$visitParam]))?>">View visit chain</a></td>
        </tr>
    <?php endforeach; if(!$rows):?><tr><td colspan="7" class="muted">No visits match the selected range and filters.</td></tr><?php endif;?>
    </tbody></table></div>
    <?php module_render_server_pagination($total,$page,$perPage,'page','per_page','visits'); ?>
    <?php endif;?>
</section>
</div>
<?php render_footer(); ?>
