<?php

declare(strict_types=1);
require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_roles(['super_admin','clinic_admin','dentist']);
require DENTAL_ROOT . '/app/layout.php';

$pdo = db();
$actualRole = actual_role();
$isDentist = active_role_is('dentist');
$isAdministrator = in_array($actualRole, ['super_admin','clinic_admin'], true);
$canEdit = $isAdministrator || $isDentist;

$branches = $pdo->query('SELECT id,name FROM `' . table('branches') . '` WHERE is_active=1 ORDER BY name')->fetchAll();
$defaultBranchId = (int) (current_user()['branch_id'] ?? 0);
$branchId = (int) ($_GET['branch_id'] ?? $_POST['branch_id'] ?? $defaultBranchId ?: ($branches[0]['id'] ?? 0));
$validBranchIds = array_map(static fn(array $row): int => (int) $row['id'], $branches);
if (!in_array($branchId, $validBranchIds, true)) $branchId = (int) ($validBranchIds[0] ?? 0);

$dentists = active_dentists($branchId ?: null);
$doctorId = $isDentist ? (int) current_user()['id'] : (int) ($_GET['doctor_id'] ?? $_POST['doctor_id'] ?? ($dentists[0]['id'] ?? 0));
$validDoctorIds = array_map(static fn(array $row): int => (int) $row['id'], $dentists);
if ($isDentist && !in_array($doctorId, $validDoctorIds, true)) {
    // A dentist may still open their own schedule while a branch assignment is being repaired.
    $doctorId = (int) current_user()['id'];
} elseif (!$isDentist && !in_array($doctorId, $validDoctorIds, true)) {
    $doctorId = (int) ($validDoctorIds[0] ?? 0);
}

$doctorName = (string) (current_user()['name'] ?? 'Dentist');
foreach ($dentists as $dentist) {
    if ((int) $dentist['id'] === $doctorId) {
        $doctorName = (string) $dentist['name'];
        break;
    }
}

$chairBinding = doctor_chair_binding($doctorId,$branchId);

$tab = (string) ($_GET['tab'] ?? 'builder');
if (!in_array($tab, ['builder','appointments','future'], true)) $tab = 'builder';
$weekAnchor = trim((string) ($_GET['week'] ?? date('Y-m-d')));
$anchorTimestamp = strtotime($weekAnchor) ?: time();
$weekStartTimestamp = strtotime('-' . ((int) date('N', $anchorTimestamp) - 1) . ' days', $anchorTimestamp);
$weekStart = date('Y-m-d', $weekStartTimestamp);
$weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
$weekNextBoundary = date('Y-m-d', strtotime($weekStart . ' +7 days'));
$days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
$weekOrder = [1,2,3,4,5,6,0];
$error = '';

function doctor_schedule_url(array $changes = []): string
{
    $params = $_GET;
    unset($params['route']);
    foreach ($changes as $key => $value) {
        if ($value === null || $value === '') unset($params[$key]);
        else $params[$key] = $value;
    }
    return 'doctor_schedule.php' . ($params ? '?' . http_build_query($params) : '');
}

function doctor_schedule_redirect(int $branchId, int $doctorId, string $weekStart, string $tab = 'builder'): never
{
    header('Location: doctor_schedule.php?' . http_build_query([
        'branch_id' => $branchId,
        'doctor_id' => $doctorId,
        'week' => $weekStart,
        'tab' => $tab,
    ]));
    exit;
}

function doctor_schedule_validate_weekly_hours(PDO $pdo, int $branchId, int $day, string $start, string $end, array $days): void
{
    if ($day < 0 || $day > 6 || !preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $start) || !preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $end) || time_minutes($end) <= time_minutes($start)) {
        throw new RuntimeException('Enter a valid weekday and working-time range.');
    }
    $stmt = $pdo->prepare('SELECT is_open,open_time,close_time FROM `' . table('branch_operating_hours') . '` WHERE branch_id=? AND day_of_week=? LIMIT 1');
    $stmt->execute([$branchId, $day]);
    $hours = $stmt->fetch();
    if (!$hours || !(bool) $hours['is_open']) throw new RuntimeException(($days[$day] ?? 'The selected day') . ' is closed for this branch.');
    if (time_minutes($start) < time_minutes((string) $hours['open_time']) || time_minutes($end) > time_minutes((string) $hours['close_time'])) {
        throw new RuntimeException(($days[$day] ?? 'This day') . ' must remain inside clinic hours ' . date('g:i A', strtotime((string) $hours['open_time'])) . '–' . date('g:i A', strtotime((string) $hours['close_time'])) . '.');
    }
}

function doctor_schedule_assert_no_overlap(PDO $pdo, int $doctorId, int $branchId, int $day, string $start, string $end, string $from, ?string $until): void
{
    $stmt = $pdo->prepare('SELECT COUNT(*) FROM `' . table('doctor_availability_rules') . '` WHERE doctor_user_id=? AND branch_id=? AND day_of_week=? AND status<>"inactive" AND start_time<? AND end_time>? AND effective_from<=? AND (effective_until IS NULL OR effective_until>=?)');
    $stmt->execute([$doctorId, $branchId, $day, $end, $start, $until ?: '9999-12-31', $from]);
    if ((int) $stmt->fetchColumn() > 0) throw new RuntimeException('The selected working hours overlap an existing active or draft block.');
}


function doctor_schedule_assert_exception_no_overlap(PDO $pdo,int $doctorId,int $branchId,string $date,bool $allDay,string $start,string $end): void
{
    $sql='SELECT exception_type,start_time,end_time,all_day FROM `' . table('doctor_availability_exceptions') . '` WHERE doctor_user_id=? AND branch_id=? AND exception_date=? AND status<>"inactive"';
    $stmt=$pdo->prepare($sql);$stmt->execute([$doctorId,$branchId,$date]);
    foreach($stmt->fetchAll() as $row){
        if($allDay || !empty($row['all_day'])) throw new RuntimeException('This date already has a schedule exception. Remove or revise the existing block before adding another.');
        if(time_minutes($start)<time_minutes((string)$row['end_time']) && time_minutes($end)>time_minutes((string)$row['start_time'])){
            throw new RuntimeException('This exception overlaps an existing '.str_replace('_',' ',(string)$row['exception_type']).' block.');
        }
    }
}

function doctor_schedule_exception_appointment_count(PDO $pdo,int $doctorId,int $branchId,array $exception): int
{
    $sql='SELECT COUNT(*) FROM `' . table('appointments') . '` WHERE dentist_user_id=? AND branch_id=? AND provider_type="dentist" AND DATE(start_at)=? AND status NOT IN ("cancelled","no_show","completed")';
    $args=[$doctorId,$branchId,(string)$exception['exception_date']];
    if(empty($exception['all_day'])){
        $sql.=' AND TIME(start_at)<? AND TIME(end_at)>?';
        $args[]=substr((string)$exception['end_time'],0,8);$args[]=substr((string)$exception['start_time'],0,8);
    }
    $stmt=$pdo->prepare($sql);$stmt->execute($args);return (int)$stmt->fetchColumn();
}

