<?php

declare(strict_types=1);

/**
 * DentalCare dental-treatment safety controls introduced in v0.9.0.7 and scope-refined in v0.9.0.8.
 *
 * This is a deterministic dental-facility safety layer for dental treatment, local anesthesia/sedation review, bleeding-risk review, and dental prescribing. It does not claim to be a
 * complete drug-interaction database and deliberately labels unverified
 * medicines as data gaps rather than reporting them as safe.
 */

function clinical_safety_schema_ready(): bool
{
    try {
        foreach ([
            table('patient_allergies'),
            table('patient_medications'),
            table('patient_conditions'),
            table('medicine_safety_profiles'),
            table('prescription_safety_assessments'),
            table('prescription_safety_findings'),
            table('clinical_safety_events'),
        ] as $requiredTable) {
            if (!schema_table_exists($requiredTable)) return false;
        }
        return schema_column_exists(table('prescriptions'), 'safety_assessment_id')
            && schema_column_exists(table('prescriptions'), 'safety_status')
            && schema_column_exists(table('prescriptions'), 'safety_override_reason');
    } catch (Throwable) {
        return false;
    }
}

function clinical_safety_normalize(string $value): string
{
    $value = function_exists('mb_strtolower') ? mb_strtolower(trim($value), 'UTF-8') : strtolower(trim($value));
    $value = str_replace(['+', '/', '\\', '_', '–', '—'], ' ', $value);
    $value = preg_replace('/[^\p{L}\p{N}%]+/u', ' ', $value) ?: $value;
    return trim((string)(preg_replace('/\s+/u', ' ', $value) ?: $value));
}

function clinical_safety_canonical_key(int $patientId, string $normalizedValue): ?string
{
    $normalizedValue = clinical_safety_normalize($normalizedValue);
    if ($patientId < 1 || $normalizedValue === '') return null;
    return hash('sha256', $patientId . '|' . $normalizedValue);
}

/** @return list<array> */
function clinical_safety_dedupe_current_rows(array $rows, string $normalizedField, string $displayField): array
{
    $deduplicated = [];
    foreach ($rows as $row) {
        $normalized = clinical_safety_normalize((string)($row[$normalizedField] ?? $row[$displayField] ?? ''));
        if ($normalized === '') continue;
        if (!isset($deduplicated[$normalized])) {
            $deduplicated[$normalized] = $row;
            continue;
        }

        // Defensive read-time reconciliation for installations that accumulated
        // duplicate current rows before v0.9.1.1. Prefer the latest verified
        // record, while the migration keeps the historical rows as superseded.
        $current = $deduplicated[$normalized];
        $currentVerified = !empty($current['verified_at']) || (string)($current['verification_status'] ?? '') === 'verified';
        $candidateVerified = !empty($row['verified_at']) || (string)($row['verification_status'] ?? '') === 'verified';
        $currentTime = strtotime((string)($current['verified_at'] ?? $current['updated_at'] ?? $current['created_at'] ?? '')) ?: 0;
        $candidateTime = strtotime((string)($row['verified_at'] ?? $row['updated_at'] ?? $row['created_at'] ?? '')) ?: 0;
        if (($candidateVerified && !$currentVerified) || ($candidateVerified === $currentVerified && $candidateTime > $currentTime)) {
            $deduplicated[$normalized] = $row;
        }
    }
    return array_values($deduplicated);
}

function clinical_safety_text_is_none(?string $value): bool
{
    $normalized = str_replace(' ', '', clinical_safety_normalize((string)$value));
    return $normalized === '' || in_array($normalized, [
        'none','nil','na','notapplicable','nka','nkda','noknownallergy','noknownallergies',
        'nomedication','nomedications','nocurrentmedication','nocurrentmedications',
        'nocondition','noconditions','noreportedcondition','noreportedconditions',
    ], true);
}

/** @return list<string> */
function clinical_safety_split_verified_text(?string $value): array
{
    if (clinical_safety_text_is_none($value)) return [];
    $rows = preg_split('/[\r\n;]+/u', (string)$value) ?: [];
    $result = [];
    foreach ($rows as $row) {
        $row = trim((string)$row, " \t\n\r\0\x0B,-");
        if ($row === '' || clinical_safety_text_is_none($row)) continue;
        $key = clinical_safety_normalize($row);
        if ($key === '' || isset($result[$key])) continue;
        $result[$key] = $row;
    }
    return array_values($result);
}

