<?php

declare(strict_types=1);

require_once __DIR__ . '/bootstrap.php';

use Dental\Services\EmailService;

const INSTALLER_VERSION = '0.7.6.5';
const INSTALLER_SESSION = 'dental_installer_state';

$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
session_name('dental_installer');
session_set_cookie_params([
    'httponly' => true,
    'secure' => $isHttps,
    'samesite' => 'Lax',
    'path' => '/',
]);
session_start();

header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');
header("Content-Security-Policy: default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'");

function installer_developer_footer(): string
{
    return '<footer class="installer-developer-footer">Developer &amp; Programmer: <a href="mailto:mtbcustomprints@gmail.com">MTBCustoms</a> <span>mtbcustomprints@gmail.com</span></footer>';
}

$distributionProfile = dental_distribution_profile();
$forcedInstallerMode = strtolower(trim((string)($distributionProfile['installer_mode'] ?? '')));
$requestedInstallerMode = strtolower(trim((string) ($_GET['mode'] ?? $_POST['installer_mode'] ?? ($_SESSION[INSTALLER_SESSION]['installer_mode'] ?? ''))));
if (in_array($forcedInstallerMode, ['production','demo'], true)) {
    $requestedInstallerMode = $forcedInstallerMode;
}
if (!in_array($requestedInstallerMode, ['production','demo'], true)) {
    ?><!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="public/favicon.svg" type="image/svg+xml"><link rel="alternate icon" href="public/favicon.ico"><title>DentalCare Bootstrap Installer</title><link rel="stylesheet" href="public/assets/css/install.css"></head><body><main class="shell"><div class="brand"><div class="mark">DC</div><div><h1>DentalCare Bootstrap Installer</h1><p>Choose the installation purpose</p></div></div><section class="panel"><h2>Installation type</h2><p class="lead">Production requires a signed domain license. Demo creates a separate, permanently read-only sample clinic and never offers adoption, erase, reset, or import actions.</p><div class="installer-mode-grid"><a class="mode-card" href="install.php?mode=production"><strong>Licensed production</strong><span>Fresh installation or non-destructive connection to a verified DentalCare database.</span><em>Signed license required</em></a><a class="mode-card" href="install.php?mode=demo"><strong>Read-only demo copy</strong><span>Creates isolated demo tables and sample clinical records. No data can be added, changed, deleted, imported, emailed, processed, erased, or reset.</span><em>No production data access</em></a></div></section><?= installer_developer_footer() ?></main></body></html><?php
    exit;
}
$stateMode = $_SESSION[INSTALLER_SESSION] ?? [];
$stateMode['installer_mode'] = $requestedInstallerMode;
$_SESSION[INSTALLER_SESSION] = $stateMode;
$installerLicense = dental_license_status();
if ($requestedInstallerMode === 'production') {
    $installerLicensePath = __DIR__ . '/storage/license.key';
    $installerLicenseToken = is_file($installerLicensePath) ? trim((string) file_get_contents($installerLicensePath)) : '';
    $installerLicense = $installerLicenseToken !== ''
        ? dental_license_status($installerLicenseToken)
        : ['valid'=>false,'mode'=>'license_required','message'=>'A signed production license must be activated before installation.'];
    if (!($installerLicense['valid'] ?? false) || ($installerLicense['mode'] ?? '') !== 'signed_license') {
        header('Location: license.php?return=' . rawurlencode('install.php?mode=production'));
        exit;
    }
}

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function installer_state(): array
{
    return $_SESSION[INSTALLER_SESSION] ?? [];
}

function save_installer_state(array $state): void
{
    $_SESSION[INSTALLER_SESSION] = $state;
}

function csrf_token(): string
{
    if (empty($_SESSION['installer_csrf'])) {
        $_SESSION['installer_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['installer_csrf'];
}

function require_csrf(): void
{
    $token = (string) ($_POST['_csrf'] ?? '');
    if ($token === '' || !hash_equals(csrf_token(), $token)) {
        throw new RuntimeException('The installer form expired. Refresh the page and try again.');
    }
}

function redirect_step(int $step): never
{
    header('Location: install.php?step=' . $step);
    exit;
}

function set_flash(string $type, string $message): void
{
    $_SESSION['installer_flash'] = ['type' => $type, 'message' => $message];
}

function pull_flash(): ?array
{
    $flash = $_SESSION['installer_flash'] ?? null;
    unset($_SESSION['installer_flash']);
    return is_array($flash) ? $flash : null;
}

function detect_base_url(): string
{
    $scheme = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https')) ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $path = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/install.php')), '/');
    return $scheme . '://' . $host . ($path === '' ? '' : $path);
}

function environment_checks(): array
{
    $phpOk = version_compare(PHP_VERSION, '8.2.0', '>=');
    $checks = [
        ['label' => 'PHP 8.2 or newer', 'ok' => $phpOk, 'value' => PHP_VERSION],
        ['label' => 'PDO extension', 'ok' => extension_loaded('pdo'), 'value' => extension_loaded('pdo') ? 'Available' : 'Missing'],
        ['label' => 'PDO MySQL extension', 'ok' => extension_loaded('pdo_mysql'), 'value' => extension_loaded('pdo_mysql') ? 'Available' : 'Missing'],
        ['label' => 'OpenSSL extension', 'ok' => extension_loaded('openssl'), 'value' => extension_loaded('openssl') ? 'Available' : 'Missing'],
        ['label' => 'JSON extension', 'ok' => extension_loaded('json'), 'value' => extension_loaded('json') ? 'Available' : 'Missing'],
        ['label' => 'ZIP extension', 'ok' => extension_loaded('zip'), 'value' => extension_loaded('zip') ? 'Available' : 'Missing'],
        ['label' => 'DOM extension', 'ok' => extension_loaded('dom'), 'value' => extension_loaded('dom') ? 'Available' : 'Missing'],
        ['label' => 'Multibyte string extension', 'ok' => extension_loaded('mbstring'), 'value' => extension_loaded('mbstring') ? 'Available' : 'Missing'],
        ['label' => 'Config directory writable', 'ok' => is_writable(__DIR__ . '/config'), 'value' => is_writable(__DIR__ . '/config') ? 'Writable' : 'Not writable'],
        ['label' => 'Storage directory writable', 'ok' => is_writable(__DIR__ . '/storage'), 'value' => is_writable(__DIR__ . '/storage') ? 'Writable' : 'Not writable'],
        ['label' => 'Private storage protected', 'ok' => is_file(__DIR__ . '/storage/.htaccess'), 'value' => is_file(__DIR__ . '/storage/.htaccess') ? 'Protection file present' : 'Protection file missing'],
    ];
    return $checks;
}

function environment_is_ready(): bool
{
    foreach (environment_checks() as $check) {
        if (!$check['ok']) {
            return false;
        }
    }
    return true;
}

function validate_prefix(string $prefix): string
{
    $prefix = trim($prefix);
    if (!preg_match('/^[A-Za-z][A-Za-z0-9_]{1,24}$/', $prefix)) {
        throw new RuntimeException('The table prefix must start with a letter and contain only letters, numbers, and underscores.');
    }
    return $prefix;
}

function inspect_database(array $db): array
{
    $pdo = dental_pdo($db);
    $version = (string) $pdo->query('SELECT VERSION()')->fetchColumn();
    $stmt = $pdo->prepare(
        'SELECT table_name FROM information_schema.tables
         WHERE table_schema = :database_name
           AND LEFT(table_name, LENGTH(:prefix_length_value)) = :prefix_match
         ORDER BY table_name'
    );
    $stmt->execute([
        ':database_name' => $db['name'],
        ':prefix_length_value' => $db['prefix'],
        ':prefix_match' => $db['prefix'],
    ]);
    $tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
    $required = array_map(static fn(string $name): string => $db['prefix'] . $name, ['roles','users','branches','settings','install_history']);
    $recognized = count(array_intersect($required,$tables)) === count($required);
    return ['version' => $version, 'tables' => $tables, 'recognized_installation' => $recognized, 'required_tables' => $required];
}

function existing_installation_profile(array $db): array
{
    $pdo = dental_pdo($db);
    $prefix = $db['prefix'];
    $branch = $pdo->query("SELECT * FROM `{$prefix}branches` ORDER BY is_active DESC,id ASC LIMIT 1")->fetch() ?: [];
    $adminStmt = $pdo->query("SELECT u.name,u.email FROM `{$prefix}users` u JOIN `{$prefix}roles` r ON r.id=u.role_id WHERE r.code='super_admin' ORDER BY u.status='active' DESC,u.id ASC LIMIT 1");
    $admin = $adminStmt->fetch() ?: [];
    $settings = [];
    foreach ($pdo->query("SELECT group_name,setting_key,setting_value FROM `{$prefix}settings` WHERE group_name IN ('clinic','mail','system')") as $row) {
        $settings[(string)$row['group_name']][(string)$row['setting_key']] = (string)($row['setting_value'] ?? '');
    }
    return [
        'clinic_name' => (string)($settings['clinic']['name'] ?? $branch['name'] ?? 'Dental Clinic'),
        'branch_code' => (string)($branch['code'] ?? 'MAIN'),
        'clinic_phone' => (string)($settings['clinic']['phone'] ?? $branch['phone'] ?? ''),
        'clinic_address' => (string)($settings['clinic']['address'] ?? $branch['address'] ?? ''),
        'base_url' => detect_base_url(),
        'timezone' => (string)($settings['clinic']['timezone'] ?? $branch['timezone'] ?? 'Asia/Manila'),
        'admin_name' => (string)($admin['name'] ?? 'Existing Superuser'),
        'admin_email' => (string)($admin['email'] ?? $branch['email'] ?? ''),
        'admin_password_hash' => '',
        'existing_mail_driver' => (string)($settings['mail']['driver'] ?? 'smtp'),
        'existing_from_email' => (string)($settings['mail']['from_email'] ?? $branch['email'] ?? $admin['email'] ?? ''),
        'existing_from_name' => (string)($settings['mail']['from_name'] ?? $settings['clinic']['name'] ?? $branch['name'] ?? 'Dental Clinic'),
    ];
}