function doctor_schedule_rule_appointment_count(PDO $pdo,int $doctorId,int $branchId,array $rule): int
{
    $sql='SELECT COUNT(*) FROM `' . table('appointments') . '` WHERE dentist_user_id=? AND branch_id=? AND provider_type="dentist" AND start_at>=NOW() AND status NOT IN ("cancelled","no_show","completed") AND DAYOFWEEK(start_at)-1=? AND DATE(start_at)>=? AND (? IS NULL OR DATE(start_at)<=?) AND TIME(start_at)<? AND TIME(end_at)>?';
    $until=$rule['effective_until']?:null;
    $stmt=$pdo->prepare($sql);$stmt->execute([$doctorId,$branchId,(int)$rule['day_of_week'],(string)$rule['effective_from'],$until,$until,substr((string)$rule['end_time'],0,8),substr((string)$rule['start_time'],0,8)]);return (int)$stmt->fetchColumn();
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        verify_csrf();
        if (!$canEdit || $doctorId < 1 || $branchId < 1) throw new RuntimeException('A valid dentist and clinic branch are required.');
        if ($isDentist && $doctorId !== (int) current_user()['id']) throw new RuntimeException('Dentists may only edit their own schedule.');
        $action = (string) ($_POST['action'] ?? '');

        if ($action === 'assign_chair') {
            if (!$isAdministrator) throw new RuntimeException('Only an administrator can manage physical treatment chairs.');
            flash('warning','Chair assignments are managed under Administration → Chairs & Resources so they cannot be entered as free text.');
            header('Location: chairs.php?branch_id=' . $branchId);
            exit;
        }


        if ($action === 'save_working_hours') {
            $day = (int) ($_POST['day_of_week'] ?? -1);
            $start = substr(trim((string) ($_POST['start_time'] ?? '')), 0, 5);
            $end = substr(trim((string) ($_POST['end_time'] ?? '')), 0, 5);
            $from = trim((string) ($_POST['effective_from'] ?? date('Y-m-d')));
            $until = trim((string) ($_POST['effective_until'] ?? ''));
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from) || ($until !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $until) || strtotime($until) < strtotime($from)))) {
                throw new RuntimeException('Enter a valid effective date range.');
            }
            $targetDays = array_values(array_unique(array_merge([$day], array_map('intval', (array) ($_POST['copy_days'] ?? [])))));
            foreach ($targetDays as $targetDay) {
                doctor_schedule_validate_weekly_hours($pdo, $branchId, $targetDay, $start, $end, $days);
                doctor_schedule_assert_no_overlap($pdo, $doctorId, $branchId, $targetDay, $start, $end, $from, $until ?: null);
            }
            $pdo->beginTransaction();
            $insert = $pdo->prepare('INSERT INTO `' . table('doctor_availability_rules') . '` (doctor_user_id,branch_id,day_of_week,start_time,end_time,effective_from,effective_until,status,created_by,approved_by,created_at) VALUES (?,?,?,?,?,?,?,"draft",?,NULL,NOW())');
            foreach ($targetDays as $targetDay) $insert->execute([$doctorId,$branchId,$targetDay,$start,$end,$from,$until ?: null,(int) current_user()['id']]);
            $pdo->commit();
            audit('doctor_schedule_draft_hours_created','doctor',$doctorId,['days'=>$targetDays,'start'=>$start,'end'=>$end,'effective_from'=>$from,'effective_until'=>$until ?: null]);
            flash('success', count($targetDays) . ' draft working-hours block' . (count($targetDays) === 1 ? '' : 's') . ' added. Publish the schedule when it is ready for appointment booking.');
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        if ($action === 'copy_day') {
            $sourceDay = (int) ($_POST['source_day'] ?? -1);
            $targetDays = array_values(array_unique(array_filter(array_map('intval', (array) ($_POST['target_days'] ?? [])), static fn(int $day): bool => $day >= 0 && $day <= 6)));
            $from = trim((string) ($_POST['copy_effective_from'] ?? date('Y-m-d')));
            $until = trim((string) ($_POST['copy_effective_until'] ?? ''));
            if ($sourceDay < 0 || $sourceDay > 6 || !$targetDays) throw new RuntimeException('Select a source day and at least one destination weekday.');
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from) || ($until !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $until) || strtotime($until) < strtotime($from)))) throw new RuntimeException('Enter a valid effective date range.');
            $source = $pdo->prepare('SELECT start_time,end_time FROM `' . table('doctor_availability_rules') . '` WHERE doctor_user_id=? AND branch_id=? AND day_of_week=? AND status<>"inactive" AND effective_from<=? AND (effective_until IS NULL OR effective_until>=?) ORDER BY start_time');
            $source->execute([$doctorId,$branchId,$sourceDay,$from,$from]);
            $sourceRules = $source->fetchAll();
            if (!$sourceRules) throw new RuntimeException(($days[$sourceDay] ?? 'The source day') . ' has no working hours to copy.');
            $pdo->beginTransaction();
            $insert = $pdo->prepare('INSERT INTO `' . table('doctor_availability_rules') . '` (doctor_user_id,branch_id,day_of_week,start_time,end_time,effective_from,effective_until,status,created_by,approved_by,created_at) VALUES (?,?,?,?,?,?,?,"draft",?,NULL,NOW())');
            $created = 0;
            foreach ($targetDays as $targetDay) {
                if ($targetDay === $sourceDay) continue;
                foreach ($sourceRules as $sourceRule) {
                    $start = substr((string) $sourceRule['start_time'],0,5);
                    $end = substr((string) $sourceRule['end_time'],0,5);
                    doctor_schedule_validate_weekly_hours($pdo,$branchId,$targetDay,$start,$end,$days);
                    doctor_schedule_assert_no_overlap($pdo,$doctorId,$branchId,$targetDay,$start,$end,$from,$until ?: null);
                    $insert->execute([$doctorId,$branchId,$targetDay,$start,$end,$from,$until ?: null,(int) current_user()['id']]);
                    $created++;
                }
            }
            $pdo->commit();
            if ($created < 1) throw new RuntimeException('No new working-hours blocks were copied.');
            audit('doctor_schedule_day_copied','doctor',$doctorId,['source_day'=>$sourceDay,'target_days'=>$targetDays,'created'=>$created]);
            flash('success',$created . ' draft working-hours block' . ($created === 1 ? '' : 's') . ' copied. Review the calendar, then publish.');
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        if ($action === 'save_exception') {
            $date = trim((string) ($_POST['exception_date'] ?? ''));
            $type = (string) ($_POST['exception_type'] ?? 'break');
            $allDay = isset($_POST['all_day']);
            $start = substr(trim((string) ($_POST['exception_start'] ?? '')),0,5);
            $end = substr(trim((string) ($_POST['exception_end'] ?? '')),0,5);
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) throw new RuntimeException('Select a valid exception date.');
            if (!in_array($type,['unavailable','special_availability','leave','break','training','closure'],true)) throw new RuntimeException('Select a valid schedule exception.');
            $dateHours = branch_hours_for_date($branchId,$date);
            if ($type === 'special_availability' && !$dateHours) throw new RuntimeException('The branch is closed on this date. An administrator must create a branch special opening first.');
            if (!$allDay) {
                if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/',$start) || !preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/',$end) || time_minutes($end) <= time_minutes($start)) throw new RuntimeException('Enter a valid exception time range.');
                if ($dateHours && (time_minutes($start) < time_minutes((string) $dateHours['open_time']) || time_minutes($end) > time_minutes((string) $dateHours['close_time']))) throw new RuntimeException('The exception must remain inside branch operating hours.');
            }
            doctor_schedule_assert_exception_no_overlap($pdo,$doctorId,$branchId,$date,$allDay,$start,$end);
            $restricted=in_array($type,['leave','closure','training','unavailable'],true);
            $draftStatus=($isDentist && $restricted)?'submitted':'draft';
            $pdo->prepare('INSERT INTO `' . table('doctor_availability_exceptions') . '` (doctor_user_id,branch_id,exception_date,exception_type,start_time,end_time,all_day,reason,status,created_by,approved_by,created_at) VALUES (?,?,?,?,?,?,?,?,?, ?,NULL,NOW())')
                ->execute([$doctorId,$branchId,$date,$type,$allDay ? null : $start,$allDay ? null : $end,$allDay ? 1 : 0,trim((string) ($_POST['reason'] ?? '')) ?: null,$draftStatus,(int) current_user()['id']]);
            audit('doctor_schedule_draft_exception_created','doctor',$doctorId,['date'=>$date,'type'=>$type,'all_day'=>$allDay]);
            flash('success',($draftStatus==='submitted'?'The '.str_replace('_',' ',$type).' request was submitted for administrator approval.':'Draft '.str_replace('_',' ',$type).' block added. Publish the schedule to apply it to appointment availability.'));
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        if ($action === 'publish_schedule') {
            $binding = doctor_chair_binding($doctorId,$branchId);
            if (!$binding['valid']) throw new RuntimeException((string)$binding['issue'] . ' Assign the dentist’s chair before publishing availability.');
            $overrideReason=trim((string)($_POST['impact_override_reason']??''));
            $allowImpactOverride=$isAdministrator && isset($_POST['impact_override']);
            $pendingExceptions=$pdo->prepare('SELECT * FROM `' . table('doctor_availability_exceptions') . '` WHERE doctor_user_id=? AND branch_id=? AND status IN ("draft","submitted") ORDER BY exception_date,start_time,id');
            $pendingExceptions->execute([$doctorId,$branchId]);
            $publishable=[];$affected=0;
            foreach($pendingExceptions->fetchAll() as $exception){
                $restricted=in_array((string)$exception['exception_type'],['leave','closure','training','unavailable'],true);
                if($isDentist && $restricted) continue;
                if($restricted)$affected+=doctor_schedule_exception_appointment_count($pdo,$doctorId,$branchId,$exception);
                $publishable[]=(int)$exception['id'];
            }
            if($affected>0 && (!$allowImpactOverride || $overrideReason==='')){
                throw new RuntimeException($affected.' future appointment'.($affected===1?' is':'s are').' affected by the pending schedule restriction. Reschedule them first, or an administrator must publish with a documented override reason.');
            }
            $pdo->beginTransaction();
            $rules = $pdo->prepare('UPDATE `' . table('doctor_availability_rules') . '` SET status="active",approved_by=?,updated_at=NOW() WHERE doctor_user_id=? AND branch_id=? AND status IN ("draft","submitted")');
            $rules->execute([(int) current_user()['id'],$doctorId,$branchId]);
            $ruleCount = $rules->rowCount();
            $exceptionCount=0;
            if($publishable){
                $marks=implode(',',array_fill(0,count($publishable),'?'));
                $args=array_merge([(int)current_user()['id']],$publishable);
                $exceptions=$pdo->prepare('UPDATE `' . table('doctor_availability_exceptions') . '` SET status="active",approved_by=?,updated_at=NOW() WHERE id IN ('.$marks.')');
                $exceptions->execute($args);$exceptionCount=$exceptions->rowCount();
            }
            $pdo->commit();
            audit('doctor_schedule_published','doctor',$doctorId,['working_blocks'=>$ruleCount,'exceptions'=>$exceptionCount,'affected_appointments'=>$affected,'override_reason'=>$overrideReason?:null]);
            $remaining=$pdo->prepare('SELECT COUNT(*) FROM `' . table('doctor_availability_exceptions') . '` WHERE doctor_user_id=? AND branch_id=? AND status="submitted"');$remaining->execute([$doctorId,$branchId]);$approvalCount=(int)$remaining->fetchColumn();
            $message=($ruleCount+$exceptionCount)>0?'Schedule published. Reception can now book against the updated working hours.':'The schedule is already published; no eligible draft changes were pending.';
            if($approvalCount>0)$message.=' '.$approvalCount.' leave or restricted schedule request'.($approvalCount===1?' remains':'s remain').' pending administrator approval.';
            flash('success',$message);
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        if ($action === 'delete_rule') {
            $ruleId = (int) ($_POST['rule_id'] ?? 0);
            $load=$pdo->prepare('SELECT * FROM `' . table('doctor_availability_rules') . '` WHERE id=? AND doctor_user_id=? AND branch_id=?');$load->execute([$ruleId,$doctorId,$branchId]);$rule=$load->fetch();if(!$rule)throw new RuntimeException('Working-hours block not found.');
            $affected=doctor_schedule_rule_appointment_count($pdo,$doctorId,$branchId,$rule);
            if($affected>0)throw new RuntimeException('This block contains '.$affected.' future appointment'.($affected===1?'':'s').'. Reschedule those patients before removing the working hours.');
            $stmt = $pdo->prepare('UPDATE `' . table('doctor_availability_rules') . '` SET status="inactive",updated_at=NOW() WHERE id=? AND doctor_user_id=? AND branch_id=?');
            $stmt->execute([$ruleId,$doctorId,$branchId]);
            audit('doctor_schedule_working_hours_removed','doctor',$doctorId,['rule_id'=>$ruleId]);
            flash('success','Working-hours block removed. Existing appointments remain unchanged.');
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        if ($action === 'delete_exception') {
            $exceptionId = (int) ($_POST['exception_id'] ?? 0);
            $stmt = $pdo->prepare('UPDATE `' . table('doctor_availability_exceptions') . '` SET status="inactive",updated_at=NOW() WHERE id=? AND doctor_user_id=? AND branch_id=?');
            $stmt->execute([$exceptionId,$doctorId,$branchId]);
            audit('doctor_schedule_exception_removed','doctor',$doctorId,['exception_id'=>$exceptionId]);
            flash('success','Schedule exception removed.');
            doctor_schedule_redirect($branchId,$doctorId,$weekStart,'builder');
        }

        throw new RuntimeException('The requested schedule action is not supported.');
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        $error = $exception->getMessage();
    }
}

