<?php
require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_documentation_schema();
require_permission('clinical.view',['super_admin','clinic_admin','dentist','dental_assistant']);
require DENTAL_ROOT . '/app/layout.php';
require DENTAL_ROOT . '/app/module_helpers.php';
if (is_file(DENTAL_ROOT . '/app/tooth_model.php')) { require_once DENTAL_ROOT . '/app/tooth_model.php'; }
require_once DENTAL_ROOT . '/app/clinical_media.php';


if (!function_exists('dental_render_tooth_model')) {
    function dental_render_tooth_model(string $tooth, array $entries, string $url, bool $selected, string $arch, bool $isPrimary = false, array $options = []): string
    {
        $class = 'tooth-button' . ($selected ? ' selected' : '');
        return '<a class="' . htmlspecialchars($class, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '" href="' . htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '"><span>' . htmlspecialchars($tooth, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</span></a>';
    }
}
if (!function_exists('dental_tooth_display_label')) {
    function dental_tooth_display_label(string $tooth): string { return 'Tooth ' . $tooth; }
}
if (!function_exists('chart_condition_class')) {
    function chart_condition_class(string $category, string $layer, string $severity = ''): string { return ''; }
}
if (!function_exists('dental_surface_display')) {
    function dental_surface_display(string $surface, string $tooth = ''): string { return $surface !== '' ? strtoupper($surface) : 'Whole tooth'; }
}

$pdo=db();
$canEdit=false;
$chartEditReason='';
$patients=patient_options();
$patientId=(int)($_GET['patient_id']??$_POST['patient_id']??0);
$dentition=(string)($_GET['dentition']??$_POST['dentition']??'permanent');
if(!in_array($dentition,['permanent','primary','mixed'],true))$dentition='permanent';
$selectedTooth=trim((string)($_GET['tooth']??''));
$encounterContextId=(int)($_GET['encounter_id']??$_POST['encounter_context_id']??0);
$chartReady=schema_table_exists(table('dental_condition_catalog'))
    && schema_table_exists(table('dental_chart_reviews'))
    && schema_column_exists(table('odontogram_entries'),'finding_type')
    && schema_column_exists(table('treatment_plan_items'),'odontogram_entry_id');

function chart_edit_access(PDO $pdo,int $patientId,int $encounterId): array
{
    if(!clinical_record_edit_allowed(['dentist'])){
        return [false,'Read-only chart. Sign in with the assigned dentist account to record tooth findings.'];
    }
    if($patientId<=0)return [false,'Select a patient before opening the dental chart.'];
    if($encounterId<=0){
        return [false,'Start or continue the patient treatment encounter from My Treatment Dashboard before recording a tooth finding.'];
    }
    $stmt=$pdo->prepare('SELECT patient_id,dentist_user_id,status FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
    $stmt->execute([$encounterId]);
    $encounter=$stmt->fetch();
    if(!$encounter || (int)$encounter['patient_id']!==$patientId){
        return [false,'The treatment encounter does not belong to this patient.'];
    }
    if((int)$encounter['dentist_user_id']!==(int)(current_user()['id']??0)){
        return [false,'Read-only chart. Only the dentist assigned to this encounter can record tooth findings.'];
    }
    if((string)($encounter['status']??'')==='finalized'){
        return [false,'This encounter is finalized. Start a new encounter to record new clinical findings.'];
    }
    return [true,''];
}

function chart_imaging_access(PDO $pdo,int $patientId,int $encounterId): array
{
    if(!clinical_record_edit_allowed(['dentist','dental_assistant'])){
        return [false,'X-ray upload requires the assigned Dentist or Dental Assistant task workspace.'];
    }
    if($patientId<=0||$encounterId<=0)return [false,'Start the treatment encounter before attaching an X-ray.'];
    $stmt=$pdo->prepare('SELECT patient_id,dentist_user_id,status FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
    $stmt->execute([$encounterId]);$encounter=$stmt->fetch();
    if(!$encounter||(int)$encounter['patient_id']!==$patientId)return [false,'The treatment encounter does not belong to this patient.'];
    if((string)($encounter['status']??'')==='finalized')return [false,'This encounter is finalized. Upload later images from Images & Documents with the appropriate record context.'];
    $role=active_role();$userId=(int)(current_user()['id']??0);
    if($role==='dentist'&&(int)$encounter['dentist_user_id']!==$userId)return [false,'Only the assigned dentist or assigned dental assistant may attach chairside imaging.'];
    if($role==='dental_assistant'&&!in_array((int)$encounter['dentist_user_id'],task_dentist_scope_ids(),true))return [false,'This encounter is outside the assistant assignment.'];
    return [true,''];
}

function chart_surface_values(array $raw): array
{
    $allowed=['M','D','F','B','L','P','O','I','Cervical','Root','Whole'];
    $values=[];
    foreach($raw as $value){$value=trim((string)$value);if(in_array($value,$allowed,true))$values[]=$value;}
    return array_values(array_unique($values));
}

function chart_valid_teeth(string $dentition): array
{
    $permanentUpper=['18','17','16','15','14','13','12','11','21','22','23','24','25','26','27','28'];
    $permanentLower=['48','47','46','45','44','43','42','41','31','32','33','34','35','36','37','38'];
    $primaryUpper=['55','54','53','52','51','61','62','63','64','65'];
    $primaryLower=['85','84','83','82','81','71','72','73','74','75'];
    return match($dentition){
        'primary'=>array_merge($primaryUpper,$primaryLower),
        'mixed'=>array_merge($permanentUpper,$primaryUpper,$permanentLower,$primaryLower),
        default=>array_merge($permanentUpper,$permanentLower),
    };
}

function chart_condition_class(string $category,string $layer,string $severity=''): string
{
    $category=preg_replace('/[^a-z0-9_-]/','-',strtolower($category))?:'condition';
    $layer=preg_replace('/[^a-z0-9_-]/','-',strtolower($layer))?:'existing';
    $severity=preg_replace('/[^a-z0-9_-]/','-',strtolower($severity))?:'none';
    return 'category-'.$category.' layer-'.$layer.' severity-'.$severity;
}
function chart_tooth_surfaces(array $entries): array
{
    $map=[];
    foreach($entries as $entry){
        $surfaces=array_filter(array_map('trim',explode(',',(string)($entry['surface']??''))));
        if(!$surfaces)$surfaces=['Whole'];
        foreach($surfaces as $surface)$map[$surface]=$entry;
    }
    return $map;
}

[$canEdit,$chartEditReason]=chart_edit_access($pdo,$patientId,$encounterContextId);
[$canUploadImaging,$imagingAccessReason]=chart_imaging_access($pdo,$patientId,$encounterContextId);

if($_SERVER['REQUEST_METHOD']==='POST'){
    verify_csrf();
    $action=(string)($_POST['action']??'add_finding');
    $patientId=(int)($_POST['patient_id']??0);
    $patient=require_patient($patientId);
    $encounterContextId=(int)($_POST['encounter_context_id']??0);
    $returnDentition=(string)($_POST['dentition']??'permanent');
    if(!in_array($returnDentition,['permanent','primary','mixed'],true))$returnDentition='permanent';

    if($action==='upload_xray'){
        require_clinical_record_editor(['dentist','dental_assistant']);
        [$postCanUpload,$postUploadReason]=chart_imaging_access($pdo,$patientId,$encounterContextId);
        if(!$postCanUpload)throw new RuntimeException($postUploadReason ?: 'This imaging record is read-only.');
        $tooth=trim((string)($_POST['tooth_region']??''));
        $category=trim((string)($_POST['category']??'Periapical X-ray'));
        if(!clinical_is_imaging_category($category))throw new RuntimeException('Choose a valid X-ray or clinical imaging type.');
        $documentId=clinical_store_document_upload(
            $pdo,(array)($_FILES['document']??[]),$patientId,$encounterContextId,
            $category,$tooth,
            trim((string)($_POST['description']??'')),'clinical'
        );
        audit('chairside_xray_uploaded','document',$documentId,['patient_id'=>$patientId,'encounter_id'=>$encounterContextId,'tooth_region'=>$tooth]);
        module_return('odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($returnDentition).'&encounter_id='.$encounterContextId.($tooth!==''?'&tooth='.urlencode($tooth).'#tooth-'.rawurlencode($tooth):''),'success','X-ray or clinical image uploaded and linked to this encounter.');
    }

    require_clinical_record_editor(['dentist']);
    if(!$chartReady)throw new RuntimeException('Apply Version 0.5.8.6 before using structured dental charting.');
    [$postCanEdit,$postEditReason]=chart_edit_access($pdo,$patientId,$encounterContextId);
    if(!$postCanEdit)throw new RuntimeException($postEditReason ?: 'This dental chart is read-only.');

    if($action==='save_review'){
        $status=(string)($_POST['review_status']??'draft');
        if(!in_array($status,['draft','finalized'],true))$status='draft';
        $encounterId=(int)($_POST['encounter_id']??0)?:null;
        if($encounterId){
            $stmt=$pdo->prepare('SELECT patient_id,dentist_user_id,status FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
            $stmt->execute([$encounterId]);$enc=$stmt->fetch();
            if(!$enc||(int)$enc['patient_id']!==$patientId)throw new RuntimeException('The selected encounter does not belong to this patient.');
            if((int)$enc['dentist_user_id']!==(int)(current_user()['id']??0))throw new RuntimeException('Only the assigned dentist can chart this encounter.');
            if((string)($enc['status']??'')==='finalized')throw new RuntimeException('This encounter is finalized and cannot accept new findings.');
            if((int)$enc['dentist_user_id']!==(int)(current_user()['id']??0))throw new RuntimeException('Only the assigned dentist can chart this encounter.');
        }
        $pdo->prepare('INSERT INTO `' . table('dental_chart_reviews') . '` (patient_id,encounter_id,dentist_user_id,dentition,review_date,caries_risk,periodontal_risk,oral_hygiene_status,occlusion_summary,clinical_summary,status,finalized_at,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,IF(?="finalized",NOW(),NULL),NOW())')->execute([
            $patientId,$encounterId,(int)current_user()['id'],$returnDentition,(string)($_POST['review_date']??date('Y-m-d')),
            trim((string)($_POST['caries_risk']??''))?:null,trim((string)($_POST['periodontal_risk']??''))?:null,
            trim((string)($_POST['oral_hygiene_status']??''))?:null,trim((string)($_POST['occlusion_summary']??''))?:null,
            trim((string)($_POST['clinical_summary']??''))?:null,$status,$status
        ]);
        $reviewId=(int)$pdo->lastInsertId();
        audit('dental_chart_review_saved','dental_chart_review',$reviewId,['patient_id'=>$patientId,'status'=>$status]);
        module_return('odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($returnDentition).($encounterContextId?'&encounter_id='.$encounterContextId:''),'success',$status==='finalized'?'Dental chart review finalized.':'Dental chart review saved as draft.');
    }

    if($action==='resolve_finding'){
        $findingId=(int)($_POST['finding_id']??0);
        $stmt=$pdo->prepare('SELECT * FROM `' . table('odontogram_entries') . '` WHERE id=? AND patient_id=? LIMIT 1');
        $stmt->execute([$findingId,$patientId]);$finding=$stmt->fetch();
        if(!$finding)throw new RuntimeException('Tooth finding was not found.');
        if(($finding['status']??'')!=='active')throw new RuntimeException('This finding is already resolved or superseded.');
        $pdo->prepare('UPDATE `' . table('odontogram_entries') . '` SET status="resolved",resolved_by=?,resolved_at=NOW() WHERE id=?')->execute([(int)current_user()['id'],$findingId]);
        audit('tooth_finding_resolved','odontogram',$findingId,['patient_id'=>$patientId,'tooth'=>$finding['tooth_number']]);
        module_return('odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($returnDentition).($encounterContextId?'&encounter_id='.$encounterContextId:'').'&tooth='.urlencode((string)$finding['tooth_number']).'#tooth-'.rawurlencode((string)$finding['tooth_number']),'success','Tooth finding marked resolved. The original record remains in history.');
    }

    if($action==='add_finding'){
        $tooth=trim((string)($_POST['tooth_number']??''));
        $condition=trim((string)($_POST['condition_code']??''));
        if($tooth===''||$condition==='')throw new RuntimeException('Select a tooth and clinical finding.');
        if(!in_array($tooth,chart_valid_teeth($returnDentition),true))throw new RuntimeException('The selected FDI tooth number is not valid for this dentition view.');
        $entryDentition=dental_tooth_dentition($tooth)??$returnDentition;
        $conditionStmt=$pdo->prepare('SELECT * FROM `' . table('dental_condition_catalog') . '` WHERE code=? AND is_active=1 LIMIT 1');
        $conditionStmt->execute([$condition]);$catalog=$conditionStmt->fetch();
        if(!$catalog)throw new RuntimeException('The selected tooth finding is not available.');
        $surfaces=chart_surface_values((array)($_POST['surfaces']??[]));
        $isCaries=strtolower(trim((string)($catalog['category']??'')))==='caries';
        if($isCaries){
            $allowedCariesSurfaces=array_keys(dental_caries_surface_options($tooth));
            $surfaces=array_values(array_filter($surfaces,static fn(string $surface):bool=>in_array($surface,$allowedCariesSurfaces,true)));
            if(!$surfaces){
                $toothClass=dental_tooth_is_anterior($tooth)?'anterior':'posterior';
                throw new RuntimeException('Choose at least one valid '.$toothClass.' caries surface before saving the finding.');
            }
        }elseif(!$surfaces){
            $surfaces=['Whole'];
        }
        $surfaceText=implode(',',$surfaces);
        $layer=(string)($_POST['chart_layer']??'existing');
        if(!in_array($layer,['existing','proposed','in_progress','completed','historical'],true))$layer='existing';
        $severity=trim((string)($_POST['severity']??''))?:trim((string)($catalog['default_severity']??''));
        if(!in_array($severity,['information','mild','moderate','severe','critical'],true))$severity=null;
        $encounterId=(int)($_POST['encounter_id']??0)?:null;
        if($encounterId){
            $stmt=$pdo->prepare('SELECT patient_id,dentist_user_id,status FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
            $stmt->execute([$encounterId]);$enc=$stmt->fetch();
            if(!$enc||(int)$enc['patient_id']!==$patientId)throw new RuntimeException('The selected encounter does not belong to this patient.');
            if((int)$enc['dentist_user_id']!==(int)(current_user()['id']??0))throw new RuntimeException('Only the assigned dentist can chart this encounter.');
            if((string)($enc['status']??'')==='finalized')throw new RuntimeException('This encounter is finalized and cannot accept new findings.');
        }
        $pdo->beginTransaction();
        try{
            $supersedesId=null;
            if(isset($_POST['replace_matching'])){
                $match=$pdo->prepare('SELECT id FROM `' . table('odontogram_entries') . '` WHERE patient_id=? AND dentition=? AND tooth_number=? AND surface=? AND status="active" ORDER BY recorded_at DESC,id DESC LIMIT 1 FOR UPDATE');
                $match->execute([$patientId,$entryDentition,$tooth,$surfaceText]);$supersedesId=(int)($match->fetchColumn()?:0)?:null;
                if($supersedesId)$pdo->prepare('UPDATE `' . table('odontogram_entries') . '` SET status="superseded",resolved_by=?,resolved_at=NOW() WHERE id=?')->execute([(int)current_user()['id'],$supersedesId]);
            }
            $stmt=$pdo->prepare('INSERT INTO `' . table('odontogram_entries') . '` (patient_id,encounter_id,dentition,tooth_number,surface,condition_code,finding_type,severity,chart_layer,status,supersedes_entry_id,notes,recorded_by,recorded_at) VALUES (?,?,?,?,?,?,?,?,?,"active",?,?,?,NOW())');
            $stmt->execute([$patientId,$encounterId,$entryDentition,$tooth,$surfaceText,$condition,(string)$catalog['category'],$severity,$layer,$supersedesId,trim((string)($_POST['notes']??''))?:null,(int)current_user()['id']]);
            $findingId=(int)$pdo->lastInsertId();
            if(isset($_POST['create_diagnosis'])){
                if(!$encounterId)throw new RuntimeException('Choose an encounter before creating a linked diagnosis.');
                $diagnosisName=trim((string)($_POST['diagnosis_name']??''))?:((string)$catalog['name']);
                $diagnosisSeverity=in_array($severity,['mild','moderate','severe','critical'],true)?$severity:null;
                $pdo->prepare('INSERT INTO `' . table('diagnoses') . '` (patient_id,encounter_id,tooth_region,diagnosis_code,diagnosis_name,severity,diagnosis_status,is_primary,notes,diagnosed_by,diagnosed_at) VALUES (?,?,?,?,?,?,"confirmed",?,?,?,NOW())')->execute([
                    $patientId,$encounterId,$tooth.($surfaceText&&$surfaceText!=='Whole'?'-'.$surfaceText:''),$condition,$diagnosisName,$diagnosisSeverity,isset($_POST['is_primary_diagnosis'])?1:0,trim((string)($_POST['notes']??''))?:null,(int)current_user()['id']
                ]);
                $diagnosisId=(int)$pdo->lastInsertId();
                $pdo->prepare('UPDATE `' . table('odontogram_entries') . '` SET diagnosis_id=? WHERE id=?')->execute([$diagnosisId,$findingId]);
            }
            $pdo->commit();
        }catch(Throwable $exception){if($pdo->inTransaction())$pdo->rollBack();throw $exception;}
        audit('tooth_finding_created','odontogram',$findingId,['patient_id'=>$patientId,'tooth'=>$tooth,'surfaces'=>$surfaces,'condition'=>$condition,'severity'=>$severity]);
        module_return('odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($returnDentition).($encounterContextId?'&encounter_id='.$encounterContextId:'').'&tooth='.urlencode($tooth).'#tooth-'.rawurlencode($tooth),'success','Tooth finding saved to the patient dental chart.');
    }
}

$patient=null;$entries=[];$activeEntries=[];$reviews=[];$encounters=[];$plans=[];$conditions=[];$latestReview=null;
if($chartReady){
    $conditions=$pdo->query('SELECT * FROM `' . table('dental_condition_catalog') . '` WHERE is_active=1 ORDER BY category,name')->fetchAll();
}
if($patientId){
    $patient=require_patient($patientId);
    $dentitionWhere=$dentition==='mixed'?'o.dentition IN ("permanent","primary","mixed")':'o.dentition=?';
    $entryArgs=$dentition==='mixed'?[$patientId]:[$patientId,$dentition];
    $entrySql=$chartReady
        ? 'SELECT o.*,COALESCE(c.name,d.diagnosis_name) condition_name,COALESCE(c.category,IF(d.id IS NOT NULL,"diagnosis",o.finding_type)) category,c.description,c.recommended_procedure,u.name recorder,d.diagnosis_name,d.diagnosis_status FROM `' . table('odontogram_entries') . '` o LEFT JOIN `' . table('dental_condition_catalog') . '` c ON c.code=o.condition_code LEFT JOIN `' . table('users') . '` u ON u.id=o.recorded_by LEFT JOIN `' . table('diagnoses') . '` d ON d.id=o.diagnosis_id WHERE o.patient_id=? AND '.$dentitionWhere.' ORDER BY o.recorded_at DESC,o.id DESC'
        : 'SELECT o.*,d.diagnosis_name condition_name,IF(d.id IS NOT NULL,"diagnosis",o.finding_type) category,NULL description,NULL recommended_procedure,u.name recorder,d.diagnosis_name,d.diagnosis_status FROM `' . table('odontogram_entries') . '` o LEFT JOIN `' . table('users') . '` u ON u.id=o.recorded_by LEFT JOIN `' . table('diagnoses') . '` d ON d.id=o.diagnosis_id WHERE o.patient_id=? AND '.$dentitionWhere.' ORDER BY o.recorded_at DESC,o.id DESC';
    $stmt=$pdo->prepare($entrySql);$stmt->execute($entryArgs);$entries=$stmt->fetchAll();
    $activeEntries=array_values(array_filter($entries,static fn(array $row):bool=>($row['status']??'')==='active'));
    if($chartReady){
        $stmt=$pdo->prepare('SELECT r.*,u.name dentist FROM `' . table('dental_chart_reviews') . '` r LEFT JOIN `' . table('users') . '` u ON u.id=r.dentist_user_id WHERE r.patient_id=? ORDER BY r.review_date DESC,r.id DESC');$stmt->execute([$patientId]);$reviews=$stmt->fetchAll();$latestReview=$reviews[0]??null;
    }
    $stmt=$pdo->prepare('SELECT id,created_at,status,chief_complaint,dentist_name FROM `' . table('clinical_encounters') . '` WHERE patient_id=? ORDER BY created_at DESC LIMIT 20');$stmt->execute([$patientId]);$encounters=$stmt->fetchAll();
    $stmt=$pdo->prepare('SELECT id,title,status,phase FROM `' . table('treatment_plans') . '` WHERE patient_id=? AND status NOT IN ("completed","cancelled","expired","revised") ORDER BY created_at DESC');$stmt->execute([$patientId]);$plans=$stmt->fetchAll();
}

$imaging=$patientId?clinical_recent_imaging($pdo,$patientId,$encounterContextId?:null,16):[];
$selectedImaging=[];foreach($imaging as $image){$region=trim((string)($image['tooth_region']??''));if($selectedTooth!==''&&($region===$selectedTooth||str_starts_with($region,$selectedTooth.' ' )||str_starts_with($region,$selectedTooth.'·')||str_starts_with($region,$selectedTooth.'-'))) $selectedImaging[]=$image;}
$byTooth=[];$selectedEntries=[];
foreach($activeEntries as $entry){$byTooth[(string)$entry['tooth_number']][]=$entry;if((string)$entry['tooth_number']===$selectedTooth)$selectedEntries[]=$entry;}
$permanentUpper=['18','17','16','15','14','13','12','11','21','22','23','24','25','26','27','28'];
$permanentLower=['48','47','46','45','44','43','42','41','31','32','33','34','35','36','37','38'];
$primaryUpper=['55','54','53','52','51','61','62','63','64','65'];
$primaryLower=['85','84','83','82','81','71','72','73','74','75'];
$upper=$dentition==='primary'?$primaryUpper:($dentition==='mixed'?array_merge($permanentUpper,$primaryUpper):$permanentUpper);
$lower=$dentition==='primary'?$primaryLower:($dentition==='mixed'?array_merge($permanentLower,$primaryLower):$permanentLower);
$validTeeth=chart_valid_teeth($dentition);
$untreated=array_filter($activeEntries,static fn(array $row):bool=>!in_array((string)($row['category']??''),['restoration','prosthesis','completed'],true)&&($row['condition_code']??'')!=='sound');
$urgent=array_filter($activeEntries,static fn(array $row):bool=>in_array((string)($row['severity']??''),['severe','critical'],true));

render_header('Dental Chart');
render_module_intro('Dental chart and tooth findings','Open the chart first. Stats, reviews, and history stay below the active tooth workspace.',$canEdit&&$patientId?'Add tooth finding':null,$canEdit&&$patientId?'finding-form':null);
render_patient_filter($patients,'odontogram.php',$patientId);
if(!$chartReady):?><div class="flash warning"><strong>Update required.</strong> Apply Version 0.5.8.6 under System Setup to enable structured tooth findings and linked treatment plans.</div><?php endif;
if(!$patientId):?><div class="card queue-empty">Select a patient to open the doctor dental chart.</div><?php else:?>
<section class="dental-chart-header card"><div><span class="eyebrow">Patient dental record</span><h2><?=e(patient_full_name($patient))?></h2><p><?=e((string)$patient['patient_no'])?><?=!empty($patient['primary_dentist_user_id'])?' · Continuity dentist assigned':''?></p></div><div class="dental-chart-actions"><?php if($encounterContextId):?><a class="btn primary" href="clinical_workspace.php?encounter_id=<?=$encounterContextId?>">Return to treatment</a><?php endif;?><?php if($canUploadImaging):?><button class="btn ghost" type="button" data-open="xray-form">Capture / upload X-ray</button><?php endif;?><a class="btn ghost" href="documents.php?patient_id=<?=$patientId?>">Images & X-rays</a><a class="btn ghost" href="patients.php?action=view&id=<?=$patientId?>">Patient record</a><a class="btn ghost" href="treatment.php?patient_id=<?=$patientId?>">Treatment plans</a></div></section>
<?php if(!empty($patient['medical_alerts'])):?><div class="flash error"><strong>Dental safety alert:</strong> <?=e((string)$patient['medical_alerts'])?></div><?php endif;?>
<?php if(!$canEdit):?><div class="flash warning chart-access-notice" data-chart-access-notice><strong>Read-only dental chart.</strong> <?=e($chartEditReason ?: 'Only the assigned dentist can record or change tooth findings.')?></div><?php else:?><div class="flash success chart-access-notice" data-chart-access-notice><strong>Clinical charting enabled.</strong> Select any tooth to open the finding form for this treatment encounter.</div><?php endif;?>
<?php $encounterQuery=$encounterContextId?'&encounter_id='.$encounterContextId:''; ?>
<nav class="tabs dental-dentition-tabs" data-scroll-target="#dental-chart-workspace" data-preserve-scroll-scope><a class="<?=$dentition==='permanent'?'active':''?>" href="odontogram.php?patient_id=<?=$patientId?>&dentition=permanent<?=e($encounterQuery)?>#dental-chart-workspace" data-chart-dentition-link data-scroll-target="#dental-chart-workspace">Permanent</a><a class="<?=$dentition==='primary'?'active':''?>" href="odontogram.php?patient_id=<?=$patientId?>&dentition=primary<?=e($encounterQuery)?>#dental-chart-workspace" data-chart-dentition-link data-scroll-target="#dental-chart-workspace">Primary</a><a class="<?=$dentition==='mixed'?'active':''?>" href="odontogram.php?patient_id=<?=$patientId?>&dentition=mixed<?=e($encounterQuery)?>#dental-chart-workspace" data-chart-dentition-link data-scroll-target="#dental-chart-workspace">Mixed view</a></nav>
<div id="dental-chart-workspace" class="dental-chart-layout dental-chart-task-first" data-scroll-context>
<section class="card dental-chart-card"><div class="card-head"><div><h2>Current tooth and surface chart</h2><p><?= $canEdit ? 'Select a tooth to record a finding for the active encounter.' : 'Select a tooth to inspect it. Editing requires the assigned dentist and an open encounter.' ?></p></div><span class="badge"><?=e(ucfirst($dentition))?> dentition</span></div><div class="fdi-orientation-note"><strong>FDI notation · patient-facing view</strong><span>Screen left is the patient's right. Permanent: 18–11 | 21–28 above and 48–41 | 31–38 below. Primary: 55–51 | 61–65 and 85–81 | 71–75.</span></div><details class="keyboard-shortcuts"><summary>Keyboard charting</summary><div>Use <kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd><kbd>↓</kbd> to move between teeth, <kbd>Enter</kbd> to open charting, and <kbd>X</kbd> to attach an X-ray. In the finding form, <kbd>Alt</kbd> + <kbd>M/D/F/B/L/P/O/I/C/R/W</kbd> toggles tooth surfaces. Caries choices automatically change for anterior and posterior teeth.</div></details>
<div class="dental-arch anatomical-arch upper-arch"><span class="arch-label">Upper arch</span><div class="tooth-model-row <?=$dentition==='mixed'?'mixed':''?>"><?php foreach($upper as $tooth):$toothEntries=$byTooth[$tooth]??[];$toothUrl='odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($dentition).'&tooth='.urlencode($tooth).($encounterContextId?'&encounter_id='.$encounterContextId:'').($canEdit?'&open_finding=1':'').'#tooth-'.rawurlencode($tooth);echo dental_render_tooth_model($tooth,$toothEntries,$toothUrl,$selectedTooth===$tooth,'upper',false,['interactive'=>true,'editable'=>$canEdit,'dialog_id'=>'finding-form','read_only_message'=>$chartEditReason]);endforeach;?></div></div>
<div class="dental-midline"><span>Patient right</span><span>Midline</span><span>Patient left</span></div>
<div class="dental-arch anatomical-arch lower-arch"><span class="arch-label">Lower arch</span><div class="tooth-model-row <?=$dentition==='mixed'?'mixed':''?>"><?php foreach($lower as $tooth):$toothEntries=$byTooth[$tooth]??[];$toothUrl='odontogram.php?patient_id='.$patientId.'&dentition='.urlencode($dentition).'&tooth='.urlencode($tooth).($encounterContextId?'&encounter_id='.$encounterContextId:'').($canEdit?'&open_finding=1':'').'#tooth-'.rawurlencode($tooth);echo dental_render_tooth_model($tooth,$toothEntries,$toothUrl,$selectedTooth===$tooth,'lower',false,['interactive'=>true,'editable'=>$canEdit,'dialog_id'=>'finding-form','read_only_message'=>$chartEditReason]);endforeach;?></div></div>
<div class="tooth-legend"><span class="legend-caries">Caries / pathology</span><span class="legend-restoration">Existing restoration</span><span class="legend-proposed">Proposed</span><span class="legend-progress">In progress</span><span class="legend-completed">Completed</span><span class="legend-severe">Severe / critical</span></div></section>
<aside class="card tooth-detail-panel"><div class="card-head"><div><span class="eyebrow">Selected tooth</span><h2 data-chart-selected-label><?=$selectedTooth!==''?e(dental_tooth_display_label($selectedTooth)):'Choose a tooth'?></h2></div><div class="status-actions"><?php if($canEdit&&$selectedTooth!==''):?><button class="btn primary" type="button" data-open="finding-form" data-chart-tooth="<?=e($selectedTooth)?>">Add finding</button><?php endif;?><?php if($canUploadImaging&&$selectedTooth!==''):?><button class="btn ghost" type="button" data-open="xray-form" data-xray-tooth="<?=e($selectedTooth)?>">Add X-ray</button><?php endif;?></div></div>
<?php if($selectedTooth===''):?><p class="muted">Select a tooth from the chart to read current surface findings, severity, linked diagnoses, treatment status, and attached imaging.</p><?php elseif(!$selectedEntries):?><div class="queue-empty">No active finding for <?=e(dental_tooth_display_label($selectedTooth))?>.</div><?php else:?><div class="tooth-finding-list"><?php foreach($selectedEntries as $entry):?><article class="tooth-finding <?=e(chart_condition_class((string)($entry['category']??''),(string)$entry['chart_layer'],(string)($entry['severity']??'')))?>"><div><strong><?=e((string)($entry['condition_name']??module_status((string)$entry['condition_code'])))?></strong><span class="badge"><?=e(module_status((string)$entry['chart_layer']))?></span></div><p><?=e(dental_surface_display((string)($entry['surface']??''),(string)$entry['tooth_number']))?><?=!empty($entry['severity'])?' · '.e(ucfirst((string)$entry['severity'])):''?></p><?php if(!empty($entry['description'])):?><small><?=e((string)$entry['description'])?></small><?php endif;?><?php if(!empty($entry['diagnosis_name'])):?><p><strong>Diagnosis:</strong> <?=e((string)$entry['diagnosis_name'])?> · <?=e(module_status((string)$entry['diagnosis_status']))?></p><?php endif;?><?php if(!empty($entry['notes'])):?><p><?=nl2br(e((string)$entry['notes']))?></p><?php endif;?><div class="status-actions"><a href="treatment.php?patient_id=<?=$patientId?>&finding_id=<?=(int)$entry['id']?>" class="btn ghost">Plan treatment</a><?php if($canEdit):?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="resolve_finding"><input type="hidden" name="patient_id" value="<?=$patientId?>"><input type="hidden" name="dentition" value="<?=e($dentition)?>"><input type="hidden" name="encounter_context_id" value="<?=$encounterContextId?>"><input type="hidden" name="finding_id" value="<?=(int)$entry['id']?>"><button class="btn ghost" data-confirm="Mark this finding resolved while keeping its history?">Resolve</button></form><?php endif;?></div></article><?php endforeach;?></div><?php endif;?><?php if($selectedTooth!==''):?><div class="tooth-imaging-list"><h3>Imaging for this tooth</h3><?php foreach(array_slice($selectedImaging,0,5) as $image):?><article><div><strong><?=e((string)$image['category'])?></strong><small><?=e(date('M j, Y g:i A',strtotime((string)$image['created_at'])))?></small></div><a href="download_document.php?id=<?=(int)$image['id']?>" target="_blank">Open</a></article><?php endforeach;?><?php if(!$selectedImaging):?><p class="muted">No X-ray or image linked to this tooth.</p><?php endif;?></div><?php endif;?></aside>
</div>
<section class="portal-summary dental-chart-stats"><?php stat_card('Active findings',count($activeEntries),'Current chart');stat_card('Needs treatment',count($untreated),'Excludes restorations');stat_card('Severe / critical',count($urgent),'Clinical priority');stat_card('Treatment plans',count($plans),'Open plans');stat_card('Images / X-rays',count($imaging),'Linked files');?></section>
<section class="grid two dental-review-grid"><div class="card"><div class="card-head"><div><h2>Latest comprehensive chart review</h2><p>Overall clinical interpretation separate from individual tooth findings.</p></div><?php if($canEdit):?><button class="btn ghost" data-open="review-form">New review</button><?php endif;?></div><?php if($latestReview):?><dl class="summary"><div>Review date</div><div><?=e(date('M j, Y',strtotime((string)$latestReview['review_date'])))?></div><div>Dentist</div><div><?=e((string)($latestReview['dentist']??'Unassigned'))?></div><div>Caries risk</div><div><?=e(module_status((string)($latestReview['caries_risk']??'not recorded')))?></div><div>Periodontal risk</div><div><?=e(module_status((string)($latestReview['periodontal_risk']??'not recorded')))?></div><div>Oral hygiene</div><div><?=e((string)($latestReview['oral_hygiene_status']??'Not recorded'))?></div><div>Status</div><div><span class="badge"><?=e(module_status((string)$latestReview['status']))?></span></div></dl><?php if(!empty($latestReview['occlusion_summary'])):?><h4>Occlusion</h4><p><?=e((string)$latestReview['occlusion_summary'])?></p><?php endif;?><?php if(!empty($latestReview['clinical_summary'])):?><h4>Clinical summary</h4><p><?=nl2br(e((string)$latestReview['clinical_summary']))?></p><?php endif;?><?php else:?><p class="muted">No comprehensive chart review has been recorded.</p><?php endif;?></div>
<div class="card table-wrap"><h2>Finding history</h2><table><thead><tr><th>Date</th><th>Tooth / surface</th><th>Finding</th><th>Layer</th><th>Status</th><th>Recorder</th></tr></thead><tbody><?php foreach(array_slice($entries,0,30) as $entry):?><tr><td><?=e(date('M j, Y',strtotime((string)$entry['recorded_at'])))?></td><td><strong><?=e((string)$entry['tooth_number'])?></strong><?=!empty($entry['surface'])?' · '.e(dental_surface_display((string)$entry['surface'],(string)$entry['tooth_number'])):''?></td><td><?=e((string)($entry['condition_name']??module_status((string)$entry['condition_code'])))?><?=!empty($entry['severity'])?'<br><small>'.e(ucfirst((string)$entry['severity'])).'</small>':''?></td><td><?=e(module_status((string)$entry['chart_layer']))?></td><td><span class="badge"><?=e(module_status((string)$entry['status']))?></span></td><td><?=e((string)($entry['recorder']??'Unknown'))?></td></tr><?php endforeach;if(!$entries):?><tr><td colspan="6" class="empty-cell">No dental chart history yet.</td></tr><?php endif;?></tbody></table></div></section>
<?php endif;?>

<?php if($canEdit&&$patientId&&$chartReady):?>
<dialog id="finding-form" class="wide-dialog"><form method="post" class="form-grid" data-dental-finding-form data-keyboard-entry><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Record tooth finding</h2><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="add_finding"><input type="hidden" name="patient_id" value="<?=$patientId?>"><input type="hidden" name="dentition" value="<?=e($dentition)?>"><input type="hidden" name="encounter_context_id" value="<?=$encounterContextId?>"><label>FDI tooth number<select name="tooth_number" data-tooth-input required><option value="">Choose tooth</option><?php foreach($validTeeth as $toothOption):?><option value="<?=e($toothOption)?>" <?=$selectedTooth===$toothOption?'selected':''?>><?=e(dental_tooth_display_label($toothOption))?></option><?php endforeach;?></select></label><input type="hidden" name="encounter_id" value="<?=$encounterContextId?>"><label>Treatment encounter<input value="Encounter #<?=$encounterContextId?> · Assigned dentist context" readonly></label><label class="wide">Clinical finding<select name="condition_code" data-condition-select required><option value="">Choose finding</option><?php $group='';foreach($conditions as $condition):if($group!==$condition['category']){if($group!=='')echo '</optgroup>';$group=(string)$condition['category'];echo '<optgroup label="'.e(ucwords(str_replace('_',' ',$group))).'">';}?><option value="<?=e((string)$condition['code'])?>" data-severity="<?=e((string)$condition['default_severity'])?>" data-procedure="<?=e((string)$condition['recommended_procedure'])?>" data-description="<?=e((string)$condition['description'])?>" data-category="<?=e(strtolower((string)$condition['category']))?>"><?=e((string)$condition['name'])?></option><?php endforeach;if($group!=='')echo '</optgroup>';?></select><small data-condition-help>Select a standardized condition to show its clinical description.</small></label><fieldset class="wide surface-picker" data-surface-picker><legend data-surface-title>Tooth surface(s)</legend><small class="surface-picker-help" data-surface-help>Choose the affected surface. Caries surfaces change automatically for anterior and posterior teeth.</small><?php foreach(['M'=>['Mesial','all'],'D'=>['Distal','all'],'F'=>['Facial','anterior'],'L'=>['Lingual','all'],'B'=>['Buccal','posterior'],'P'=>['Palatal','general'],'O'=>['Occlusal','general'],'I'=>['Incisal','general'],'Cervical'=>['Cervical','general'],'Root'=>['Root','general'],'Whole'=>['Whole tooth','general']] as $code=>$surfaceMeta):?><label data-surface-option data-surface-scope="<?=e($surfaceMeta[1])?>"><input type="checkbox" name="surfaces[]" value="<?=e($code)?>" data-surface-code="<?=e($code)?>" <?=$code==='Whole'?'checked':''?>><span><strong><?=e($code)?></strong><?=e($surfaceMeta[0])?></span></label><?php endforeach;?><small class="surface-picker-error" data-surface-error hidden>Choose at least one valid caries surface.</small></fieldset><label>Severity<select name="severity" data-severity-select><option value="information">Information</option><option value="mild">Mild</option><option value="moderate">Moderate</option><option value="severe">Severe</option><option value="critical">Critical</option></select></label><label>Chart layer<select name="chart_layer"><option value="existing">Existing condition</option><option value="proposed">Proposed treatment</option><option value="in_progress">Treatment in progress</option><option value="completed">Completed treatment</option><option value="historical">Historical</option></select></label><label class="wide">Clinical notes<textarea name="notes" placeholder="Symptoms, test results, radiographic correlation, differential considerations, or monitoring instructions"></textarea></label><label class="wide check-label"><input type="checkbox" name="replace_matching" value="1"><span>Supersede the latest active entry for this exact tooth and surface selection</span></label><label class="wide check-label"><input type="checkbox" name="create_diagnosis" value="1" data-diagnosis-toggle><span>Create a confirmed diagnosis linked to this finding</span></label><div class="wide form-grid" data-diagnosis-fields hidden><label class="wide">Diagnosis name<input name="diagnosis_name" placeholder="Defaults to the selected finding name"></label><label class="wide check-label"><input type="checkbox" name="is_primary_diagnosis" value="1"><span>Mark as primary diagnosis for the selected encounter</span></label></div><div class="wide form-actions"><button class="btn primary" type="submit">Save tooth finding</button><?php if($canUploadImaging):?><button class="btn ghost" type="button" data-open="xray-form" data-xray-for-selected>Capture / upload X-ray</button><?php endif;?></div></form></dialog>
<?php if($canUploadImaging):?><dialog id="xray-form" class="wide-dialog"><form method="post" enctype="multipart/form-data" class="form-grid" data-keyboard-entry><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Capture or upload clinical imaging</h2><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="upload_xray"><input type="hidden" name="patient_id" value="<?=$patientId?>"><input type="hidden" name="dentition" value="<?=e($dentition)?>"><input type="hidden" name="encounter_context_id" value="<?=$encounterContextId?>"><label>Imaging type<select name="category"><?php foreach(clinical_imaging_categories() as $category):?><option><?=e($category)?></option><?php endforeach;?></select></label><label>Tooth or region<select name="tooth_region" data-xray-tooth-input><option value="">General / full arch</option><?php foreach($validTeeth as $toothOption):?><option value="<?=e($toothOption)?>" <?=$selectedTooth===$toothOption?'selected':''?>><?=e(dental_tooth_display_label($toothOption))?></option><?php endforeach;?></select></label><label class="wide">X-ray or image file<input type="file" name="document" accept="image/jpeg,image/png,image/webp,application/pdf,.jpg,.jpeg,.png,.webp,.pdf" capture="environment" required><small>Use the camera on a supported device or choose an exported image/PDF. Maximum 10 MB.</small></label><label class="wide">Clinical note<textarea name="description" placeholder="Projection, reason captured, image quality, observations, or retake note"></textarea></label><button class="btn primary wide">Upload and link to encounter</button></form></dialog><?php endif;?>
<dialog id="review-form" class="wide-dialog"><form method="post" class="form-grid" data-keyboard-entry><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Comprehensive dental chart review</h2><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="save_review"><input type="hidden" name="patient_id" value="<?=$patientId?>"><input type="hidden" name="dentition" value="<?=e($dentition)?>"><input type="hidden" name="encounter_context_id" value="<?=$encounterContextId?>"><label>Review date<input type="date" name="review_date" value="<?=date('Y-m-d')?>" required></label><input type="hidden" name="encounter_id" value="<?=$encounterContextId?>"><label>Treatment encounter<input value="Encounter #<?=$encounterContextId?> · Assigned dentist context" readonly></label><label>Caries risk<select name="caries_risk"><option value="">Not assessed</option><option value="low">Low</option><option value="moderate">Moderate</option><option value="high">High</option><option value="extreme">Extreme</option></select></label><label>Periodontal risk<select name="periodontal_risk"><option value="">Not assessed</option><option value="low">Low</option><option value="moderate">Moderate</option><option value="high">High</option></select></label><label>Oral hygiene<select name="oral_hygiene_status"><option value="">Not recorded</option><option>Good</option><option>Fair</option><option>Poor</option><option>Requires intensive instruction</option></select></label><label>Review status<select name="review_status"><option value="draft">Draft</option><option value="finalized">Finalized</option></select></label><label class="wide">Occlusion summary<input name="occlusion_summary" placeholder="Class, crossbite, open bite, overjet, overbite, wear pattern"></label><label class="wide">Clinical summary<textarea name="clinical_summary" placeholder="Overall interpretation, priority findings, risks, imaging correlation, and recommended next steps"></textarea></label><button class="btn primary wide">Save chart review</button></form></dialog>
<?php endif;?>
<script src="public/assets/js/dental-chart.js?v=<?=e(app_release_version())?>"></script><?php render_footer();
