<?php
/**
 * Projet                    : MageStock
 * Logiciel                  : MageStock
 * Fichier                   : install.php
 * Rôle                      : Installateur guidé de premier lancement, précontrôle serveur et création atomique de l’instance.
 * Auteur                    : Régis KREMER
 * Version du fichier        : 2.6.124
 * Version générale          : 2.6.124
 * Lot                       : MAGESTOCK UPDATE CENTER INSTALLER PROXY RESPONSIVE RELEASE FINAL1
 * Base requise              : MageStock 2.6.123
 * Création                  : 2026-07-20
 * Dernière modification     : 2026-07-30
 * Dépendances               : config.php, stockage JSON MageStock, Sodium, Phar, Fileinfo
 * Données touchées          : users.json, settings.json, warehouses.json, locations.json, état UPDATE
 * Permissions               : premier lancement uniquement
 * Sécurité                  : CSRF, signature de release, précontrôle, verrou exclusif, écritures atomiques, aucun état intermédiaire par étape
 * Compatibilité             : PHP 8.1 à 8.4, Apache/Nginx/reverse proxy, ordinateur/smartphone/tablette
 *
 * Résumé :
 * - remplace le formulaire monolithique par un assistant responsive en six étapes ;
 * - contrôle PHP, extensions, écriture des dossiers et signature de la distribution avant installation ;
 * - expose un diagnostic de protocole/base-path sans dépendre d’un nom de dossier fixe ;
 * - ne stocke aucune progression d’étape en session, évitant les boucles de reprise derrière proxy ;
 * - crée le premier administrateur local et la structure initiale en une transaction verrouillée.
 *
 * Historique :
 * - 2.6.99 — création de l’assistant de premier lancement.
 * - 2.6.102 — compte administrateur local de secours explicite.
 * - 2.6.119 — politique commune de mot de passe et version de session.
 * - 2.6.124 — installateur multi-étapes, préflight complet et compatibilité reverse proxy.
 */
require_once __DIR__ . '/config.php';
startSession();

final class MageStockInstallFormException extends RuntimeException {
    public int $step;
    public function __construct(string $message, int $step = 5) {
        parent::__construct($message);
        $this->step = max(0, min(5, $step));
    }
}

function installTextLength(string $value): int {
    return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
}

function installRedirect(string $path): never {
    header('Location: ' . appUrl($path));
    exit;
}

function installAtomicJson(string $path, array $data): void {
    $dir = dirname($path);
    if(!is_dir($dir) && !@mkdir($dir, 0775, true)) {
        throw new RuntimeException('Impossible de créer le dossier ' . basename($dir) . '.');
    }
    $tmp = $path . '.tmp-' . bin2hex(random_bytes(5));
    $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if($json === false || file_put_contents($tmp, $json, LOCK_EX) === false) {
        @unlink($tmp);
        throw new RuntimeException('Impossible d’écrire ' . basename($path) . '.');
    }
    @chmod($tmp, 0664);
    if(!@rename($tmp, $path)) {
        @unlink($tmp);
        throw new RuntimeException('Impossible de finaliser ' . basename($path) . '.');
    }
}