$weeklyHours = [];
$stmt = $pdo->prepare('SELECT * FROM `' . table('branch_operating_hours') . '` WHERE branch_id=? ORDER BY day_of_week');
$stmt->execute([$branchId]);
foreach ($stmt->fetchAll() as $row) $weeklyHours[(int) $row['day_of_week']] = $row;

$stmt = $pdo->prepare('SELECT * FROM `' . table('doctor_availability_rules') . '` WHERE doctor_user_id=? AND branch_id=? AND status<>"inactive" ORDER BY day_of_week,start_time,effective_from');
$stmt->execute([$doctorId,$branchId]);
$rules = $stmt->fetchAll();

$stmt = $pdo->prepare('SELECT * FROM `' . table('doctor_availability_exceptions') . '` WHERE doctor_user_id=? AND branch_id=? AND exception_date>=? AND exception_date<? AND status<>"inactive" ORDER BY exception_date,start_time,id');
$stmt->execute([$doctorId,$branchId,$weekStart,$weekNextBoundary]);
$exceptions = $stmt->fetchAll();

$stmt = $pdo->prepare('SELECT a.*,CONCAT(p.first_name," ",p.last_name) patient,p.patient_no FROM `' . table('appointments') . '` a JOIN `' . table('patients') . '` p ON p.id=a.patient_id WHERE a.branch_id=? AND a.dentist_user_id=? AND a.start_at>=? AND a.start_at<? ORDER BY a.start_at');
$stmt->execute([$branchId,$doctorId,$weekStart . ' 00:00:00',$weekNextBoundary . ' 00:00:00']);
$appointments = $stmt->fetchAll();