function clinical_safety_sync_verified_history(PDO $pdo, int $patientId, int $medicalHistoryId, array $values, int $verifiedBy): void
{
    if (!clinical_safety_schema_ready() || $patientId < 1 || $medicalHistoryId < 1 || $verifiedBy < 1) return;

    $now = date('Y-m-d H:i:s');
    $canonicalColumns = [
        table('patient_allergies') => schema_column_exists(table('patient_allergies'), 'canonical_key'),
        table('patient_medications') => schema_column_exists(table('patient_medications'), 'canonical_key'),
        table('patient_conditions') => schema_column_exists(table('patient_conditions'), 'canonical_key'),
    ];
    foreach ($canonicalColumns as $tableName => $hasCanonicalKey) {
        $set = $hasCanonicalKey ? 'is_current=0,status=IF(status="active","superseded",status),canonical_key=NULL,updated_at=NOW()' : 'is_current=0,status=IF(status="active","superseded",status),updated_at=NOW()';
        $pdo->prepare('UPDATE `' . $tableName . '` SET ' . $set . ' WHERE patient_id=? AND is_current=1')
            ->execute([$patientId]);
    }

    $allergyHasCanonicalKey = $canonicalColumns[table('patient_allergies')];
    $allergyInsert = $pdo->prepare(
        'INSERT INTO `' . table('patient_allergies') . '` '
        . ($allergyHasCanonicalKey
            ? '(patient_id,medical_history_id,substance_name,normalized_substance,canonical_key,reaction,severity,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?,?,?,NULL,"unknown","active",1,"verified","clinician_verified",?,?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE medical_history_id=VALUES(medical_history_id),substance_name=VALUES(substance_name),status="active",is_current=1,verification_status="verified",source_type="clinician_verified",recorded_by=VALUES(recorded_by),verified_by=VALUES(verified_by),verified_at=VALUES(verified_at),updated_at=NOW()'
            : '(patient_id,medical_history_id,substance_name,normalized_substance,reaction,severity,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?,?,NULL,"unknown","active",1,"verified","clinician_verified",?,?,?,NOW(),NOW())')
    );
    foreach (clinical_safety_split_verified_text($values['allergies'] ?? null) as $entry) {
        $normalized = clinical_safety_normalize($entry);
        $args = [$patientId,$medicalHistoryId,$entry,$normalized];
        if ($allergyHasCanonicalKey) $args[] = clinical_safety_canonical_key($patientId, $normalized);
        array_push($args,$verifiedBy,$verifiedBy,$now);
        $allergyInsert->execute($args);
    }

    $medicationHasCanonicalKey = $canonicalColumns[table('patient_medications')];
    $medicationInsert = $pdo->prepare(
        'INSERT INTO `' . table('patient_medications') . '` '
        . ($medicationHasCanonicalKey
            ? '(patient_id,medical_history_id,medication_name,normalized_medication,canonical_key,generic_name,dose_text,frequency,route,reason,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?,?,?,NULL,NULL,NULL,NULL,NULL,"active",1,"verified","clinician_verified",?,?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE medical_history_id=VALUES(medical_history_id),medication_name=VALUES(medication_name),status="active",is_current=1,verification_status="verified",source_type="clinician_verified",recorded_by=VALUES(recorded_by),verified_by=VALUES(verified_by),verified_at=VALUES(verified_at),updated_at=NOW()'
            : '(patient_id,medical_history_id,medication_name,normalized_medication,generic_name,dose_text,frequency,route,reason,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?,?,NULL,NULL,NULL,NULL,NULL,"active",1,"verified","clinician_verified",?,?,?,NOW(),NOW())')
    );
    foreach (clinical_safety_split_verified_text($values['medications'] ?? null) as $entry) {
        $normalized = clinical_safety_normalize($entry);
        $args = [$patientId,$medicalHistoryId,$entry,$normalized];
        if ($medicationHasCanonicalKey) $args[] = clinical_safety_canonical_key($patientId, $normalized);
        array_push($args,$verifiedBy,$verifiedBy,$now);
        $medicationInsert->execute($args);
    }

    $conditionHasCanonicalKey = $canonicalColumns[table('patient_conditions')];
    $conditionInsert = $pdo->prepare(
        'INSERT INTO `' . table('patient_conditions') . '` '
        . ($conditionHasCanonicalKey
            ? '(patient_id,medical_history_id,condition_name,normalized_condition,canonical_key,severity,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?,?,?,"unknown","active",1,"verified","clinician_verified",?,?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE medical_history_id=VALUES(medical_history_id),condition_name=VALUES(condition_name),status="active",is_current=1,verification_status="verified",source_type="clinician_verified",recorded_by=VALUES(recorded_by),verified_by=VALUES(verified_by),verified_at=VALUES(verified_at),updated_at=NOW()'
            : '(patient_id,medical_history_id,condition_name,normalized_condition,severity,status,is_current,verification_status,source_type,recorded_by,verified_by,verified_at,created_at,updated_at) VALUES (?,?,?, ?,"unknown","active",1,"verified","clinician_verified",?,?,?,NOW(),NOW())')
    );
    foreach (clinical_safety_split_verified_text($values['conditions'] ?? null) as $entry) {
        $normalized = clinical_safety_normalize($entry);
        $args = [$patientId,$medicalHistoryId,$entry,$normalized];
        if ($conditionHasCanonicalKey) $args[] = clinical_safety_canonical_key($patientId, $normalized);
        array_push($args,$verifiedBy,$verifiedBy,$now);
        $conditionInsert->execute($args);
    }

    try {
        audit_required('structured_medical_safety_history_synced', 'patient', $patientId, [
            'medical_history_id' => $medicalHistoryId,
            'allergy_count' => count(clinical_safety_split_verified_text($values['allergies'] ?? null)),
            'medication_count' => count(clinical_safety_split_verified_text($values['medications'] ?? null)),
            'condition_count' => count(clinical_safety_split_verified_text($values['conditions'] ?? null)),
            'verified_by' => $verifiedBy,
        ]);
    } catch (Throwable $exception) {
        error_log('Clinical safety history sync audit failed: ' . $exception->getMessage());
    }
}

