<?php

declare(strict_types=1);

require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_roles(['super_admin', 'clinic_admin', 'receptionist', 'staff']);
require_task_action('appointment_requests.manage');
require DENTAL_ROOT . '/app/layout.php';
require DENTAL_ROOT . '/app/module_helpers.php';
require_once DENTAL_ROOT . '/app/transaction_integrity.php';
require_once DENTAL_ROOT . '/app/appointment_planning.php';
require_once DENTAL_ROOT . '/app/appointment_schedule_guard.php';

$pdo = db();
$canTransferDentist = patient_dentist_transfer_allowed();

if (!public_intake_schema_ready()) {
    render_header('Online Appointment Requests');
    ?>
    <div class="card empty-state">
        <h2>Public appointment intake requires an update</h2>
        <p>The clinic database is connected, but the online appointment-request table has not been added yet.</p>
        <?php if (has_role("super_admin") && role_context_has("super_admin")): ?><a class="btn primary" href="system_setup.php?tab=updates">Open System Setup</a><?php else: ?><p>Ask the Superuser to apply pending updates in System Setup.</p><?php endif; ?>
    </div>
    <?php
    render_footer();
    exit;
}

function load_public_request(PDO $pdo, int $id): array
{
    $emailReady=schema_column_exists(table('appointment_requests'),'receipt_email_outbox_id');
    $emailSelect=$emailReady?',o.status receipt_email_status,o.attempts receipt_email_attempts,o.last_error receipt_email_error,o.sent_at receipt_email_sent_at':',NULL receipt_email_status,0 receipt_email_attempts,NULL receipt_email_error,NULL receipt_email_sent_at';
    $emailJoin=$emailReady?'LEFT JOIN `' . table('email_outbox') . '` o ON o.id=r.receipt_email_outbox_id ':'';
    $stmt = $pdo->prepare(
        'SELECT r.*, TRIM(CONCAT_WS(" ",p.first_name,NULLIF(p.middle_name,""),p.last_name)) linked_patient_name, p.patient_no linked_patient_no, '
        . 'CONCAT(u.name," · ",u.email) reviewer_name ' . $emailSelect . ' '
        . 'FROM `' . table('appointment_requests') . '` r '
        . 'LEFT JOIN `' . table('patients') . '` p ON p.id=r.patient_id '
        . 'LEFT JOIN `' . table('users') . '` u ON u.id=r.reviewed_by ' . $emailJoin
        . 'WHERE r.id=? LIMIT 1'
    );
    $stmt->execute([$id]);
    $request = $stmt->fetch();
    if (!$request) {
        throw new RuntimeException('Appointment request not found.');
    }
    $requestBranchId=(int)($request['branch_id']?:public_request_branch_id());
    if(!integrity_branch_scope_allowed($requestBranchId,null))throw new RuntimeException('This appointment request belongs to another clinic branch.');
    return $request;
}