$futureFrom = trim((string) ($_GET['future_from'] ?? date('Y-m-d')));
$futureTo = trim((string) ($_GET['future_to'] ?? date('Y-m-d',strtotime('+1 year'))));
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/',$futureFrom)) $futureFrom=date('Y-m-d');
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/',$futureTo) || $futureTo<$futureFrom) $futureTo=date('Y-m-d',strtotime($futureFrom.' +1 year'));
$futurePage=max(1,(int)($_GET['future_page']??1));$futurePerPage=50;$futureOffset=($futurePage-1)*$futurePerPage;
$futureTotalStmt=$pdo->prepare('SELECT COUNT(*) FROM `' . table('appointments') . '` WHERE branch_id=? AND dentist_user_id=? AND status NOT IN ("cancelled","no_show") AND start_at>=? AND start_at<DATE_ADD(?,INTERVAL 1 DAY)');
$futureTotalStmt->execute([$branchId,$doctorId,$futureFrom.' 00:00:00',$futureTo]);
$futureTotal=(int)$futureTotalStmt->fetchColumn();
$futurePages=max(1,(int)ceil($futureTotal/$futurePerPage));if($futurePage>$futurePages){$futurePage=$futurePages;$futureOffset=($futurePage-1)*$futurePerPage;}
$futureAppointments=[];
if($tab==='future'){
    $futureStmt=$pdo->prepare('SELECT a.*,CONCAT(p.first_name," ",p.last_name) patient,p.patient_no,p.mobile FROM `' . table('appointments') . '` a JOIN `' . table('patients') . '` p ON p.id=a.patient_id WHERE a.branch_id=? AND a.dentist_user_id=? AND a.status NOT IN ("cancelled","no_show") AND a.start_at>=? AND a.start_at<DATE_ADD(?,INTERVAL 1 DAY) ORDER BY a.start_at LIMIT '.$futurePerPage.' OFFSET '.$futureOffset);
    $futureStmt->execute([$branchId,$doctorId,$futureFrom.' 00:00:00',$futureTo]);
    $futureAppointments=$futureStmt->fetchAll();
}
$futureConflictWhere = ' FROM `' . table('appointments') . '` a1 JOIN `' . table('appointments') . '` a2 '
    . 'ON a1.id<a2.id AND a1.branch_id=a2.branch_id AND a1.start_at<a2.end_at AND a1.end_at>a2.start_at AND a2.status NOT IN ("cancelled","no_show") '
    . 'JOIN `' . table('patients') . '` p1 ON p1.id=a1.patient_id JOIN `' . table('patients') . '` p2 ON p2.id=a2.patient_id '
    . 'WHERE a1.branch_id=? AND a1.status NOT IN ("cancelled","no_show") AND a1.start_at>=NOW() '
    . 'AND (a1.dentist_user_id=? OR a2.dentist_user_id=?) '
    . 'AND (a1.patient_id=a2.patient_id OR a1.dentist_user_id=a2.dentist_user_id OR (TRIM(COALESCE(a1.chair_name,""))<>"" AND LOWER(TRIM(a1.chair_name))=LOWER(TRIM(a2.chair_name))))';
$futureConflictStmt=$pdo->prepare('SELECT COUNT(*)' . $futureConflictWhere);
$futureConflictStmt->execute([$branchId,$doctorId,$doctorId]);
$futureConflictCount=(int)$futureConflictStmt->fetchColumn();
$futureConflicts=[];
if($futureConflictCount>0){
    $conflictListStmt=$pdo->prepare('SELECT a1.id first_id,a1.patient_id first_patient_id,a1.dentist_user_id first_doctor_id,a1.dentist_name first_doctor,a1.chair_name first_chair,a1.start_at first_start,a1.end_at first_end,a1.service_name first_service,CONCAT(p1.first_name," ",p1.last_name) first_patient,a2.id second_id,a2.patient_id second_patient_id,a2.dentist_user_id second_doctor_id,a2.dentist_name second_doctor,a2.chair_name second_chair,a2.start_at second_start,a2.end_at second_end,a2.service_name second_service,CONCAT(p2.first_name," ",p2.last_name) second_patient' . $futureConflictWhere . ' ORDER BY LEAST(a1.start_at,a2.start_at),a1.id,a2.id LIMIT 30');
    $conflictListStmt->execute([$branchId,$doctorId,$doctorId]);
    $futureConflicts=$conflictListStmt->fetchAll();
}

$rulesByDate = [];
$exceptionsByDate = [];
$appointmentsByDate = [];
foreach ($exceptions as $row) $exceptionsByDate[(string) $row['exception_date']][] = $row;
foreach ($appointments as $row) $appointmentsByDate[date('Y-m-d',strtotime((string) $row['start_at']))][] = $row;