/** @return array{patient:array,age:?int,pregnancy_status:string,allergies:list<array>,medications:list<array>,conditions:list<array>,verified_history:?array,history_verified:bool} */
function clinical_safety_patient_context(PDO $pdo, int $patientId): array
{
    $patientStmt = $pdo->prepare('SELECT id,birth_date,sex,medical_alerts FROM `' . table('patients') . '` WHERE id=? LIMIT 1');
    $patientStmt->execute([$patientId]);
    $patient = $patientStmt->fetch() ?: [];

    $age = null;
    $birthDate = trim((string)($patient['birth_date'] ?? ''));
    if ($birthDate !== '') {
        try { $age = (new DateTimeImmutable($birthDate))->diff(new DateTimeImmutable('today'))->y; }
        catch (Throwable) { $age = null; }
    }

    $historyStmt = $pdo->prepare(
        'SELECT * FROM `' . table('medical_histories') . '` WHERE patient_id=? '
        . 'AND (verification_status="verified" OR confirmed_at IS NOT NULL) ORDER BY COALESCE(verified_at,confirmed_at,created_at) DESC,id DESC LIMIT 1'
    );
    $historyStmt->execute([$patientId]);
    $verifiedHistory = $historyStmt->fetch() ?: null;

    $allergies = [];
    $medications = [];
    $conditions = [];
    if (clinical_safety_schema_ready()) {
        $stmt = $pdo->prepare('SELECT * FROM `' . table('patient_allergies') . '` WHERE patient_id=? AND is_current=1 AND status="active" ORDER BY id');
        $stmt->execute([$patientId]); $allergies = clinical_safety_dedupe_current_rows($stmt->fetchAll() ?: [], 'normalized_substance', 'substance_name');
        $stmt = $pdo->prepare('SELECT * FROM `' . table('patient_medications') . '` WHERE patient_id=? AND is_current=1 AND status="active" ORDER BY id');
        $stmt->execute([$patientId]); $medications = clinical_safety_dedupe_current_rows($stmt->fetchAll() ?: [], 'normalized_medication', 'medication_name');
        $stmt = $pdo->prepare('SELECT * FROM `' . table('patient_conditions') . '` WHERE patient_id=? AND is_current=1 AND status="active" ORDER BY id');
        $stmt->execute([$patientId]); $conditions = clinical_safety_dedupe_current_rows($stmt->fetchAll() ?: [], 'normalized_condition', 'condition_name');
    }

    if (!$allergies && $verifiedHistory) {
        foreach (clinical_safety_split_verified_text($verifiedHistory['allergies'] ?? null) as $entry) {
            $allergies[] = ['substance_name'=>$entry,'normalized_substance'=>clinical_safety_normalize($entry),'severity'=>'unknown'];
        }
    }
    if (!$medications && $verifiedHistory) {
        foreach (clinical_safety_split_verified_text($verifiedHistory['medications'] ?? null) as $entry) {
            $medications[] = ['medication_name'=>$entry,'normalized_medication'=>clinical_safety_normalize($entry)];
        }
    }
    if (!$conditions && $verifiedHistory) {
        foreach (clinical_safety_split_verified_text($verifiedHistory['conditions'] ?? null) as $entry) {
            $conditions[] = ['condition_name'=>$entry,'normalized_condition'=>clinical_safety_normalize($entry),'severity'=>'unknown'];
        }
    }

    $pregnancyStatus = '';
    if (schema_table_exists(table('vital_signs')) && schema_column_exists(table('vital_signs'), 'pregnancy_status')) {
        $stmt = $pdo->prepare('SELECT pregnancy_status FROM `' . table('vital_signs') . '` WHERE patient_id=? AND pregnancy_status IS NOT NULL AND TRIM(pregnancy_status)<>"" ORDER BY recorded_at DESC,id DESC LIMIT 1');
        $stmt->execute([$patientId]);
        $pregnancyStatus = trim((string)($stmt->fetchColumn() ?: ''));
    }

    return [
        'patient'=>$patient,
        'age'=>$age,
        'pregnancy_status'=>$pregnancyStatus,
        'allergies'=>$allergies,
        'medications'=>$medications,
        'conditions'=>$conditions,
        'verified_history'=>$verifiedHistory,
        'history_verified'=>$verifiedHistory !== null,
    ];
}