function generate_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 < 10; $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)));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        verify_csrf();
        $requestId = (int) ($_POST['request_id'] ?? 0);
        $request = load_public_request($pdo, $requestId);
        $action = (string) ($_POST['action'] ?? '');

        if($action==='resend_receipt'){
            $delivery=public_request_email((string)($request['email']??''),(string)$request['first_name'],(string)$request['public_reference'],$request,public_clinic_profile(),$requestId);
            $feedback=email_delivery_feedback($delivery,'Appointment request receipt');
            audit('appointment_request_receipt_resent','appointment_request',$requestId,['delivery_status'=>$delivery['status']??'unknown','outbox_id'=>$delivery['id']??null]);
            flash($feedback['type'],$feedback['message']);
            header('Location: appointment_requests.php?id='.$requestId);exit;
        }

        if ($action === 'record_contact') {
            if (in_array((string)$request['status'],['scheduled','declined','cancelled'],true)) {
                throw new RuntimeException('Contact attempts for a scheduled request should be recorded from the patient Communication tab.');
            }
            $channel = (string) ($_POST['contact_channel'] ?? 'phone');
            if (!in_array($channel, ['email','sms','phone','portal','in_person','other'], true)) $channel = 'other';
            $outcome = (string) ($_POST['contact_outcome'] ?? 'attempted');
            $allowedOutcomes = ['connected','patient_agreed','no_answer','left_message','email_sent','sms_sent','awaiting_reply','unreachable','attempted'];
            if (!in_array($outcome, $allowedOutcomes, true)) {
                throw new RuntimeException('Select a valid contact outcome.');
            }
            if ($outcome === 'email_sent' && $channel !== 'email') {
                throw new RuntimeException('Choose Email as the contact channel before sending an email through the system.');
            }
            if ($outcome === 'sms_sent' && $channel !== 'sms') {
                throw new RuntimeException('Choose SMS as the contact channel when recording an SMS as sent.');
            }
            $summary = trim((string) ($_POST['contact_summary'] ?? ''));
            if ($summary === '') throw new RuntimeException('Enter a brief note describing the contact attempt.');
            $recipient = trim((string) ($_POST['contact_recipient'] ?? ''));
            $recipient = $recipient ?: ((string) ($request['email'] ?: $request['mobile']));
            $followUpNeeded = isset($_POST['follow_up_needed']) ? 1 : 0;
            $sendThroughSystem = $channel === 'email' && $outcome === 'email_sent';
            $deliveryStatus = $sendThroughSystem ? 'pending' : match ($outcome) {
                'connected', 'patient_agreed' => 'connected',
                'sms_sent', 'left_message' => 'reported_sent',
                'no_answer', 'unreachable' => 'not_reached',
                'awaiting_reply' => 'pending_reply',
                default => 'recorded',
            };
            $subject = 'Appointment request ' . (string) $request['public_reference'];
            $pdo->beginTransaction();
            $lockRequest=$pdo->prepare('SELECT * FROM `' . table('appointment_requests') . '` WHERE id=? FOR UPDATE');
            $lockRequest->execute([$requestId]);
            $request=$lockRequest->fetch();
            if(!$request)throw new RuntimeException('Appointment request not found.');
            if(!integrity_branch_scope_allowed((int)($request['branch_id']?:public_request_branch_id()),null))throw new RuntimeException('This appointment request belongs to another clinic branch.');
            if(in_array((string)$request['status'],['scheduled','declined','cancelled'],true))throw new RuntimeException('This request is already closed and cannot receive another request-level contact transaction.');
            $pdo->prepare(
                'INSERT INTO `' . table('communications') . '` '
                . '(patient_id,appointment_id,user_id,channel,direction,recipient,subject,summary,delivery_status,follow_up_needed,communicated_at,created_at) '
                . 'VALUES (?,NULL,? ,?,"outbound",? ,? ,? ,? ,?,NOW(),NOW())'
            )->execute([
                $request['patient_id'] ?: null,
                (int) current_user()['id'],
                $channel,
                $recipient,
                $subject,
                '[' . ($sendThroughSystem ? 'email delivery requested' : str_replace('_', ' ', $outcome)) . '] ' . $summary,
                $deliveryStatus,
                $followUpNeeded,
            ]);
            $communicationId = (int)$pdo->lastInsertId();
            $emailDelivery = null;
            if ($sendThroughSystem) {
                if (!filter_var($recipient,FILTER_VALIDATE_EMAIL)) {
                    $emailDelivery=['id'=>0,'status'=>'not_available','error'=>'Enter a valid email recipient.','attempts'=>0];
                    $pdo->prepare('UPDATE `' . table('communications') . '` SET delivery_status="failed" WHERE id=?')->execute([$communicationId]);
                } else {
                    try {
                        $meta=['type'=>'appointment_request_contact','appointment_request_id'=>$requestId,'communication_id'=>$communicationId,'patient_id'=>(int)($request['patient_id']??0)];
                        $emailDelivery=queue_mail_message_with_attempt($recipient,$subject,'<p>'.nl2br(e($summary)).'</p>',$summary,$meta);
                        email_delivery_link_entity((int)($emailDelivery['id']??0),$meta);
                    } catch (Throwable $mailError) {
                        $emailDelivery=['id'=>0,'status'=>'failed','error'=>$mailError->getMessage(),'attempts'=>0];
                        $pdo->prepare('UPDATE `' . table('communications') . '` SET delivery_status="failed" WHERE id=?')->execute([$communicationId]);
                    }
                }
            }
            $newStatus = in_array((string) $request['status'], ['submitted','reviewing','approved'], true)
                ? 'needs_verification'
                : (string) $request['status'];
            $staffOutcome = $sendThroughSystem ? 'Email delivery requested' : ucwords(str_replace('_',' ',$outcome));
            $staffLine = '[' . date('Y-m-d H:i') . '] ' . $staffOutcome . ': ' . $summary;
            $combinedNotes = trim(implode("\n", array_filter([(string) ($request['staff_notes'] ?? ''), $staffLine])));
            $contactUpdate=$pdo->prepare(
                'UPDATE `' . table('appointment_requests') . '` SET status=?,staff_notes=?,reviewed_by=?,reviewed_at=NOW(),updated_at=NOW() WHERE id=? AND status=?'
            );
            $contactUpdate->execute([$newStatus, $combinedNotes, current_user()['id'], $requestId,(string)$request['status']]);
            if($contactUpdate->rowCount()!==1)throw new RuntimeException('The appointment request changed while the contact attempt was being recorded.');
            $pdo->commit();
            audit('appointment_request_contact_recorded', 'appointment_request', $requestId, [
                'channel'=>$channel,'outcome'=>$outcome,'follow_up_needed'=>$followUpNeeded,'summary'=>$summary,'communication_id'=>$communicationId,'delivery_status'=>$emailDelivery['status']??$deliveryStatus
            ]);
            if($emailDelivery){$feedback=email_delivery_feedback($emailDelivery,'Appointment-request email');flash($feedback['type'],'Contact attempt recorded. '.$feedback['message'].' The request remains pending until a schedule is agreed and confirmed.');}
            else flash('success', 'Contact attempt recorded. The request remains pending until a schedule is agreed and confirmed.');
            header('Location: appointment_requests.php?id=' . $requestId);
            exit;
        }

        if ($action === 'update_status') {
            $status = (string) ($_POST['status'] ?? 'reviewing');
            if (!in_array($status, ['reviewing', 'needs_verification', 'declined', 'cancelled'], true)) {
                throw new RuntimeException('Invalid request status.');
            }
            $notes = trim((string) ($_POST['staff_notes'] ?? ''));
            $pdo->beginTransaction();
            $lockRequest=$pdo->prepare('SELECT * FROM `' . table('appointment_requests') . '` WHERE id=? FOR UPDATE');
            $lockRequest->execute([$requestId]);
            $request=$lockRequest->fetch();
            if(!$request)throw new RuntimeException('Appointment request not found.');
            if(!integrity_branch_scope_allowed((int)($request['branch_id']?:public_request_branch_id()),null))throw new RuntimeException('This appointment request belongs to another clinic branch.');
            $oldStatus=(string)$request['status'];
            $allowedTransitions=[
                'submitted'=>['reviewing','needs_verification','declined','cancelled'],
                'reviewing'=>['needs_verification','declined','cancelled'],
                'needs_verification'=>['reviewing','declined','cancelled'],
                'approved'=>['reviewing','cancelled'],
            ];
            if($oldStatus!==$status&&!in_array($status,$allowedTransitions[$oldStatus]??[],true))throw new RuntimeException('This request cannot move from '.str_replace('_',' ',$oldStatus).' to '.str_replace('_',' ',$status).'.');
            $stmt = $pdo->prepare(
                'UPDATE `' . table('appointment_requests') . '` SET status=?,staff_notes=?,reviewed_by=?,reviewed_at=NOW(),updated_at=NOW() WHERE id=? AND status=?'
            );
            $stmt->execute([$status, $notes ?: null, current_user()['id'], $requestId,$oldStatus]);
            if($stmt->rowCount()!==1&&($oldStatus!==$status||trim((string)($request['staff_notes']??''))!==$notes))throw new RuntimeException('The appointment request changed while the review status was being saved.');
            audit('public_appointment_request_status_changed', 'appointment_request', $requestId, ['status' => $status, 'staff_notes' => $notes], ['status' => $oldStatus]);
            $pdo->commit();
            flash('success', 'Appointment request status updated.');
            header('Location: appointment_requests.php?id=' . $requestId);
            exit;
        }

        if ($action === 'quick_approve_requested_schedule') {
            if (!in_array((string)$request['status'], ['submitted','reviewing','needs_verification','approved'], true)) {
                throw new RuntimeException('Only an active appointment request can be approved.');
            }
            $quickBranchId = (int) ($request['branch_id'] ?: public_request_branch_id());
            $quickServiceId = (int) ($request['service_id'] ?? 0);
            if ($quickServiceId < 1) throw new RuntimeException('This request has no service selected. Use manual approval to choose the service first.');
            $quickService = service_definition($quickServiceId, $quickBranchId);
            if (!$quickService || !in_array(service_booking_scope($quickService), ['public','reception'], true)) {
                throw new RuntimeException('The requested service is no longer available for Reception booking. Use manual approval to choose an active service.');
            }
            if ((int)($request['patient_id'] ?? 0) < 1) {
                $match = $pdo->prepare('SELECT COUNT(*) FROM `' . table('patients') . '` WHERE deleted_at IS NULL AND branch_id=? AND LOWER(first_name)=LOWER(?) AND LOWER(last_name)=LOWER(?) AND birth_date=?');
                $match->execute([$quickBranchId, $request['first_name'], $request['last_name'], $request['birth_date']]);
                if ((int)$match->fetchColumn() > 0) {
                    throw new RuntimeException('Manual review is required because an existing patient with the same name and birth date was found. Link the correct patient or choose Create new patient intentionally.');
                }
            }
            $quickDate = (string) $request['preferred_date'];
            $quickTime = substr((string) $request['preferred_time'], 0, 5);
            $quickDoctorId = 0;
            if (service_provider_type($quickService) === 'dentist') {
                $availableDoctors = available_doctors_for_slot($quickBranchId, $quickServiceId, $quickDate, $quickTime, (string)($request['patient_type'] ?? '') === 'new');
                if (!$availableDoctors) throw new RuntimeException('The requested date and time are no longer available. Use manual approval to choose another slot and record the change note.');
                $preferredDoctorId = (int)($request['preferred_dentist_user_id'] ?? 0);
                $specificChoice = (($request['provider_preference_mode'] ?? 'specific') !== 'clinic_decides') && $preferredDoctorId > 0;
                $chosenDoctor = null;
                if ($specificChoice) {
                    foreach ($availableDoctors as $candidate) {
                        if ((int)$candidate['id'] === $preferredDoctorId) { $chosenDoctor = $candidate; break; }
                    }
                    if (!$chosenDoctor) throw new RuntimeException('The patient-selected Dentist is no longer available at the requested time. Use manual approval to choose another Dentist and record the change note.');
                } else {
                    $chosenDoctor = $availableDoctors[0];
                }
                $quickDoctorId = (int)($chosenDoctor['id'] ?? 0);
                if ($quickDoctorId < 1) throw new RuntimeException('No qualified Dentist can be assigned to the requested time.');
            }
            $_POST['date'] = $quickDate;
            $_POST['time'] = $quickTime;
            $_POST['service_id'] = (string)$quickServiceId;
            $_POST['dentist_user_id'] = (string)$quickDoctorId;
            $_POST['appointment_status'] = 'confirmed';
            $_POST['booking_source'] = 'online_request';
            $_POST['confirmation_source'] = 'online_request';
            $_POST['confirmation_note'] = 'Patient submitted this service, provider, date, and time through the online appointment request. Reception approved the requested schedule and sent the clinic confirmation.';
            if ((int)($request['patient_id'] ?? 0) > 0) {
                $_POST['patient_resolution'] = 'linked';
                $_POST['identity_verified'] = '1';
            } else {
                $_POST['patient_resolution'] = 'create';
                unset($_POST['identity_verified']);
            }
            if (trim((string)($request['medical_changes'] ?? '')) !== '') {
                $_POST['record_medical_changes'] = '1';
            }
            $action = 'schedule';
        }

        if ($action === 'schedule') {
            $pdo->beginTransaction();
            $lockRequest = $pdo->prepare('SELECT * FROM `' . table('appointment_requests') . '` WHERE id=? FOR UPDATE');
            $lockRequest->execute([$requestId]);
            $request = $lockRequest->fetch();
            if (!$request) throw new RuntimeException('Appointment request not found.');
            if(!integrity_branch_scope_allowed((int)($request['branch_id']?:public_request_branch_id()),null))throw new RuntimeException('This appointment request belongs to another clinic branch.');
            if(!schema_column_exists(table('appointments'),'chair_id'))throw new RuntimeException('Apply the v0.5.8.4 database update from System Setup before scheduling appointment requests.');
            if (!in_array((string)$request['status'],['submitted','reviewing','needs_verification','approved'],true)) {
                throw new RuntimeException('Only an active submitted, reviewing, verification, or approved request can be scheduled.');
            }
            $date = trim((string) ($_POST['date'] ?? ''));
            $time = trim((string) ($_POST['time'] ?? ''));
            $doctorId = (int) ($_POST['dentist_user_id'] ?? 0);
            $serviceId = (int) ($_POST['service_id'] ?? 0);
            $branchId = (int) ($request['branch_id'] ?: public_request_branch_id());
            $serviceRow = service_definition($serviceId, $branchId);
            if (!$serviceRow || !in_array(service_booking_scope($serviceRow),['public','reception'],true)) throw new RuntimeException('Choose a service that Reception is authorized to book.');
            $providerType = service_provider_type($serviceRow);
            $doctorRow = null;
            if ($providerType === 'dentist') {
                foreach (active_dentists($branchId) as $candidate) {
                    if ((int) $candidate['id'] === $doctorId) { $doctorRow = $candidate; break; }
                }
            } else {
                $doctorId = 0;
            }
            if(!appointment_confirmation_schema_ready())throw new RuntimeException('Apply Version 0.5.9.8.32 before converting online requests into appointments.');
            $appointmentStatus = (string) ($_POST['appointment_status'] ?? 'pending_confirmation');
            $bypassRequested=$providerType==='dentist' && schedule_bypass_allowed() && can('appointments.schedule_bypass',['super_admin','clinic_admin']) && isset($_POST['schedule_bypass']);
            $bypassReason=trim((string)($_POST['schedule_bypass_reason']??''));
            if($bypassRequested&&$bypassReason==='')throw new RuntimeException('Enter the reason for the schedule bypass.');
            if($bypassRequested){
                $manualTime=substr(trim((string)($_POST['bypass_time']??'')),0,5);
                $manualDoctorId=(int)($_POST['bypass_dentist_user_id']??0);
                if(!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/',$manualTime)||$manualDoctorId<1)throw new RuntimeException('Choose the bypass dentist and manual bypass time.');
                $time=$manualTime;$doctorId=$manualDoctorId;$doctorRow=null;
                foreach(active_dentists($branchId) as $candidate){if((int)$candidate['id']===$doctorId){$doctorRow=$candidate;break;}}
            }
            $rescheduleNote=trim((string)($_POST['reschedule_note']??''));
            $requestedTime=substr((string)$request['preferred_time'],0,5);
            $preferredDoctorId=(int)($request['preferred_dentist_user_id']??0);
            $doctorChanged=$providerType==='dentist' && $preferredDoctorId>0 && $doctorId!==$preferredDoctorId;
            $serviceChanged=(int)($request['service_id']??0)>0 && $serviceId!==(int)$request['service_id'];
            $scheduleChanged=$date!==(string)$request['preferred_date'] || substr($time,0,5)!==$requestedTime || $doctorChanged || $serviceChanged;
            if($scheduleChanged&&$rescheduleNote==='')throw new RuntimeException('Enter a change note because the requested service, date, time, or provider changed.');
            if ($date === '' || $time === '' || ($providerType==='dentist' && !$doctorRow)) {
                throw new RuntimeException($providerType==='inhouse'?'Complete the service date and time.':'Complete the schedule, dentist, and service fields.');
            }
            if($providerType==='dentist'&&$doctorRow){$readinessReason=null;if(!dentist_service_readiness($doctorId,$serviceRow,$readinessReason))throw new RuntimeException('The selected Dentist cannot provide this service yet: '.($readinessReason?:'credentials or clinical privilege are incomplete.').' Choose another qualified Dentist or correct the professional record.');}
            $duration = (int) $serviceRow['duration_minutes'];
            $providerName = $providerType==='inhouse' ? 'In-house clinic team' : (string)$doctorRow['name'];
            $service = (string) $serviceRow['name'];
            $requestAllergiesNormalized = function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($request['allergies'] ?? '') : (trim((string)($request['allergies'] ?? '')) ?: null);
            $requestMedicationsNormalized = function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($request['medications'] ?? '') : (trim((string)($request['medications'] ?? '')) ?: null);
            $requestConditionsNormalized = function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($request['conditions'] ?? '') : (trim((string)($request['conditions'] ?? '')) ?: null);
            $requestMedicalChangesNormalized = function_exists('dentalcare_normalize_medical_text') ? dentalcare_normalize_medical_text($request['medical_changes'] ?? '') : (trim((string)($request['medical_changes'] ?? '')) ?: null);
            $requestCombinedConditions = trim(implode("\n", array_filter([$requestConditionsNormalized, $requestMedicalChangesNormalized], static fn($value): bool => trim((string)$value) !== '')));
            if (!validate_service_slot($branchId,$serviceRow,$date,$time,$doctorId) && !$bypassRequested) {
                throw new RuntimeException($providerType==='inhouse'?'The selected in-house time has already reached capacity. Choose another schedule.':'The selected time is outside published availability or has already been booked. Choose another schedule or ask an Administrator to use Schedule Bypass.');
            }
            if (!in_array($appointmentStatus, ['confirmed', 'pending_confirmation'], true)) {
                $appointmentStatus = 'pending_confirmation';
            }
            $confirmation=appointment_confirmation_capture($_POST,$appointmentStatus,'online_request');
            $startAt = date('Y-m-d H:i:s', strtotime($date . ' ' . $time));
            $endAt = date('Y-m-d H:i:s', strtotime($startAt . ' +' . $duration . ' minutes'));
            if (strtotime($startAt) < strtotime('-5 minutes')) {
                throw new RuntimeException('The appointment schedule cannot be in the past.');
            }

            $patientId = (int) ($request['patient_id'] ?? 0);
            $resolution = (string) ($_POST['patient_resolution'] ?? 'linked');
            if(in_array($resolution,['linked','existing'],true)&&!isset($_POST['identity_verified']))throw new RuntimeException('Confirm that staff verified the patient using name and birth date before linking this request.');
            if ($resolution === 'existing') {
                $patientId = (int) ($_POST['existing_patient_id'] ?? 0);
            } elseif ($resolution === 'create') {
                $patientNumber = generate_patient_number($pdo, $branchId);
                $stmt = $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 (?,?,?,?,?,?,?,?,?,?,?,?,?, ?,NOW())'
                );
                $stmt->execute([
                    $branchId,
                    $patientNumber,
                    $request['first_name'],
                    $request['middle_name'],
                    $request['last_name'],
                    $request['birth_date'],
                    $request['sex'],
                    $request['mobile'],
                    $request['email'],
                    $request['address'],
                    $request['emergency_contact'],
                    null,
                    'active',
                    current_user()['id'],
                ]);
                $patientId = (int) $pdo->lastInsertId();
                if ($requestAllergiesNormalized !== null || $requestMedicationsNormalized !== null || $requestCombinedConditions !== '') {
                    integrity_insert_reported_medical_history($pdo,$patientId,[
                        'allergies'=>$requestAllergiesNormalized ?? '',
                        'medications'=>$requestMedicationsNormalized ?? '',
                        'conditions'=>$requestCombinedConditions,
                    ],'patient_reported',(int)(current_user()['id'] ?? 0),'Initial information submitted through public appointment request ' . (string)$request['public_reference'] . '. Pending dentist review.');
                }
                if (!empty($request['guardian_name'])) {
                    integrity_insert_pending_guardian($pdo,$patientId,[
                        'name'=>(string)$request['guardian_name'],
                        'relationship'=>'Parent / Guardian',
                        'mobile'=>(string)($request['guardian_mobile'] ?? ''),
                        'email'=>'',
                        'address'=>'',
                    ]);
                }
                workflow_event($patientId,'registered','New patient record created',['appointment_request_id'=>$requestId],'Created from public request '.$request['public_reference']);
                audit('patient_created_from_public_request', 'patient', $patientId, ['request_id' => $requestId]);
            }

            if ($patientId < 1) {
                throw new RuntimeException('Link an existing patient or choose Create new patient before scheduling.');
            }
            $patientLock = $pdo->prepare('SELECT id,branch_id FROM `' . table('patients') . '` WHERE id=? AND deleted_at IS NULL FOR UPDATE');
            $patientLock->execute([$patientId]);
            $lockedPatient = $patientLock->fetch();
            if (!$lockedPatient) throw new RuntimeException('The selected patient record is not available.');
            if ((int)($lockedPatient['branch_id'] ?? 0) > 0 && (int)$lockedPatient['branch_id'] !== $branchId) {
                throw new RuntimeException('The selected patient belongs to another clinic branch.');
            }

            $transferRequested = $providerType==='dentist' && $canTransferDentist && isset($_POST['change_continuity_dentist']);
            $transferReason = trim((string) ($_POST['continuity_change_reason'] ?? ''));
            if($providerType==='dentist')validate_patient_dentist_continuity($patientId, $doctorId, $transferRequested, $transferReason);

            if (isset($_POST['update_contact'])) {
                $stmt = $pdo->prepare('UPDATE `' . table('patients') . '` SET mobile=?,email=?,updated_at=NOW() WHERE id=?');
                $stmt->execute([$request['mobile'], $request['email'], $patientId]);
            }
            if ($requestMedicalChangesNormalized !== null && isset($_POST['record_medical_changes'])) {
                integrity_insert_reported_medical_history($pdo,$patientId,[
                    'conditions'=>(string)$requestMedicalChangesNormalized,
                ],'patient_reported',(int)(current_user()['id'] ?? 0),'Medical changes submitted with returning-patient appointment request ' . (string)$request['public_reference'] . '. Pending dentist review.');
            }

            $chair = '';
            $chairId = 0;
            $inhouseProviderId = 0;
            if($providerType==='dentist'){
                $binding = function_exists('chair_resource_resolve_for_doctor')
                    ? chair_resource_resolve_for_doctor($doctorId,$branchId,$startAt,$endAt)
                    : doctor_chair_binding($doctorId,$branchId);
                if (!$binding['valid']) throw new RuntimeException((string)($binding['issue'] ?? 'The selected Dentist has no available chair for this time.') . ' Assign a unique chair before confirming this request.');
                $chair = (string) $binding['chair_name'];
                $chairId = (int) ($binding['chair_id'] ?? 0);
            } else {
                if (!inhouse_resource_schema_ready()) throw new RuntimeException('Apply Version 0.5.8.4 in System Setup before confirming an in-house request.');
                $assignment = inhouse_slot_assignment($branchId,$date,substr($time,0,5),$serviceRow,0,(int)($_POST['inhouse_provider_user_id']??0),(int)($_POST['chair_id']??0));
                if (empty($assignment['valid'])) throw new RuntimeException((string)($assignment['issue'] ?? 'No in-house resource is available for this time.'));
                $inhouseProviderId = (int) $assignment['provider_id'];
                $providerName = (string) $assignment['provider_name'];
                $chairId = (int) $assignment['chair_id'];
                $chair = (string) $assignment['chair_name'];
            }
            appointment_guard_assert_schedule_available($pdo,$branchId,$patientId,$doctorId,$chairId,$inhouseProviderId,$chair,$startAt,$endAt,0,$bypassRequested,$bypassReason);
            integrity_lock_appointment_resources($pdo,$branchId,$patientId,$doctorId,$chairId,$inhouseProviderId,$chair,$startAt,$endAt,0);
            $resourceConflicts = appointment_resource_conflicts($branchId,$patientId,$doctorId,$chair,$startAt,$endAt,0,(int)$serviceRow['preparation_minutes'],(int)$serviceRow['cleanup_minutes'],$inhouseProviderId,$chairId);
            if ($resourceConflicts['has_conflict']) throw new RuntimeException(appointment_conflict_message($resourceConflicts));

            $notes = 'Created from online request ' . $request['public_reference'] . ".\nReason: " . $request['reason'];
            $stmt = $pdo->prepare(
                'INSERT INTO `' . table('appointments') . '` (branch_id,patient_id,service_id,dentist_user_id,dentist_name,inhouse_provider_user_id,inhouse_provider_name,service_name,provider_type,start_at,end_at,chair_id,chair_name,status,appointment_origin,booking_source,source_request_id,reason_for_visit,confirmation_status,confirmation_source,confirmation_note,patient_agreed_at,schedule_bypass,schedule_bypass_reason,schedule_bypass_by,schedule_bypass_at,notes,created_by,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())'
            );
            $origin = $request['patient_type']==='new' ? 'public_new' : 'public_returning';
            $stmt->execute([
                $branchId,$patientId,$serviceId,$providerType==='dentist'?$doctorId:null,$providerType==='dentist'?$providerName:null,$inhouseProviderId?:null,$providerType==='inhouse'?$providerName:null,$service,$providerType,$startAt,$endAt,$chairId?:null,$chair ?: null,$appointmentStatus,
                $origin,$confirmation['booking_source'],$requestId,(string)$request['reason'],$appointmentStatus,$confirmation['confirmation_source'],$confirmation['confirmation_note'],$confirmation['patient_agreed_at'],$bypassRequested?1:0,$bypassReason?:null,$bypassRequested?(int)current_user()['id']:null,$bypassRequested?date('Y-m-d H:i:s'):null,$notes,current_user()['id'],
            ]);
            $appointmentId = (int) $pdo->lastInsertId();
            appointment_guard_mark_checked($pdo,$appointmentId,$bypassRequested?'bypass_approved':'passed',$bypassRequested?'Schedule bypass approved: '.$bypassReason:'Schedule guard passed from online request.');
            appointment_guard_sync_assignment($pdo,['id'=>$appointmentId,'branch_id'=>$branchId,'patient_id'=>$patientId,'provider_type'=>$providerType,'dentist_user_id'=>$providerType==='dentist'?$doctorId:0,'start_at'=>$startAt],$requestId,$providerType==='dentist'?'Provider assigned from online request.':'In-house service does not require dentist assignment.');
            if($providerType==='dentist')commit_patient_dentist_continuity($patientId, $doctorId, $transferRequested, $transferReason);
            $history = $pdo->prepare(
                'INSERT INTO `' . table('appointment_status_history') . '` (appointment_id,previous_status,new_status,reason,changed_by,created_at) VALUES (?,NULL,?,?,?,NOW())'
            );
            $history->execute([$appointmentId, $appointmentStatus, 'Scheduled from public request ' . $request['public_reference'], current_user()['id']]);
            $stmt = $pdo->prepare(
                'UPDATE `' . table('appointment_requests') . '` SET patient_id=?,appointment_id=?,status="scheduled",assignment_queue_status=IF(provider_preference_mode="clinic_decides","resolved",assignment_queue_status),staff_notes=?,reschedule_required=?,reschedule_note=?,reviewed_by=?,reviewed_at=NOW(),updated_at=NOW() WHERE id=? AND status<>"scheduled"'
            );
            $stmt->execute([
                $patientId,
                $appointmentId,
                trim((string) ($_POST['staff_notes'] ?? '')) ?: null,
                $scheduleChanged?1:0,
                $rescheduleNote?:null,
                current_user()['id'],
                $requestId,
            ]);
            if ($stmt->rowCount() !== 1) throw new RuntimeException('This appointment request was already processed by another user.');
            if (clinical_sequence_schema_ready()) {
                $pdo->prepare('INSERT INTO `' . table('patient_intakes') . '` (patient_id,appointment_request_id,appointment_id,intake_type,chief_complaint,allergy_changes,medication_changes,medical_changes,consent_acknowledged,status,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,1,"draft",NOW(),NOW())')->execute([
                    $patientId,$requestId,$appointmentId,$request['patient_type']==='new'?'new_patient':'returning_patient',(string)$request['reason'],$requestAllergiesNormalized ?? '',$requestMedicationsNormalized ?? '',$requestCombinedConditions
                ]);
            }
            workflow_event($patientId,'appointment_scheduled','Appointment scheduled from online request',['appointment_request_id'=>$requestId,'appointment_id'=>$appointmentId],date('M j, Y g:i A',strtotime($startAt)).' · '.$service.' · '.$providerName);
            audit('public_appointment_request_scheduled', 'appointment_request', $requestId, ['appointment_id' => $appointmentId, 'patient_id' => $patientId,'schedule_changed'=>$scheduleChanged,'service_changed'=>$serviceChanged,'requested_service_id'=>(int)($request['service_id']??0),'scheduled_service_id'=>$serviceId,'reschedule_note'=>$rescheduleNote,'schedule_bypass'=>$bypassRequested,'bypass_reason'=>$bypassReason,'continuity_transfer'=>$transferRequested,'continuity_reason'=>$transferReason]);
            $pdo->commit();

            $saved=$providerType==='inhouse' ? 'The request was scheduled and assigned to '.$providerName.($chair!==''?' Chair: '.$chair.'.':'') : 'The request was converted into a clinic appointment.';
            if($appointmentStatus==='confirmed'){
                $delivery=['id'=>0,'status'=>'not_available','error'=>'No valid patient email address.','attempts'=>0];
                if (filter_var((string) $request['email'], FILTER_VALIDATE_EMAIL)) {
                    $calendarUrl = app_absolute_url(appointment_calendar_relative_url(['id'=>$appointmentId,'patient_id'=>$patientId,'start_at'=>$startAt], true));
                    try {
                        $delivery=queue_template_email('appointment_confirmed',(string)$request['email'],[
                            'recipient_name'=>(string)$request['first_name'],
                            'appointment_datetime'=>date('F j, Y g:i A',strtotime($startAt)),
                            'service_name'=>$service,
                            'provider_name'=>$providerName,
                            'branch_name'=>(string)(public_clinic_profile()['name']??'Dental clinic'),
                            'calendar_url'=>$calendarUrl,
                        ],['type'=>'appointment_confirmed','appointment_id'=>$appointmentId,'request_reference'=>$request['public_reference']],true);
                    } catch (Throwable $mailError) {
                        $delivery=['id'=>0,'status'=>'failed','error'=>$mailError->getMessage(),'attempts'=>0];
                        error_log('Appointment confirmation email could not be queued: ' . $mailError->getMessage());
                    }
                }
                $feedback=email_delivery_feedback($delivery,'Appointment confirmation');
                flash($feedback['type'],$saved.' '.$feedback['message']);
            }else flash('success',$saved.' It remains pending confirmation, so no confirmation email was sent.');
            header('Location: appointment_requests.php?id=' . $requestId);
            exit;
        }
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        flash('error', $exception->getMessage());
        $redirectId = (int) ($_POST['request_id'] ?? 0);
        header('Location: appointment_requests.php' . ($redirectId ? '?id=' . $redirectId : ''));
        exit;
    }
}