function render_template(string $file, array $variables): string
{
    $path = __DIR__ . '/resources/email/' . $file . '.php';
    if (!is_file($path)) {
        throw new RuntimeException('Email template not found.');
    }
    extract($variables, EXTR_SKIP);
    ob_start();
    require $path;
    return (string) ob_get_clean();
}

function mail_config_from_post(array $state): array
{
    $driver = (string) ($_POST['mail_driver'] ?? 'smtp');
    if (!in_array($driver, ['smtp', 'mail', 'log'], true)) {
        throw new RuntimeException('Invalid email driver.');
    }

    $fromEmail = trim((string) ($_POST['from_email'] ?? ''));
    $fromName = trim((string) ($_POST['from_name'] ?? ''));
    if (!filter_var($fromEmail, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('Enter a valid sender email address.');
    }
    if ($fromName === '') {
        throw new RuntimeException('Enter the sender name.');
    }

    $encryption = (string) ($_POST['smtp_encryption'] ?? 'tls');
    if (!in_array($encryption, ['tls', 'ssl', 'none'], true)) {
        throw new RuntimeException('Invalid SMTP encryption setting.');
    }

    $postedSmtpPassword = (string) ($_POST['smtp_password'] ?? '');
    $smtpPassword = $postedSmtpPassword !== '' ? $postedSmtpPassword : (string) ($state['mail']['smtp']['password'] ?? '');

    $mail = [
        'driver' => $driver,
        'from_email' => $fromEmail,
        'from_name' => $fromName,
        'smtp' => [
            'host' => trim((string) ($_POST['smtp_host'] ?? '')),
            'port' => (int) ($_POST['smtp_port'] ?? 587),
            'encryption' => $encryption,
            'username' => trim((string) ($_POST['smtp_username'] ?? '')),
            'password' => $smtpPassword,
            'timeout' => 15,
            'verify_peer' => isset($_POST['smtp_verify_peer']),
        ],
        'test_recipient' => trim((string) ($_POST['test_recipient'] ?? ($state['clinic']['admin_email'] ?? ''))),
    ];

    if ($driver === 'smtp') {
        if ($mail['smtp']['host'] === '') {
            throw new RuntimeException('Enter the SMTP host.');
        }
        if ($mail['smtp']['port'] < 1 || $mail['smtp']['port'] > 65535) {
            throw new RuntimeException('Enter a valid SMTP port.');
        }
    }

    return $mail;
}

function execute_schema(PDO $pdo, string $prefix): void
{
    $schema = file_get_contents(__DIR__ . '/database/schema.sql');
    if ($schema === false) {
        throw new RuntimeException('Unable to read the database schema.');
    }
    $schema = str_replace('{{prefix}}', $prefix, $schema);
    $statements = array_filter(array_map('trim', preg_split('/;\s*(?:\r?\n|$)/', $schema) ?: []));
    foreach ($statements as $statement) {
        if ($statement !== '') {
            $pdo->exec($statement);
        }
    }
}

function installer_column_exists(PDO $pdo, string $table, string $column): bool
{
    $stmt=$pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=? AND column_name=?');
    $stmt->execute([$table,$column]);
    return (int)$stmt->fetchColumn()>0;
}

function installer_add_column(PDO $pdo, string $table, string $column, string $definition): void
{
    if(installer_column_exists($pdo,$table,$column)) return;
    $pdo->exec('ALTER TABLE `' . str_replace('`','``',$table) . '` ADD COLUMN `' . str_replace('`','``',$column) . '` ' . $definition);
}

function execute_pos_schema(PDO $pdo, string $prefix): void
{
    $path=__DIR__ . '/database/migrations/0.5.9.3_queue_navigation_cashier_pos.sql';
    if(!is_file($path)) throw new RuntimeException('Unable to read the cashier POS migration.');
    $schema=file_get_contents($path);
    if($schema===false) throw new RuntimeException('Unable to read the cashier POS migration.');
    $schema=str_replace('{{prefix}}',$prefix,$schema);
    $statements=array_filter(array_map('trim',preg_split('/;\s*(?:\r?\n|$)/',$schema)?:[]));
    foreach($statements as $statement) if($statement!=='') $pdo->exec($statement);

    installer_add_column($pdo,$prefix.'service_catalog','standard_fee','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `duration_minutes`');
    installer_add_column($pdo,$prefix.'invoices','subtotal','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `description`');
    installer_add_column($pdo,$prefix.'invoices','discount_type','VARCHAR(20) NOT NULL DEFAULT "none" AFTER `subtotal`');
    installer_add_column($pdo,$prefix.'invoices','discount_value','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_type`');
    installer_add_column($pdo,$prefix.'invoices','discount_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_value`');
    installer_add_column($pdo,$prefix.'invoices','discount_reason','VARCHAR(255) NULL AFTER `discount_amount`');
    installer_add_column($pdo,$prefix.'invoices','tax_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `discount_reason`');
    installer_add_column($pdo,$prefix.'invoices','pos_finalized_at','DATETIME NULL AFTER `updated_at`');
    installer_add_column($pdo,$prefix.'invoices','pos_finalized_by','BIGINT UNSIGNED NULL AFTER `pos_finalized_at`');
    installer_add_column($pdo,$prefix.'payments','tendered_amount','DECIMAL(12,2) NULL AFTER `amount`');
    installer_add_column($pdo,$prefix.'payments','change_amount','DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER `tendered_amount`');
    installer_add_column($pdo,$prefix.'payments','notes','VARCHAR(500) NULL AFTER `reference_no`');
    $pdo->exec('UPDATE `' . str_replace('`','``',$prefix.'invoices') . '` SET subtotal=amount WHERE subtotal=0 AND amount>0');
}

function execute_v05982_schema(PDO $pdo, string $prefix): void
{
    $path=__DIR__ . '/database/migrations/0.5.9.8.2_schedule_scope_stock_treatment.sql';
    if(!is_file($path)) throw new RuntimeException('Unable to read the Version 0.5.9.8.2 seed migration.');
    $schema=file_get_contents($path);
    if($schema===false) throw new RuntimeException('Unable to read the Version 0.5.9.8.2 seed migration.');
    $schema=str_replace('{{prefix}}',$prefix,$schema);
    $statements=array_filter(array_map('trim',preg_split('/;\s*(?:\r?\n|$)/',$schema)?:[]));
    foreach($statements as $statement) if($statement!=='') $pdo->exec($statement);
}

function wipe_prefixed_tables(PDO $pdo, string $database, string $prefix): int
{
    $stmt = $pdo->prepare(
        'SELECT table_name FROM information_schema.tables
         WHERE table_schema = :database_name
           AND LEFT(table_name, LENGTH(:prefix_length_value)) = :prefix_match'
    );
    $stmt->execute([
        ':database_name' => $database,
        ':prefix_length_value' => $prefix,
        ':prefix_match' => $prefix,
    ]);
    $tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
    if (!$tables) {
        return 0;
    }

    $pdo->exec('SET FOREIGN_KEY_CHECKS=0');
    try {
        foreach ($tables as $table) {
            if (!is_string($table) || !str_starts_with($table, $prefix) || !preg_match('/^[A-Za-z0-9_]+$/', $table)) {
                continue;
            }
            $pdo->exec('DROP TABLE IF EXISTS `' . $table . '`');
        }
    } finally {
        $pdo->exec('SET FOREIGN_KEY_CHECKS=1');
    }
    return count($tables);
}

function write_generated_config(array $state): array
{
    $config = [
        'app' => [
            'name' => $state['clinic']['clinic_name'],
            'url' => rtrim($state['clinic']['base_url'], '/'),
            'timezone' => $state['clinic']['timezone'],
            'key' => bin2hex(random_bytes(32)),
            'installed_at' => date(DATE_ATOM),
            'version' => INSTALLER_VERSION,
            'installation_mode' => (string) ($state['installer_mode'] ?? 'production'),
            'distribution_package_id' => (string) (dental_distribution_profile()['package_id'] ?? ''),
        ],
        'database' => $state['database'],
        'mail' => [
            'driver' => $state['mail']['driver'],
            'from_email' => $state['mail']['from_email'],
            'from_name' => $state['mail']['from_name'],
            'smtp' => $state['mail']['smtp'],
        ],
        'security' => [
            'session_timeout_minutes' => 30,
            'force_https' => str_starts_with(strtolower($state['clinic']['base_url']), 'https://'),
            'email_queue_token' => bin2hex(random_bytes(32)),
            'demo_read_only' => (($state['installer_mode'] ?? 'production') === 'demo'),
        ],
    ];

    $content = "<?php\n\ndeclare(strict_types=1);\n\nreturn " . var_export($config, true) . ";\n";
    $target = __DIR__ . '/config/generated.php';
    $temp = $target . '.tmp-' . bin2hex(random_bytes(4));
    if (file_put_contents($temp, $content, LOCK_EX) === false) {
        throw new RuntimeException('Unable to write generated configuration.');
    }
    @chmod($temp, 0640);
    if (!rename($temp, $target)) {
        @unlink($temp);
        throw new RuntimeException('Unable to activate generated configuration.');
    }
    return $config;
}

function seed_installation(PDO $pdo, array $state): int
{
    $prefix = $state['database']['prefix'];
    $roles = [
        ['super_admin', 'Superuser', 'Protected system ownership, licensing, updates, backups, security, and organization-wide administration'],
        ['clinic_admin', 'Administrator', 'Clinic operations, staff, schedules, services, inventory oversight, and reports'],
        ['receptionist', 'Receptionist', 'Patient registration, appointments, and queue'],
        ['staff', 'General Staff', 'Combined Receptionist and Cashier role for registration, appointments, queue, communications, cashier sessions, payments, receipts, and checkout without clinical, inventory, approval, or system authority'],
        ['dentist', 'Dentist', 'Clinical records, diagnosis, treatment, and prescriptions'],
        ['dental_assistant', 'Dental Assistant', 'Screening, chairside support, and draft clinical entries'],
        ['cashier', 'Cashier', 'Invoices, payments, and receipts'],
        ['inventory_officer', 'Inventory Officer', 'Stock, suppliers, and expiration monitoring'],
        ['patient', 'Patient', 'Patient portal access'],
        ['guardian', 'Parent or Guardian', 'Dependent patient portal access'],
    ];
    $roleStmt = $pdo->prepare("INSERT INTO `{$prefix}roles` (code,name,description) VALUES (:code,:name,:description) ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description)");
    foreach ($roles as [$code, $name, $description]) {
        $roleStmt->execute([':code' => $code, ':name' => $name, ':description' => $description]);
    }

    // The permission catalog is created by schema.sql before roles are seeded,
    // so link the default role permissions here for a complete fresh install.
    $defaultPermissions = [
        'clinic_admin' => ['patients.view','patients.manage','appointments.manage','queue.manage','clinical.view','clinical.manage','clinical.finalize','billing.manage','insurance.manage','inventory.manage','sterilization.manage','communications.manage','reports.view','staff.manage','configuration.manage','system.manage','uploads.manage'],
        'receptionist' => ['patients.view','patients.manage','appointments.manage','queue.manage','communications.manage'],
        'staff' => ['patients.view','appointments.manage','queue.manage','communications.manage'],
        'dentist' => ['patients.view','appointments.manage','queue.manage','clinical.view','clinical.manage','clinical.finalize','reports.view'],
        'dental_assistant' => ['patients.view','queue.manage','clinical.view','clinical.manage','sterilization.manage'],
        'cashier' => ['patients.view','queue.manage','billing.manage','reports.view'],
        'inventory_officer' => ['inventory.manage','sterilization.manage','reports.view'],
    ];
    $roleIds = [];
    foreach ($pdo->query("SELECT id,code FROM `{$prefix}roles`") as $row) $roleIds[(string) $row['code']] = (int) $row['id'];
    $permissionIds = [];
    foreach ($pdo->query("SELECT id,code FROM `{$prefix}permissions`") as $row) $permissionIds[(string) $row['code']] = (int) $row['id'];
    $permissionLink = $pdo->prepare("INSERT INTO `{$prefix}role_permissions` (role_id,permission_id,allowed) VALUES (?,?,1) ON DUPLICATE KEY UPDATE allowed=VALUES(allowed)");
    foreach ($defaultPermissions as $roleCode => $permissionCodes) {
        if (!isset($roleIds[$roleCode])) continue;
        foreach ($permissionCodes as $permissionCode) {
            if (isset($permissionIds[$permissionCode])) $permissionLink->execute([$roleIds[$roleCode], $permissionIds[$permissionCode]]);
        }
    }

    $branchStmt = $pdo->prepare(
        "INSERT INTO `{$prefix}branches` (name, code, email, phone, address, timezone, is_active, created_at)
         VALUES (:name, :code, :email, :phone, :address, :timezone, 1, NOW())
         ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email), phone=VALUES(phone), address=VALUES(address), timezone=VALUES(timezone), is_active=1, updated_at=NOW()"
    );
    $branchStmt->execute([
        ':name' => $state['clinic']['clinic_name'],
        ':code' => $state['clinic']['branch_code'],
        ':email' => $state['mail']['from_email'],
        ':phone' => $state['clinic']['clinic_phone'],
        ':address' => $state['clinic']['clinic_address'],
        ':timezone' => $state['clinic']['timezone'],
    ]);
    $branchIdStmt = $pdo->prepare("SELECT id FROM `{$prefix}branches` WHERE code=:code LIMIT 1");
    $branchIdStmt->execute([':code' => $state['clinic']['branch_code']]);
    $branchId = (int) $branchIdStmt->fetchColumn();

    $roleIdStmt = $pdo->prepare("SELECT id FROM `{$prefix}roles` WHERE code='super_admin' LIMIT 1");
    $roleIdStmt->execute();
    $roleId = (int) $roleIdStmt->fetchColumn();

    $userStmt = $pdo->prepare(
        "INSERT INTO `{$prefix}users` (branch_id, role_id, name, email, password_hash, status, email_verified_at, created_at)
         VALUES (:branch, :role, :name, :email, :password, 'active', NOW(), NOW())
         ON DUPLICATE KEY UPDATE name=VALUES(name), role_id=VALUES(role_id), branch_id=VALUES(branch_id), status='active'"
    );
    $userStmt->execute([
        ':branch' => $branchId,
        ':role' => $roleId,
        ':name' => $state['clinic']['admin_name'],
        ':email' => $state['clinic']['admin_email'],
        ':password' => $state['clinic']['admin_password_hash'],
    ]);

    $settings = [
        ['clinic', 'name', $state['clinic']['clinic_name']],
        ['clinic', 'phone', $state['clinic']['clinic_phone']],
        ['clinic', 'address', $state['clinic']['clinic_address']],
        ['clinic', 'timezone', $state['clinic']['timezone']],
        ['mail', 'driver', $state['mail']['driver']],
        ['mail', 'from_email', $state['mail']['from_email']],
        ['mail', 'from_name', $state['mail']['from_name']],
        ['system', 'version', INSTALLER_VERSION],
        ['system', 'application_version', INSTALLER_VERSION],
    ];
    $settingStmt = $pdo->prepare(
        "INSERT INTO `{$prefix}settings` (group_name,setting_key,setting_value,is_secret,created_at)
         VALUES (:group_name,:setting_key,:setting_value,0,NOW())
         ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW()"
    );
    foreach ($settings as [$group, $key, $value]) {
        $settingStmt->execute([':group_name' => $group, ':setting_key' => $key, ':setting_value' => $value]);
    }

    $historyStmt = $pdo->prepare(
        "INSERT INTO `{$prefix}install_history` (version,database_action,installed_by_email,installed_at)
         VALUES (:version,:action,:email,NOW())"
    );
    $historyStmt->execute([
        ':version' => INSTALLER_VERSION,
        ':action' => $state['database_action'],
        ':email' => $state['clinic']['admin_email'],
    ]);

    $hoursStmt = $pdo->prepare("INSERT INTO `{$prefix}branch_operating_hours` (branch_id,day_of_week,is_open,open_time,close_time,slot_interval_minutes,created_at) VALUES (?,?,?,?,?,?,NOW()) ON DUPLICATE KEY UPDATE branch_id=VALUES(branch_id)");
    for ($day = 0; $day <= 6; $day++) {
        $open = $day >= 1 && $day <= 6;
        $hoursStmt->execute([$branchId, $day, $open ? 1 : 0, $open ? '08:00:00' : null, $day === 6 ? '12:00:00' : ($open ? '17:00:00' : null), 30]);
    }
    $serviceStmt = $pdo->prepare("INSERT INTO `{$prefix}service_catalog` (branch_id,name,duration_minutes,is_public,is_active,created_at) VALUES (NULL,?,?,1,1,NOW())");
    foreach ([['Dental Examination',30],['Oral Prophylaxis',45],['Tooth Restoration',60],['Tooth Extraction',60],['Root Canal Consultation',60],['Orthodontic Consultation',45],['Denture Consultation',45],['Emergency Dental Visit',60]] as [$serviceName,$duration]) {
        $serviceStmt->execute([$serviceName,$duration]);
    }

    return $branchId;
}