/** @return array{ingredient:string,medication_class:string,allergy_groups:list<string>,interaction_tags:list<string>,contraindication_tags:list<string>,pregnancy_note:string,renal_note:string,hepatic_note:string,minimum_age_years:?float,evidence_status:string} */
function clinical_safety_inferred_profile(array $item): array
{
    $name = clinical_safety_normalize(trim((string)($item['generic_name'] ?? '')) . ' ' . trim((string)($item['medication_name'] ?? '')));
    $profile = [
        'ingredient'=>clinical_safety_normalize((string)($item['generic_name'] ?? $item['medication_name'] ?? '')),
        'medication_class'=>'unknown',
        'allergy_groups'=>[],
        'interaction_tags'=>[],
        'contraindication_tags'=>[],
        'pregnancy_note'=>'',
        'renal_note'=>'',
        'hepatic_note'=>'',
        'minimum_age_years'=>null,
        'evidence_status'=>'unverified',
    ];

    $matches = static fn(array $needles): bool => (bool)array_filter($needles, static fn(string $needle): bool => str_contains($name, clinical_safety_normalize($needle)));
    if ($matches(['amoxicillin clavulanate','amoxicillin clavulanic','amoxicillin'])) {
        $profile['ingredient'] = str_contains($name,'clav') ? 'amoxicillin clavulanate' : 'amoxicillin';
        $profile['medication_class'] = 'penicillin antibiotic';
        $profile['allergy_groups'] = ['penicillin','beta lactam','amoxicillin'];
        $profile['evidence_status'] = 'clinic_curated';
    } elseif ($matches(['azithromycin'])) {
        $profile['ingredient']='azithromycin';$profile['medication_class']='macrolide antibiotic';$profile['allergy_groups']=['macrolide','azithromycin'];$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['clindamycin'])) {
        $profile['ingredient']='clindamycin';$profile['medication_class']='lincosamide antibiotic';$profile['allergy_groups']=['clindamycin','lincosamide'];$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['metronidazole'])) {
        $profile['ingredient']='metronidazole';$profile['medication_class']='nitroimidazole antibiotic';$profile['allergy_groups']=['metronidazole','nitroimidazole'];$profile['interaction_tags']=['warfarin'];$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['ibuprofen'])) {
        $profile['ingredient']='ibuprofen';$profile['medication_class']='nsaid';$profile['allergy_groups']=['nsaid','aspirin','ibuprofen'];$profile['interaction_tags']=['anticoagulant_bleeding','methotrexate'];$profile['contraindication_tags']=['renal_risk','ulcer_bleeding_risk','asthma_nsaid_risk'];$profile['pregnancy_note']='Systemic NSAID use requires pregnancy-specific clinical review.';$profile['renal_note']='Renal impairment requires clinical review before systemic NSAID use.';$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['naproxen'])) {
        $profile['ingredient']='naproxen';$profile['medication_class']='nsaid';$profile['allergy_groups']=['nsaid','aspirin','naproxen'];$profile['interaction_tags']=['anticoagulant_bleeding','methotrexate'];$profile['contraindication_tags']=['renal_risk','ulcer_bleeding_risk','asthma_nsaid_risk'];$profile['pregnancy_note']='Systemic NSAID use requires pregnancy-specific clinical review.';$profile['renal_note']='Renal impairment requires clinical review before systemic NSAID use.';$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['mefenamic'])) {
        $profile['ingredient']='mefenamic acid';$profile['medication_class']='nsaid';$profile['allergy_groups']=['nsaid','aspirin','mefenamic acid'];$profile['interaction_tags']=['anticoagulant_bleeding','methotrexate'];$profile['contraindication_tags']=['renal_risk','ulcer_bleeding_risk','asthma_nsaid_risk'];$profile['pregnancy_note']='Systemic NSAID use requires pregnancy-specific clinical review.';$profile['renal_note']='Renal impairment requires clinical review before systemic NSAID use.';$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['celecoxib'])) {
        $profile['ingredient']='celecoxib';$profile['medication_class']='cox2 nsaid';$profile['allergy_groups']=['nsaid','aspirin','celecoxib','sulfonamide'];$profile['interaction_tags']=['anticoagulant_bleeding'];$profile['contraindication_tags']=['renal_risk','ulcer_bleeding_risk'];$profile['pregnancy_note']='Systemic NSAID use requires pregnancy-specific clinical review.';$profile['renal_note']='Renal impairment requires clinical review before systemic NSAID use.';$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['paracetamol','acetaminophen'])) {
        $profile['ingredient']='paracetamol acetaminophen';$profile['medication_class']='non opioid analgesic';$profile['allergy_groups']=['paracetamol','acetaminophen'];$profile['contraindication_tags']=['hepatic_risk'];$profile['hepatic_note']='Liver disease or substantial alcohol exposure requires dose and duration review.';$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['aspirin','acetylsalicylic'])) {
        $profile['ingredient']='aspirin';$profile['medication_class']='salicylate nsaid';$profile['allergy_groups']=['aspirin','nsaid','salicylate'];$profile['interaction_tags']=['anticoagulant_bleeding'];$profile['contraindication_tags']=['renal_risk','ulcer_bleeding_risk','asthma_nsaid_risk'];$profile['minimum_age_years']=16.0;$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['chlorhexidine'])) {
        $profile['ingredient']='chlorhexidine';$profile['medication_class']='topical antiseptic';$profile['allergy_groups']=['chlorhexidine'];$profile['evidence_status']='clinic_curated';
    } elseif ($matches(['benzydamine'])) {
        $profile['ingredient']='benzydamine';$profile['medication_class']='topical anti inflammatory';$profile['allergy_groups']=['benzydamine'];$profile['evidence_status']='clinic_curated';
    }
    return $profile;
}

function clinical_safety_decode_list(?string $value): array
{
    $decoded = json_decode((string)$value, true);
    if (is_array($decoded)) return array_values(array_filter(array_map('strval', $decoded), static fn(string $v): bool => trim($v) !== ''));
    if (trim((string)$value) === '') return [];
    return array_values(array_filter(array_map('trim', preg_split('/[,;]+/', (string)$value) ?: [])));
}

