<?php

declare(strict_types=1);
require dirname(__DIR__) . '/app/bootstrap.php';
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

if (!current_user()) {
    http_response_code(401);
    echo json_encode(['ok'=>false,'results'=>[],'message'=>'Your session has expired. Sign in again, then reopen this page.'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    exit;
}
if (!application_schema_ready() || !availability_schema_ready() || !queue_cashier_schema_ready()) {
    http_response_code(503);
    echo json_encode(['ok'=>false,'results'=>[],'message'=>'A DentalCare database update is pending. Ask the Superuser to apply all pending updates.'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    exit;
}
if (!can('patients.view', ['super_admin','clinic_admin','receptionist','staff','dentist','dental_assistant','cashier'])) {
    http_response_code(403);
    echo json_encode(['ok'=>false,'results'=>[],'message'=>'Your signed-in role cannot search patient records.'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    exit;
}

/**
 * Normalize a human-entered patient/payee search without changing the stored
 * value. Commas are treated as name separators so both "Last, First" and
 * "First Middle Last" searches work.
 */
function dental_patient_search_normalize(string $value): string
{
    $value = str_replace([',', "\t", "\r", "\n"], ' ', $value);
    $value = preg_replace('/\s+/u', ' ', trim($value)) ?: '';
    return mb_strtolower($value, 'UTF-8');
}

try {
    $rawQuery = trim((string)($_GET['q'] ?? ''));
    $normalizedQuery = dental_patient_search_normalize($rawQuery);
    if (mb_strlen($normalizedQuery, 'UTF-8') < 2) {
        echo json_encode(['ok'=>true,'results'=>[],'message'=>'Type at least two characters.'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        exit;
    }

    $tokens = preg_split('/\s+/u', $normalizedQuery, -1, PREG_SPLIT_NO_EMPTY) ?: [];
    $tokens = array_values(array_unique(array_slice($tokens, 0, 6)));
    if (!$tokens) {
        echo json_encode(['ok'=>true,'results'=>[],'message'=>'Type a patient number, patient or payee name, mobile number, or email address.'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        exit;
    }

    $limit = max(5, min(40, (int)($_GET['limit'] ?? 20)));
    $pdo = db();
    $continuity = patient_continuity_schema_ready();
    $guardianReady = schema_table_exists(table('patient_guardians'));
    $insuranceReady = schema_table_exists(table('patient_insurance'));
    $invoiceReady = schema_table_exists(table('invoices'));
    $payerReady = schema_table_exists(table('payments')) && schema_column_exists(table('payments'), 'payer_name')
        && schema_column_exists(table('payments'), 'invoice_id');

    $select = $continuity
        ? ',p.primary_dentist_user_id,u.name primary_dentist_name'
        : ',NULL primary_dentist_user_id,NULL primary_dentist_name';
    $select .= ',p.branch_id,b.name branch_name,p.sex,p.address,p.emergency_contact,p.medical_alerts,p.updated_at';
    $select .= $guardianReady
        ? ',(SELECT CONCAT(g.name,IF(g.relationship IS NULL OR g.relationship="","",CONCAT(" · ",g.relationship)),IF(g.mobile IS NULL OR g.mobile="","",CONCAT(" · ",g.mobile))) FROM `' . table('patient_guardians') . '` g WHERE g.patient_id=p.id AND g.verification_status<>"revoked" ORDER BY (g.verification_status="verified") DESC,g.id LIMIT 1) guardian_summary'
        : ',NULL guardian_summary';
    $select .= $insuranceReady
        ? ',(SELECT CONCAT(pi.provider,IF(pi.plan_name IS NULL OR pi.plan_name="","",CONCAT(" · ",pi.plan_name))) FROM `' . table('patient_insurance') . '` pi WHERE pi.patient_id=p.id AND pi.is_active=1 AND (pi.coverage_start IS NULL OR pi.coverage_start<=CURDATE()) AND (pi.coverage_end IS NULL OR pi.coverage_end>=CURDATE()) ORDER BY pi.id DESC LIMIT 1) insurance_summary'
        : ',NULL insurance_summary';
    $select .= $invoiceReady
        ? ',(SELECT COALESCE(SUM(GREATEST(i.amount-i.paid_amount,0)),0) FROM `' . table('invoices') . '` i WHERE i.patient_id=p.id AND i.status IN ("unpaid","partial")) outstanding_balance'
        : ',0 outstanding_balance';
    $select .= $payerReady
        ? ',(SELECT pay.payer_name FROM `' . table('payments') . '` pay JOIN `' . table('invoices') . '` pinv ON pinv.id=pay.invoice_id WHERE pinv.patient_id=p.id AND pay.payer_name IS NOT NULL AND pay.payer_name<>"" ORDER BY pay.id DESC LIMIT 1) latest_payer_name'
        : ',NULL latest_payer_name';

    $sql = 'SELECT p.id,p.patient_no,p.first_name,p.middle_name,p.last_name,p.birth_date,p.mobile,p.email' . $select
        . ' FROM `' . table('patients') . '` p '
        . ($continuity ? 'LEFT JOIN `' . table('users') . '` u ON u.id=p.primary_dentist_user_id ' : 'LEFT JOIN `' . table('users') . '` u ON 1=0 ')
        . 'LEFT JOIN `' . table('branches') . '` b ON b.id=p.branch_id '
        . 'WHERE p.deleted_at IS NULL AND p.status IN ("active","pending") ';

    $args = [];
    $tokenClauses = [];
    foreach ($tokens as $token) {
        $like = '%' . $token . '%';
        $or = [
            'LOWER(COALESCE(p.patient_no,"")) LIKE ?',
            'LOWER(COALESCE(p.first_name,"")) LIKE ?',
            'LOWER(COALESCE(p.middle_name,"")) LIKE ?',
            'LOWER(COALESCE(p.last_name,"")) LIKE ?',
            'LOWER(CONCAT_WS(" ",p.first_name,p.middle_name,p.last_name)) LIKE ?',
            'LOWER(CONCAT_WS(" ",p.last_name,p.first_name,p.middle_name)) LIKE ?',
            'LOWER(COALESCE(p.mobile,"")) LIKE ?',
            'LOWER(COALESCE(p.email,"")) LIKE ?',
        ];
        array_push($args, $like, $like, $like, $like, $like, $like, $like, $like);

        if ($guardianReady) {
            $or[] = 'EXISTS(SELECT 1 FROM `' . table('patient_guardians') . '` gs WHERE gs.patient_id=p.id AND gs.verification_status<>"revoked" AND (LOWER(COALESCE(gs.name,"")) LIKE ? OR LOWER(COALESCE(gs.mobile,"")) LIKE ? OR LOWER(COALESCE(gs.email,"")) LIKE ?))';
            array_push($args, $like, $like, $like);
        }
        if ($payerReady) {
            $or[] = 'EXISTS(SELECT 1 FROM `' . table('payments') . '` ps JOIN `' . table('invoices') . '` pis ON pis.id=ps.invoice_id WHERE pis.patient_id=p.id AND LOWER(COALESCE(ps.payer_name,"")) LIKE ?)';
            $args[] = $like;
        }
        $tokenClauses[] = '(' . implode(' OR ', $or) . ')';
    }
    $sql .= ' AND ' . implode(' AND ', $tokenClauses);

    [$scopeSql, $scopeArgs] = task_patient_scope_condition('p.id');
    $sql .= $scopeSql . ' ORDER BY p.last_name,p.first_name,p.middle_name,p.id LIMIT ' . $limit;

    $stmt = $pdo->prepare($sql);
    $stmt->execute(array_merge($args, $scopeArgs));

    $results = [];
    foreach ($stmt->fetchAll() as $row) {
        $displayName = trim((string)$row['first_name'] . ' ' . (string)($row['middle_name'] ?? '') . ' ' . (string)$row['last_name']);
        $displayName = preg_replace('/\s+/u', ' ', $displayName) ?: $displayName;
        $labelParts = array_filter([(string)$row['patient_no'], $displayName, (string)($row['mobile'] ?? '')]);

        $patientNameNormalized = dental_patient_search_normalize($displayName . ' ' . (string)$row['last_name'] . ' ' . (string)$row['first_name']);
        $guardianNormalized = dental_patient_search_normalize((string)($row['guardian_summary'] ?? ''));
        $payerNormalized = dental_patient_search_normalize((string)($row['latest_payer_name'] ?? ''));
        $patientNoNormalized = dental_patient_search_normalize((string)$row['patient_no']);
        $mobileNormalized = dental_patient_search_normalize((string)($row['mobile'] ?? ''));
        $emailNormalized = dental_patient_search_normalize((string)($row['email'] ?? ''));

        $matchContext = 'Patient record';
        if ($guardianNormalized !== '' && str_contains($guardianNormalized, $normalizedQuery)) {
            $matchContext = 'Matched guardian/payee: ' . (string)$row['guardian_summary'];
        } elseif ($payerNormalized !== '' && str_contains($payerNormalized, $normalizedQuery)) {
            $matchContext = 'Matched previous payee: ' . (string)$row['latest_payer_name'];
        } elseif ($patientNoNormalized !== '' && str_contains($patientNoNormalized, $normalizedQuery)) {
            $matchContext = 'Matched patient number';
        } elseif ($mobileNormalized !== '' && str_contains($mobileNormalized, $normalizedQuery)) {
            $matchContext = 'Matched mobile number';
        } elseif ($emailNormalized !== '' && str_contains($emailNormalized, $normalizedQuery)) {
            $matchContext = 'Matched email address';
        } elseif ($patientNameNormalized !== '') {
            $matchContext = 'Matched patient name';
        }

        $results[] = [
            'id'=>(int)$row['id'],
            'label'=>implode(' · ', $labelParts),
            'patient_no'=>(string)$row['patient_no'],
            'name'=>$displayName,
            'mobile'=>(string)($row['mobile'] ?? ''),
            'email'=>(string)($row['email'] ?? ''),
            'birth_date'=>(string)($row['birth_date'] ?? ''),
            'age'=>(string)($row['birth_date'] ?? '') !== '' ? (int)(new DateTime((string)$row['birth_date']))->diff(new DateTime('today'))->y : null,
            'sex'=>(string)($row['sex'] ?? ''),
            'address'=>(string)($row['address'] ?? ''),
            'emergency_contact'=>(string)($row['emergency_contact'] ?? ''),
            'medical_alerts'=>(string)($row['medical_alerts'] ?? ''),
            'branch_id'=>(int)($row['branch_id'] ?? 0),
            'branch_name'=>(string)($row['branch_name'] ?? ''),
            'guardian_summary'=>(string)($row['guardian_summary'] ?? ''),
            'insurance_summary'=>(string)($row['insurance_summary'] ?? ''),
            'outstanding_balance'=>(float)($row['outstanding_balance'] ?? 0),
            'updated_at'=>(string)($row['updated_at'] ?? ''),
            'primary_dentist_id'=>(int)($row['primary_dentist_user_id'] ?? 0),
            'primary_dentist_name'=>(string)($row['primary_dentist_name'] ?? ''),
            'match_context'=>$matchContext,
        ];
    }

    echo json_encode([
        'ok'=>true,
        'results'=>$results,
        'message'=>$results ? '' : 'No matching patient or payee account was found. Try a patient number, any part of the full name, guardian/payee name, mobile number, or email address.',
    ], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
} catch (Throwable $exception) {
    $reference = strtoupper(substr(hash('sha256', $exception->getMessage() . '|' . microtime(true)), 0, 12));
    error_log('DentalCare patient search failure [' . $reference . ']: ' . $exception->getMessage());
    http_response_code(422);
    echo json_encode([
        'ok'=>false,
        'results'=>[],
        'message'=>'Patient search could not be completed. Reference: ' . $reference . '.',
    ], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