function seed_demo_installation(PDO $pdo, array $state, int $branchId): void
{
    $prefix = $state['database']['prefix'];
    $passwordHash = password_hash('DentalDemo2026!', PASSWORD_DEFAULT);
    $roles = [];
    foreach ($pdo->query("SELECT id,code FROM `{$prefix}roles`") as $row) $roles[(string)$row['code']] = (int)$row['id'];
    $users = [
        ['super_admin','DEMO-ADM-001','Demo Administrator','admin@demo.dentalcare.local'],
        ['dentist','DEMO-DEN-001','Dr. Maria Santos','dentist@demo.dentalcare.local'],
        ['receptionist','DEMO-REC-001','Ana Reception','reception@demo.dentalcare.local'],
        ['dental_assistant','DEMO-ASST-001','Leo Dental Assistant','assistant@demo.dentalcare.local'],
        ['cashier','DEMO-CASH-001','Mina Cashier','cashier@demo.dentalcare.local'],
    ];
    $userStmt = $pdo->prepare("INSERT INTO `{$prefix}users` (branch_id,role_id,employee_no,name,email,password_hash,status,email_verified_at,must_change_password,is_demo,created_at) VALUES (?,?,?,?,?,?,'active',NOW(),0,1,NOW()) ON DUPLICATE KEY UPDATE role_id=VALUES(role_id),employee_no=VALUES(employee_no),name=VALUES(name),password_hash=VALUES(password_hash),status='active',is_demo=1");
    foreach ($users as [$role,$employee,$name,$email]) $userStmt->execute([$branchId,$roles[$role],$employee,$name,$email,$passwordHash]);
    $pdo->prepare("UPDATE `{$prefix}users` SET is_demo=1,employee_no='DEMO-ADM-001',password_hash=? WHERE email='admin@demo.dentalcare.local'")->execute([$passwordHash]);
    $userIds=[]; foreach($pdo->query("SELECT id,email FROM `{$prefix}users` WHERE is_demo=1") as $row)$userIds[(string)$row['email']] = (int)$row['id'];
    $adminId=$userIds['admin@demo.dentalcare.local'];$dentistId=$userIds['dentist@demo.dentalcare.local'];$receptionId=$userIds['reception@demo.dentalcare.local'];$assistantId=$userIds['assistant@demo.dentalcare.local'];$cashierId=$userIds['cashier@demo.dentalcare.local'];

    $specialtyId=(int)($pdo->query("SELECT id FROM `{$prefix}dentist_specialties` WHERE code='general_dentistry' LIMIT 1")->fetchColumn()?:0);
    $pdo->prepare("INSERT INTO `{$prefix}dentist_profiles` (user_id,professional_title,display_name,specialty_id,license_number,license_expiry,years_in_practice,education,qualifications,languages_json,biography,public_biography,preferred_tooth_numbering,default_note_format,employment_type,primary_branch_id,default_chair_name,default_duration_minutes,maximum_daily_appointments,accepting_new_patients,public_profile_enabled,profile_status,created_at) VALUES (?,'Dentist','Dr. Maria Santos',?,'DEMO-PRC-0001',DATE_ADD(CURDATE(),INTERVAL 2 YEAR),12,'Doctor of Dental Medicine','General Dentistry',JSON_ARRAY('English','Filipino'),'Demo dentist profile for product evaluation.','General dentist providing preventive and restorative care.','FDI','Structured','full_time',?,'Chair 1',45,12,1,1,'active',NOW()) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),license_number=VALUES(license_number),primary_branch_id=VALUES(primary_branch_id),profile_status='active'")->execute([$dentistId,$specialtyId?:null,$branchId]);

    $pdo->prepare("INSERT INTO `{$prefix}clinic_chairs` (branch_id,chair_code,chair_name,room_name,usage_mode,status,notes,created_by,created_at) VALUES (?,'DEMO-CHAIR-1','Chair 1','Operatory 1','dentist_bound','available','Demo treatment chair',?,NOW()) ON DUPLICATE KEY UPDATE status='available',room_name=VALUES(room_name)")->execute([$branchId,$adminId]);
    $chairId=(int)$pdo->query("SELECT id FROM `{$prefix}clinic_chairs` WHERE branch_id={$branchId} AND chair_code='DEMO-CHAIR-1' LIMIT 1")->fetchColumn();
    $pdo->prepare("INSERT INTO `{$prefix}doctor_chair_assignments` (dentist_user_id,branch_id,chair_id,chair_name,is_active,created_by,created_at) VALUES (?,?,?,'Chair 1',1,?,NOW()) ON DUPLICATE KEY UPDATE chair_id=VALUES(chair_id),chair_name='Chair 1',is_active=1")->execute([$dentistId,$branchId,$chairId?:null,$adminId]);

    $serviceIds=[]; foreach($pdo->query("SELECT id,name FROM `{$prefix}service_catalog`") as $row)$serviceIds[(string)$row['name']] = (int)$row['id'];
    $patientStmt=$pdo->prepare("INSERT INTO `{$prefix}patients` (branch_id,primary_dentist_user_id,primary_dentist_assigned_at,patient_no,first_name,middle_name,last_name,preferred_name,birth_date,sex,civil_status,nationality,occupation,preferred_language,communication_preference,mobile,email,address,emergency_contact,medical_alerts,status,is_demo,created_by,created_at) VALUES (?,?,NOW(),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'active',1,?,NOW())");
    $patientStmt->execute([$branchId,$dentistId,'DEMO-PT-0001','Maria','L.','Dela Cruz','Maria','1988-05-14','Female','Married','Filipino','Teacher','Filipino','Email','09170000001','patient@demo.dentalcare.local','Legazpi City, Albay','Jose Dela Cruz · 09170000011','Penicillin allergy',$receptionId]);
    $patient1=(int)$pdo->lastInsertId();
    $patientStmt->execute([$branchId,$dentistId,'DEMO-PT-0002','Paolo',null,'Reyes','Paolo','1996-11-02','Male','Single','Filipino','Engineer','English','SMS','09170000002','paolo@demo.dentalcare.local','Daraga, Albay','Lina Reyes · 09170000012','None reported',$receptionId]);
    $patient2=(int)$pdo->lastInsertId();
    $pdo->prepare("INSERT INTO `{$prefix}medical_histories` (patient_id,allergies,medications,conditions,dental_history,confirmed_at,source_type,verification_status,verified_by,verified_at,verification_note,created_by,created_at) VALUES (?,'Penicillin','None','Controlled asthma','Previous restorations on posterior teeth',NOW(),'clinician_verified','verified',?,NOW(),'Verified demo health information relevant to dental care.',?,NOW()),(?,'No known drug allergies','None','None reported','Irregular prophylaxis history',NOW(),'clinician_verified','verified',?,NOW(),'Verified demo health information relevant to dental care.',?,NOW())")->execute([$patient1,$dentistId,$dentistId,$patient2,$dentistId,$dentistId]);
    $pdo->prepare("INSERT INTO `{$prefix}medical_alerts` (patient_id,alert_type,severity,description,is_active,created_by,created_at) VALUES (?,'Drug allergy','critical','Penicillin allergy. Verify antibiotic selection before prescribing.',1,?,NOW())")->execute([$patient1,$dentistId]);

    $pastStart=date('Y-m-d 10:00:00',strtotime('-14 days'));$pastEnd=date('Y-m-d 11:00:00',strtotime('-14 days'));
    $futureTs=strtotime('+7 days');while((int)date('N',$futureTs)>5)$futureTs=strtotime('+1 day',$futureTs);$futureStart=date('Y-m-d 09:00:00',$futureTs);$futureEnd=date('Y-m-d 09:45:00',$futureTs);
    $appt=$pdo->prepare("INSERT INTO `{$prefix}appointments` (branch_id,patient_id,service_id,dentist_user_id,dentist_name,service_name,provider_type,start_at,end_at,chair_id,chair_name,status,appointment_origin,reason_for_visit,confirmation_status,notes,created_by,created_at) VALUES (?,?,?,?,?,?,'dentist',?,?,?,?,?,'manual',?,'confirmed',?,?,NOW())");
    $appt->execute([$branchId,$patient1,$serviceIds['Dental Examination']??null,$dentistId,'Dr. Maria Santos','Dental Examination',$pastStart,$pastEnd,$chairId,'Chair 1','completed','Sensitivity on upper right posterior tooth','Completed demo examination',$receptionId]);$pastAppt=(int)$pdo->lastInsertId();
    $appt->execute([$branchId,$patient1,$serviceIds['Tooth Restoration']??null,$dentistId,'Dr. Maria Santos','Tooth Restoration',$futureStart,$futureEnd,$chairId,'Chair 1','confirmed','Restore tooth 16 occlusal caries','Accepted treatment-plan appointment',$receptionId]);$futureAppt=(int)$pdo->lastInsertId();

    $pdo->prepare("INSERT INTO `{$prefix}clinical_encounters` (branch_id,patient_id,appointment_id,dentist_user_id,dentist_name,visit_type,chief_complaint,history_present_condition,examination,diagnosis,treatment_plan,procedure_done,soap_note,follow_up_date,follow_up_purpose,follow_up_urgency,disposition,status,created_at,updated_at,finalized_at,signed_by_name,signed_license_number,finalized_hash) VALUES (?,?,?,?,?,'scheduled','Sensitivity when chewing on upper right posterior tooth','Intermittent sensitivity for two weeks. No spontaneous pain.','Occlusal cavitation on tooth 16; existing composite on tooth 26; tooth 36 missing.','Dentinal caries, tooth 16 occlusal.','Restore tooth 16 after caries removal. Review tooth 26 restoration and discuss replacement options for missing tooth 36.','Comprehensive examination and radiographic review.','S: Chewing sensitivity. O: Tooth 16 occlusal cavitation. A: Dentinal caries. P: Direct composite restoration.',DATE_ADD(CURDATE(),INTERVAL 14 DAY),'Restoration of tooth 16','important','follow_up_planned','finalized',DATE_SUB(NOW(),INTERVAL 14 DAY),DATE_SUB(NOW(),INTERVAL 14 DAY),DATE_SUB(NOW(),INTERVAL 14 DAY),'Dr. Maria Santos','DEMO-PRC-0001',SHA2(CONCAT('demo-',?),256))")->execute([$branchId,$patient1,$pastAppt,$dentistId,'Dr. Maria Santos',$patient1]);
    $encounterId=(int)$pdo->lastInsertId();
    $pdo->prepare("INSERT INTO `{$prefix}clinical_assessments` (encounter_id,patient_id,extraoral_findings,intraoral_findings,soft_tissue_findings,tmj_findings,occlusion_findings,oral_hygiene_status,radiographic_findings,risk_assessment,prognosis,recorded_by,created_at) VALUES (?,?,'No facial swelling or asymmetry.','Cavitation on 16-O; existing composite on 26-O; missing 36.','No ulceration or suspicious lesion.','Within normal limits.','Mild posterior crossbite.','Fair','Radiolucency into dentin on 16-O; no periapical pathology.','Moderate caries risk.','Good with restorative treatment and preventive recall.',?,DATE_SUB(NOW(),INTERVAL 14 DAY))")->execute([$encounterId,$patient1,$dentistId]);
    $pdo->prepare("INSERT INTO `{$prefix}diagnoses` (patient_id,encounter_id,tooth_region,diagnosis_code,diagnosis_name,severity,diagnosis_status,is_primary,notes,diagnosed_by,diagnosed_at) VALUES (?,?,'16-O','K02.9','Dentinal caries','moderate','confirmed',1,'Clinical and radiographic finding.',?,DATE_SUB(NOW(),INTERVAL 14 DAY)),(?,?,'36','K08.1','Missing tooth','moderate','chronic',0,'Long-standing missing mandibular first molar.',?,DATE_SUB(NOW(),INTERVAL 14 DAY))")->execute([$patient1,$encounterId,$dentistId,$patient1,$encounterId,$dentistId]);
    $diagnosisId=(int)$pdo->query("SELECT id FROM `{$prefix}diagnoses` WHERE encounter_id={$encounterId} AND is_primary=1 LIMIT 1")->fetchColumn();
    $missingDiagnosisId=(int)$pdo->query("SELECT id FROM `{$prefix}diagnoses` WHERE encounter_id={$encounterId} AND tooth_region='36' LIMIT 1")->fetchColumn();
    $odon=$pdo->prepare("INSERT INTO `{$prefix}odontogram_entries` (patient_id,branch_id,service_id,encounter_id,dentition,tooth_number,surface,condition_code,finding_type,severity,chart_layer,status,diagnosis_id,notes,recorded_by,recorded_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,'active',?,?,?,DATE_SUB(NOW(),INTERVAL 14 DAY))");
    $odon->execute([$patient1,$branchId,$serviceIds['Dental Examination']??null,$encounterId,'permanent','16','O','dentinal_caries','condition','moderate','existing',$diagnosisId?:null,'Radiographic extension into dentin.',$dentistId]);$finding16=(int)$pdo->lastInsertId();
    $odon->execute([$patient1,$branchId,$serviceIds['Dental Examination']??null,$encounterId,'permanent','26','O','composite_restoration','restoration','information','existing',null,'Existing composite restoration with acceptable margins.',$dentistId]);$finding26=(int)$pdo->lastInsertId();
    $odon->execute([$patient1,$branchId,$serviceIds['Dental Examination']??null,$encounterId,'permanent','36','Whole','missing','condition','moderate','existing',$missingDiagnosisId?:null,'Missing tooth; replacement options discussed.',$dentistId]);$finding36=(int)$pdo->lastInsertId();
    $pdo->prepare("INSERT INTO `{$prefix}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 (?,?,?,'permanent',DATE_SUB(CURDATE(),INTERVAL 14 DAY),'moderate','low','Fair','Mild posterior crossbite.','Active dentinal caries on 16-O, serviceable restoration on 26-O, and missing tooth 36. Restore tooth 16 first and discuss replacement options for 36.','finalized',DATE_SUB(NOW(),INTERVAL 14 DAY),DATE_SUB(NOW(),INTERVAL 14 DAY))")->execute([$patient1,$encounterId,$dentistId]);

    $pdo->prepare("INSERT INTO `{$prefix}treatment_plans` (patient_id,branch_id,encounter_id,dentist_user_id,title,phase,status,version_no,estimate_total,accepted_at,expires_at,notes,created_at,updated_at) VALUES (?,?,?,?,'Comprehensive restorative treatment','Disease Control','accepted',1,8500,NOW(),DATE_ADD(CURDATE(),INTERVAL 60 DAY),'Prioritize tooth 16 restoration, monitor tooth 26, then evaluate tooth 36 replacement.',DATE_SUB(NOW(),INTERVAL 13 DAY),NOW())")->execute([$patient1,$branchId,$encounterId,$dentistId]);
    $planId=(int)$pdo->lastInsertId();
    $item=$pdo->prepare("INSERT INTO `{$prefix}treatment_plan_items` (treatment_plan_id,diagnosis,diagnosis_id,odontogram_entry_id,tooth_region,tooth_number,surfaces,procedure_code,procedure_name,priority,sequence_no,sessions,duration_minutes,estimated_fee,status,clinical_rationale,prognosis,consent_status,notes,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())");
    $item->execute([$planId,'Dentinal caries',$diagnosisId?:null,$finding16,'16-O','16','O','REST-COMP','Direct composite restoration','important',1,1,60,3500,'scheduled','Remove caries and restore tooth form, function, and seal.','Good','accepted','Linked to confirmed clinical finding.']);$item16=(int)$pdo->lastInsertId();
    $item->execute([$planId,'Missing tooth',$missingDiagnosisId?:null,$finding36,'36','36','Whole','CONS-PROS','Replacement-options consultation','routine',2,1,45,5000,'accepted','Discuss implant, fixed bridge, and removable replacement options based on clinical and radiographic assessment.','Fair','accepted','Patient accepted consultation and options review.']);$item36=(int)$pdo->lastInsertId();
    $pdo->prepare("UPDATE `{$prefix}odontogram_entries` SET treatment_plan_item_id=? WHERE id=?")->execute([$item16,$finding16]);
    $pdo->prepare("UPDATE `{$prefix}odontogram_entries` SET treatment_plan_item_id=? WHERE id=?")->execute([$item36,$finding36]);
    $pdo->prepare("INSERT INTO `{$prefix}follow_ups` (patient_id,branch_id,service_id,encounter_id,dentist_user_id,purpose,recommended_date,preferred_time,duration_minutes,urgency,status,instructions,appointment_id,scheduled_at,scheduled_by,created_at) VALUES (?,?,?,?,?,'Restore tooth 16',DATE(?),'09:00:00',45,'important','scheduled','Return sooner for spontaneous pain or swelling.',?,NOW(),?,NOW())")->execute([$patient1,$branchId,$serviceIds['Tooth Restoration']??null,$encounterId,$dentistId,substr($futureStart,0,10),$futureAppt,$receptionId]);

    $pdo->prepare("INSERT INTO `{$prefix}patient_portal_accounts` (patient_id,email,password_hash,status,created_at) VALUES (?,'patient@demo.dentalcare.local',?,'active',NOW()) ON DUPLICATE KEY UPDATE password_hash=VALUES(password_hash),status='active'")->execute([$patient1,$passwordHash]);
    $pdo->prepare("INSERT INTO `{$prefix}invoices` (branch_id,invoice_no,patient_id,service_id,encounter_id,description,amount,paid_amount,status,created_by,created_at) VALUES (?,'DEMO-INV-0001',?,?,?,?,1200,1200,'paid',?,DATE_SUB(NOW(),INTERVAL 14 DAY))")->execute([$branchId,$patient1,$serviceIds['Dental Examination']??null,$encounterId,'Dental examination and diagnostic records',$cashierId]);
    $invoiceId=(int)$pdo->lastInsertId();
    $pdo->prepare("INSERT INTO `{$prefix}payments` (invoice_id,amount,method,reference_no,received_by,created_at) VALUES (?,1200,'cash','DEMO-OR-0001',?,DATE_SUB(NOW(),INTERVAL 14 DAY))")->execute([$invoiceId,$cashierId]);

    $setting=$pdo->prepare("INSERT INTO `{$prefix}settings` (group_name,setting_key,setting_value,is_secret,created_at) VALUES (?,?,?,?,NOW()) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value),updated_at=NOW()");
    foreach ([['system','installation_mode','demo',0],['system','demo_read_only','1',0],['system','demo_credentials','All demo staff accounts use DentalDemo2026!',1],['clinic','tagline','Read-only DentalCare product demonstration',0],['email_queue','automatic_enabled','0',0],['reminders','enabled','0',0]] as $row)$setting->execute($row);
}