function clinical_safety_profile_for_item(PDO $pdo, array $item): array
{
    $profile = clinical_safety_inferred_profile($item);
    $catalogId = (int)($item['medicine_catalog_id'] ?? 0);
    if ($catalogId < 1 || !clinical_safety_schema_ready()) return $profile;
    try {
        $stmt = $pdo->prepare('SELECT * FROM `' . table('medicine_safety_profiles') . '` WHERE medicine_catalog_id=? AND is_active=1 ORDER BY reviewed_at DESC,id DESC LIMIT 1');
        $stmt->execute([$catalogId]);
        $row = $stmt->fetch();
        if (!$row) return $profile;
        return [
            'ingredient'=>clinical_safety_normalize((string)($row['normalized_ingredient'] ?: $profile['ingredient'])),
            'medication_class'=>clinical_safety_normalize((string)($row['medication_class'] ?: $profile['medication_class'])),
            'allergy_groups'=>clinical_safety_decode_list($row['allergy_groups_json'] ?? null),
            'interaction_tags'=>clinical_safety_decode_list($row['interaction_tags_json'] ?? null),
            'contraindication_tags'=>clinical_safety_decode_list($row['contraindication_tags_json'] ?? null),
            'pregnancy_note'=>(string)($row['pregnancy_note'] ?? ''),
            'renal_note'=>(string)($row['renal_note'] ?? ''),
            'hepatic_note'=>(string)($row['hepatic_note'] ?? ''),
            'minimum_age_years'=>$row['minimum_age_years'] !== null ? (float)$row['minimum_age_years'] : null,
            'evidence_status'=>(string)($row['evidence_status'] ?? 'unverified'),
        ];
    } catch (Throwable) {
        return $profile;
    }
}

function clinical_safety_text_contains_any(string $haystack, array $needles): bool
{
    $haystack = clinical_safety_normalize($haystack);
    foreach ($needles as $needle) {
        $needle = clinical_safety_normalize((string)$needle);
        if ($needle !== '' && (function_exists('mb_strlen') ? mb_strlen($needle) : strlen($needle)) >= 3 && str_contains($haystack, $needle)) return true;
    }
    return false;
}

function clinical_safety_add_finding(array &$findings, string $severity, string $code, int $itemIndex, string $medicineName, string $title, string $detail, bool $overrideAllowed): void
{
    $key = $code . '|' . $itemIndex;
    if (isset($findings[$key])) return;
    $findings[$key] = [
        'severity'=>$severity,
        'code'=>$code,
        'medicine_index'=>$itemIndex,
        'medicine_name'=>$medicineName,
        'title'=>$title,
        'detail'=>$detail,
        'override_allowed'=>$overrideAllowed,
    ];
}

