<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>安装进度 — DBErp 安装向导</title>
    <style>
        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
        :root {
            color-scheme: light;
            --blue:    #2563eb; --blue-d:  #1d4ed8;
            --blue-bg: #eff6ff; --blue-br: #bfdbfe;
            --green-bg: #f0fdf4; --green-br: #a7f3d0;
            --red-bg: #fef2f2; --red-br: #fecaca; --red-d: #b91c1c;
            --s50: #f8fafc; --s100: #f1f5f9; --s200: #e2e8f0; --s300: #cbd5e1;
            --s400: #94a3b8; --s500: #64748b; --s600: #475569; --s700: #334155;
            --s800: #1e293b; --s900: #0f172a;
        }
        body {
            font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif;
            background-color: #eef2ff;
            background-image:
                radial-gradient(ellipse 100% 55% at 0% 0%, rgba(219,234,254,.65) 0%, transparent 55%),
                radial-gradient(ellipse 70% 50% at 100% 100%, rgba(220,252,231,.45) 0%, transparent 55%);
            color: var(--s900);
            line-height: 1.65;
            min-height: 100vh;
        }
        .wrap { max-width: 44rem; margin: 0 auto; padding: 1.375rem 1.5rem 3rem; }
        .card {
            background: #fff; border-radius: 1rem; border: 1px solid var(--s200);
            box-shadow: 0 8px 20px -4px rgba(0,0,0,.09);
            overflow: hidden; margin-bottom: 1.25rem; position: relative;
        }
        .card::before {
            content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
            background: linear-gradient(90deg, #2563eb 0%, #6366f1 50%, #2563eb 100%);
        }
        .card-hd { padding: 1.5rem 1.75rem 1.25rem; border-bottom: 1px solid var(--s100); background: linear-gradient(to bottom right, #fbfbff, var(--s50)); }
        .card-bd { padding: 1.5rem 1.75rem; }
        h1 { font-size: 1.375rem; font-weight: 800; margin-bottom: .45rem; }
        .lead { color: var(--s600); font-size: .9375rem; }
        .alert {
            display: flex; align-items: flex-start; gap: .75rem;
            padding: .875rem 1.125rem; border-radius: .625rem;
            margin-bottom: 1rem; font-size: .9375rem;
        }
        .alert-warn { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; }
        .alert-err { background: var(--red-bg); border: 1px solid var(--red-br); color: var(--red-d); }
        .alert-info { background: var(--blue-bg); border: 1px solid var(--blue-br); color: #1e40af; }
        .alert-ok { background: var(--green-bg); border: 1px solid var(--green-br); color: #065f46; }
        .alert[hidden] { display: none !important; }
        .status-box {
            background: var(--s50); border: 1px solid var(--s200); border-radius: .625rem;
            padding: 1rem 1.125rem; margin-bottom: 1rem;
        }
        .status-row { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
        .status-box dt { font-size: .8125rem; color: var(--s500); font-weight: 600; margin-bottom: .25rem; }
        .status-box dd { font-size: 1rem; color: var(--s800); font-weight: 600; }
        .percent-text { font-size: .875rem; color: var(--s600); font-weight: 600; }
        .stage-list { list-style: none; }
        .stage-item {
            display: flex; align-items: flex-start; gap: .75rem;
            padding: .75rem 0; border-bottom: 1px solid var(--s100);
        }
        .stage-item:last-child { border-bottom: none; }
        .stage-dot {
            width: 1.5rem; height: 1.5rem; border-radius: 50%; flex-shrink: 0;
            display: flex; align-items: center; justify-content: center;
            font-size: .6875rem; font-weight: 700; margin-top: .1rem;
            border: 2px solid var(--s300); color: var(--s400); background: #fff;
        }
        .stage-item.is-done .stage-dot { background: var(--blue); border-color: var(--blue); color: #fff; }
        .stage-item.is-active .stage-dot { border-color: var(--blue); color: var(--blue); box-shadow: 0 0 0 4px rgba(37,99,235,.12); }
        .stage-item.is-failed .stage-dot { border-color: var(--red-d); color: var(--red-d); }
        .stage-label { font-weight: 600; color: var(--s800); font-size: .9375rem; }
        .stage-hint { font-size: .8125rem; color: var(--s500); margin-top: .15rem; }
        .actions { margin-top: 1.25rem; display: flex; flex-wrap: wrap; gap: .75rem; }
        .btn {
            display: inline-flex; align-items: center; justify-content: center;
            padding: .625rem 1.375rem; border-radius: .5rem;
            font-size: .9375rem; font-weight: 600; text-decoration: none; cursor: pointer;
            background: #fff; color: var(--s700); border: 1.5px solid var(--s200);
        }
        .btn:hover { background: var(--s50); }
        .btn-primary {
            background: linear-gradient(135deg, var(--blue), #5b50f0); color: #fff; border: none;
        }
        .btn-primary:hover { background: linear-gradient(135deg, var(--blue-d), #4338ca); color: #fff; }
        .btn[hidden] { display: none !important; }
        .percent-bar {
            height: .5rem; background: var(--s200); border-radius: 999px;
            overflow: hidden; margin-top: .5rem;
        }
        .percent-fill {
            height: 100%; background: linear-gradient(90deg, var(--blue), #6366f1);
            border-radius: 999px; transition: width .3s ease;
        }
        .msg-list { margin: .375rem 0 0 1.1rem; }
    </style>
</head>
<body>

@include('install.partials.install-wizard-header')

<div class="wrap">
    @if ($missingTask ?? false)
        <div class="card">
            <div class="card-hd"><h1>安装进度</h1></div>
            <div class="card-bd">
                <div class="alert alert-err" role="alert">
                    <div>{{ $missingTaskMessage }}</div>
                </div>
                @include('install.partials.official-feedback-entry', ['variant' => 'failure', 'compact' => true])
                <div class="actions">
                    <a class="btn" href="{{ route('install.initialize') }}">返回系统初始化</a>
                </div>
            </div>
        </div>
    @else
        <div class="card" id="install-progress-card">
            <div class="card-hd">
                <h1>{{ $headline }}</h1>
                <p class="lead">系统正在按下列步骤自动完成安装。请勿关闭本页；安装成功后将<strong>自动跳转</strong>到完成页（约 1 秒内），您也可随时点击「进入系统」。</p>
            </div>
            <div class="card-bd">
                <div class="alert alert-info" id="progress-info-alert" role="status">
                    <div id="progress-info-text">{{ \App\Services\Install\WebInstallProgressDisplay::WAITING_FOR_EXECUTION_LABEL }}</div>
                </div>
                <div class="alert alert-warn" id="progress-warn-alert" role="alert" hidden>
                    <div><strong>收尾提示：</strong><ul class="msg-list" id="progress-warnings"></ul></div>
                </div>
                <div class="alert alert-err" id="progress-err-alert" role="alert" hidden>
                    <div><strong id="progress-err-title">安装失败</strong><ul class="msg-list" id="progress-errors"></ul></div>
                </div>
                <div id="install-feedback-on-failure" hidden>
                    @include('install.partials.official-feedback-entry', ['variant' => 'failure', 'compact' => true])
                </div>
                <div class="alert alert-ok" id="progress-ok-alert" role="status" hidden>
                    <div id="progress-ok-text"></div>
                </div>

                <dl class="status-box">
                    <div class="status-row">
                        <div>
                            <dt>当前阶段</dt>
                            <dd id="current-status-label">{{ \App\Services\Install\WebInstallProgressDisplay::WAITING_FOR_EXECUTION_LABEL }}</dd>
                        </div>
                        <div class="percent-text" id="percent-text" aria-live="polite">安装进度：0%</div>
                    </div>
                    <div class="percent-bar" aria-hidden="true">
                        <div class="percent-fill" id="percent-fill" style="width: 0%;"></div>
                    </div>
                </dl>

                <h2 style="font-size:1rem;font-weight:700;margin:1rem 0 .75rem;color:var(--s800);">安装步骤</h2>
                <ul class="stage-list" id="stage-list">
                    @foreach ($stages as $stage)
                        <li class="stage-item" data-stage-key="{{ $stage['key'] }}">
                            <span class="stage-dot" aria-hidden="true"></span>
                            <div>
                                <div class="stage-label">{{ $stage['label'] }}</div>
                                <div class="stage-hint">待执行</div>
                            </div>
                        </li>
                    @endforeach
                    <li class="stage-item" data-stage-key="completed">
                        <span class="stage-dot" aria-hidden="true"></span>
                        <div>
                            <div class="stage-label">安装完成</div>
                            <div class="stage-hint">待执行</div>
                        </div>
                    </li>
                </ul>

                @include('install.partials.official-feedback-entry', ['variant' => 'default', 'compact' => true])

                <div class="actions">
                    <button type="button" class="btn btn-primary" id="retry-btn" hidden>重试当前步骤</button>
                    <a class="btn btn-primary" id="finished-btn" href="{{ $finishedUrl }}" hidden>进入系统</a>
                    <a class="btn" href="{{ route('install.initialize') }}">返回系统初始化</a>
                </div>
            </div>
        </div>
    @endif
</div>

@if (! ($missingTask ?? false))
<script>
(function () {
    const stateUrl = @json($stateUrl);
    const runStepUrl = @json($runStepUrl);
    const retryStepUrl = @json($retryStepUrl);
    const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
    const pollIntervalMs = 4000;
    const messages = {
        completed: @json(\App\Services\Install\WebInstallProgressDisplay::COMPLETED_USER_MESSAGE, JSON_UNESCAPED_UNICODE),
        warning: @json(\App\Services\Install\WebInstallProgressDisplay::WARNING_USER_MESSAGE, JSON_UNESCAPED_UNICODE),
        failed: @json(\App\Services\Install\WebInstallProgressDisplay::FAILED_USER_MESSAGE, JSON_UNESCAPED_UNICODE),
        networkRetry: @json(\App\Services\Install\WebInstallProgressDisplay::NETWORK_RETRY_MESSAGE, JSON_UNESCAPED_UNICODE),
        waiting: @json(\App\Services\Install\WebInstallProgressDisplay::WAITING_FOR_EXECUTION_LABEL, JSON_UNESCAPED_UNICODE),
        serverError: '服务端返回错误，系统将自动重试…',
        sessionExpired: '会话已过期，请刷新页面或重新开始安装。',
        csrfExpired: '安全凭证已过期，请刷新页面重试。',
    };

    let pollTimer = null;
    let runningStep = false;
    let terminal = false;
    let autoRedirectScheduled = false;
    let autoRedirectTimer = null;
    const autoRedirectDelayMs = 900;

    const els = {
        statusLabel: document.getElementById('current-status-label'),
        percentFill: document.getElementById('percent-fill'),
        percentText: document.getElementById('percent-text'),
        infoAlert: document.getElementById('progress-info-alert'),
        infoText: document.getElementById('progress-info-text'),
        warnAlert: document.getElementById('progress-warn-alert'),
        warnings: document.getElementById('progress-warnings'),
        errAlert: document.getElementById('progress-err-alert'),
        errTitle: document.getElementById('progress-err-title'),
        errors: document.getElementById('progress-errors'),
        okAlert: document.getElementById('progress-ok-alert'),
        okText: document.getElementById('progress-ok-text'),
        retryBtn: document.getElementById('retry-btn'),
        finishedBtn: document.getElementById('finished-btn'),
        feedbackOnFailure: document.getElementById('install-feedback-on-failure'),
        stageItems: document.querySelectorAll('#stage-list .stage-item'),
    };

    function syncFeedbackOnFailure() {
        if (! els.feedbackOnFailure) {
            return;
        }

        els.feedbackOnFailure.hidden = els.errAlert.hidden;
    }

    async function fetchJson(url, method) {
        let response;
        try {
            response = await fetch(url, {
                method: method,
                headers: {
                    'Accept': 'application/json',
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-CSRF-TOKEN': csrfToken,
                },
                credentials: 'same-origin',
            });
        } catch (_networkErr) {
            throw new Error(messages.networkRetry);
        }

        const data = await response.json().catch(function () { return {}; });

        if (!response.ok) {
            if (response.status === 419) {
                throw new Error(messages.csrfExpired);
            }
            if (response.status === 404) {
                throw new Error(data.message || messages.sessionExpired);
            }
            if (response.status >= 500) {
                throw new Error(data.message || messages.serverError);
            }
            throw new Error(data.message || ('服务端错误（HTTP ' + response.status + '），系统将自动重试…'));
        }

        return data;
    }

    function isActiveStatus(status) {
        return status === 'pending' || status === 'running';
    }

    function isTerminalStatus(status) {
        return status === 'completed' || status === 'warning' || status === 'failed';
    }

    function scheduleAutoRedirectToFinished(url) {
        if (!url || autoRedirectScheduled) {
            return;
        }

        autoRedirectScheduled = true;
        if (autoRedirectTimer !== null) {
            window.clearTimeout(autoRedirectTimer);
        }

        autoRedirectTimer = window.setTimeout(function () {
            window.location.assign(url);
        }, autoRedirectDelayMs);
    }

    function renderState(data) {
        const percent = data.percent || 0;
        els.statusLabel.textContent = data.current_label || messages.waiting;
        els.percentFill.style.width = percent + '%';
        els.percentText.textContent = '安装进度：' + percent + '%';

        const completed = new Set(data.completed_stages || []);
        const currentStage = data.current_stage || '';
        const isFailed = data.status === 'failed';
        // already_installed 仅在 status 也已是终止态时才视为已完成，
        // 避免写完 installed.lock（WritingInstallMarkers）后跳过后续收尾阶段
        const isCompleted = data.status === 'completed' || data.status === 'warning';

        els.stageItems.forEach(function (item) {
            const key = item.getAttribute('data-stage-key');
            const hint = item.querySelector('.stage-hint');
            const dot = item.querySelector('.stage-dot');
            item.classList.remove('is-done', 'is-active', 'is-failed');
            dot.textContent = '';

            if (key === 'completed') {
                if (data.status === 'completed' || data.status === 'warning') {
                    item.classList.add('is-done');
                    dot.textContent = '✓';
                    hint.textContent = data.status === 'warning' ? '已完成（有提示）' : '已完成';
                } else {
                    hint.textContent = '待执行';
                }
                return;
            }

            if (completed.has(key)) {
                item.classList.add('is-done');
                dot.textContent = '✓';
                hint.textContent = '已完成';
            } else if (isFailed && key === currentStage) {
                item.classList.add('is-failed');
                dot.textContent = '!';
                hint.textContent = '失败';
            } else if (key === currentStage && isActiveStatus(data.status)) {
                item.classList.add('is-active');
                dot.textContent = '●';
                if (key === 'migrating' && data.migration_progress && data.migration_progress.total > 0) {
                    var migrationPercent = typeof data.migration_progress.percent === 'number'
                        ? data.migration_progress.percent
                        : Math.min(100, Math.max(0, Math.round((data.migration_progress.completed / data.migration_progress.total) * 100)));
                    hint.textContent = '进行中 · ' + migrationPercent + '%';
                } else {
                    hint.textContent = data.status === 'pending' ? '待执行' : '进行中';
                }
            } else {
                hint.textContent = '待执行';
            }
        });

        els.warnings.innerHTML = '';
        (data.warnings || []).forEach(function (msg) {
            const li = document.createElement('li');
            li.textContent = msg;
            els.warnings.appendChild(li);
        });
        els.warnAlert.hidden = !(data.warnings && data.warnings.length);

        els.errors.innerHTML = '';
        (data.errors || []).forEach(function (msg) {
            const li = document.createElement('li');
            li.textContent = msg;
            els.errors.appendChild(li);
        });

        els.retryBtn.hidden = !isFailed;
        els.finishedBtn.hidden = !isCompleted;

        if (isCompleted) {
            terminal = true;
            els.okAlert.hidden = false;
            els.infoAlert.hidden = true;
            els.errAlert.hidden = true;
            syncFeedbackOnFailure();
            if (data.already_installed === true) {
                els.okText.textContent = data.user_message || messages.completed;
            } else if (data.status === 'warning') {
                els.okText.textContent = data.user_message || messages.warning;
                els.finishedBtn.textContent = '进入系统';
            } else {
                els.okText.textContent = data.user_message || messages.completed;
                els.finishedBtn.textContent = '进入系统';
            }
            if (data.finished_url) {
                els.finishedBtn.href = data.finished_url;
                scheduleAutoRedirectToFinished(data.finished_url);
            }
            stopPolling();
            return;
        }

        if (isFailed) {
            terminal = true;
            els.okAlert.hidden = true;
            els.infoAlert.hidden = true;
            els.errAlert.hidden = false;
            syncFeedbackOnFailure();
            els.errTitle.textContent = messages.failed;
            if (data.user_message) {
                els.infoText.textContent = data.user_message;
            }
            stopPolling();
            return;
        }

        terminal = false;
        els.okAlert.hidden = true;
        els.errAlert.hidden = !(data.errors && data.errors.length);
        syncFeedbackOnFailure();
        els.infoAlert.hidden = false;
        els.infoText.textContent = data.user_message || (isActiveStatus(data.status)
            ? (data.status === 'pending' ? messages.waiting : '正在执行：' + (data.current_label || '安装步骤'))
            : messages.waiting);
    }

    const terminalErrors = [messages.csrfExpired, messages.sessionExpired];

    function showNetworkRetry(message) {
        const msg = message || messages.networkRetry;
        const isTerminalError = terminalErrors.includes(msg);

        if (isTerminalError) {
            terminal = true;
            stopPolling();
            els.infoAlert.hidden = true;
            els.errAlert.hidden = false;
            syncFeedbackOnFailure();
            els.errTitle.textContent = '安装中断';
            els.errors.innerHTML = '<li>' + msg + '</li>';
        } else {
            els.infoAlert.hidden = false;
            els.infoText.textContent = msg;
        }
    }

    async function pollState() {
        if (terminal || runningStep) {
            return;
        }
        try {
            const data = await fetchJson(stateUrl, 'GET');
            renderState(data);
            // 即使 already_installed=true，只要 status 不是终止态，仍需继续 runStep
            // 以完成 RunningOptionalFinalizers 和 Completed 阶段
            const shouldRun = !terminal && !runningStep && !isTerminalStatus(data.status);
            if (shouldRun) {
                await runStep();
            }
        } catch (e) {
            showNetworkRetry(e.message || messages.networkRetry);
        }
    }

    async function runStep() {
        if (runningStep || terminal) {
            return;
        }
        runningStep = true;
        try {
            const data = await fetchJson(runStepUrl, 'POST');
            renderState(data);
        } catch (e) {
            showNetworkRetry(e.message || messages.networkRetry);
        } finally {
            runningStep = false;
        }
    }

    async function retryStep() {
        if (runningStep) {
            return;
        }
        runningStep = true;
        terminal = false;
        els.retryBtn.disabled = true;
        try {
            const data = await fetchJson(retryStepUrl, 'POST');
            renderState(data);
            if (!terminal && isActiveStatus(data.status)) {
                startPolling();
                await runStep();
            }
        } catch (e) {
            els.errAlert.hidden = false;
            syncFeedbackOnFailure();
            els.errTitle.textContent = messages.failed;
            els.errors.innerHTML = '<li>' + (e.message || '重试失败，请稍后再试。') + '</li>';
        } finally {
            runningStep = false;
            els.retryBtn.disabled = false;
        }
    }

    function startPolling() {
        stopPolling();
        pollTimer = window.setInterval(pollState, pollIntervalMs);
    }

    function stopPolling() {
        if (pollTimer !== null) {
            window.clearInterval(pollTimer);
            pollTimer = null;
        }
    }

    els.retryBtn.addEventListener('click', retryStep);

    pollState();
    startPolling();
})();
</script>
@endif

</body>
</html>