$lockFile = __DIR__ . '/storage/installed.lock';
$installationState = dental_installation_state();

if (($installationState['recovery_required'] ?? false) && !isset($_GET['complete'])) {
    ?><!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="public/favicon.svg" type="image/svg+xml"><link rel="alternate icon" href="public/favicon.ico"><title>Installation recovery required</title><link rel="stylesheet" href="public/assets/css/install.css"></head><body><main class="shell"><div class="brand"><div class="mark">D</div><div><h1>Dental System Recovery</h1><p>Existing installation protection</p></div></div><section class="panel"><div class="alert error">An installation lock exists, but the saved database configuration could not be read.</div><h2>Do not reinstall the database</h2><p class="lead">Restore the existing <code>config/generated.php</code> file from your application backup. Reinstalling may create a second configuration or put existing records at risk.</p><div class="summary"><div>Expected configuration</div><div><code>config/generated.php</code></div><div>Existing installation lock</div><div><?= h((string) ($installationState['lock_path'] ?? 'Detected')) ?></div></div><p class="small" style="margin-top:18px">The system also checks common backup names such as <code>generated.php.bak</code>, <code>generated.bak.php</code>, and legacy separate database configuration files.</p><div class="actions"><a class="button secondary" href="install.php?recovery=1">Check again</a><a class="button primary" href="index.php">Retry application</a></div></section><?= installer_developer_footer() ?></main></body></html><?php
    exit;
}