function installStoreLogo(array $file): string {
    if(($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) return '';
    if(($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) throw new MageStockInstallFormException('Le logo n’a pas pu être envoyé.', 1);
    if((int)($file['size'] ?? 0) > 2 * 1024 * 1024) throw new MageStockInstallFormException('Le logo dépasse la limite de 2 Mo.', 1);
    $tmp = (string)($file['tmp_name'] ?? '');
    if($tmp === '' || !is_uploaded_file($tmp)) throw new MageStockInstallFormException('Le fichier logo reçu est invalide.', 1);
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = $finfo ? (string)finfo_file($finfo, $tmp) : '';
    if($finfo) finfo_close($finfo);
    $allowed = ['image/jpeg'=>'jpg', 'image/png'=>'png', 'image/webp'=>'webp', 'image/gif'=>'gif'];
    if(!isset($allowed[$mime])) throw new MageStockInstallFormException('Format logo non accepté : JPG, PNG, WEBP ou GIF uniquement.', 1);
    $dir = UPLOADS_DIR . '/company';
    if(!is_dir($dir) && !@mkdir($dir, 0775, true)) throw new RuntimeException('Impossible de créer le dossier du logo.');
    $name = 'company-logo-' . date('Ymd-His') . '-' . bin2hex(random_bytes(4)) . '.' . $allowed[$mime];
    if(!move_uploaded_file($tmp, $dir . '/' . $name)) throw new RuntimeException('Impossible d’enregistrer le logo.');
    @chmod($dir . '/' . $name, 0644);
    return 'uploads/company/' . $name;
}

function installWritableProbe(string $dir): bool {
    if(!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) return false;
    if(!is_writable($dir)) return false;
    $probe = rtrim($dir, '/\\') . '/.install-probe-' . bin2hex(random_bytes(4));
    $ok = @file_put_contents($probe, 'MageStock', LOCK_EX) !== false;
    if($ok) @unlink($probe);
    return $ok;
}

function installReleaseSignatureStatus(): array {
    $manifestPath = APP_ROOT . '/release-manifest.json';
    $signaturePath = APP_ROOT . '/release-signature.txt';
    if(!is_file($manifestPath) || !is_file($signaturePath)) return [false, 'Manifeste ou signature absent'];
    $manifestRaw = @file_get_contents($manifestPath);
    $signatureRaw = @file_get_contents($signaturePath);
    $key = magestockUpdatePublicKey();
    $public = base64_decode((string)($key['publicKey'] ?? ''), true);
    $signature = is_string($signatureRaw) ? base64_decode(trim($signatureRaw), true) : false;
    if(!is_string($manifestRaw) || !is_string($public) || strlen($public) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES || !is_string($signature) || strlen($signature) !== SODIUM_CRYPTO_SIGN_BYTES) {
        return [false, 'Matériel de signature invalide'];
    }
    if(!sodium_crypto_sign_verify_detached($signature, $manifestRaw, $public)) return [false, 'Signature Ed25519 invalide'];
    $manifest = json_decode($manifestRaw, true);
    if(!is_array($manifest) || ($manifest['application'] ?? '') !== 'MageStock' || ($manifest['version'] ?? '') !== APP_VER) return [false, 'Manifeste non aligné avec VERSION'];
    return [true, (string)($manifest['keyId'] ?? 'Ed25519')];
}

function installPreflight(): array {
    $checks = [];
    $add = static function(string $id, string $label, bool $ok, string $value, string $detail, bool $critical = true) use (&$checks): void {
        $checks[] = compact('id','label','ok','value','detail','critical');
    };
    $add('php', 'Version PHP', version_compare(PHP_VERSION, '8.1.0', '>='), PHP_VERSION, 'PHP 8.1 ou supérieur est requis.');
    foreach(['json','openssl','sodium','phar','fileinfo'] as $ext) {
        $loaded = extension_loaded($ext);
        $add('ext-' . $ext, 'Extension ' . $ext, $loaded, $loaded ? 'Disponible' : 'Absente', 'Extension requise au fonctionnement sécurisé de MageStock.');
    }
    $dirs = ['data'=>DATA_DIR, 'uploads'=>UPLOADS_DIR, 'backups'=>BACKUPS_DIR];
    foreach($dirs as $name=>$path) $add('dir-'.$name, 'Écriture ' . $name . '/', installWritableProbe($path), is_writable($path) ? 'Accessible' : 'Bloquée', 'Le serveur PHP doit pouvoir créer, modifier et remplacer des fichiers dans ce dossier.');
    [$signed,$signatureValue] = installReleaseSignatureStatus();
    $add('signature', 'Authenticité de la distribution', $signed, $signed ? 'Signée' : 'Échec', $signed ? 'Racine de confiance ' . $signatureValue . '.' : $signatureValue . '.');

    $proxy = appProxyContext();
    $secure = (bool)$proxy['secure'];
    $add('https', 'Connexion publique', $secure, strtoupper((string)$proxy['scheme']), $secure ? 'Le protocole public est détecté comme HTTPS.' : 'HTTPS est fortement recommandé pour caméra, PWA et notifications.', false);
    $add('base-path', 'Chemin applicatif', true, appBasePath() === '' ? '/' : appBasePath() . '/', 'Détection dynamique : aucun nom de dossier MageStock n’est imposé.', false);
    $add('proxy', 'Reverse proxy', true, $proxy['proxyDetected'] ? 'Détecté' : 'Non détecté', $proxy['proxyDetected'] ? 'Les en-têtes de protocole transmis sont pris en compte.' : 'Accès direct ou proxy sans en-tête détecté.', false);

    $ready = true;
    foreach($checks as $check) if($check['critical'] && !$check['ok']) $ready = false;
    return ['ready'=>$ready,'checks'=>$checks,'proxy'=>$proxy];
}

$summary = null;
if(isset($_GET['complete']) && isset($_SESSION['magestock_install_summary']) && is_array($_SESSION['magestock_install_summary'])) {
    $summary = $_SESSION['magestock_install_summary'];
    unset($_SESSION['magestock_install_summary']);
}
if(!$summary && !appInstallationRequired()) installRedirect(isLoggedIn() ? 'dashboard' : 'login');

$error = '';
$initialStep = 0;
$values = [
    'companyName'=>'', 'language'=>'fr', 'locale'=>'auto', 'country'=>'FR', 'currency'=>'EUR',
    'timezone'=>'Europe/Paris', 'warehouseName'=>'', 'warehouseAddress'=>'',
    'locationCode'=>'', 'locationLabel'=>'', 'locationZone'=>'', 'enablePush'=>'0'
];
$preflight = installPreflight();

if($_SERVER['REQUEST_METHOD'] === 'POST' && !$summary) {
    foreach(array_keys($values) as $key) if(isset($_POST[$key])) $values[$key] = trim((string)$_POST[$key]);
    $initialStep = 5;
    try {
        $token = (string)($_POST['csrf_token'] ?? '');
        if(!hash_equals((string)($_SESSION['csrf_token'] ?? ''), $token)) throw new MageStockInstallFormException('Session expirée ou jeton de sécurité invalide. Rechargez la page.', 0);
        if(!appInstallationRequired()) throw new MageStockInstallFormException('L’installation a déjà été finalisée.', 0);
        $preflight = installPreflight();
        if(!$preflight['ready']) throw new MageStockInstallFormException('Le serveur ne satisfait pas encore tous les prérequis obligatoires. Corrigez les éléments signalés puis rechargez cette page.', 0);

        $company = trim((string)($_POST['companyName'] ?? ''));
        $password = (string)($_POST['adminPassword'] ?? '');
        $confirm = (string)($_POST['adminPasswordConfirm'] ?? '');
        $language = normalizeAppLanguage((string)($_POST['language'] ?? 'fr'));
        $locale = normalizeAppLocale((string)($_POST['locale'] ?? 'auto'));
        $country = normalizeAppCountry((string)($_POST['country'] ?? 'FR'));
        $currency = normalizeAppCurrency((string)($_POST['currency'] ?? 'EUR'));
        $timezone = normalizeAppTimezone((string)($_POST['timezone'] ?? 'Europe/Paris'));
        $warehouseName = trim((string)($_POST['warehouseName'] ?? ''));
        $warehouseAddress = trim((string)($_POST['warehouseAddress'] ?? ''));
        $locationCode = trim((string)($_POST['locationCode'] ?? ''));
        $locationLabel = trim((string)($_POST['locationLabel'] ?? ''));
        $locationZone = trim((string)($_POST['locationZone'] ?? ''));
        $pushEnabled = isset($_POST['enablePush']) && $_POST['enablePush'] === '1';

        if(installTextLength($company) < 2 || installTextLength($company) > 120) throw new MageStockInstallFormException('Le nom de l’organisation doit contenir entre 2 et 120 caractères.', 1);
        $passwordErrors = magestockPasswordPolicyErrors($password, ['username'=>'admin','name'=>'Administrateur']);
        if($passwordErrors) throw new MageStockInstallFormException(implode(' ', $passwordErrors), 2);
        if(!hash_equals($password, $confirm)) throw new MageStockInstallFormException('Les deux mots de passe ne correspondent pas.', 2);
        if($warehouseName === '' || installTextLength($warehouseName) > 120) throw new MageStockInstallFormException('Le nom du premier magasin ou dépôt est obligatoire.', 3);
        if($locationCode !== '' && installTextLength($locationCode) > 60) throw new MageStockInstallFormException('Le code emplacement est trop long.', 3);
        if(!isset($_POST['installConfirm']) || $_POST['installConfirm'] !== '1') throw new MageStockInstallFormException('Confirmez la création définitive de cette instance.', 5);

        $lockPath = DATA_DIR . '/.installation.lock';
        $lock = fopen($lockPath, 'c+');
        if(!$lock || !flock($lock, LOCK_EX)) throw new RuntimeException('Impossible de verrouiller l’installation.');
        try {
            $existingUsers = loadData('users');
            if(count($existingUsers) > 0 || !appInstallationRequired()) throw new RuntimeException('L’installation initiale est refusée car des utilisateurs existent déjà. Utilisez la récupération administrative sans réinitialiser les données.');

            $now = date('c');
            $warehouseId = genId();
            $logoUrl = !empty($_FILES['logo']) ? installStoreLogo($_FILES['logo']) : '';
            $admin = [[
                'id'=>1,'username'=>'admin','password'=>password_hash($password, PASSWORD_DEFAULT),'role'=>'admin','name'=>'Administrateur',
                'active'=>true,'authSource'=>'local','mustChangePassword'=>false,'authVersion'=>1,'allowedWarehouses'=>[],'lang'=>$language,
                'createdAt'=>$now,'updatedAt'=>$now,
            ]];
            $settings = [[
                'id'=>1,'companyName'=>$company,'skuPrefix'=>'PRD-','logoUrl'=>$logoUrl,'locale'=>$locale,'country'=>$country,'currency'=>$currency,
                'timezone'=>$timezone,'pushEnabled'=>$pushEnabled,'vapidPublicKey'=>'','vapidPrivateKey'=>'','schemaVersion'=>'2.1.2','localeCurrencyVersion'=>'1.0',
                'installationComplete'=>true,'installedAt'=>$now,'updatedAt'=>$now,
            ]];
            $warehouses = [[
                'id'=>$warehouseId,'name'=>$warehouseName,'address'=>$warehouseAddress,'color'=>'#00b4d8','categories'=>[],'createdAt'=>$now,'updatedAt'=>$now,
            ]];
            $locations = [];
            if($locationCode !== '') $locations[] = [
                'id'=>genId(),'warehouseId'=>$warehouseId,'code'=>$locationCode,'label'=>$locationLabel,'zone'=>$locationZone,'type'=>'autre',
                'active'=>true,'createdAt'=>$now,'updatedAt'=>$now,
            ];

            installAtomicJson(dataPath('settings'), $settings);
            installAtomicJson(dataPath('warehouses'), $warehouses);
            installAtomicJson(dataPath('locations'), $locations);
            installAtomicJson(dataPath('rate_limits'), []);
            installAtomicJson(dataPath('push_subscriptions'), []);
            // L’utilisateur est écrit en dernier : sans lui l’installateur reste légitimement accessible.
            installAtomicJson(dataPath('users'), $admin);
            $systemState = magestockUpdateDefaultState(APP_VER, 'completed');
            $systemState['installedVersion'] = APP_VER;
            $systemState['migrationVersion'] = APP_VER;
            $systemState['lastUpdateAt'] = $now;
            installAtomicJson(MAGESTOCK_UPDATE_STATE_FILE, $systemState);
            installAtomicJson(MAGESTOCK_UPDATE_HISTORY_FILE, []);
            @unlink(DATA_DIR . '/INITIAL_PASSWORD.txt');

            session_regenerate_id(true);
            $_SESSION['user_id'] = 1;
            $_SESSION['user_role'] = 'admin';
            $_SESSION['auth_version'] = 1;
            $_SESSION['authenticated_at'] = time();
            setcookie('sm_lang', $language, [
                'expires'=>time()+31536000,'path'=>appCookiePath(),'secure'=>appRequestIsSecure(),'httponly'=>false,'samesite'=>'Lax'
            ]);
            $_SESSION['magestock_install_summary'] = [
                'Organisation'=>$company,'Administrateur'=>'admin','Langue'=>$language,'Pays'=>$country,'Devise'=>$currency,'Fuseau'=>$timezone,
                'Magasin'=>$warehouseName,'Emplacement'=>$locationCode !== '' ? $locationCode : 'Non créé',
                'Notifications'=>$pushEnabled ? 'Préparées — clés à générer sur ce serveur' : 'Désactivées',
            ];
        } finally {
            flock($lock, LOCK_UN); fclose($lock); @unlink($lockPath);
        }
        installRedirect('install.php?complete=1');
    } catch(MageStockInstallFormException $e) {
        $error = $e->getMessage(); $initialStep = $e->step;
    } catch(Throwable $e) {
        $error = $e->getMessage(); $initialStep = 5;
    }
}

$langs = ['fr'=>'Français','en'=>'English','de'=>'Deutsch','es'=>'Español','it'=>'Italiano','nl'=>'Nederlands','pt'=>'Português','pl'=>'Polski','ru'=>'Русский','tr'=>'Türkçe','ar'=>'العربية','ja'=>'日本語','ko'=>'한국어','zh'=>'中文','hi'=>'हिन्दी','id'=>'Bahasa Indonesia','uk'=>'Українська'];
$commonTimezones = ['Europe/Paris','Europe/London','Europe/Berlin','Europe/Madrid','Europe/Rome','Europe/Amsterdam','Europe/Warsaw','Europe/Kyiv','UTC','America/New_York','America/Toronto','America/Los_Angeles','Asia/Riyadh','Asia/Tokyo','Asia/Seoul','Asia/Shanghai','Asia/Kolkata','Asia/Jakarta'];
?>
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="robots" content="noindex,nofollow">
<meta name="theme-color" content="#07111f">
<title><?= $summary ? 'Installation terminée' : 'Installation' ?> — MageStock</title>
<link rel="icon" href="<?= htmlspecialchars(assetUrl('img/favicon-32.png')) ?>">
<link rel="stylesheet" href="<?= htmlspecialchars(assetUrl('css/install.css?v=' . filemtime(APP_ROOT . '/assets/css/install.css'))) ?>">
</head>
<body>
<div class="install-shell" data-install-root data-system-ready="<?= $preflight['ready'] ? '1' : '0' ?>" data-initial-step="<?= (int)$initialStep ?>">
<header class="install-header">
    <a class="install-brand" href="<?= htmlspecialchars(appUrl('install.php')) ?>" aria-label="MageStock — installation">
        <img src="<?= htmlspecialchars(assetUrl('img/icon-192.png')) ?>" alt="">
        <span class="install-brand-copy"><strong>MageStock</strong><span>Installation sécurisée et guidée</span></span>
    </a>
    <span class="install-version"><span>Version</span><strong><?= htmlspecialchars(APP_VER) ?></strong></span>
</header>

<?php if($summary): ?>
<main class="install-workspace" style="display:block;min-height:auto">
    <div class="install-panel-scroll">
        <section class="install-complete">
            <div class="install-complete-mark" aria-hidden="true">✓</div>
            <span class="install-kicker">Instance prête</span>
            <h1>Installation terminée</h1>
            <p>L’administrateur local et la structure initiale sont créés. Aucun mot de passe temporaire ni secret de publication n’a été enregistré dans la distribution.</p>
            <div class="install-review">
                <?php foreach($summary as $label=>$value): ?><div><small><?= htmlspecialchars((string)$label) ?></small><strong><?= htmlspecialchars((string)$value) ?></strong></div><?php endforeach; ?>
            </div>
            <a class="install-button is-primary" href="<?= htmlspecialchars(appUrl('dashboard')) ?>">Ouvrir MageStock</a>
        </section>
    </div>
</main>
<?php else: ?>
<main class="install-workspace">
    <aside class="install-sidebar" aria-label="Étapes d’installation">
        <div class="install-sidebar-intro"><small>Premier lancement</small><h1>Configurer MageStock</h1><p>Six étapes courtes. Les données ne sont écrites qu’au dernier écran.</p></div>
        <ol class="install-steps">
            <?php
            $stepLabels = [
                ['Système','Précontrôle'],['Organisation','Identité'],['Administrateur','Sécurité'],['Stockage','Premier dépôt'],['Services','Options'],['Validation','Création']
            ];
            foreach($stepLabels as $i=>$labels): ?>
            <li><button class="install-step-button" type="button" data-install-step="<?= $i ?>"><span class="step-index"><?= $i+1 ?></span><span class="step-copy"><strong><?= htmlspecialchars($labels[0]) ?></strong><small><?= htmlspecialchars($labels[1]) ?></small></span></button></li>
            <?php endforeach; ?>
        </ol>
        <div class="install-security-note"><strong>Aucune réinitialisation silencieuse</strong>L’assistant devient inaccessible dès qu’un utilisateur existe. Une installation existante doit passer par la récupération ou les mises à jour.</div>
    </aside>

    <form class="install-main" id="installForm" method="post" enctype="multipart/form-data" autocomplete="off">
        <input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrfToken()) ?>">
        <div class="install-panel-scroll">
            <?php if($error !== ''): ?><div class="install-alert" role="alert"><?= htmlspecialchars($error) ?></div><?php endif; ?>

            <section class="install-pane" data-install-pane="0">
                <header class="install-pane-header"><span class="install-kicker">Étape 1 — environnement</span><h2>Vérifier le serveur avant toute écriture</h2><p>MageStock contrôle les composants indispensables, les dossiers persistants, sa signature officielle et le contexte réseau. Aucun compte n’est créé à cette étape.</p></header>
                <?php if($preflight['ready']): ?><div class="install-notice install-success">Tous les prérequis obligatoires sont validés. Vous pouvez poursuivre.</div><?php else: ?><div class="install-alert">Un ou plusieurs prérequis obligatoires bloquent l’installation. Corrigez-les puis rechargez la page.</div><?php endif; ?>
                <div class="install-check-grid">
                    <?php foreach($preflight['checks'] as $check):
                        $statusClass = $check['ok'] ? 'is-ok' : ($check['critical'] ? 'is-error' : 'is-warning');
                        $symbol = $check['ok'] ? '✓' : ($check['critical'] ? '!' : 'i'); ?>
                    <article class="install-check <?= $statusClass ?>"><span class="install-check-icon" aria-hidden="true"><?= $symbol ?></span><span class="install-check-copy"><strong><?= htmlspecialchars($check['label']) ?></strong><small><?= htmlspecialchars($check['detail']) ?></small></span><span class="install-check-value"><?= htmlspecialchars($check['value']) ?></span></article>
                    <?php endforeach; ?>
                </div>
                <dl class="install-context">
                    <div class="install-context-row"><dt>Chemin détecté</dt><dd><?= htmlspecialchars(appBasePath() === '' ? '/' : appBasePath() . '/') ?></dd></div>
                    <div class="install-context-row"><dt>Protocole public</dt><dd><?= htmlspecialchars(strtoupper((string)$preflight['proxy']['scheme'])) ?><?= $preflight['proxy']['proxyDetected'] ? ' · reverse proxy détecté' : '' ?></dd></div>
                    <div class="install-context-row"><dt>Politique proxy</dt><dd>Redirections relatives + prise en compte de X-Forwarded-Proto / Forwarded pour HTTPS et cookies Secure</dd></div>
                </dl>
                <?php if(!$preflight['proxy']['secure']): ?><div class="install-notice install-warning" style="margin-top:16px">L’installation peut être préparée en HTTP, mais la caméra, la PWA et les notifications exigent un accès public HTTPS. Si un reverse proxy termine TLS, vérifiez qu’il transmet bien le protocole d’origine.</div><?php endif; ?>
            </section>

            <section class="install-pane" data-install-pane="1">
                <header class="install-pane-header"><span class="install-kicker">Étape 2 — identité</span><h2>Organisation et paramètres régionaux</h2><p>Ces choix initialisent l’affichage de MageStock. Ils restent modifiables plus tard depuis les paramètres.</p></header>
                <div class="install-grid">
                    <div class="install-field is-wide"><label for="companyName">Nom de l’organisation</label><input id="companyName" name="companyName" maxlength="120" required value="<?= htmlspecialchars($values['companyName']) ?>" autocomplete="organization"><span class="hint">Nom affiché dans l’application et les documents.</span></div>
                    <div class="install-field"><label for="language">Langue</label><select id="language" name="language"><?php foreach($langs as $code=>$label): ?><option value="<?= htmlspecialchars($code) ?>" <?= $values['language']===$code?'selected':'' ?>><?= htmlspecialchars($label) ?></option><?php endforeach; ?></select></div>
                    <div class="install-field"><label for="locale">Format régional</label><select id="locale" name="locale"><?php foreach(appLocaleOptions() as $o): ?><option value="<?= htmlspecialchars($o['code']) ?>" <?= $values['locale']===$o['code']?'selected':'' ?>><?= htmlspecialchars($o['label']) ?></option><?php endforeach; ?></select></div>
                    <div class="install-field"><label for="country">Pays</label><select id="country" name="country"><?php foreach(appCountryOptions() as $o): ?><option value="<?= htmlspecialchars($o['code']) ?>" <?= $values['country']===$o['code']?'selected':'' ?>><?= htmlspecialchars($o['label']) ?></option><?php endforeach; ?></select></div>
                    <div class="install-field"><label for="currency">Devise</label><select id="currency" name="currency"><?php foreach(appCurrencyOptions() as $o): ?><option value="<?= htmlspecialchars($o['code']) ?>" <?= $values['currency']===$o['code']?'selected':'' ?>><?= htmlspecialchars($o['label']) ?></option><?php endforeach; ?></select></div>
                    <div class="install-field is-wide"><label for="timezone">Fuseau horaire</label><input id="timezone" name="timezone" list="timezones" required value="<?= htmlspecialchars($values['timezone']) ?>" autocomplete="off"><datalist id="timezones"><?php foreach($commonTimezones as $tz): ?><option value="<?= htmlspecialchars($tz) ?>"><?php endforeach; ?></datalist></div>
                    <div class="install-field is-wide"><label for="logo">Logo de l’organisation <span class="hint">— facultatif</span></label><input id="logo" name="logo" type="file" accept="image/png,image/jpeg,image/webp,image/gif"><span class="hint">JPG, PNG, WEBP ou GIF · 2 Mo maximum.</span></div>
                </div>
            </section>

            <section class="install-pane" data-install-pane="2">
                <header class="install-pane-header"><span class="install-kicker">Étape 3 — sécurité</span><h2>Créer l’administrateur local de secours</h2><p>Le compte <strong>admin</strong> reste local même si LDAP est configuré ensuite. Aucun mot de passe initial n’est généré ou stocké en clair.</p></header>
                <div class="install-grid">
                    <div class="install-field"><label for="adminIdentity">Identifiant</label><input id="adminIdentity" value="admin" disabled><span class="hint">Identifiant réservé au compte initial.</span></div>
                    <div class="install-field"><label for="adminPassword">Mot de passe</label><input id="adminPassword" name="adminPassword" type="password" minlength="14" maxlength="255" required autocomplete="new-password" enterkeyhint="next"></div>
                    <div class="install-field is-wide"><label for="adminPasswordConfirm">Confirmer le mot de passe</label><input id="adminPasswordConfirm" name="adminPasswordConfirm" type="password" minlength="14" maxlength="255" required autocomplete="new-password" enterkeyhint="done"></div>
                    <div class="install-field is-wide"><span class="install-field-label">Qualité du mot de passe</span><div class="password-meter" aria-hidden="true"><span></span></div><div class="password-rules"><span data-password-rule="length">✓ 14 caractères minimum</span><span data-password-rule="lower">✓ une minuscule</span><span data-password-rule="upper">✓ une majuscule</span><span data-password-rule="number">✓ un chiffre</span><span data-password-rule="symbol">✓ un symbole</span><span data-password-rule="identity">✓ sans MageStock/admin</span></div></div>
                </div>
            </section>

            <section class="install-pane" data-install-pane="3">
                <header class="install-pane-header"><span class="install-kicker">Étape 4 — structure</span><h2>Créer le premier magasin ou dépôt</h2><p>Vous pourrez ensuite créer autant de magasins, zones, emplacements et plans que nécessaire.</p></header>
                <div class="install-grid">
                    <div class="install-field"><label for="warehouseName">Nom du magasin ou dépôt</label><input id="warehouseName" name="warehouseName" maxlength="120" required value="<?= htmlspecialchars($values['warehouseName']) ?>"></div>
                    <div class="install-field"><label for="warehouseAddress">Adresse <span class="hint">— facultative</span></label><input id="warehouseAddress" name="warehouseAddress" maxlength="240" value="<?= htmlspecialchars($values['warehouseAddress']) ?>" autocomplete="street-address"></div>
                    <div class="install-field"><label for="locationCode">Premier emplacement <span class="hint">— facultatif</span></label><input id="locationCode" name="locationCode" maxlength="60" placeholder="Ex. A1" value="<?= htmlspecialchars($values['locationCode']) ?>"></div>
                    <div class="install-field"><label for="locationLabel">Libellé</label><input id="locationLabel" name="locationLabel" maxlength="120" placeholder="Ex. Rack principal" value="<?= htmlspecialchars($values['locationLabel']) ?>"></div>
                    <div class="install-field is-wide"><label for="locationZone">Zone</label><input id="locationZone" name="locationZone" maxlength="120" placeholder="Ex. Réserve" value="<?= htmlspecialchars($values['locationZone']) ?>"></div>
                </div>
            </section>

            <section class="install-pane" data-install-pane="4">
                <header class="install-pane-header"><span class="install-kicker">Étape 5 — services</span><h2>Préparer les fonctions serveur</h2><p>Les services sensibles restent neutres à l’installation. Vous pourrez activer LDAP et les notifications depuis l’administration après connexion.</p></header>
                <div class="install-section">
                    <label class="install-checkbox" for="enablePush"><input id="enablePush" type="checkbox" name="enablePush" value="1" <?= $values['enablePush']==='1'?'checked':'' ?>><span><strong>Préparer les notifications push</strong><span>Les clés VAPID restent vides dans la distribution. Elles seront générées spécifiquement sur cette instance.</span></span></label>
                    <div class="install-notice"><strong>LDAP / Active Directory</strong><br>La configuration d’annuaire n’est jamais demandée pendant le premier lancement. Elle se réalise après connexion, avec stockage protégé des secrets et conservation du compte admin local de secours.</div>
                    <div class="install-notice install-warning"><strong>Reverse proxy</strong><br>MageStock utilise des chemins relatifs et détecte le protocole public transmis par le proxy. Le proxy doit conserver l’hôte et transmettre <code>X-Forwarded-Proto: https</code> ou l’équivalent <code>Forwarded: proto=https</code>.</div>
                </div>
            </section>

            <section class="install-pane" data-install-pane="5">
                <header class="install-pane-header"><span class="install-kicker">Étape 6 — validation</span><h2>Vérifier puis créer l’instance</h2><p>Cette action écrit la configuration initiale en une seule opération verrouillée. Elle ne supprime aucune donnée préexistante et refuse de s’exécuter si un utilisateur existe déjà.</p></header>
                <div class="install-review">
                    <div><small>Organisation</small><strong data-review="companyName">—</strong></div><div><small>Langue</small><strong data-review="language">—</strong></div>
                    <div><small>Format régional</small><strong data-review="locale">—</strong></div><div><small>Pays</small><strong data-review="country">—</strong></div>
                    <div><small>Devise</small><strong data-review="currency">—</strong></div><div><small>Fuseau</small><strong data-review="timezone">—</strong></div>
                    <div><small>Premier dépôt</small><strong data-review="warehouseName">—</strong></div><div><small>Adresse</small><strong data-review="warehouseAddress">—</strong></div>
                    <div><small>Premier emplacement</small><strong data-review="locationCode">—</strong></div><div><small>Notifications</small><strong data-review="enablePush">—</strong></div>
                </div>
                <label class="install-checkbox install-final-confirm" for="installConfirm"><input id="installConfirm" name="installConfirm" type="checkbox" value="1" required><span><strong>Je confirme la création de cette instance MageStock.</strong><span>Le compte admin et le premier dépôt seront créés. L’assistant sera ensuite verrouillé.</span></span></label>
            </section>
        </div>
        <footer class="install-actions">
            <span class="install-progress-text" id="installProgressText">Étape 1 sur 6</span>
            <div class="install-actions-group"><button class="install-button is-secondary" id="installBack" type="button">Retour</button><button class="install-button is-primary" id="installNext" type="button">Continuer</button><button class="install-button is-primary" id="installSubmit" type="submit" hidden>Installer MageStock</button></div>
        </footer>
    </form>
</main>
<?php endif; ?>

<footer class="install-footer">
    <a class="install-footer-brand" href="https://magekorp.com/contact" target="_blank" rel="noopener noreferrer"><picture><source media="(max-width:680px)" srcset="<?= htmlspecialchars(assetUrl('img/magekorp-mark.png')) ?>"><img src="<?= htmlspecialchars(assetUrl('img/magekorp-logo-horizontal.png')) ?>" alt="MageKorp"></picture><span>MageStock · une application web MageKorp</span></a>
    <nav class="install-footer-links" aria-label="Liens de pied de page"><span>© 2026</span><a href="https://magekorp.com/contact" target="_blank" rel="noopener noreferrer">Contacter MageKorp</a></nav>
</footer>
</div>
<?php if(!$summary): ?><script src="<?= htmlspecialchars(assetUrl('js/install.js?v=' . filemtime(APP_ROOT . '/assets/js/install.js'))) ?>" defer></script><?php endif; ?>
</body>
</html>
