<?php

declare(strict_types=1);

function module_patient_select(array $patients, string $name = 'patient_id', int|string|null $selected = null, bool $required = true, string $id = ''): void
{
    $idAttribute = $id !== '' ? ' id="' . e($id) . '"' : '';
    ?><select<?= $idAttribute ?> name="<?= e($name) ?>" <?= $required ? 'required' : '' ?>><option value=""><?= $required ? 'Select patient' : 'All patients' ?></option><?php foreach ($patients as $patient): $patientFullName=trim((string)($patient['first_name']??'') . ' ' . (string)($patient['middle_name']??'') . ' ' . (string)($patient['last_name']??'')); ?><option value="<?= (int) $patient['id'] ?>" data-patient-name="<?= e($patientFullName) ?>" <?= (string) $selected === (string) $patient['id'] ? 'selected' : '' ?>><?= e($patient['patient_no'] . ' · ' . $patient['last_name'] . ', ' . $patient['first_name']) ?></option><?php endforeach; ?></select><?php
}

function module_dentist_select(array $dentists, string $name = 'dentist_user_id', int|string|null $selected = null, bool $required = false): void
{
    ?><select name="<?= e($name) ?>" <?= $required ? 'required' : '' ?>><option value="">Select dentist</option><?php foreach ($dentists as $dentist): ?><option value="<?= (int) $dentist['id'] ?>" <?= (string) $selected === (string) $dentist['id'] ? 'selected' : '' ?>><?= e($dentist['name']) ?></option><?php endforeach; ?></select><?php
}

function module_status(string $status): string
{
    return ucwords(str_replace('_', ' ', $status));
}

function module_return(string $path, string $type, string $message): never
{
    flash($type, $message);
    header('Location: ' . $path);
    exit;
}

function normalize_date_time_local(?string $value): ?string
{
    $value = trim((string) $value);
    if ($value === '') return null;
    $timestamp = strtotime($value);
    return $timestamp === false ? null : date('Y-m-d H:i:s', $timestamp);
}

function patient_filter_clause(string $alias = '', string $paramName = 'patient_id'): array
{
    $patientId = (int) ($_GET[$paramName] ?? 0);
    if ($patientId <= 0) return ['', [], 0];
    $prefix = $alias !== '' ? $alias . '.' : '';
    return [' WHERE ' . $prefix . 'patient_id=?', [$patientId], $patientId];
}

function render_module_intro(string $heading, string $description, ?string $buttonLabel = null, ?string $dialogId = null): void
{
    ?><div class="toolbar module-toolbar"><div><strong><?= e($heading) ?></strong><span class="muted"><?= e($description) ?></span></div><?php if ($buttonLabel && $dialogId): ?><button class="btn primary" type="button" data-open="<?= e($dialogId) ?>"><?= e($buttonLabel) ?></button><?php endif; ?></div><?php
}

function render_patient_filter(array $patients, string $currentPage, int $selected = 0): void
{
    $parts = parse_url($currentPage);
    $action = (string) ($parts['path'] ?? $currentPage);
    $query = [];
    if (!empty($parts['query'])) parse_str((string) $parts['query'], $query);
    unset($query['patient_id']);
    $selectId = 'patient-filter-' . substr(md5($currentPage), 0, 10);
    ?><form method="get" action="<?= e($action) ?>" class="card compact-filter patient-auto-filter" data-auto-submit><?php foreach ($query as $key => $value): if (is_scalar($value)): ?><input type="hidden" name="<?= e((string) $key) ?>" value="<?= e((string) $value) ?>"><?php endif; endforeach; ?><label>Search patient<input type="search" data-select-search="<?= e($selectId) ?>" placeholder="Type a name or patient number" autocomplete="off"></label><label>Patient<?php module_patient_select($patients, 'patient_id', $selected, false, $selectId); ?></label><?php if ($selected): ?><a class="btn link" href="<?= e($currentPage) ?>">Clear</a><?php endif; ?></form><?php
}

/**
 * Load a record and enforce patient/dentist scope before a mutable clinical action.
 * Administrators retain oversight access; dentists and assistants remain limited to
 * records assigned to their real account/supported dentist scope.
 */