if (($installationState['installed'] ?? false) && !isset($_GET['complete'])) {
    $config = dental_config();
    $repairMessage = ($installationState['repaired_lock'] ?? false)
        ? ' The missing installer lock was restored automatically.'
        : '';
    ?><!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="public/favicon.svg" type="image/svg+xml"><link rel="alternate icon" href="public/favicon.ico"><title>Installer locked</title><link rel="stylesheet" href="public/assets/css/install.css"></head><body><main class="shell"><div class="brand"><div class="mark">D</div><div><h1>Dental System Installer</h1><p>Installation protection</p></div></div><section class="panel"><div class="alert success">This system is already installed. The installer is locked to prevent accidental database changes.<?= h($repairMessage) ?></div><h2><?= h((string) ($config['app']['name'] ?? 'Dental Appointment and Records System')) ?></h2><p class="lead">The existing database configuration remains active. A missing lock file no longer causes the application to request installation again.</p><div class="actions"><span></span><a class="button primary" href="index.php">Open system</a></div></section><?= installer_developer_footer() ?></main></body></html><?php
    exit;
}

$state = installer_state();
$state['max_step'] = max(1, (int) ($state['max_step'] ?? 1));
$error = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        require_csrf();
        $action = (string) ($_POST['action'] ?? '');

        if ($action === 'continue_environment') {
            if (!environment_is_ready()) {
                throw new RuntimeException('Resolve the failed environment checks before continuing.');
            }
            $state['max_step'] = max($state['max_step'], 2);
            save_installer_state($state);
            redirect_step(2);
        }

        if ($action === 'inspect_database') {
            $db = [
                'host' => trim((string) ($_POST['db_host'] ?? 'localhost')),
                'port' => (int) ($_POST['db_port'] ?? 3306),
                'name' => trim((string) ($_POST['db_name'] ?? '')),
                'username' => trim((string) ($_POST['db_username'] ?? '')),
                'password' => ((string) ($_POST['db_password'] ?? '')) !== '' ? (string) $_POST['db_password'] : (string) ($state['database']['password'] ?? ''),
                'prefix' => validate_prefix((string) ($_POST['db_prefix'] ?? 'dent_')),
            ];
            if ($db['host'] === '' || $db['name'] === '' || $db['username'] === '') {
                throw new RuntimeException('Database host, name, and username are required.');
            }
            if ($db['port'] < 1 || $db['port'] > 65535) {
                throw new RuntimeException('Enter a valid database port.');
            }
            $inspection = inspect_database($db);
            $state['database'] = $db;
            $state['database_inspection'] = $inspection;
            unset($state['database_action']);
            save_installer_state($state);
            set_flash('success', 'Database connection successful. Review the detected tables and choose how to continue.');
            redirect_step(2);
        }

        if ($action === 'save_database_action') {
            if (empty($state['database']) || empty($state['database_inspection'])) {
                throw new RuntimeException('Test the database connection first.');
            }
            $detectedTables = (array)($state['database_inspection']['tables'] ?? []);
            $recognized = !empty($state['database_inspection']['recognized_installation']);
            $demoInstall = (($state['installer_mode'] ?? 'production') === 'demo');
            $dbAction = $demoInstall ? 'fresh_install' : (string) ($_POST['database_action'] ?? ($recognized ? 'adopt_existing' : 'fresh_install'));
            if (!in_array($dbAction, ['fresh_install','adopt_existing','wipe_prefix'], true)) {
                throw new RuntimeException('Select a valid database action.');
            }
            if ($demoInstall && $detectedTables) {
                throw new RuntimeException('A demo copy must use an unused table prefix. The demo installer cannot adopt, erase, reset, or overwrite any existing table.');
            }
            if ($dbAction === 'adopt_existing' && !$recognized) {
                throw new RuntimeException('The selected prefix does not contain a recognized DentalCare installation. Choose a clean prefix or wipe only after confirming a backup.');
            }
            if ($dbAction === 'fresh_install' && $detectedTables) {
                throw new RuntimeException('Fresh installation requires an unused table prefix. Choose another prefix or explicitly select the wipe option.');
            }
            $state['database_action'] = $dbAction;
            if ($demoInstall) {
                $state['clinic'] = [
                    'clinic_name' => 'Syllabuild Dental Demo Clinic',
                    'branch_code' => 'DEMO',
                    'clinic_phone' => '+63 917 000 0000',
                    'clinic_address' => 'Legazpi City, Albay, Philippines',
                    'base_url' => detect_base_url(),
                    'timezone' => 'Asia/Manila',
                    'admin_name' => 'Demo Administrator',
                    'admin_email' => 'admin@demo.dentalcare.local',
                    'admin_password_hash' => password_hash('DentalDemo2026!', PASSWORD_DEFAULT),
                ];
                $state['mail'] = [
                    'driver' => 'log',
                    'from_email' => 'noreply@demo.dentalcare.local',
                    'from_name' => 'Syllabuild Dental Demo Clinic',
                    'smtp' => ['host'=>'','port'=>587,'encryption'=>'tls','username'=>'','password'=>'','timeout'=>15,'verify_peer'=>true],
                    'test_recipient' => 'admin@demo.dentalcare.local',
                ];
                $state['max_step'] = 5;
                save_installer_state($state);
                redirect_step(5);
            }
            if ($dbAction === 'adopt_existing') {
                $state['clinic'] = existing_installation_profile($state['database']);
            } else {
                unset($state['clinic'],$state['mail']);
            }
            $state['max_step'] = max($state['max_step'], 3);
            save_installer_state($state);
            redirect_step(3);
        }

        if ($action === 'save_clinic') {
            if (($state['installer_mode'] ?? 'production') === 'demo') throw new RuntimeException('Demo clinic information is fixed and cannot be changed.');
            $clinicName = trim((string) ($_POST['clinic_name'] ?? ''));
            $branchCode = strtoupper(trim((string) ($_POST['branch_code'] ?? 'MAIN')));
            $baseUrl = rtrim(trim((string) ($_POST['base_url'] ?? detect_base_url())), '/');
            $timezone = trim((string) ($_POST['timezone'] ?? 'Asia/Manila'));
            $adminName = trim((string) ($_POST['admin_name'] ?? ''));
            $adminEmail = trim((string) ($_POST['admin_email'] ?? ''));
            $adminPassword = (string) ($_POST['admin_password'] ?? '');
            $confirmPassword = (string) ($_POST['admin_password_confirmation'] ?? '');

            $adoptingExisting = ($state['database_action'] ?? '') === 'adopt_existing';
            if ($clinicName === '' || $adminName === '') {
                throw new RuntimeException('Clinic name and administrator name are required.');
            }
            if (!preg_match('/^[A-Z0-9_-]{2,20}$/', $branchCode)) {
                throw new RuntimeException('Branch code may contain uppercase letters, numbers, underscores, and hyphens.');
            }
            if (!filter_var($baseUrl, FILTER_VALIDATE_URL)) {
                throw new RuntimeException('Enter a valid application URL including http:// or https://.');
            }
            if (!in_array($timezone, timezone_identifiers_list(), true)) {
                throw new RuntimeException('Select a valid timezone.');
            }
            if (!filter_var($adminEmail, FILTER_VALIDATE_EMAIL)) {
                throw new RuntimeException('Enter a valid administrator email address.');
            }
            if (!$adoptingExisting) {
                if (strlen($adminPassword) < 12 || !preg_match('/[A-Z]/', $adminPassword) || !preg_match('/[a-z]/', $adminPassword) || !preg_match('/\d/', $adminPassword)) {
                    throw new RuntimeException('Administrator password must be at least 12 characters and contain uppercase, lowercase, and a number.');
                }
                if (!hash_equals($adminPassword, $confirmPassword)) {
                    throw new RuntimeException('Administrator passwords do not match.');
                }
            }

            $state['clinic'] = [
                'clinic_name' => $clinicName,
                'branch_code' => $branchCode,
                'clinic_phone' => trim((string) ($_POST['clinic_phone'] ?? '')),
                'clinic_address' => trim((string) ($_POST['clinic_address'] ?? '')),
                'base_url' => $baseUrl,
                'timezone' => $timezone,
                'admin_name' => $adminName,
                'admin_email' => $adminEmail,
                'admin_password_hash' => $adoptingExisting ? '' : password_hash($adminPassword, PASSWORD_DEFAULT),
            ];
            $state['max_step'] = max($state['max_step'], 4);
            save_installer_state($state);
            redirect_step(4);
        }

        if ($action === 'test_email') {
            if (($state['installer_mode'] ?? 'production') === 'demo') throw new RuntimeException('Email delivery is disabled in the read-only demo copy.');
            if (empty($state['clinic'])) {
                throw new RuntimeException('Complete clinic setup before testing email.');
            }
            $mail = mail_config_from_post($state);
            $recipient = $mail['test_recipient'];
            if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
                throw new RuntimeException('Enter a valid test recipient.');
            }
            $temporaryConfig = [
                'mail' => [
                    'driver' => $mail['driver'],
                    'from_email' => $mail['from_email'],
                    'from_name' => $mail['from_name'],
                    'smtp' => $mail['smtp'],
                ],
            ];
            $html = render_template('test', [
                'clinicName' => $state['clinic']['clinic_name'],
                'baseUrl' => $state['clinic']['base_url'],
            ]);
            (new EmailService($temporaryConfig))->send($recipient, 'Dental system email test', $html);
            $mail['test_succeeded_at'] = date(DATE_ATOM);
            $state['mail'] = $mail;
            save_installer_state($state);
            if ($mail['driver'] === 'log') {
                set_flash('warning', 'The test message was written to the development email log only. It was not delivered to ' . $recipient . '.');
            } else {
                set_flash('success', 'The configured mail transport accepted the test message for ' . $recipient . '. Inbox placement is not guaranteed.');
            }
            redirect_step(4);
        }

        if ($action === 'save_email') {
            if (($state['installer_mode'] ?? 'production') === 'demo') throw new RuntimeException('Email delivery is disabled in the read-only demo copy.');
            $mail = mail_config_from_post($state);
            $mail['test_succeeded_at'] = $state['mail']['test_succeeded_at'] ?? null;
            $state['mail'] = $mail;
            $state['max_step'] = max($state['max_step'], 5);
            save_installer_state($state);
            redirect_step(5);
        }

        if ($action === 'run_install') {
            foreach (['database', 'database_action', 'clinic', 'mail'] as $required) {
                if (empty($state[$required])) {
                    throw new RuntimeException('Installation data is incomplete. Return to the previous steps.');
                }
            }

            date_default_timezone_set($state['clinic']['timezone']);
            $pdo = dental_pdo($state['database']);
            $dropped = 0;
            $demoInstall = (($state['installer_mode'] ?? 'production') === 'demo');
            $adoptingExisting = $state['database_action'] === 'adopt_existing';
            if ($demoInstall) {
                $inspection = inspect_database($state['database']);
                if (!empty($inspection['tables'])) {
                    throw new RuntimeException('Demo installation stopped because the selected prefix is no longer empty. No table was erased or changed.');
                }
                if ($state['database_action'] !== 'fresh_install') {
                    throw new RuntimeException('Demo installation supports only a new isolated prefix.');
                }
            } elseif ($state['database_action'] === 'wipe_prefix') {
                $dropped = wipe_prefixed_tables($pdo, $state['database']['name'], $state['database']['prefix']);
            }
            if ($adoptingExisting) {
                $inspection = inspect_database($state['database']);
                if (empty($inspection['recognized_installation'])) {
                    throw new RuntimeException('The existing DentalCare tables could not be verified. No configuration was written.');
                }
            } else {
                execute_schema($pdo, $state['database']['prefix']);
                execute_pos_schema($pdo, $state['database']['prefix']);
                execute_v05982_schema($pdo, $state['database']['prefix']);
                $branchId = seed_installation($pdo, $state);
                if ($demoInstall) seed_demo_installation($pdo, $state, $branchId);
            }
            $config = write_generated_config($state);

            $lockContent = json_encode([
                'installed_at' => date(DATE_ATOM),
                'version' => INSTALLER_VERSION,
                'database_action' => $state['database_action'],
                'installation_mode' => (string) ($state['installer_mode'] ?? 'production'),
                'demo_read_only' => $demoInstall,
                'distribution_package_id' => (string) (dental_distribution_profile()['package_id'] ?? ''),
                'tables_removed' => $dropped,
            ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
            if (file_put_contents($lockFile, $lockContent, LOCK_EX) === false) {
                throw new RuntimeException('Database installed, but the installer lock could not be written.');
            }
            @chmod($lockFile, 0640);

            $welcomeStatus = $demoInstall ? 'disabled in read-only demo' : ($adoptingExisting ? 'not sent during existing-database adoption' : 'not attempted');
            if (!$adoptingExisting && !$demoInstall) {
                try {
                    $html = render_template('admin_welcome', [
                        'clinicName' => $state['clinic']['clinic_name'],
                        'adminName' => $state['clinic']['admin_name'],
                        'baseUrl' => $state['clinic']['base_url'],
                    ]);
                    (new EmailService($config, $pdo))->send($state['clinic']['admin_email'], 'Your dental system is ready', $html);
                    $welcomeStatus = (($state['mail']['driver'] ?? 'smtp') === 'log') ? 'logged only; not delivered' : 'accepted by configured mail transport';
                } catch (Throwable $mailError) {
                    $welcomeStatus = 'failed: ' . $mailError->getMessage();
                }
            }

            $_SESSION['installer_complete'] = [
                'clinic' => $state['clinic']['clinic_name'],
                'admin_email' => $state['clinic']['admin_email'],
                'welcome_status' => $welcomeStatus,
                'mode' => $state['database_action'],
                'installer_mode' => (string) ($state['installer_mode'] ?? 'production'),
            ];
            unset($_SESSION[INSTALLER_SESSION]);
            header('Location: install.php?complete=1');
            exit;
        }

        throw new RuntimeException('Unknown installer action.');
    } catch (Throwable $e) {
        $error = $e->getMessage();
        save_installer_state($state);
    }
}