$id = (int) ($_GET['id'] ?? 0);
if ($id > 0) {
    try {
        $request = load_public_request($pdo, $id);
    } catch (Throwable $exception) {
        http_response_code(404);
        exit(e($exception->getMessage()));
    }
    $requestBranchId = (int) ($request['branch_id'] ?: public_request_branch_id());
    $continuityFields = patient_continuity_schema_ready()
        ? ',primary_dentist_user_id,(SELECT name FROM `' . table('users') . '` WHERE id=`' . table('patients') . '`.primary_dentist_user_id LIMIT 1) primary_dentist_name'
        : ',NULL primary_dentist_user_id,NULL primary_dentist_name';
    $possible = $pdo->prepare(
        'SELECT id,patient_no,first_name,middle_name,last_name,birth_date,mobile,email' . $continuityFields . ' FROM `' . table('patients') . '` '
        . 'WHERE deleted_at IS NULL AND branch_id=? AND LOWER(first_name)=LOWER(?) AND LOWER(last_name)=LOWER(?) AND birth_date=? ORDER BY updated_at DESC,created_at DESC LIMIT 10'
    );
    $possible->execute([$requestBranchId,$request['first_name'], $request['last_name'], $request['birth_date']]);
    $possiblePatients = $possible->fetchAll();
    $linkedContinuityDoctor = !empty($request['patient_id']) ? patient_primary_dentist((int)$request['patient_id']) : null;
    $requestDentists = active_dentists($requestBranchId);
    $requestServices = appointment_booking_services_catalog($requestBranchId);
    $preferredAvailableDoctors=[];
    if((int)($request['service_id']??0)>0){$preferredAvailableDoctors=available_doctors_for_slot($requestBranchId,(int)$request['service_id'],(string)$request['preferred_date'],substr((string)$request['preferred_time'],0,5),(string)($request['patient_type']??'')==='new');}
    $preferredScheduleUnavailable=count($preferredAvailableDoctors)===0;
    $preferredDoctorIdForQuick = (int)($request['preferred_dentist_user_id'] ?? 0);
    $specificDoctorRequested = (($request['provider_preference_mode'] ?? 'specific') !== 'clinic_decides') && $preferredDoctorIdForQuick > 0;
    $preferredSpecificDoctorAvailable = !$specificDoctorRequested;
    if ($specificDoctorRequested) {
        foreach ($preferredAvailableDoctors as $doctorOption) {
            if ((int)($doctorOption['id'] ?? 0) === $preferredDoctorIdForQuick) { $preferredSpecificDoctorAvailable = true; break; }
        }
    }
    $quickApprovalReason = '';
    $quickApprovalReady = $request['status'] !== 'scheduled'
        && (int)($request['service_id'] ?? 0) > 0
        && !$preferredScheduleUnavailable
        && $preferredSpecificDoctorAvailable
        && ((int)($request['patient_id'] ?? 0) > 0 || empty($possiblePatients));
    if (!$quickApprovalReady && $request['status'] !== 'scheduled') {
        if ((int)($request['service_id'] ?? 0) < 1) $quickApprovalReason = 'The request has no service record. Choose a service manually.';
        elseif ($preferredScheduleUnavailable) $quickApprovalReason = 'The requested time is no longer available. Choose a new time and record the change note.';
        elseif (!$preferredSpecificDoctorAvailable) $quickApprovalReason = 'The patient-selected Dentist is no longer available at the requested time.';
        elseif ((int)($request['patient_id'] ?? 0) < 1 && !empty($possiblePatients)) $quickApprovalReason = 'A matching patient already exists. Link the correct patient before approving.';
        else $quickApprovalReason = 'Manual review is required for this request.';
    }
    render_header('Online Appointment Request');
    ?>
    <div class="toolbar"><div><a href="appointment_requests.php">← All requests</a><strong><?= e((string) $request['public_reference']) ?></strong></div><span class="badge request-status <?= e((string) $request['status']) ?>"><?= e(str_replace('_', ' ', (string) $request['status'])) ?></span></div>
    <section class="card"><div class="card-head"><div><h3>Request receipt email</h3><p><?=e((string)($request['email']?:'No patient email address'))?></p></div><div class="inline-actions"><?php if(!empty($request['receipt_email_status'])):?><span class="badge email-<?=e((string)$request['receipt_email_status'])?>"><?=e(email_delivery_label((string)$request['receipt_email_status']))?></span><?php else:?><span class="badge">Not sent</span><?php endif;?><?php if(filter_var((string)$request['email'],FILTER_VALIDATE_EMAIL)):?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="resend_receipt"><input type="hidden" name="request_id" value="<?=(int)$request['id']?>"><button class="btn ghost"><?=in_array((string)($request['receipt_email_status']??''),['failed','retry'],true)?'Retry receipt':'Resend receipt'?></button></form><?php endif;?></div></div><?php if(!empty($request['receipt_email_error'])):?><div class="alert warning"><?=e((string)$request['receipt_email_error'])?></div><?php endif;?></section>
    <?php if($preferredScheduleUnavailable&&$request['status']!=='scheduled'):?><div class="alert warning"><strong>Preferred schedule is no longer available.</strong> Contact the patient, choose another available date/time, and enter a reschedule note before creating the appointment.</div><?php elseif($preferredAvailableDoctors&&$request['status']!=='scheduled'):?><div class="alert success"><strong>Preferred schedule is available.</strong> <?php if(($request['provider_preference_mode']??'specific')==='clinic_decides'):?>The patient asked the clinic to assign the provider. Available at this time: <?=e(implode(', ',array_map(static fn($d)=>(string)$d['name'],$preferredAvailableDoctors)))?>.<?php else:?><?=e(implode(', ',array_map(static fn($d)=>(string)$d['name'],$preferredAvailableDoctors)))?> can accept this service at the requested time.<?php endif;?></div><?php endif;?>
    <section class="grid-2 request-review-grid">
        <div class="card">
            <div class="card-head"><div><span class="eyebrow"><?= e(ucfirst((string) $request['patient_type'])) ?> patient request</span><h2><?= e((string) person_name_from_parts((string)$request['first_name'],(string)($request['middle_name']??''),(string)$request['last_name'])) ?></h2></div><span class="urgency <?= e((string) $request['urgency']) ?>"><?= e((string) $request['urgency']) ?></span></div>
            <div class="request-section"><h3>Patient identity</h3><dl><dt>Patient number entered</dt><dd><?= e((string) ($request['patient_no_entered'] ?: 'Not provided')) ?></dd><dt>Birth date</dt><dd><?= e((string) $request['birth_date']) ?></dd><dt>Mobile</dt><dd><?= e((string) $request['mobile']) ?></dd><dt>Email</dt><dd><?= e((string) ($request['email'] ?: 'Not provided')) ?></dd><dt>Address</dt><dd><?= nl2br(e((string) ($request['address'] ?: 'Not provided'))) ?></dd><dt>Guardian</dt><dd><?= e((string) ($request['guardian_name'] ?: 'Not applicable')) ?> <?= $request['guardian_mobile'] ? '· ' . e((string) $request['guardian_mobile']) : '' ?></dd></dl></div>
            <div class="request-section"><h3>Appointment request</h3><dl><dt>Service</dt><dd><?= e((string) $request['service_name']) ?></dd><dt>Provider choice</dt><dd><?=e(($request['provider_preference_mode']??'specific')==='clinic_decides'?'Let the dental clinic decide':'Patient selected a specific dentist')?></dd><dt>Preferred dentist</dt><dd><?= e((string) ($request['preferred_dentist'] ?: 'No preference')) ?></dd><dt>Preferred schedule</dt><dd><?= e(date('F j, Y', strtotime((string) $request['preferred_date']))) ?> at <?= e(date('g:i A', strtotime((string) $request['preferred_time']))) ?></dd><dt>Alternate schedule</dt><dd><?= $request['alternate_date'] ? e(date('F j, Y', strtotime((string) $request['alternate_date']))) . ($request['alternate_time'] ? ' at ' . e(date('g:i A', strtotime((string) $request['alternate_time']))) : '') : 'Not provided' ?></dd><dt>Reason</dt><dd><?= nl2br(e((string) $request['reason'])) ?></dd></dl></div>
            <div class="request-section"><h3>Medical information</h3><dl><dt>Allergies</dt><dd><?= nl2br(e(function_exists('dentalcare_medical_display') ? dentalcare_medical_display($request['allergies'] ?? '', 'Not reported') : (string) ($request['allergies'] ?: 'Not reported'))) ?></dd><dt>Medications</dt><dd><?= nl2br(e(function_exists('dentalcare_medical_display') ? dentalcare_medical_display($request['medications'] ?? '', 'Not reported') : (string) ($request['medications'] ?: 'Not reported'))) ?></dd><dt>Conditions</dt><dd><?= nl2br(e(function_exists('dentalcare_medical_display') ? dentalcare_medical_display($request['conditions'] ?? '', 'Not reported') : (string) ($request['conditions'] ?: 'Not reported'))) ?></dd><dt>Recent changes</dt><dd><?= nl2br(e((string) ($request['medical_changes'] ?: 'Not reported'))) ?></dd></dl></div>
        </div>
        <div>
            <?php if ($request['status'] !== 'scheduled'): ?>
                <section class="card quick-approval-card">
                    <div class="card-head"><div><span class="eyebrow">Fast approval</span><h2>Approve the requested appointment</h2><p>Use this when the patient-requested service, provider, date, and time are still available.</p></div></div>
                    <?php if ($quickApprovalReady): ?>
                        <form method="post" class="quick-approve-form"><input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>"><input type="hidden" name="action" value="quick_approve_requested_schedule"><input type="hidden" name="request_id" value="<?= e((string) $request['id']) ?>"><button class="btn primary wide" type="submit">Approve requested schedule</button></form>
                        <p class="muted">This creates the patient record when needed, confirms <?= e(date('M j, Y g:i A', strtotime((string)$request['preferred_date'].' '.(string)$request['preferred_time']))) ?>, assigns the available dentist/chair, and sends the appointment confirmation.</p>
                    <?php else: ?>
                        <div class="alert warning"><strong>Manual approval needed.</strong> <?= e($quickApprovalReason) ?></div>
                    <?php endif; ?>
                </section>
                <details class="card manual-request-details" <?= $quickApprovalReady ? '' : 'open' ?>>
                    <summary><strong>Manual changes / advanced approval</strong><span>Open only if you need to link an existing patient, change the time, change the dentist, leave it pending, or record a contact attempt.</span></summary>
                <form method="post" class="form-grid schedule-request-form" data-slot-picker data-confirmation-form data-schedule-first data-patient-type="<?=e((string)($request['patient_type']??''))?>" data-branch-id="<?= e((string)$requestBranchId) ?>" data-preferred-date="<?=e((string)$request['preferred_date'])?>" data-preferred-time="<?=e(substr((string)$request['preferred_time'],0,5))?>" data-preferred-doctor="<?=e((string)($linkedContinuityDoctor['id'] ?? $request['preferred_dentist_user_id'] ?? ''))?>" data-preferred-service="<?=e((string)($request['service_id']??''))?>">
                    <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>"><input type="hidden" name="action" value="schedule"><input type="hidden" name="request_id" value="<?= e((string) $request['id']) ?>">
                    <h2 class="wide">Manual schedule approval</h2>
                    <?php if ($request['patient_id']): ?>
                        <label class="wide check-label"><input type="radio" name="patient_resolution" value="linked" checked><span>Use linked patient: <strong><?= e((string) ($request['linked_patient_name'] ?: 'Existing patient')) ?></strong> <?= $request['linked_patient_no'] ? '· ' . e((string) $request['linked_patient_no']) : '' ?></span></label>
                    <?php endif; ?>
                    <?php if ($possiblePatients): ?>
                        <label class="wide check-label"><input type="radio" name="patient_resolution" value="existing" <?= !$request['patient_id'] ? 'checked' : '' ?>><span>Link another existing patient</span></label>
                        <label class="wide">Existing patient<select name="existing_patient_id" data-continuity-patient><option value="">Select patient</option><?php foreach ($possiblePatients as $patient): ?><option value="<?= e((string) $patient['id']) ?>" data-primary-dentist-id="<?=e((string)($patient['primary_dentist_user_id'] ?? ''))?>" data-primary-dentist-name="<?=e((string)($patient['primary_dentist_name'] ?? ''))?>"><?= e((string) $patient['last_name'] . ', ' . (string) $patient['first_name'] . ((string)($patient['middle_name']??'')!==''?' '.(string)$patient['middle_name']:'') . ' · ' . (string) $patient['patient_no'] . ' · ' . (string) $patient['birth_date']) ?></option><?php endforeach; ?></select></label>
                    <?php endif; ?>
                    <label class="wide check-label"><input type="radio" name="patient_resolution" value="create" <?= (!$request['patient_id'] && !$possiblePatients) ? 'checked' : '' ?>><span>Create a new patient record from this intake</span></label><label class="wide check-label" data-identity-verified-wrap><input type="checkbox" name="identity_verified" value="1"><span>I verified the selected existing patient using the request name and birth date. Mobile or email alone was not used as proof of identity.</span></label>
                    <label>Service<select name="service_id" data-slot-service required><option value="">Choose service</option><?php foreach($requestServices as $service): ?><option value="<?=e((string)$service['id'])?>" data-provider-type="<?=e(service_provider_type($service))?>" data-requires-chair="<?=(int)($service['requires_chair']??1)?>" <?=((int)($request['service_id']??0)===(int)$service['id'])?'selected':''?>><?=e((string)$service['name'].' · '.(string)$service['duration_minutes'].' min')?></option><?php endforeach;?></select></label>
                    <label>Date<input type="date" name="date" data-slot-date min="<?= e(date('Y-m-d')) ?>" value="<?= e((string) $request['preferred_date']) ?>" required></label>
                    <div class="available-dates-panel" data-available-dates>Choose the service to see the next available dates.</div>
                    <label>Available time<select name="time" data-slot-time required><option value="">Loading availability…</option></select></label>
                    <label>Available dentist<select name="dentist_user_id" data-slot-doctor><option value="">Choose a time first</option></select></label>
                    <div class="slot-status wide" data-slot-status>Times are checked against operation hours, doctor availability, leave, and current appointments.</div>
                    <div class="wide continuity-care-note" data-continuity-note><?php if($linkedContinuityDoctor): ?>Continuity dentist: <strong><?=e((string)$linkedContinuityDoctor['name'])?></strong>. Choose a date and time when this dentist is available.<?php else: ?>The first confirmed dentist becomes the patient’s continuity dentist for later appointments and follow-ups.<?php endif; ?></div>
                    <?php if ($canTransferDentist): ?><label class="wide check-label"><input type="checkbox" name="change_continuity_dentist" value="1" data-continuity-transfer><span>Transfer this patient’s future care to the selected dentist</span></label><label class="wide" data-continuity-reason-wrap hidden>Care-transfer reason<textarea name="continuity_change_reason" data-continuity-reason placeholder="Patient request, specialty referral, dentist departure, or approved reassignment"></textarea></label><?php endif; ?>
                    <label>Assigned chair<input type="hidden" name="inhouse_provider_user_id" data-inhouse-provider><input type="hidden" name="chair_id" data-slot-chair-id><input type="hidden" name="chair_name" data-slot-chair><input data-slot-chair-display value="Assigned from dentist" readonly></label>
                    <label>Status<select name="appointment_status" data-confirmation-status><option value="pending_confirmation">Pending confirmation</option><option value="confirmed" selected>Confirmed</option></select></label><input type="hidden" name="booking_source" value="online_request"><div class="wide form-grid" data-confirmation-fields hidden><label>Agreement source<select name="confirmation_source"><option value="online_request" selected>Patient submitted online request</option><option value="patient_phone">Patient by phone</option><option value="patient_in_person">Patient in person</option><option value="patient_email">Patient by email</option><option value="patient_portal">Patient portal</option><option value="guardian_phone">Guardian by phone</option><option value="guardian_in_person">Guardian in person</option></select></label><label class="wide">Confirmation note<textarea name="confirmation_note" maxlength="500" placeholder="Record who accepted the final service and schedule, how they agreed, and the outcome.">Patient submitted the requested service, provider, date, and time through the online appointment form. Reception approved the request and will send the clinic confirmation.</textarea></label></div>
                    <label class="wide check-label"><input type="checkbox" name="update_contact" value="1"><span>Update the linked patient’s mobile number and email using this request</span></label>
                    <?php if ($request['medical_changes']): ?><label class="wide check-label"><input type="checkbox" name="record_medical_changes" value="1" checked><span>Add the reported dental-safety changes as a new dental-safety history entry</span></label><?php endif; ?>
                    <label class="wide" data-reschedule-note <?= $preferredScheduleUnavailable?'':'hidden' ?>>Service / schedule change note<textarea name="reschedule_note" placeholder="Explain any changed service, date, time, or provider and record the proposed or agreed change."><?=e((string)($request['reschedule_note']??''))?></textarea></label>
                    <?php if(schedule_bypass_allowed()&&can('appointments.schedule_bypass',['super_admin','clinic_admin'])):?><label class="wide check-label"><input type="checkbox" name="schedule_bypass" value="1" data-bypass-toggle="#request-bypass-reason"><span>Use Schedule Bypass only for an authorized time outside published availability. It cannot double-book a patient, dentist, or chair.</span></label><div class="wide form-grid" id="request-bypass-reason" hidden><label>Bypass dentist<select name="bypass_dentist_user_id"><option value="">Choose dentist</option><?php foreach($requestDentists as $dentistOption):?><option value="<?=(int)$dentistOption['id']?>"><?=e((string)$dentistOption['name'])?></option><?php endforeach;?></select></label><label>Manual time<input type="time" name="bypass_time"></label><label class="wide">Bypass reason<textarea name="schedule_bypass_reason" placeholder="Mandatory reason for this exception"></textarea></label></div><?php endif;?>
                    <label class="wide">Staff notes<textarea name="staff_notes"><?= e((string) ($request['staff_notes'] ?? '')) ?></textarea></label>
                    <button class="btn primary wide" type="submit" data-status-submit data-confirmed-label="Create confirmed appointment" data-pending-label="Create pending appointment">Create confirmed appointment</button>
                </form>
                </details>
            <?php else: ?>
                <div class="card success-card"><h2>Appointment scheduled</h2><p>This request is linked to appointment #<?= e((string) $request['appointment_id']) ?> and patient <?= e((string) ($request['linked_patient_name'] ?: $request['patient_id'])) ?>.</p><div class="button-row"><a class="btn primary" href="appointments.php">Open Appointments</a><a class="btn ghost" href="calendar.php?type=appointment&amp;appointment_id=<?=(int)$request['appointment_id']?>">Add to calendar</a></div></div>
            <?php endif; ?>
            <?php if ($request['status'] !== 'scheduled'): ?>
            <details class="card request-secondary-action">
                <summary><strong>Contact attempt</strong><span>Use only when you are not approving yet.</span></summary>
            <form method="post" class="form-grid">
                <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
                <input type="hidden" name="action" value="record_contact">
                <input type="hidden" name="request_id" value="<?= e((string) $request['id']) ?>">
                <p class="wide muted">Use this when the preferred schedule is unavailable or the patient has not yet replied. An unanswered call or an email accepted by the mail server does not create or reschedule an appointment.</p>
                <label>Channel<select name="contact_channel"><option value="phone">Phone call</option><option value="email">Email</option><option value="sms">SMS</option><option value="portal">Patient portal</option><option value="in_person">In person</option><option value="other">Other</option></select></label>
                <label>Outcome<select name="contact_outcome"><option value="connected">Connected</option><option value="patient_agreed">Patient agreed</option><option value="no_answer">No answer</option><option value="left_message">Left message</option><option value="email_sent">Send email now</option><option value="sms_sent">SMS sent</option><option value="awaiting_reply">Awaiting reply</option><option value="unreachable">Unreachable</option></select></label>
                <label class="wide">Recipient<input name="contact_recipient" value="<?= e((string) ($request['email'] ?: $request['mobile'])) ?>"></label>
                <label class="wide">Contact note<textarea name="contact_summary" required placeholder="Example: Called twice; no answer. Sent the available schedules by email and awaiting reply."></textarea></label>
                <label class="wide check-label"><input type="checkbox" name="follow_up_needed" value="1" checked><span>Keep this request on the contact follow-up list</span></label>
                <button class="btn ghost wide" type="submit">Save Contact Attempt</button>
            </form>
            </details>
            <?php endif; ?>
            <?php if ($request['status'] !== 'scheduled'): ?><details class="card request-secondary-action"><summary><strong>Review status only</strong><span>Use for decline, cancel, or mark needs verification without booking.</span></summary><form method="post" class="status-form">
                <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>"><input type="hidden" name="action" value="update_status"><input type="hidden" name="request_id" value="<?= e((string) $request['id']) ?>">
                <h3>Review status</h3>
                <label>Status<select name="status"><option value="reviewing">Reviewing</option><option value="needs_verification" <?= $request['status'] === 'needs_verification' ? 'selected' : '' ?>>Needs verification</option><option value="declined">Declined</option><option value="cancelled">Cancelled</option></select></label>
                <label>Staff notes<textarea name="staff_notes"><?= e((string) ($request['staff_notes'] ?? '')) ?></textarea></label>
                <button class="btn ghost" type="submit">Save Review Status</button>
            </form></details><?php endif; ?>
        </div>
    </section>
    <?php
    render_footer();
    exit;
}