/** @return array{status:string,counts:array,findings:list<array>,context_hash:string,context:array} */
function clinical_safety_evaluate_prescription(PDO $pdo, int $patientId, array $items): array
{
    $context = clinical_safety_patient_context($pdo, $patientId);
    $findings = [];
    $allergyText = implode(' | ', array_map(static fn(array $row): string => (string)($row['normalized_substance'] ?? $row['substance_name'] ?? ''), $context['allergies']));
    $medicationText = implode(' | ', array_map(static fn(array $row): string => (string)($row['normalized_medication'] ?? $row['medication_name'] ?? ''), $context['medications']));
    $conditionText = implode(' | ', array_map(static fn(array $row): string => (string)($row['normalized_condition'] ?? $row['condition_name'] ?? ''), $context['conditions']));
    $pregnancyText = clinical_safety_normalize((string)$context['pregnancy_status']);

    if (!$context['history_verified']) {
        clinical_safety_add_finding($findings, 'data_gap', 'verified_history_missing', -1, '', 'Dental treatment safety history is not verified', 'No dentist-verified safety history is available. Review allergies, current medicines, bleeding risk, conditions relevant to dental treatment, and pregnancy/radiography status before issuing a dental prescription.', true);
    }

    $seenIngredients = [];
    $seenClasses = [];
    foreach ($items as $index => $item) {
        $medicineName = trim((string)($item['medication_name'] ?? 'Medicine ' . ($index + 1)));
        $profile = clinical_safety_profile_for_item($pdo, $item);
        $ingredient = clinical_safety_normalize((string)$profile['ingredient']);
        $class = clinical_safety_normalize((string)$profile['medication_class']);

        if ($ingredient !== '') {
            if (isset($seenIngredients[$ingredient])) {
                clinical_safety_add_finding($findings, 'hard_stop', 'duplicate_ingredient', $index, $medicineName, 'Duplicate active ingredient', $medicineName . ' duplicates the active ingredient already entered in this prescription.', false);
            }
            $seenIngredients[$ingredient] = true;
        }
        if (in_array($class, ['nsaid','cox2 nsaid','salicylate nsaid'], true)) {
            if (!empty($seenClasses['systemic_nsaid'])) {
                clinical_safety_add_finding($findings, 'hard_stop', 'multiple_systemic_nsaids', $index, $medicineName, 'Multiple systemic NSAIDs', 'More than one systemic NSAID is present in the same prescription. Remove the duplicate therapeutic class or document a different clinically appropriate plan outside this order.', false);
            }
            $seenClasses['systemic_nsaid'] = true;
        }

        if (($profile['evidence_status'] ?? 'unverified') === 'unverified') {
            clinical_safety_add_finding($findings, 'data_gap', 'unverified_medicine_profile', $index, $medicineName, 'No verified local safety profile', 'DentalCare has no clinic-reviewed interaction profile for this medicine. The absence of a local warning must not be interpreted as evidence of safety.', true);
        }

        $allergyNeedles = array_merge([$ingredient, $medicineName, (string)($item['generic_name'] ?? '')], (array)$profile['allergy_groups']);
        if ($allergyText !== '' && clinical_safety_text_contains_any($allergyText, $allergyNeedles)) {
            clinical_safety_add_finding($findings, 'hard_stop', 'allergy_match', $index, $medicineName, 'Possible allergy match', $medicineName . ' matches a verified allergy or allergy group in the patient record. Do not issue until the allergy record and alternative medicine are clinically resolved.', false);
        }

        if ($medicationText !== '' && clinical_safety_text_contains_any($medicationText, [$ingredient,$medicineName,(string)($item['generic_name'] ?? '')])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'active_medication_duplicate', $index, $medicineName, 'Medicine already appears active', $medicineName . ' appears in the verified current-medication list. Confirm whether this is a renewal, replacement, duplicate, or intentional concurrent order.', true);
        }

        $interactionTags = array_map('clinical_safety_normalize', (array)$profile['interaction_tags']);
        if (in_array('anticoagulant bleeding', $interactionTags, true)
            && clinical_safety_text_contains_any($medicationText, ['warfarin','apixaban','rivaroxaban','dabigatran','edoxaban','clopidogrel','ticagrelor','anticoagulant','blood thinner'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'bleeding_interaction', $index, $medicineName, 'Bleeding-risk medicine combination', 'The patient record contains an anticoagulant or antiplatelet medicine and this order may increase bleeding risk. Review indication, duration, dose, and coordination with the prescribing physician.', true);
        }
        if (in_array('warfarin', $interactionTags, true) && clinical_safety_text_contains_any($medicationText, ['warfarin'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'warfarin_interaction', $index, $medicineName, 'Warfarin interaction review required', $medicineName . ' requires explicit review when warfarin is active. Confirm the plan, monitoring, and communication with the responsible physician.', true);
        }
        if (in_array('methotrexate', $interactionTags, true) && clinical_safety_text_contains_any($medicationText, ['methotrexate'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'methotrexate_interaction', $index, $medicineName, 'Methotrexate interaction review required', 'Methotrexate appears in the current-medication list. Review dose, timing, renal function, and alternative analgesia before issuing.', true);
        }

        $contraTags = array_map('clinical_safety_normalize', (array)$profile['contraindication_tags']);
        if (in_array('renal risk', $contraTags, true) && clinical_safety_text_contains_any($conditionText, ['renal','kidney','dialysis','nephropathy'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'renal_condition_warning', $index, $medicineName, 'Renal-condition review required', (string)($profile['renal_note'] ?: 'A verified renal or kidney condition is recorded. Review whether this medicine, dose, and duration are appropriate.'), true);
        }
        if (in_array('hepatic risk', $contraTags, true) && clinical_safety_text_contains_any($conditionText, ['hepatic','liver','cirrhosis','hepatitis'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'hepatic_condition_warning', $index, $medicineName, 'Liver-condition review required', (string)($profile['hepatic_note'] ?: 'A verified liver condition is recorded. Review dose, total exposure, and duration.'), true);
        }
        if (in_array('ulcer bleeding risk', $contraTags, true) && clinical_safety_text_contains_any($conditionText, ['ulcer','gastrointestinal bleed','gi bleed','bleeding disorder'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'ulcer_bleeding_warning', $index, $medicineName, 'Gastrointestinal or bleeding-condition review', 'A verified ulcer or bleeding-related condition is recorded. Review whether this medicine is appropriate and whether another analgesic strategy is required.', true);
        }
        if (in_array('asthma nsaid risk', $contraTags, true) && clinical_safety_text_contains_any($conditionText, ['asthma','aspirin induced respiratory','nsaid exacerbated'])) {
            clinical_safety_add_finding($findings, 'serious_warning', 'asthma_nsaid_warning', $index, $medicineName, 'Asthma/NSAID sensitivity review', 'Asthma or respiratory sensitivity is recorded. Confirm prior NSAID tolerance and consider an alternative if sensitivity is suspected.', true);
        }

        if ($pregnancyText !== '' && clinical_safety_text_contains_any($pregnancyText, ['pregnant','positive','confirmed','yes']) && trim((string)$profile['pregnancy_note']) !== '') {
            clinical_safety_add_finding($findings, 'serious_warning', 'pregnancy_warning', $index, $medicineName, 'Pregnancy-specific review required', (string)$profile['pregnancy_note'], true);
        }

        $minimumAge = $profile['minimum_age_years'];
        if ($minimumAge !== null && $context['age'] !== null && $context['age'] < $minimumAge) {
            clinical_safety_add_finding($findings, 'hard_stop', 'minimum_age_block', $index, $medicineName, 'Patient below configured minimum age', $medicineName . ' is blocked by the clinic safety profile for patients younger than ' . rtrim(rtrim(number_format((float)$minimumAge,1,'.',''),'0'),'.') . ' years.', false);
        }
    }

    $findings = array_values($findings);
    $counts = ['hard_stop'=>0,'serious_warning'=>0,'advisory'=>0,'data_gap'=>0];
    foreach ($findings as $finding) $counts[$finding['severity']] = ($counts[$finding['severity']] ?? 0) + 1;
    $status = $counts['hard_stop'] > 0 ? 'blocked' : (($counts['serious_warning'] + $counts['data_gap']) > 0 ? 'warning' : 'clear');
    $contextForHash = [
        'patient_id'=>$patientId,
        'age'=>$context['age'],
        'pregnancy_status'=>$context['pregnancy_status'],
        'allergies'=>array_map(static fn(array $row): string => (string)($row['normalized_substance'] ?? $row['substance_name'] ?? ''), $context['allergies']),
        'medications'=>array_map(static fn(array $row): string => (string)($row['normalized_medication'] ?? $row['medication_name'] ?? ''), $context['medications']),
        'conditions'=>array_map(static fn(array $row): string => (string)($row['normalized_condition'] ?? $row['condition_name'] ?? ''), $context['conditions']),
        'items'=>$items,
        'findings'=>$findings,
    ];
    $contextHash = hash('sha256', (string)json_encode($contextForHash, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION));
    return ['status'=>$status,'counts'=>$counts,'findings'=>$findings,'context_hash'=>$contextHash,'context'=>$context];
}

function clinical_safety_assert_prescription(PDO $pdo, int $patientId, int $encounterId, array $items, bool $acknowledged, string $overrideReason): array
{
    if (!clinical_safety_schema_ready()) throw new RuntimeException('Apply DentalCare v0.9.0.7 before issuing a prescription.');
    $assessment = clinical_safety_evaluate_prescription($pdo, $patientId, $items);
    $GLOBALS['clinical_safety_last_assessment'] = $assessment;
    $GLOBALS['clinical_safety_last_items'] = $items;
    $GLOBALS['clinical_safety_last_patient_id'] = $patientId;
    $GLOBALS['clinical_safety_last_encounter_id'] = $encounterId;

    if (($assessment['counts']['hard_stop'] ?? 0) > 0) {
        $GLOBALS['clinical_safety_block_event'] = [
            'patient_id'=>$patientId,'encounter_id'=>$encounterId,'assessment'=>$assessment,'items'=>$items,
        ];
        $titles = array_map(static fn(array $finding): string => (string)$finding['title'], array_filter($assessment['findings'], static fn(array $finding): bool => $finding['severity'] === 'hard_stop'));
        throw new RuntimeException('Dental prescription blocked by treatment-safety controls: ' . implode('; ', array_unique($titles)) . '.');
    }
    $requiresAcknowledgement = (($assessment['counts']['serious_warning'] ?? 0) + ($assessment['counts']['data_gap'] ?? 0)) > 0;
    if ($requiresAcknowledgement && !$acknowledged) {
        $GLOBALS['clinical_finalize_incomplete'] = true;
        $GLOBALS['clinical_finalize_focus'] = 'prescription';
        throw new RuntimeException('Review and acknowledge the dental prescription safety findings before issuing this prescription.');
    }
    if (($assessment['counts']['serious_warning'] ?? 0) > 0 && (function_exists('mb_strlen') ? mb_strlen(trim($overrideReason)) : strlen(trim($overrideReason))) < 20) {
        $GLOBALS['clinical_finalize_incomplete'] = true;
        $GLOBALS['clinical_finalize_focus'] = 'prescription';
        throw new RuntimeException('Serious dental prescription warnings require a clinical justification of at least 20 characters.');
    }
    return $assessment;
}

function clinical_safety_storage_status(array $assessment): string
{
    $status=(string)($assessment['status']??'warning');
    if($status==='warning'&&(((int)($assessment['counts']['serious_warning']??0)>0)||((int)($assessment['counts']['data_gap']??0)>0)))return 'warning_acknowledged';
    return in_array($status,['clear','blocked','warning_acknowledged'],true)?$status:'pending';
}

function clinical_safety_persist_assessment(PDO $pdo, int $prescriptionId, int $patientId, int $encounterId, int $dentistUserId, array $assessment, string $overrideReason): int
{
    if (!clinical_safety_schema_ready()) return 0;
    $status = clinical_safety_storage_status($assessment);
    $summaryJson = json_encode([
        'counts'=>$assessment['counts'] ?? [],
        'context_hash'=>$assessment['context_hash'] ?? '',
        'engine_version'=>'0.9.0.8',
        'limitations'=>'Dental-treatment and dental-prescribing safety rules only; not a general medical system and not a complete external drug-interaction database.',
    ], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    $stmt = $pdo->prepare(
        'INSERT INTO `' . table('prescription_safety_assessments') . '` '
        . '(prescription_id,patient_id,encounter_id,dentist_user_id,status,hard_stop_count,serious_warning_count,advisory_count,data_gap_count,context_hash,summary_json,override_reason,acknowledged_by,acknowledged_at,assessed_at) '
        . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW(),NOW())'
    );
    $stmt->execute([
        $prescriptionId,$patientId,$encounterId ?: null,$dentistUserId,$status,
        (int)($assessment['counts']['hard_stop'] ?? 0),(int)($assessment['counts']['serious_warning'] ?? 0),
        (int)($assessment['counts']['advisory'] ?? 0),(int)($assessment['counts']['data_gap'] ?? 0),
        (string)($assessment['context_hash'] ?? ''),$summaryJson ?: null,trim($overrideReason) ?: null,$dentistUserId,
    ]);
    $assessmentId = (int)$pdo->lastInsertId();
    $insert = $pdo->prepare(
        'INSERT INTO `' . table('prescription_safety_findings') . '` '
        . '(assessment_id,severity,code,medicine_index,medicine_name,title,detail,override_allowed,resolution_status,created_at) '
        . 'VALUES (?,?,?,?,?,?,?,?,?,NOW())'
    );
    foreach ((array)($assessment['findings'] ?? []) as $finding) {
        $resolution = $finding['severity'] === 'hard_stop' ? 'blocked' : (($finding['severity'] === 'serious_warning' || $finding['severity'] === 'data_gap') ? 'acknowledged' : 'not_required');
        $insert->execute([$assessmentId,$finding['severity'],$finding['code'],(int)$finding['medicine_index'],$finding['medicine_name'] ?: null,$finding['title'],$finding['detail'],!empty($finding['override_allowed'])?1:0,$resolution]);
    }
    $pdo->prepare('UPDATE `' . table('prescriptions') . '` SET safety_assessment_id=?,safety_status=?,safety_override_reason=? WHERE id=?')
        ->execute([$assessmentId,$status,trim($overrideReason) ?: null,$prescriptionId]);
    return $assessmentId;
}

function clinical_safety_record_block_event(PDO $pdo, array $event): void
{
    if (!clinical_safety_schema_ready()) return;
    try {
        $assessment = (array)($event['assessment'] ?? []);
        $details = json_encode([
            'counts'=>$assessment['counts'] ?? [],
            'findings'=>$assessment['findings'] ?? [],
            'context_hash'=>$assessment['context_hash'] ?? '',
        ], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        $stmt = $pdo->prepare(
            'INSERT INTO `' . table('clinical_safety_events') . '` '
            . '(patient_id,encounter_id,event_type,severity,code,details_json,action_taken,actor_user_id,created_at) '
            . 'VALUES (?,? ,"prescription_blocked","critical","medication_safety_hard_stop",?,"issuance_blocked",?,NOW())'
        );
        $stmt->execute([(int)($event['patient_id'] ?? 0),(int)($event['encounter_id'] ?? 0) ?: null,$details ?: null,(int)(current_user()['id'] ?? 0) ?: null]);
        audit_required('prescription_safety_blocked', 'patient', (int)($event['patient_id'] ?? 0), [
            'encounter_id'=>(int)($event['encounter_id'] ?? 0) ?: null,
            'counts'=>$assessment['counts'] ?? [],
            'context_hash'=>$assessment['context_hash'] ?? '',
        ]);
    } catch (Throwable $exception) {
        error_log('Clinical safety block-event logging failed: ' . $exception->getMessage());
    }
}

function clinical_safety_preview_key(int $encounterId): string
{
    return 'clinical_safety_preview_' . max(0, $encounterId);
}

function clinical_safety_store_preview(int $encounterId, array $items, array $assessment, array $fields = []): void
{
    $_SESSION[clinical_safety_preview_key($encounterId)] = [
        'items'=>$items,
        'assessment'=>$assessment,
        'fields'=>$fields,
        'stored_at'=>time(),
    ];
}

function clinical_safety_get_preview(int $encounterId, bool $consume = false): ?array
{
    $key = clinical_safety_preview_key($encounterId);
    $preview = $_SESSION[$key] ?? null;
    if (!is_array($preview)) return null;
    if ((int)($preview['stored_at'] ?? 0) < time() - 3600) {
        unset($_SESSION[$key]);
        return null;
    }
    if ($consume) unset($_SESSION[$key]);
    return $preview;
}

function clinical_safety_clear_preview(int $encounterId): void
{
    unset($_SESSION[clinical_safety_preview_key($encounterId)]);
}


function clinical_safety_load_assessment(PDO $pdo, int $assessmentId): ?array
{
    if (!clinical_safety_schema_ready() || $assessmentId < 1) return null;
    try {
        $stmt = $pdo->prepare('SELECT * FROM `' . table('prescription_safety_assessments') . '` WHERE id=? LIMIT 1');
        $stmt->execute([$assessmentId]);
        $row = $stmt->fetch();
        if (!$row) return null;
        $findingStmt = $pdo->prepare('SELECT severity,code,medicine_index,medicine_name,title,detail,override_allowed,resolution_status FROM `' . table('prescription_safety_findings') . '` WHERE assessment_id=? ORDER BY FIELD(severity,"hard_stop","serious_warning","data_gap","advisory"),id');
        $findingStmt->execute([$assessmentId]);
        $row['findings'] = $findingStmt->fetchAll() ?: [];
        $row['counts'] = [
            'hard_stop'=>(int)($row['hard_stop_count'] ?? 0),
            'serious_warning'=>(int)($row['serious_warning_count'] ?? 0),
            'advisory'=>(int)($row['advisory_count'] ?? 0),
            'data_gap'=>(int)($row['data_gap_count'] ?? 0),
        ];
        return $row;
    } catch (Throwable) {
        return null;
    }
}

function clinical_safety_status_label(string $status): string
{
    return match ($status) {
        'clear' => 'Safety review clear',
        'warning_acknowledged' => 'Warnings reviewed',
        'blocked' => 'Blocked',
        'warning' => 'Review required',
        default => 'Safety review pending',
    };
}