function module_require_record_scope(PDO $pdo, string $logicalTable, int $id, string $patientColumn = 'patient_id', ?string $dentistColumn = null, bool $write = true): array
{
    if ($id < 1) throw new RuntimeException('The selected record is invalid.');
    $stmt = $pdo->prepare('SELECT * FROM `' . table($logicalTable) . '` WHERE id=? LIMIT 1');
    $stmt->execute([$id]);
    $row = $stmt->fetch();
    if (!$row) throw new RuntimeException('The selected record was not found.');
    $patientId = (int)($row[$patientColumn] ?? 0);
    if ($patientId > 0) require_patient($patientId);

    $role = active_role();
    $userId = (int)(current_user()['id'] ?? 0);
    $recordDentistId = $dentistColumn !== null ? (int)($row[$dentistColumn] ?? 0) : 0;
    if ($recordDentistId < 1 && !empty($row['encounter_id'])) {
        $enc = $pdo->prepare('SELECT dentist_user_id,status,patient_id FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
        $enc->execute([(int)$row['encounter_id']]);
        if ($encounter = $enc->fetch()) {
            $recordDentistId = (int)($encounter['dentist_user_id'] ?? 0);
            if ($patientId < 1 && !empty($encounter['patient_id'])) {
                $patientId = (int)$encounter['patient_id'];
                require_patient($patientId);
            }
            if ($write && ($encounter['status'] ?? '') === 'finalized') {
                throw new RuntimeException('The finalized clinical record is locked. Create a signed addendum instead of changing the original data.');
            }
        }
    }
    if ($role === 'dentist' && $recordDentistId > 0 && $recordDentistId !== $userId) {
        throw new RuntimeException('This clinical record belongs to another dentist.');
    }
    if ($role === 'dental_assistant' && $recordDentistId > 0 && !in_array($recordDentistId, task_dentist_scope_ids(), true)) {
        throw new RuntimeException('This clinical record is outside the assistant assignment.');
    }
    return $row;
}

function module_require_encounter_scope(PDO $pdo, int $encounterId, bool $write = true, bool $allowFinalizedAddendum = false): array
{
    $stmt = $pdo->prepare('SELECT * FROM `' . table('clinical_encounters') . '` WHERE id=? LIMIT 1');
    $stmt->execute([$encounterId]);
    $encounter = $stmt->fetch();
    if (!$encounter) throw new RuntimeException('Clinical encounter not found.');
    require_patient((int)$encounter['patient_id']);
    $role = active_role();
    $userId = (int)(current_user()['id'] ?? 0);
    if ($role === 'dentist' && (int)$encounter['dentist_user_id'] !== $userId) {
        throw new RuntimeException('This encounter belongs to another dentist.');
    }
    if ($role === 'dental_assistant' && !in_array((int)$encounter['dentist_user_id'], task_dentist_scope_ids(), true)) {
        throw new RuntimeException('This encounter is outside the assistant assignment.');
    }
    if ($write && ($encounter['status'] ?? '') === 'finalized' && !$allowFinalizedAddendum) {
        throw new RuntimeException('The finalized clinical record is locked. Create a signed addendum instead.');
    }
    return $encounter;
}

/**
 * Clinical edit controls are intentionally narrower than clinical read access.
 * Administrative roles may review records and exceptions but cannot create or
 * alter clinical findings while working in an administrative task view.
 */
function clinical_record_edit_allowed(array $roles = ['dentist']): bool
{
    foreach ($roles as $role) if (active_role_is((string)$role)) return true;
    return false;
}

function require_clinical_record_editor(array $roles = ['dentist']): void
{
    if (!clinical_record_edit_allowed($roles)) {
        throw new RuntimeException('This clinical record is read-only in the active role. Switch to an assigned Dentist or Dental Assistant role.');
    }
}

/**
 * Server-side pagination helpers. Keep query state while changing pages and
 * never allow a client-supplied page size to create unbounded database reads.
 */
function module_page_number(string $parameter = 'page'): int
{
    return max(1, (int)($_GET[$parameter] ?? 1));
}

function module_page_size(string $parameter = 'per_page', int $default = 25, array $allowed = [10,25,50,100]): int
{
    $requested = (int)($_GET[$parameter] ?? $default);
    return in_array($requested, $allowed, true) ? $requested : $default;
}

function module_query_url(array $changes = [], ?string $path = null): string
{
    $params = $_GET;
    foreach ($changes as $key => $value) {
        if ($value === null || $value === '') unset($params[$key]);
        else $params[$key] = $value;
    }
    if ($path === null) {
        $requestPath = (string)(parse_url((string)($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH) ?: '');
        $path = basename($requestPath);
        if ($path === '' || $path === 'route.php') {
            $routeName = (string)($_GET['route'] ?? '');
            $path = $routeName !== '' ? basename($routeName) : basename((string)($_SERVER['PHP_SELF'] ?? ''));
        }
    }
    $url = $path . ($params ? '?' . http_build_query($params) : '');
    return function_exists('dental_clean_url') ? dental_clean_url($url) : $url;
}

function module_render_server_pagination(
    int $total,
    int $page,
    int $perPage,
    string $pageParameter = 'page',
    string $perPageParameter = 'per_page',
    string $label = 'records'
): void {
    $pages = max(1, (int)ceil($total / max(1, $perPage)));
    $page = min(max(1, $page), $pages);
    $from = $total > 0 ? (($page - 1) * $perPage) + 1 : 0;
    $to = min($total, $page * $perPage);
    $windowStart = max(1, $page - 2);
    $windowEnd = min($pages, $page + 2);
    ?>
    <nav class="server-pagination" aria-label="<?= e(ucfirst($label)) ?> pages" data-no-pagination data-preserve-scroll-scope>
        <div class="server-pagination-summary"><strong><?= e(number_format($total)) ?></strong> <?= e($label) ?> · showing <?= e(number_format($from)) ?>–<?= e(number_format($to)) ?> · page <?= $page ?> of <?= $pages ?></div>
        <div class="server-pagination-controls">
            <?php if ($page > 1): ?>
                <a class="btn ghost compact" href="<?= e(module_query_url([$pageParameter=>1])) ?>" aria-label="First page" data-preserve-scroll>« First</a>
                <a class="btn ghost compact" href="<?= e(module_query_url([$pageParameter=>$page-1])) ?>" aria-label="Previous page" data-preserve-scroll>‹ Previous</a>
            <?php endif; ?>
            <?php for ($number=$windowStart; $number<=$windowEnd; $number++): ?>
                <a class="pagination-number <?= $number===$page?'active':'' ?>" href="<?= e(module_query_url([$pageParameter=>$number])) ?>" data-preserve-scroll aria-current="<?= $number===$page?'page':'false' ?>"><?= $number ?></a>
            <?php endfor; ?>
            <?php if ($page < $pages): ?>
                <a class="btn ghost compact" href="<?= e(module_query_url([$pageParameter=>$page+1])) ?>" aria-label="Next page" data-preserve-scroll>Next ›</a>
                <a class="btn ghost compact" href="<?= e(module_query_url([$pageParameter=>$pages])) ?>" aria-label="Last page" data-preserve-scroll>Last »</a>
            <?php endif; ?>
            <form method="get" class="server-page-size" data-auto-submit data-preserve-scroll>
                <?php foreach ($_GET as $key=>$value): if ($key===$pageParameter || $key===$perPageParameter || !is_scalar($value)) continue; ?>
                    <input type="hidden" name="<?= e((string)$key) ?>" value="<?= e((string)$value) ?>">
                <?php endforeach; ?>
                <input type="hidden" name="<?= e($pageParameter) ?>" value="1">
                <label>Rows<select name="<?= e($perPageParameter) ?>"><?php foreach ([10,25,50,100] as $size): ?><option value="<?= $size ?>" <?= $perPage===$size?'selected':'' ?>><?= $size ?></option><?php endforeach; ?></select></label>
            </form>
        </div>
    </nav>
    <?php
}

function module_remote_patient_picker(string $name='patient_id', ?array $selected=null, bool $required=true, string $placeholder='Search patient number, name, mobile, or email'): void
{
    $label='';
    if($selected){
        $label=trim((string)($selected['patient_no']??'') . ' · ' . (string)($selected['last_name']??'') . ', ' . (string)($selected['first_name']??''),' ·,');
    }
    ?><div class="remote-patient-picker" data-patient-picker <?= $required?'':'data-optional="true"' ?> data-endpoint="<?=e(app_absolute_url('api/patient_search.php'))?>"><input type="search" data-patient-query value="<?=e($label)?>" placeholder="<?=e($placeholder)?>" autocomplete="off" <?=$required?'required':''?>><input type="hidden" name="<?=e($name)?>" data-patient-id value="<?=e((string)($selected['id']??''))?>" data-primary-dentist-id="<?=e((string)($selected['primary_dentist_user_id']??''))?>" data-primary-dentist-name="<?=e((string)($selected['primary_dentist_name']??''))?>"><div class="remote-patient-selection" data-patient-selection <?=$selected?'':'hidden'?>><?=$selected?'Selected: '.e($label):''?></div><div class="remote-patient-results" data-patient-results hidden></div></div><?php
}

/**
 * Shared management-workspace list state. Pages use this instead of loading an
 * arbitrary number of records and rendering one form/dialog for every row.
 */
function module_management_state(string $pageParameter='page', string $perPageParameter='per_page', int $defaultPerPage=25): array
{
    $page=module_page_number($pageParameter);
    $perPage=module_page_size($perPageParameter,$defaultPerPage);
    return [
        'page'=>$page,
        'per_page'=>$perPage,
        'offset'=>($page-1)*$perPage,
        'query'=>trim((string)($_GET['q']??'')),
        'manage_id'=>max(0,(int)($_GET['manage_id']??0)),
    ];
}

function module_like_term(string $value): string
{
    return '%' . str_replace(['\\','%','_'],['\\\\','\\%','\\_'],$value) . '%';
}

function module_safe_status_filter(string $parameter, array $allowed, string $default=''): string
{
    $value=trim((string)($_GET[$parameter]??$default));
    return in_array($value,$allowed,true)?$value:$default;
}

function module_management_url(array $changes=[]): string
{
    return module_query_url($changes);
}

function module_render_list_count(int $total, string $label='records'): void
{
    ?><span class="badge management-count"><?=e(number_format($total))?> <?=e($label)?></span><?php
}

/**
 * Remote lookup control for large operational catalogs. The browser downloads
 * only the selected record rather than hundreds of <option> elements.
 */
function module_remote_record_picker(
    string $name,
    string $type,
    int $branchId=0,
    bool $required=false,
    string $placeholder='Search and select a record',
    ?array $selected=null,
    array $extra=[]
): void {
    $endpoint=app_absolute_url('api/management_lookup.php');
    $query=['type'=>$type];
    if($branchId>0)$query['branch_id']=$branchId;
    foreach($extra as $key=>$value){if(is_scalar($value)&&$value!=='')$query[(string)$key]=(string)$value;}
    $endpoint.='?'.http_build_query($query);
    $label=trim((string)($selected['label']??$selected['name']??''));
    $value=(string)($selected['id']??'');
    ?>
    <div class="remote-patient-picker remote-record-picker" data-record-picker data-endpoint="<?=e($endpoint)?>" <?=$required?'':'data-optional="true"'?>>
        <input type="search" data-record-query value="<?=e($label)?>" placeholder="<?=e($placeholder)?>" autocomplete="off" <?=$required?'required':''?>>
        <input type="hidden" name="<?=e($name)?>" data-record-id value="<?=e($value)?>">
        <div class="remote-patient-selection" data-record-selection <?=$value!==''?'':'hidden'?>><?=$value!==''?'Selected: '.e($label):''?></div>
        <div class="remote-patient-results" data-record-results hidden></div>
    </div>
    <?php
}

/** Search-and-add picker for many records without rendering the full catalog. */
function module_remote_multi_record_picker(
    string $name,
    string $type,
    int $branchId=0,
    bool $required=true,
    string $placeholder='Search and add records',
    array $extra=[]
): void {
    $endpoint=app_absolute_url('api/management_lookup.php');
    $query=['type'=>$type];if($branchId>0)$query['branch_id']=$branchId;
    foreach($extra as $key=>$value){if(is_scalar($value)&&$value!=='')$query[(string)$key]=(string)$value;}
    $endpoint.='?'.http_build_query($query);
    ?>
    <div class="remote-multi-picker" data-multi-record-picker data-endpoint="<?=e($endpoint)?>" data-input-name="<?=e($name)?>" <?=$required?'data-required="true"':''?>>
        <input type="search" data-multi-record-query placeholder="<?=e($placeholder)?>" autocomplete="off">
        <div class="remote-patient-results" data-multi-record-results hidden></div>
        <div class="remote-multi-selection" data-multi-record-selection><span class="muted">No records selected.</span></div>
        <div data-multi-record-inputs></div>
    </div>
    <?php
}