$weekDates = [];
$gridStart = 24 * 60;
$gridEnd = 0;
$slotInterval = 30;
foreach (range(0,6) as $offset) {
    $date = date('Y-m-d',strtotime($weekStart . ' +' . $offset . ' days'));
    $dayIndex = (int) date('w',strtotime($date));
    $dateHours = branch_hours_for_date($branchId,$date);
    $weekDates[] = ['date'=>$date,'day'=>$dayIndex,'hours'=>$dateHours];
    if ($dateHours) {
        $gridStart = min($gridStart,time_minutes((string) $dateHours['open_time']));
        $gridEnd = max($gridEnd,time_minutes((string) $dateHours['close_time']));
        $slotInterval = min($slotInterval,max(15,(int) ($dateHours['slot_interval_minutes'] ?? 30)));
    }
    foreach ($rules as $rule) {
        if ((int) $rule['day_of_week'] !== $dayIndex) continue;
        if ((string) $rule['effective_from'] > $date) continue;
        if (!empty($rule['effective_until']) && (string) $rule['effective_until'] < $date) continue;
        $rulesByDate[$date][] = $rule;
    }
}
if ($gridEnd <= $gridStart) { $gridStart = 8 * 60; $gridEnd = 17 * 60; }
$gridStart = intdiv($gridStart,$slotInterval) * $slotInterval;
$gridEnd = (int) (ceil($gridEnd / $slotInterval) * $slotInterval);
$rowHeight = 38;
$gridHeight = max(220,(int) (($gridEnd - $gridStart) / $slotInterval) * $rowHeight);
$draftRuleCount = count(array_filter($rules,static fn(array $row): bool => in_array((string) $row['status'],['draft','submitted'],true)));
$draftExceptionStmt = $pdo->prepare('SELECT COUNT(*) FROM `' . table('doctor_availability_exceptions') . '` WHERE doctor_user_id=? AND branch_id=? AND status IN ("draft","submitted")');
$draftExceptionStmt->execute([$doctorId,$branchId]);
$draftExceptionCount = (int) $draftExceptionStmt->fetchColumn();
$publishedRuleCount = count(array_filter($rules,static fn(array $row): bool => in_array((string) $row['status'],['active','approved'],true)));

render_header($isDentist ? 'My Schedule' : 'Doctor Schedule Builder');
?>
<?php if ($error !== ''): ?><div class="flash error"><?=e($error)?></div><?php endif; ?>

<section class="doctor-schedule-hero card">
    <div>
        <span class="eyebrow"><?= $isDentist ? 'Dentist workspace' : 'Schedule administration' ?></span>
        <h2><?=e($doctorName)?></h2>
        <p>Build recurring working hours, place breaks or leave directly on the week, and review booked patients in one calendar.</p>
    </div>
    <div class="doctor-schedule-summary">
        <span><strong><?=$publishedRuleCount?></strong> published blocks</span>
        <span class="<?=($draftRuleCount + $draftExceptionCount) > 0 ? 'pending' : ''?>"><strong><?=$draftRuleCount + $draftExceptionCount?></strong> draft changes</span>
        <span><strong><?=count($appointments)?></strong> appointments this week</span>
        <span><strong><?=$futureTotal?></strong> future appointments</span>
        <?php if($futureConflictCount>0):?><span class="pending"><strong><?=$futureConflictCount?></strong> schedule conflicts</span><?php endif;?>
    </div>
</section>
<section class="chair-binding-note <?= $chairBinding['valid'] ? '' : 'warning' ?>"><div><strong><?= $chairBinding['valid'] ? 'Bound chair: ' . e((string)$chairBinding['chair_name']) : 'Chair assignment required' ?></strong><small><?= $chairBinding['valid'] ? 'This chair is reserved for this dentist in the selected branch and is assigned automatically to appointments.' : e((string)$chairBinding['issue']) ?></small></div><?php if($isAdministrator):?><a class="btn <?= $chairBinding['valid'] ? 'ghost' : 'primary' ?>" href="chairs.php?branch_id=<?=$branchId?>"><?= $chairBinding['valid'] ? 'Manage chair' : 'Assign chair' ?></a><?php endif;?></section>
<?php if($futureConflictCount>0):?><section class="card resource-conflict-panel"><div class="card-head"><div><strong>Future schedule conflicts need correction.</strong><p><?=$futureConflictCount?> overlapping appointment pair<?= $futureConflictCount===1?'':'s' ?> involve this dentist, a patient, or a bound chair. New bookings cannot create another overlap.</p></div></div><div class="resource-conflict-list"><?php foreach($futureConflicts as $conflict):$conflictTypes=[];if((int)$conflict['first_patient_id']===(int)$conflict['second_patient_id'])$conflictTypes[]='Same patient';if((int)$conflict['first_doctor_id']>0&&(int)$conflict['first_doctor_id']===(int)$conflict['second_doctor_id'])$conflictTypes[]='Same dentist';if(trim((string)$conflict['first_chair'])!==''&&strcasecmp(trim((string)$conflict['first_chair']),trim((string)$conflict['second_chair']))===0)$conflictTypes[]='Same chair';?><article><div><span class="badge warning"><?=e(implode(' · ',$conflictTypes))?></span><strong><?=e(date('M j, Y g:i A',strtotime((string)$conflict['first_start'])))?></strong><small><?=e((string)$conflict['first_patient'])?> with <?=e((string)$conflict['first_doctor'])?> · <?=e((string)($conflict['first_chair']?:'No chair'))?></small><small>overlaps <?=e((string)$conflict['second_patient'])?> with <?=e((string)$conflict['second_doctor'])?> · <?=e((string)($conflict['second_chair']?:'No chair'))?></small></div><div class="table-actions"><a href="patients.php?action=view&id=<?=(int)$conflict['first_patient_id']?>">First patient</a><a href="patients.php?action=view&id=<?=(int)$conflict['second_patient_id']?>">Second patient</a><?php if($isAdministrator):?><a href="appointments.php?edit_id=<?=(int)$conflict['first_id']?>">Reschedule first</a><a href="appointments.php?edit_id=<?=(int)$conflict['second_id']?>">Reschedule second</a><?php else:?><span class="muted">Ask reception or administration to reschedule.</span><?php endif;?></div></article><?php endforeach;?></div><?php if($futureConflictCount>count($futureConflicts)):?><small class="muted">Showing the first <?=count($futureConflicts)?> conflict pairs.</small><?php endif;?></section><?php endif;?>

<form method="get" action="doctor_schedule.php" class="card compact-filter doctor-schedule-filter" data-auto-submit>
    <input type="hidden" name="week" value="<?=e($weekStart)?>"><input type="hidden" name="tab" value="<?=e($tab)?>">
    <label>Branch<select name="branch_id"><?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 if (!$isDentist): ?>
        <label>Search dentist<input type="search" data-select-search="doctor-schedule-doctor" placeholder="Type dentist name" autocomplete="off"></label>
        <label>Dentist<select id="doctor-schedule-doctor" name="doctor_id"><?php foreach ($dentists as $dentist): ?><option value="<?=(int)$dentist['id']?>" <?=$doctorId === (int) $dentist['id'] ? 'selected' : ''?>><?=e((string)$dentist['name'])?></option><?php endforeach; ?></select></label>
    <?php else: ?><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><?php endif; ?>
</form>