if (isset($_GET['complete'])) {
    $done = $_SESSION['installer_complete'] ?? null;
    if (!$done) {
        header('Location: index.php');
        exit;
    }
    unset($_SESSION['installer_complete']);
    ?><!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="public/favicon.svg" type="image/svg+xml"><link rel="alternate icon" href="public/favicon.ico"><title>Installation complete</title><link rel="stylesheet" href="public/assets/css/install.css"></head><body><main class="shell"><div class="brand"><div class="mark">D</div><div><h1>Dental System Installer</h1><p>Installation complete</p></div></div><section class="panel"><div class="alert success"><?php if(($done['installer_mode']??'production')==='demo'):?>The isolated DentalCare demonstration was created in permanent read-only mode. Existing tables were not adopted, erased, reset, or overwritten.<?php else:?><?= ($done['mode'] ?? "") === "adopt_existing" ? "The existing DentalCare database was connected without replacing clinic records or administrator credentials." : "The licensed database, administrator, and email service have been configured successfully." ?><?php endif;?></div><h2><?= h((string) $done['clinic']) ?></h2><div class="summary"><div>Administrator</div><div><?= h((string) $done['admin_email']) ?></div><div>Welcome email</div><div><?= h((string) $done['welcome_status']) ?></div><?php if(($done['installer_mode']??'production')==='demo'):?><div>Dentist login</div><div><code>dentist@demo.dentalcare.local</code></div><div>Demo password</div><div><code>DentalDemo2026!</code></div><div>Protection</div><div>All record mutations and background delivery processing are disabled</div><?php endif;?><div>Installer protection</div><div>Locked by <code>storage/installed.lock</code></div></div><p class="small" style="margin-top:18px">For additional protection, rename or remove <code>install.php</code> after confirming the application opens correctly.</p><div class="actions"><span></span><a class="button primary" href="index.php">Open system</a></div></section><?= installer_developer_footer() ?></main></body></html><?php
    exit;
}