$status = trim((string) ($_GET['status'] ?? ''));
$allowedStatuses = ['submitted', 'reviewing', 'needs_verification', 'scheduled', 'declined', 'cancelled'];
$q=trim((string)($_GET['q']??''));
$page=module_page_number('page');
$perPage=module_page_size('per_page',25);
$where = ' WHERE 1=1';
$args = [];
$userBranchId=integrity_user_branch_id();
if(active_role()!=='super_admin'&&$userBranchId>0){$where.=' AND COALESCE(r.branch_id,?)=?';$args[]=(int)(public_request_branch_id()?:0);$args[]=$userBranchId;}
if (in_array($status, $allowedStatuses, true)) {
    $where .= ' AND r.status=?';
    $args[] = $status;
}
if($q!==''){
    $like='%'.$q.'%';
    $where.=' AND (r.public_reference LIKE ? OR r.first_name LIKE ? OR r.last_name LIKE ? OR r.mobile LIKE ? OR r.email LIKE ? OR r.service_name LIKE ? OR r.reason LIKE ?)';
    array_push($args,$like,$like,$like,$like,$like,$like,$like);
}
$countStmt=$pdo->prepare('SELECT COUNT(*) FROM `' . table('appointment_requests') . '` r'.$where);
$countStmt->execute($args);$requestTotal=(int)$countStmt->fetchColumn();
$pages=max(1,(int)ceil($requestTotal/$perPage));$page=min($page,$pages);$offset=($page-1)*$perPage;
$requestEmailReady=schema_column_exists(table('appointment_requests'),'receipt_email_outbox_id');
$requestEmailSelect=$requestEmailReady?',o.status receipt_email_status,o.last_error receipt_email_error':',NULL receipt_email_status,NULL receipt_email_error';
$requestEmailJoin=$requestEmailReady?' LEFT JOIN `' . table('email_outbox') . '` o ON o.id=r.receipt_email_outbox_id ':'';
$stmt = $pdo->prepare(
    'SELECT r.*,TRIM(CONCAT_WS(" ",p.first_name,NULLIF(p.middle_name,""),p.last_name)) linked_patient'.$requestEmailSelect.' FROM `' . table('appointment_requests') . '` r '
    . 'LEFT JOIN `' . table('patients') . '` p ON p.id=r.patient_id'.$requestEmailJoin . $where . ' ORDER BY FIELD(r.status,"submitted","needs_verification","reviewing","approved","scheduled","declined","cancelled"),FIELD(r.urgency,"urgent","soon","routine"),r.created_at DESC LIMIT '.$perPage.' OFFSET '.$offset
);
$stmt->execute($args);
$requests = $stmt->fetchAll();
$counts = [];
$countByStatusSql='SELECT status,COUNT(*) total FROM `' . table('appointment_requests') . '` r WHERE 1=1';$countByStatusArgs=[];
if(active_role()!=='super_admin'&&$userBranchId>0){$countByStatusSql.=' AND COALESCE(r.branch_id,?)=?';$countByStatusArgs[]=(int)(public_request_branch_id()?:0);$countByStatusArgs[]=$userBranchId;}$countByStatusSql.=' GROUP BY status';
$countByStatusStmt=$pdo->prepare($countByStatusSql);$countByStatusStmt->execute($countByStatusArgs);
foreach ($countByStatusStmt as $row) {
    $counts[(string) $row['status']] = (int) $row['total'];
}
render_header('Online Appointment Requests');
?>
<div class="toolbar"><div><strong><?= e((string) array_sum($counts)) ?> online requests</strong><span><?= e((string) (($counts['submitted'] ?? 0) + ($counts['needs_verification'] ?? 0))) ?> require attention</span></div><form method="get" action="appointment_requests.php" class="auto-search-form" data-auto-submit><?php if($status!==''):?><input type="hidden" name="status" value="<?=e($status)?>"><?php endif;?><input type="search" name="q" value="<?=e($q)?>" placeholder="Search reference, patient, mobile, service, or reason" data-auto-search data-min-length="2" autocomplete="off"><?php if($q!==''):?><a class="btn link" href="<?=e(module_query_url(['q'=>null,'page'=>null]))?>">Clear</a><?php endif;?></form><a class="btn ghost" href="index.php#appointment">View Public Appointment Page</a></div>
<div class="request-filter-row"><a href="<?=e(module_query_url(['status'=>null,'page'=>null]))?>" class="<?= $status === '' ? 'active' : '' ?>">All</a><?php foreach ($allowedStatuses as $filter): ?><a href="<?=e(module_query_url(['status'=>$filter,'page'=>null]))?>" class="<?= $status === $filter ? 'active' : '' ?>"><?= e(ucwords(str_replace('_', ' ', $filter))) ?> <span><?= e((string) ($counts[$filter] ?? 0)) ?></span></a><?php endforeach; ?></div>
<div class="card server-list-card"><?php module_render_server_pagination($requestTotal,$page,$perPage,'page','per_page','requests'); ?><div class="table-wrap" data-no-pagination><table data-no-pagination="true"><thead><tr><th>Submitted</th><th>Reference</th><th>Patient</th><th>Requested visit</th><th>Preferred schedule</th><th>Status</th><th></th></tr></thead><tbody><?php if (!$requests): ?><tr><td colspan="7" class="empty-cell">No appointment requests match this filter.</td></tr><?php endif; ?><?php foreach ($requests as $request): ?><tr><td><?= e(date('M j, Y g:i A', strtotime((string) $request['created_at']))) ?></td><td><strong><?= e((string) $request['public_reference']) ?></strong><small><?= e(ucfirst((string) $request['patient_type'])) ?> patient</small></td><td><strong><?= e((string) $request['last_name'] . ', ' . (string) $request['first_name'] . ((string)($request['middle_name']??'')!==''?' '.(string)$request['middle_name']:'')) ?></strong><small><?= e((string) $request['mobile']) ?><?= $request['linked_patient'] ? ' · Linked' : '' ?></small></td><td><?= e((string) $request['service_name']) ?><small><?= e(ucfirst((string) $request['urgency'])) ?> · <?= e(strlen((string) $request['reason']) > 80 ? substr((string) $request['reason'], 0, 77) . '...' : (string) $request['reason']) ?></small></td><td><?= e(date('M j, Y', strtotime((string) $request['preferred_date']))) ?><small><?= e(date('g:i A', strtotime((string) $request['preferred_time']))) ?></small></td><td><span class="badge request-status <?= e((string) $request['status']) ?>"><?= e(str_replace('_', ' ', (string) $request['status'])) ?></span><?php if(!empty($request['receipt_email_status'])):?><small><span class="badge email-<?=e((string)$request['receipt_email_status'])?>"><?=e(email_delivery_label((string)$request['receipt_email_status']))?></span></small><?php endif;?></td><td><a href="?id=<?= e((string) $request['id']) ?>">Review</a></td></tr><?php endforeach; ?></tbody></table></div><?php module_render_server_pagination($requestTotal,$page,$perPage,'page','per_page','requests'); ?></div>
<?php render_footer();