<div class="doctor-schedule-toolbar">
    <div class="view-switch doctor-schedule-tabs">
        <a class="<?=$tab === 'builder' ? 'active' : ''?>" href="<?=e(doctor_schedule_url(['tab'=>'builder']))?>">Availability Builder</a>
        <a class="<?=$tab === 'appointments' ? 'active' : ''?>" href="<?=e(doctor_schedule_url(['tab'=>'appointments']))?>">Week Appointments</a>
        <a class="<?=$tab === 'future' ? 'active' : ''?>" href="<?=e(doctor_schedule_url(['tab'=>'future','future_page'=>1]))?>">Future Appointments · <?=$futureTotal?></a>
    </div>
    <div class="doctor-schedule-actions">
        <a class="btn ghost" href="calendar.php?<?=e(http_build_query(['type'=>'doctor_week','doctor_id'=>$doctorId,'branch_id'=>$branchId,'date'=>$weekStart]))?>">Add this week to calendar</a>
        <a class="btn ghost" href="calendar.php?<?=e(http_build_query(['type'=>'doctor_month','doctor_id'=>$doctorId,'branch_id'=>$branchId,'date'=>$weekStart]))?>">Add this month to calendar</a>
        <?php if ($canEdit && $tab === 'builder'): ?>
            <button class="btn ghost" type="button" data-open="working-hours-dialog">Add working hours</button>
            <button class="btn ghost" type="button" data-open="copy-day-dialog">Copy a day</button>
            <button class="btn ghost" type="button" data-open="exception-dialog">Add break or leave</button>
            <form method="post" class="inline-form publish-schedule-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="publish_schedule"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><?php if($isAdministrator):?><label class="check-label"><input type="checkbox" name="impact_override" value="1"><span>Allow documented impact</span></label><input name="impact_override_reason" placeholder="Required when affected appointments remain"><?php endif;?><button class="btn primary" type="submit">Publish Schedule<?=($draftRuleCount + $draftExceptionCount) ? ' · ' . ($draftRuleCount + $draftExceptionCount) : ''?></button></form>
        <?php endif; ?>
    </div>
</div>

<?php if($tab !== 'future'): ?>
<section class="card doctor-week-card" data-doctor-schedule data-mode="<?=e($tab)?>" data-slot-interval="<?=$slotInterval?>">
    <div class="week-calendar-head">
        <a class="icon-button" href="<?=e(doctor_schedule_url(['week'=>date('Y-m-d',strtotime($weekStart . ' -7 days'))]))?>" aria-label="Previous week">‹</a>
        <div><span class="eyebrow"><?= $tab === 'builder' ? 'Working hours and exceptions' : 'Booked patient schedule' ?></span><h2><?=e(date('F j',strtotime($weekStart)) . '–' . date('F j, Y',strtotime($weekEnd)))?></h2><p><?= $tab === 'builder' ? 'Drag over an open day to select working hours. Draft changes do not affect booking until published.' : 'Appointments are overlaid against the doctor’s published availability.' ?></p></div>
        <div class="week-calendar-actions"><a class="btn link" href="<?=e(doctor_schedule_url(['week'=>date('Y-m-d')]))?>">This week</a><a class="icon-button" href="<?=e(doctor_schedule_url(['week'=>date('Y-m-d',strtotime($weekStart . ' +7 days'))]))?>" aria-label="Next week">›</a></div>
    </div>
    <div class="doctor-calendar-legend">
        <span><i class="legend-published"></i>Published hours</span><span><i class="legend-draft"></i>Draft hours</span><span><i class="legend-exception"></i>Break / leave</span><span><i class="legend-appointment"></i>Appointment</span>
    </div>
    <div class="doctor-calendar-scroll">
        <div class="doctor-calendar" style="--schedule-row-height:<?=$rowHeight?>px;--schedule-grid-height:<?=$gridHeight?>px">
            <aside class="doctor-time-axis"><div class="doctor-time-axis-head">Time</div><div class="doctor-time-axis-body" style="height:<?=$gridHeight?>px"><?php for ($minute=$gridStart; $minute<$gridEnd; $minute+=$slotInterval): $top=(int)(($minute-$gridStart)/$slotInterval)*$rowHeight; ?><span style="top:<?=$top?>px"><?=e(date('g:i A',strtotime(minutes_time($minute))))?></span><?php endfor; ?></div></aside>
            <?php foreach ($weekDates as $day):
                $date = (string) $day['date']; $dayIndex = (int) $day['day']; $dateHours = $day['hours']; $isToday = $date === date('Y-m-d');
            ?>
            <section class="doctor-calendar-day <?=$isToday ? 'today' : ''?> <?=$dateHours ? '' : 'closed'?>" data-schedule-date="<?=e($date)?>" data-schedule-day="<?=$dayIndex?>">
                <header><div><strong><?=e(date('D',strtotime($date)))?></strong><span><?=e(date('M j',strtotime($date)))?></span></div><?php if ($dateHours): ?><small><?=e(date('g:i A',strtotime((string)$dateHours['open_time'])) . '–' . date('g:i A',strtotime((string)$dateHours['close_time'])))?></small><?php else: ?><small>Closed</small><?php endif; ?><?php if ($canEdit && $tab === 'builder'): ?><button type="button" class="day-exception-button" data-exception-date="<?=e($date)?>" aria-label="Add break or leave on <?=e($date)?>">＋</button><?php endif; ?></header>
                <div class="doctor-calendar-day-body" style="height:<?=$gridHeight?>px">
                    <?php for ($minute=$gridStart; $minute<$gridEnd; $minute+=$slotInterval):
                        $top = (int)(($minute-$gridStart)/$slotInterval)*$rowHeight;
                        $inside = $dateHours && $minute >= time_minutes((string)$dateHours['open_time']) && ($minute + $slotInterval) <= time_minutes((string)$dateHours['close_time']);
                    ?>
                        <button type="button" class="doctor-calendar-slot <?=$inside ? '' : 'outside-hours'?>" style="top:<?=$top?>px;height:<?=$rowHeight?>px" data-slot-start="<?=e(minutes_time($minute))?>" data-slot-end="<?=e(minutes_time($minute+$slotInterval))?>" <?=$inside && $tab === 'builder' && $canEdit ? '' : 'disabled'?> aria-label="<?=e(date('D M j',strtotime($date)) . ' ' . date('g:i A',strtotime(minutes_time($minute))))?>"></button>
                    <?php endfor; ?>

                    <?php foreach ($rulesByDate[$date] ?? [] as $rule):
                        $startMinute = max($gridStart,time_minutes((string)$rule['start_time']));
                        $endMinute = min($gridEnd,time_minutes((string)$rule['end_time']));
                        if ($endMinute <= $startMinute) continue;
                        $top = (int)(($startMinute-$gridStart)/$slotInterval)*$rowHeight;
                        $height = max(28,(int)(($endMinute-$startMinute)/$slotInterval)*$rowHeight - 3);
                        $isDraft = in_array((string)$rule['status'],['draft','submitted'],true);
                    ?>
                        <article class="doctor-calendar-block availability-block <?=$isDraft ? 'draft' : 'published'?>" style="top:<?=$top?>px;height:<?=$height?>px" title="<?=e(date('g:i A',strtotime((string)$rule['start_time'])) . '–' . date('g:i A',strtotime((string)$rule['end_time'])))?>">
                            <strong><?=e(date('g:i A',strtotime((string)$rule['start_time'])) . '–' . date('g:i A',strtotime((string)$rule['end_time'])))?></strong><small><?=$isDraft ? 'Draft' : 'Published'?></small>
                        </article>
                    <?php endforeach; ?>

                    <?php foreach ($exceptionsByDate[$date] ?? [] as $exception):
                        $startMinute = $exception['all_day'] ? ($dateHours ? time_minutes((string)$dateHours['open_time']) : $gridStart) : time_minutes((string)$exception['start_time']);
                        $endMinute = $exception['all_day'] ? ($dateHours ? time_minutes((string)$dateHours['close_time']) : $gridEnd) : time_minutes((string)$exception['end_time']);
                        $startMinute=max($gridStart,$startMinute);$endMinute=min($gridEnd,$endMinute);if($endMinute<=$startMinute)continue;
                        $top=(int)(($startMinute-$gridStart)/$slotInterval)*$rowHeight;$height=max(30,(int)(($endMinute-$startMinute)/$slotInterval)*$rowHeight-3);
                        $isDraft=in_array((string)$exception['status'],['draft','submitted'],true);
                    ?>
                        <article class="doctor-calendar-block exception-block <?=$isDraft ? 'draft' : 'published'?>" style="top:<?=$top?>px;height:<?=$height?>px"><strong><?=e(ucwords(str_replace('_',' ',(string)$exception['exception_type'])))?></strong><small><?=e((string)($exception['reason'] ?: ($isDraft ? 'Draft change' : 'Published')))?></small></article>
                    <?php endforeach; ?>

                    <?php foreach ($appointmentsByDate[$date] ?? [] as $appointment):
                        $startMinute=time_minutes(date('H:i',strtotime((string)$appointment['start_at'])));$endMinute=time_minutes(date('H:i',strtotime((string)$appointment['end_at'])));$startMinute=max($gridStart,$startMinute);$endMinute=min($gridEnd,$endMinute);if($endMinute<=$startMinute)continue;
                        $top=(int)(($startMinute-$gridStart)/$slotInterval)*$rowHeight;$height=max(34,(int)(($endMinute-$startMinute)/$slotInterval)*$rowHeight-4);
                    ?>
                        <a class="doctor-calendar-block appointment-block status-<?=e((string)$appointment['status'])?>" style="top:<?=$top?>px;height:<?=$height?>px" href="patients.php?action=view&id=<?=(int)$appointment['patient_id']?>" title="<?=e((string)$appointment['patient'] . ' · ' . (string)$appointment['service_name'])?>"><strong><?=e(date('g:i A',strtotime((string)$appointment['start_at'])) . ' · ' . (string)$appointment['patient'])?></strong><small><?=e((string)$appointment['service_name'])?></small></a>
                    <?php endforeach; ?>
                </div>
            </section>
            <?php endforeach; ?>
        </div>
    </div>