$state = installer_state();
$maxStep = max(1, (int) ($state['max_step'] ?? 1));
$requestedStep = max(1, min(5, (int) ($_GET['step'] ?? 1)));
$step = min($requestedStep, $maxStep);
$flash = pull_flash();
$defaultMail = $state['mail'] ?? [
    'driver' => $state['clinic']['existing_mail_driver'] ?? 'smtp',
    'from_email' => $state['clinic']['existing_from_email'] ?? ($state['clinic']['admin_email'] ?? ''),
    'from_name' => $state['clinic']['existing_from_name'] ?? ($state['clinic']['clinic_name'] ?? 'Dental Clinic'),
    'smtp' => ['host' => '', 'port' => 587, 'encryption' => 'tls', 'username' => '', 'password' => '', 'verify_peer' => true],
    'test_recipient' => $state['clinic']['admin_email'] ?? '',
];
$steps = [1 => 'Environment', 2 => 'Database', 3 => 'Clinic & Admin', 4 => 'Emailer', 5 => 'Install'];
?><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="public/favicon.svg" type="image/svg+xml">
<link rel="alternate icon" href="public/favicon.ico">
<title>Dental System Installer</title>
<link rel="stylesheet" href="public/assets/css/install.css">
<script src="public/assets/js/install.js" defer></script>
</head>
<body>
<main class="shell">
    <div class="brand"><div class="mark">D</div><div><h1>Dental Appointment and Records System</h1><p><?=($state['installer_mode']??'production')==='demo'?'Read-only demo bootstrap':'Licensed production bootstrap'?> · Version <?= h(INSTALLER_VERSION) ?></p></div></div>
    <div class="layout">
        <aside class="steps">
            <?php foreach ($steps as $number => $label): ?>
                <div class="step <?= $number === $step ? 'active' : ($number < $step ? 'done' : '') ?>"><b><?= $number < $step ? '✓' : $number ?></b><span><?= h($label) ?></span></div>
            <?php endforeach; ?>
        </aside>
        <section class="panel">
            <?php if ($error): ?><div class="alert error"><?= h($error) ?></div><?php endif; ?>
            <?php if ($flash): ?><div class="alert <?= h($flash['type']) ?>"><?= h($flash['message']) ?></div><?php endif; ?>

            <?php if ($step === 1): ?>
                <h2>Server environment</h2><p class="lead">Confirm that the hosting environment can securely run the application, database, and email service.</p>
                <div class="checks">
                    <?php foreach (environment_checks() as $check): ?>
                        <div class="check"><span><?= h($check['label']) ?></span><span class="<?= $check['ok'] ? 'ok' : 'bad' ?>"><?= h($check['value']) ?></span></div>
                    <?php endforeach; ?>
                </div>
                <form method="post"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="continue_environment"><div class="actions"><span></span><button class="primary" <?= environment_is_ready() ? '' : 'disabled' ?>>Continue</button></div></form>

            <?php elseif ($step === 2): ?>
                <h2>Database connection</h2><p class="lead"><?=($state['installer_mode']??'production')==='demo'?'Choose an unused prefix for an isolated demonstration copy. Existing tables are never adopted, erased, reset, or overwritten.':'Connect to a MySQL or MariaDB database. Only tables using the chosen prefix are managed by this installer.'?></p>
                <form method="post">
                    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="inspect_database">
                    <div class="grid">
                        <div class="field"><label>Database host</label><input name="db_host" value="<?= h((string) ($state['database']['host'] ?? 'localhost')) ?>" required></div>
                        <div class="field"><label>Port</label><input type="number" name="db_port" value="<?= h((string) ($state['database']['port'] ?? 3306)) ?>" required></div>
                        <div class="field"><label>Database name</label><input name="db_name" value="<?= h((string) ($state['database']['name'] ?? '')) ?>" required></div>
                        <div class="field"><label>Table prefix</label><input name="db_prefix" value="<?= h((string) ($state['database']['prefix'] ?? (($state['installer_mode']??'production')==='demo'?'demo_':'dent_'))) ?>" required><div class="hint">Protects unrelated tables in a shared database.</div></div>
                        <div class="field"><label>Database username</label><input name="db_username" autocomplete="username" value="<?= h((string) ($state['database']['username'] ?? '')) ?>" required></div>
                        <div class="field"><label>Database password</label><input type="password" name="db_password" autocomplete="current-password" value=""></div>
                    </div>
                    <div class="actions"><a class="button secondary" href="install.php?step=1">Back</a><button class="primary">Test and inspect</button></div>
                </form>
                <?php if (!empty($state['database_inspection'])): $count = count($state['database_inspection']['tables']);$recognized=!empty($state['database_inspection']['recognized_installation']); ?>
                    <div class="alert info" style="margin-top:22px">Connected to database server <?= h((string) $state['database_inspection']['version']) ?>. Found <?= $count ?> table(s) using prefix <code><?= h($state['database']['prefix']) ?></code>. <?= $recognized?'A compatible DentalCare installation was recognized.':'No complete DentalCare installation was recognized for this prefix.' ?></div>
                    <form method="post"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="save_database_action">
                        <div class="checks">
                            <?php if(($state['installer_mode']??'production')==='demo'):?>
                                <?php if($count===0):?><label class="choice"><input type="radio" name="database_action" value="fresh_install" checked><span><strong>Create isolated read-only demo</strong><br><span class="small">Creates sample clinic, patient, appointment, dental-chart, treatment-plan, billing, and portal records under this unused prefix.</span></span></label><?php else:?><div class="alert error">This prefix is not empty. Select another prefix. The demo bootstrap has no adopt, wipe, reset, or overwrite action.</div><?php endif;?>
                            <?php else:?>
                                <?php if($count===0):?><label class="choice"><input type="radio" name="database_action" value="fresh_install" checked><span><strong>Create a fresh installation</strong><br><span class="small">Create all DentalCare tables, the first branch, and the Superuser account under this unused prefix.</span></span></label><?php endif;?>
                                <?php if($recognized):?><label class="choice"><input type="radio" name="database_action" value="adopt_existing" checked><span><strong>Connect this existing DentalCare database</strong><br><span class="small">Preserve all records and existing account passwords. The bootstrap installer writes only runtime configuration and its installation lock; database updates remain available in System Setup.</span></span></label><?php endif;?>
                                <?php if($count>0):?><label class="choice"><input type="radio" name="database_action" value="wipe_prefix"><span><strong>Wipe tables using this prefix and install clean</strong><br><span class="small">Permanently delete all <?= $count ?> table(s) beginning with <code><?= h($state['database']['prefix']) ?></code>. Unrelated prefixes remain untouched.</span></span></label><?php endif;?>
                                <?php if($count>0&&!$recognized):?><div class="alert warning">These prefixed tables do not form a recognized DentalCare installation. Use another empty prefix unless you have verified a backup and intentionally want to wipe them.</div><?php endif;?>
                            <?php endif;?>
                        </div>
                        <div class="actions"><span></span><button class="primary" data-confirm="Continue with the selected database action?">Continue</button></div>
                    </form>
                <?php endif; ?>

            <?php elseif ($step === 3): ?>
                <?php $adoptingExisting=($state['database_action']??'')==='adopt_existing';?><h2><?=$adoptingExisting?'Existing clinic connection':'Clinic and administrator'?></h2><p class="lead"><?=$adoptingExisting?'Confirm the detected clinic identity and set this deployment URL. Existing users, passwords, and clinic records will not be rewritten.':'Create the first clinic branch and the system-owner account.'?></p>
                <form method="post"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="save_clinic">
                    <div class="grid">
                        <div class="field"><label>Clinic name</label><input name="clinic_name" value="<?= h((string) ($state['clinic']['clinic_name'] ?? '')) ?>" <?=$adoptingExisting?'readonly':''?> required></div>
                        <div class="field"><label>Branch code</label><input name="branch_code" value="<?= h((string) ($state['clinic']['branch_code'] ?? 'MAIN')) ?>" <?=$adoptingExisting?'readonly':''?> required></div>
                        <div class="field"><label>Clinic phone</label><input name="clinic_phone" value="<?= h((string) ($state['clinic']['clinic_phone'] ?? '')) ?>" <?=$adoptingExisting?'readonly':''?>></div>
                        <div class="field"><label>Timezone</label><select name="timezone"><option value="Asia/Manila" selected>Asia/Manila</option><option value="UTC">UTC</option></select></div>
                        <div class="field full"><label>Clinic address</label><textarea name="clinic_address" rows="2" <?=$adoptingExisting?'readonly':''?>><?= h((string) ($state['clinic']['clinic_address'] ?? '')) ?></textarea></div>
                        <div class="field full"><label>Application URL</label><input type="url" name="base_url" value="<?= h((string) ($state['clinic']['base_url'] ?? detect_base_url())) ?>" required></div>
                        <div class="field"><label>Administrator name</label><input name="admin_name" value="<?= h((string) ($state['clinic']['admin_name'] ?? '')) ?>" <?=$adoptingExisting?'readonly':''?> required></div>
                        <div class="field"><label>Administrator email</label><input type="email" name="admin_email" value="<?= h((string) ($state['clinic']['admin_email'] ?? '')) ?>" <?=$adoptingExisting?'readonly':''?> required></div>
                        <?php if(!$adoptingExisting):?><div class="field"><label>Administrator password</label><input type="password" name="admin_password" autocomplete="new-password" required><div class="hint">At least 12 characters with uppercase, lowercase, and a number.</div></div><div class="field"><label>Confirm password</label><input type="password" name="admin_password_confirmation" autocomplete="new-password" required></div><?php else:?><div class="field full"><div class="alert success">Existing administrator credentials will be preserved. The installer does not request or reset the password.</div></div><?php endif;?>
                    </div>
                    <div class="actions"><a class="button secondary" href="install.php?step=2">Back</a><button class="primary">Continue</button></div>
                </form>

            <?php elseif ($step === 4): ?>
                <h2>Email service</h2><p class="lead">Configure SMTP for account invitations, appointment reminders, confirmations, treatment notices, password recovery, and billing messages.</p>
                <form method="post">
                    <input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>">
                    <div class="grid">
                        <div class="field"><label>Email driver</label><select name="mail_driver"><option value="smtp" <?= $defaultMail['driver'] === 'smtp' ? 'selected' : '' ?>>SMTP — recommended</option><option value="mail" <?= $defaultMail['driver'] === 'mail' ? 'selected' : '' ?>>PHP mail()</option><option value="log" <?= $defaultMail['driver'] === 'log' ? 'selected' : '' ?>>Log only — development</option></select></div>
                        <div class="field"><label>Test recipient</label><input type="email" name="test_recipient" value="<?= h((string) ($defaultMail['test_recipient'] ?? '')) ?>"></div>
                        <div class="field"><label>Sender email</label><input type="email" name="from_email" value="<?= h((string) $defaultMail['from_email']) ?>" required></div>
                        <div class="field"><label>Sender name</label><input name="from_name" value="<?= h((string) $defaultMail['from_name']) ?>" required></div>
                    </div>
                    <div class="grid" data-smtp-fields style="margin-top:16px">
                        <div class="field"><label>SMTP host</label><input name="smtp_host" value="<?= h((string) $defaultMail['smtp']['host']) ?>" placeholder="smtp.example.com"></div>
                        <div class="field"><label>SMTP port</label><input type="number" name="smtp_port" value="<?= h((string) $defaultMail['smtp']['port']) ?>"></div>
                        <div class="field"><label>Encryption</label><select name="smtp_encryption"><option value="tls" <?= $defaultMail['smtp']['encryption'] === 'tls' ? 'selected' : '' ?>>STARTTLS</option><option value="ssl" <?= $defaultMail['smtp']['encryption'] === 'ssl' ? 'selected' : '' ?>>SSL/TLS</option><option value="none" <?= $defaultMail['smtp']['encryption'] === 'none' ? 'selected' : '' ?>>None</option></select></div>
                        <div class="field"><label>SMTP username</label><input name="smtp_username" autocomplete="username" value="<?= h((string) $defaultMail['smtp']['username']) ?>"></div>
                        <div class="field"><label>SMTP password</label><input type="password" name="smtp_password" autocomplete="new-password" value=""></div>
                        <div class="field"><label>&nbsp;</label><label class="choice"><input type="checkbox" name="smtp_verify_peer" value="1" <?= !empty($defaultMail['smtp']['verify_peer']) ? 'checked' : '' ?>><span><strong>Verify TLS certificate</strong><br><span class="small">Keep enabled in production.</span></span></label></div>
                    </div>
                    <?php if (!empty($defaultMail['test_succeeded_at'])): ?><div class="alert success" style="margin-top:18px">Email test succeeded on <?= h((string) $defaultMail['test_succeeded_at']) ?>.</div><?php endif; ?>
                    <div class="actions"><a class="button secondary" href="install.php?step=3">Back</a><div style="display:flex;gap:10px"><button class="secondary" name="action" value="test_email">Send test email</button><button class="primary" name="action" value="save_email">Save and continue</button></div></div>
                </form>

            <?php elseif ($step === 5): ?>
                <h2>Review and install</h2><p class="lead"><?php if(($state['installer_mode']??'production')==='demo'):?>The bootstrap will create a separate sample clinic under the unused prefix and permanently enable application-level read-only protection. It will not erase, reset, adopt, email, or process data.<?php else:?><?=($state['database_action']??'')==='adopt_existing'?'The installer will connect this deployment to the verified existing database, save runtime and email configuration, and lock itself without replacing records or passwords.':'The installer will create the complete schema, seed roles, create the clinic administrator, save email configuration, and lock itself.'?><?php endif;?></p>
                <div class="summary">
                    <div>Installation type</div><div><?=($state['installer_mode']??'production')==='demo'?'Read-only demonstration':'Licensed production'?></div>
                    <div>Clinic</div><div><?= h($state['clinic']['clinic_name']) ?></div>
                    <div>Application URL</div><div><?= h($state['clinic']['base_url']) ?></div>
                    <div>Database</div><div><?= h($state['database']['name']) ?> · prefix <code><?= h($state['database']['prefix']) ?></code></div>
                    <div>Database action</div><div><?= $state['database_action'] === 'wipe_prefix' ? 'Wipe prefixed tables and install clean' : ($state['database_action']==='adopt_existing'?'Connect verified existing installation without changing records':'Create fresh installation') ?></div>
                    <div>Administrator</div><div><?= h($state['clinic']['admin_name']) ?> · <?= h($state['clinic']['admin_email']) ?></div>
                    <div>Emailer</div><div><?= h(strtoupper($state['mail']['driver'])) ?> · <?= h($state['mail']['from_email']) ?></div>
                    <div>Email test</div><div><?=($state['installer_mode']??'production')==='demo'?'Disabled; no delivery allowed':(!empty($state['mail']['test_succeeded_at']) ? 'Passed' : 'Not completed; installation may continue')?></div>
                    <?php if(($state['installer_mode']??'production')==='demo'):?><div>Demo sign-in</div><div><code>dentist@demo.dentalcare.local</code> or <code>admin@demo.dentalcare.local</code><br>Password: <code>DentalDemo2026!</code></div><div>Data protection</div><div>No add, edit, delete, import, reset, email, queue processing, or workflow mutation</div><?php endif;?>
                </div>
                <?php if ($state['database_action'] === 'wipe_prefix'): ?><div class="alert error" style="margin-top:18px">The selected action will permanently delete all tables beginning with <code><?= h($state['database']['prefix']) ?></code>.</div><?php endif; ?>
                <form method="post"><input type="hidden" name="_csrf" value="<?= h(csrf_token()) ?>"><input type="hidden" name="action" value="run_install"><div class="actions"><a class="button secondary" href="install.php?step=<?=($state['installer_mode']??'production')==='demo'?'2':'4'?>">Back</a><button class="primary" data-confirm="Proceed with this protected installation action?">Complete installation</button></div></form>
            <?php endif; ?>
        </section>
    </div>
<?= installer_developer_footer() ?>
</main>
</body></html>