</section>
<?php endif; ?>

<?php if ($tab === 'builder'): ?>
<div class="grid two doctor-schedule-detail-grid">
    <section class="card">
        <div class="card-head"><div><h2>Working-hour blocks</h2><p>Draft blocks appear on the calendar but are ignored by appointment booking until published.</p></div></div>
        <div class="schedule-block-list"><?php foreach ($rules as $rule): ?><article><div><strong><?=e($days[(int)$rule['day_of_week']] . ' · ' . date('g:i A',strtotime((string)$rule['start_time'])) . '–' . date('g:i A',strtotime((string)$rule['end_time'])))?></strong><small><?=e(date('M j, Y',strtotime((string)$rule['effective_from'])))?><?=!empty($rule['effective_until']) ? ' to ' . e(date('M j, Y',strtotime((string)$rule['effective_until']))) : ' onward'?> · <?=e(ucfirst((string)$rule['status']))?></small></div><?php if($canEdit):?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="delete_rule"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><input type="hidden" name="rule_id" value="<?=(int)$rule['id']?>"><button class="icon-text-button" type="submit" aria-label="Remove working-hour block">×</button></form><?php endif;?></article><?php endforeach; ?><?php if(!$rules):?><p class="muted">No working hours have been created.</p><?php endif;?></div>
    </section>
    <section class="card">
        <div class="card-head"><div><h2>This week’s exceptions</h2><p>Breaks, leave, training, closures, and special availability override recurring hours.</p></div></div>
        <div class="schedule-block-list"><?php foreach($exceptions as $exception):?><article><div><strong><?=e(date('D, M j',strtotime((string)$exception['exception_date'])) . ' · ' . ucwords(str_replace('_',' ',(string)$exception['exception_type'])))?></strong><small><?=$exception['all_day'] ? 'All day' : e(date('g:i A',strtotime((string)$exception['start_time'])) . '–' . date('g:i A',strtotime((string)$exception['end_time'])))?> · <?=e(ucfirst((string)$exception['status']))?><?=!empty($exception['reason']) ? ' · ' . e((string)$exception['reason']) : ''?></small></div><?php if($canEdit):?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="delete_exception"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><input type="hidden" name="exception_id" value="<?=(int)$exception['id']?>"><button class="icon-text-button" type="submit" aria-label="Remove schedule exception">×</button></form><?php endif;?></article><?php endforeach;?><?php if(!$exceptions):?><p class="muted">No exception blocks in this week.</p><?php endif;?></div>
    </section>
</div>
<?php elseif ($tab === 'appointments'): ?>
<section class="card table-wrap"><div class="card-head"><div><h2>Appointments for this week</h2><p>Open the patient record from the calendar or this list.</p></div></div><table><thead><tr><th>Date and time</th><th>Patient</th><th>Service</th><th>Chair</th><th>Status</th><th></th></tr></thead><tbody><?php foreach($appointments as $appointment):?><tr><td><?=e(date('D, M j · g:i A',strtotime((string)$appointment['start_at'])))?></td><td><strong><?=e((string)$appointment['patient'])?></strong><small><?=e((string)$appointment['patient_no'])?></small></td><td><?=e((string)$appointment['service_name'])?></td><td><?=e((string)($appointment['chair_name'] ?: '—'))?></td><td><span class="badge"><?=e(ucwords(str_replace('_',' ',(string)$appointment['status'])))?></span></td><td><div class="table-actions"><a href="patients.php?action=view&id=<?=(int)$appointment['patient_id']?>">Open patient</a><a href="<?=e(appointment_calendar_relative_url($appointment))?>">Add to calendar</a></div></td></tr><?php endforeach;?><?php if(!$appointments):?><tr><td colspan="6" class="muted">No appointments are assigned to this dentist during the selected week.</td></tr><?php endif;?></tbody></table></section>
<?php else: ?>
<section class="card"><div class="card-head"><div><h2>Future appointments</h2><p>Appointments from <?=e(date('M j, Y',strtotime($futureFrom)))?> through <?=e(date('M j, Y',strtotime($futureTo)))?>. Cancelled and no-show records are excluded.</p></div><a class="btn ghost" href="calendar.php?<?=e(http_build_query(['type'=>'doctor_month','doctor_id'=>$doctorId,'branch_id'=>$branchId,'date'=>$futureFrom]))?>">Add month to calendar</a></div><form method="get" action="doctor_schedule.php" class="future-appointment-controls" data-auto-submit><input type="hidden" name="tab" value="future"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><label>From<input type="date" name="future_from" value="<?=e($futureFrom)?>"></label><label>Through<input type="date" name="future_to" value="<?=e($futureTo)?>"></label></form></section>
<section class="card table-wrap"><table><thead><tr><th>Date and time</th><th>Patient</th><th>Service</th><th>Chair</th><th>Status</th><th></th></tr></thead><tbody><?php foreach($futureAppointments as $appointment):?><tr><td><?=e(date('D, M j, Y · g:i A',strtotime((string)$appointment['start_at'])))?><small><?=e(date('g:i A',strtotime((string)$appointment['end_at'])))?> end</small></td><td><strong><?=e((string)$appointment['patient'])?></strong><small><?=e((string)$appointment['patient_no'])?><?=!empty($appointment['mobile'])?' · '.e((string)$appointment['mobile']):''?></small></td><td><?=e((string)$appointment['service_name'])?></td><td><?=e((string)($appointment['chair_name']?:'Unassigned'))?></td><td><span class="badge"><?=e(ucwords(str_replace('_',' ',(string)$appointment['status'])))?></span></td><td><div class="table-actions"><a href="patients.php?action=view&id=<?=(int)$appointment['patient_id']?>">Open patient</a><?php if($isAdministrator):?><a href="appointments.php?edit_id=<?=(int)$appointment['id']?>">Reschedule</a><?php endif;?><a href="<?=e(appointment_calendar_relative_url($appointment))?>">Add to calendar</a></div></td></tr><?php endforeach;?><?php if(!$futureAppointments):?><tr><td colspan="6" class="muted">No future appointments fall inside this date range.</td></tr><?php endif;?></tbody></table><?php if($futurePages>1):?><nav class="pagination-bar" aria-label="Future appointments pages"><span>Page <?=$futurePage?> of <?=$futurePages?> · <?=$futureTotal?> appointments</span><div><?php if($futurePage>1):?><a class="btn ghost" href="<?=e(doctor_schedule_url(['tab'=>'future','future_page'=>$futurePage-1,'future_from'=>$futureFrom,'future_to'=>$futureTo]))?>">Previous</a><?php endif;?><?php if($futurePage<$futurePages):?><a class="btn ghost" href="<?=e(doctor_schedule_url(['tab'=>'future','future_page'=>$futurePage+1,'future_from'=>$futureFrom,'future_to'=>$futureTo]))?>">Next</a><?php endif;?></div></nav><?php endif;?></section>
<?php endif; ?>

<?php if ($canEdit): ?>
<dialog id="working-hours-dialog" class="schedule-builder-dialog"><form method="post" class="form-grid" data-working-hours-form><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Add working hours</h2><p class="wide muted">Drag over the calendar or choose the times below. Select additional weekdays to copy this same block in one action.</p><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_working_hours"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>">
<label>Weekday<select name="day_of_week" data-builder-day required><?php foreach($weekOrder as $dayIndex):$branchDay=$weeklyHours[$dayIndex]??null;?><option value="<?=$dayIndex?>" <?=(!$branchDay || !(bool)$branchDay['is_open'])?'disabled':''?>><?=e($days[$dayIndex])?><?=($branchDay && (bool)$branchDay['is_open'])?' · '.e(date('g:i A',strtotime((string)$branchDay['open_time'])) . '–' . date('g:i A',strtotime((string)$branchDay['close_time']))):' · Closed'?></option><?php endforeach;?></select></label>
<label>Start<input type="time" name="start_time" data-builder-start required></label><label>End<input type="time" name="end_time" data-builder-end required></label><label>Effective from<input type="date" name="effective_from" value="<?=e(max(date('Y-m-d'),$weekStart))?>" required></label><label>Effective until<input type="date" name="effective_until"></label>
<fieldset class="wide weekday-copy-fieldset"><legend>Copy the same hours to</legend><?php foreach($weekOrder as $dayIndex):$branchDay=$weeklyHours[$dayIndex]??null;?><label><input type="checkbox" name="copy_days[]" value="<?=$dayIndex?>" <?=(!$branchDay || !(bool)$branchDay['is_open'])?'disabled data-closed-day="1"':''?>><span><?=e(substr($days[$dayIndex],0,3))?></span></label><?php endforeach;?></fieldset>
<div class="wide public-notice">This is saved as a draft. Use <strong>Publish Schedule</strong> after reviewing all blocks.</div><button class="btn primary wide" type="submit">Add draft working hours</button></form></dialog>

<dialog id="copy-day-dialog" class="schedule-builder-dialog"><form method="post" class="form-grid"><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Copy one day’s schedule</h2><p class="wide muted">Copies every working-hours block from the source weekday to the selected weekdays as draft blocks.</p><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="copy_day"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><label>Source weekday<select name="source_day" required><?php foreach($weekOrder as $dayIndex):?><option value="<?=$dayIndex?>"><?=e($days[$dayIndex])?></option><?php endforeach;?></select></label><label>Effective from<input type="date" name="copy_effective_from" value="<?=e(max(date('Y-m-d'),$weekStart))?>" required></label><label>Effective until<input type="date" name="copy_effective_until"></label><fieldset class="wide weekday-copy-fieldset"><legend>Destination weekdays</legend><?php foreach($weekOrder as $dayIndex):$branchDay=$weeklyHours[$dayIndex]??null;?><label><input type="checkbox" name="target_days[]" value="<?=$dayIndex?>" <?=(!$branchDay || !(bool)$branchDay['is_open'])?'disabled data-closed-day="1"':''?>><span><?=e(substr($days[$dayIndex],0,3))?></span></label><?php endforeach;?></fieldset><button class="btn primary wide" type="submit">Copy as draft</button></form></dialog>

<dialog id="exception-dialog" class="schedule-builder-dialog"><form method="post" class="form-grid" data-schedule-exception-form><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Add break, leave, or special hours</h2><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_exception"><input type="hidden" name="branch_id" value="<?=$branchId?>"><input type="hidden" name="doctor_id" value="<?=$doctorId?>"><label>Date<input type="date" name="exception_date" data-exception-date value="<?=e(max(date('Y-m-d'),$weekStart))?>" required></label><label>Type<select name="exception_type"><option value="break">Break</option><option value="leave">Leave</option><option value="training">Training</option><option value="unavailable">Unavailable</option><option value="closure">Closure</option><option value="special_availability">Special availability</option></select></label><label class="check-label"><input type="checkbox" name="all_day" value="1" data-all-day><span>All day</span></label><label>Start<input type="time" name="exception_start" data-exception-part></label><label>End<input type="time" name="exception_end" data-exception-part></label><label class="wide">Reason<input name="reason" maxlength="255" placeholder="Lunch, conference, personal leave, emergency coverage, etc."></label><div class="wide public-notice">The exception remains a draft until the schedule is published.</div><button class="btn primary wide" type="submit">Add draft exception</button></form></dialog>
<?php endif; ?>

<?php render_footer(); ?>
