<?php

declare(strict_types=1);



require_once __DIR__ . DIRECTORY_SEPARATOR . 'auth.php';

require_once __DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'layout.php';

require_once __DIR__ . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'dashboard_icons.php';

fate_admin_require_login();



$pdo = null;

try {

    require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'pdo.php';

    if (isset($GLOBALS['pdo']) && $GLOBALS['pdo'] instanceof PDO) {

        $pdo = $GLOBALS['pdo'];

    }

} catch (Throwable $e) {

    fate_admin_layout_start(['page' => '同步', 'active' => 'sync', 'body_class' => 'admin-body--sync']);

    echo '<div class="msg bad">無法連線資料庫：' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';

    fate_admin_layout_end();

    exit;

}



if (!$pdo instanceof PDO) {

    fate_admin_layout_start(['page' => '同步', 'active' => 'sync', 'body_class' => 'admin-body--sync']);

    echo '<div class="msg bad">未取得 PDO。</div>';

    fate_admin_layout_end();

    exit;

}



$csrf = fate_admin_csrf_token();

$batchDetail = defined('FATE_SYNC_BATCH_DETAIL') ? max(1, (int) FATE_SYNC_BATCH_DETAIL) : 6;

require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'spa_store_sync.php';

$batchImages = defined('FATE_SYNC_BATCH_IMAGES') ? max(1, (int) FATE_SYNC_BATCH_IMAGES) : 4;

$uploadStats = fate_spa_stores_local_upload_stats();

$uploadFreeBytes = fate_spa_stores_upload_free_bytes();

$uploadMinFreeBytes = defined('FATE_SYNC_IMAGE_MIN_FREE_BYTES') ? (int) FATE_SYNC_IMAGE_MIN_FREE_BYTES : 6442450944;

$uploadLowDisk = $uploadFreeBytes !== null && $uploadFreeBytes < $uploadMinFreeBytes;

$imageInventoryPreview = fate_spa_image_sync_build_inventory($pdo);

$snapshotTableOk = false;
try {
    $pdo->query('SELECT 1 FROM `spa_store_snapshots` LIMIT 1');
    $snapshotTableOk = true;
} catch (Throwable $e) {
    $msg = $e->getMessage();
    $snapshotTableOk = !(
        str_contains($msg, 'spa_store_snapshots')
        || str_contains($msg, "doesn't exist")
        || str_contains($msg, '1146')
    );
}

$syncKeyGate = fate_spa_sync_api_key_gate();

$keyOk = !empty($syncKeyGate['ok']);

$keyHint = trim((string) ($syncKeyGate['hint_zh'] ?? ''));



fate_admin_layout_start(['page' => '同步店家', 'active' => 'sync', 'body_class' => 'admin-body--sync']);

?>

        <div class="sync">

            <header class="sync-hero" aria-label="同步店家">

                <div class="sync-hero__glow" aria-hidden="true"></div>

                <div class="sync-hero__inner">

                    <div class="sync-hero__lead">

                        <span class="sync-hero__icon" aria-hidden="true"><?= fate_admin_dashboard_icon('sync') ?></span>

                        <div class="sync-hero__copy">

                            <h1 class="sync-hero__title">同步店家</h1>

                            <p class="sync-hero__sub">從新手村 API 分批更新 <code>spa_store_snapshots</code> 店家資料。圖片無須另做設定：<code>fate_spa_asset_url()</code> 會自動以本站 <code>uploads/stores/</code> 快取為優先，沒有則改走新手村。</p>

                        </div>

                    </div>

                    <span class="sync-pill<?= $keyOk ? ' sync-pill--ok' : ' sync-pill--bad' ?>">

                        <?= $keyOk ? '金鑰已就緒' : '尚無法同步' ?>

                    </span>

                </div>

            </header>



            <?php if (!$keyOk): ?>

                <div class="sync-banner sync-banner--bad" role="alert">

                    <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('alert-bad') ?></span>

                    <div class="sync-banner__body">

                        <p class="sync-banner__text"><?= htmlspecialchars($keyHint !== '' ? $keyHint : '請至新手村 API 確認金鑰已啟用。', ENT_QUOTES, 'UTF-8') ?></p>

                        <p class="sync-banner__links">

                            <a href="spa_api_key.php">新手村 API</a>

                            <span aria-hidden="true">·</span>

                            <a href="template.php">網站與模版</a>

                        </p>

                    </div>

                </div>

            <?php elseif ($keyHint !== ''): ?>

                <div class="sync-banner sync-banner--ok" role="status">

                    <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('check') ?></span>

                    <p class="sync-banner__text"><?= htmlspecialchars($keyHint, ENT_QUOTES, 'UTF-8') ?></p>

                </div>

            <?php endif; ?>



            <?php if (!$snapshotTableOk): ?>

                <div class="sync-banner sync-banner--bad" role="alert">

                    <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('alert-bad') ?></span>

                    <div class="sync-banner__body">

                        <p class="sync-banner__text">尚未建立店家快照表，請先完成流程步驟 1：<a href="setup_snapshots_table.php">建立快照表</a>。</p>

                    </div>

                </div>

            <?php endif; ?>



            <div class="sync-layout">

                <div class="sync-main">

                <section class="sync-panel sync-panel--run" aria-labelledby="sync-run-title">

                    <header class="sync-panel__head">

                        <h2 id="sync-run-title" class="sync-panel__title">執行同步</h2>

                    </header>



                    <div class="sync-run" id="batch-panel" data-csrf="<?= htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8') ?>">

                        <label class="sync-option" for="force-sync">

                            <input type="checkbox" id="force-sync">

                            <span class="sync-option__box" aria-hidden="true"></span>

                            <span class="sync-option__text">

                                <strong>強制更新</strong>

                                <span class="sync-option__hint">略過版本比對，全部重新寫入</span>

                            </span>

                        </label>



                        <div class="sync-run__actions">

                            <button type="button" class="sync-btn sync-btn--primary" id="btn-start-batch" <?= (!$keyOk || !$snapshotTableOk) ? 'disabled title="' . htmlspecialchars(!$snapshotTableOk ? '請先建立快照表' : '金鑰未啟用或未設定', ENT_QUOTES, 'UTF-8') . '"' : '' ?>>

                                <?= fate_admin_dashboard_icon('sync') ?>

                                <span>開始分批同步</span>

                            </button>

                            <button type="button" class="sync-btn sync-btn--danger" id="btn-cancel-batch" disabled>

                                <span>停止並清除進度</span>

                            </button>

                        </div>



                        <div class="sync-progress">

                            <div class="sync-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="prog-bar">

                                <i id="prog-fill"></i>

                            </div>

                            <p class="sync-progress__meta" id="prog-text">尚未開始</p>

                        </div>



                        <div id="batch-msg" class="sync-run__msg"></div>



                        <details class="sync-log-wrap">

                            <summary>執行日誌</summary>

                            <pre class="sync-log" id="sync-log" aria-live="polite"></pre>

                        </details>

                    </div>

                </section>



                <section class="sync-panel sync-panel--run sync-panel--images" aria-labelledby="sync-images-title">

                    <header class="sync-panel__head">

                        <h2 id="sync-images-title" class="sync-panel__title">同步圖片至本站</h2>

                    </header>



                    <div class="sync-banner sync-banner--ok" role="note">

                        <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('check') ?></span>

                        <div class="sync-banner__body">

                            <p class="sync-banner__text"><strong>選用功能。</strong>僅完成「同步店家」時，封面／相簿仍會正常顯示（自動從新手村載入）。下方「同步圖片至本站」只是把檔案預先拉回 <code><?= htmlspecialchars(fate_spa_stores_local_upload_rel(), ENT_QUOTES, 'UTF-8') ?></code>，之後同一張圖會<strong>自動改由本站提供</strong>，無須改資料庫或手動切換。</p>

                        </div>

                    </div>



                    <div class="sync-banner sync-banner--bad" role="note">

                        <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('alert-bad') ?></span>

                        <div class="sync-banner__body">

                            <p class="sync-banner__text"><strong>若執行本項：</strong>建議主機可用空間 ≥ 6GB，下載所產生的<strong>對外流量由站方自負</strong>（依店家數與相簿張數而異）。請先完成上方「同步店家」。<strong>本站並不擁有這些相片的版權</strong>；同步或下載後請勿加上浮水印。</p>

                        </div>

                    </div>



                    <?php if ($uploadLowDisk): ?>

                        <div class="sync-banner sync-banner--bad" role="alert">

                            <span class="sync-banner__icon"><?= fate_admin_dashboard_icon('alert-bad') ?></span>

                            <p class="sync-banner__text">偵測到可用磁碟空間約 <?= htmlspecialchars(fate_spa_format_bytes($uploadFreeBytes ?? 0), ENT_QUOTES, 'UTF-8') ?>，低於建議的 6GB；仍要同步請自行評估風險。</p>

                        </div>

                    <?php endif; ?>



                    <dl class="spa-api-info sync-images-info">

                        <div class="spa-api-info__row">

                            <dt>本站儲存目錄</dt>

                            <dd><code><?= htmlspecialchars(fate_spa_stores_local_upload_rel(), ENT_QUOTES, 'UTF-8') ?></code>（實體路徑：<code><?= htmlspecialchars($uploadStats['dir'], ENT_QUOTES, 'UTF-8') ?></code>）</dd>

                        </div>

                        <div class="spa-api-info__row">

                            <dt>目前已快取</dt>

                            <dd><?= (int) $uploadStats['count'] ?> 張 · <?= htmlspecialchars(fate_spa_format_bytes((int) $uploadStats['bytes']), ENT_QUOTES, 'UTF-8') ?></dd>

                        </div>

                        <div class="spa-api-info__row">

                            <dt>快照待下載（唯一 path）</dt>

                            <dd><?= (int) ($imageInventoryPreview['unique_paths'] ?? 0) ?> 個 path · 引用 <?= (int) ($imageInventoryPreview['photo_refs'] ?? 0) ?> 次 · <?= (int) ($imageInventoryPreview['stores_with_photos'] ?? 0) ?> 家有圖</dd>

                        </div>

                        <div class="spa-api-info__row">

                            <dt>預估本地檔名</dt>

                            <dd><?= (int) ($imageInventoryPreview['unique_basenames'] ?? 0) ?> 個（同檔名不同 path 只留一檔）</dd>

                        </div>

                        <?php if ($uploadFreeBytes !== null): ?>

                        <div class="spa-api-info__row">

                            <dt>磁碟可用</dt>

                            <dd><?= htmlspecialchars(fate_spa_format_bytes($uploadFreeBytes), ENT_QUOTES, 'UTF-8') ?></dd>

                        </div>

                        <?php endif; ?>

                    </dl>



                    <p class="spa-api-lead">自快照中店家的封面／相簿 path 自新手村下載至 <code><?= htmlspecialchars(fate_spa_stores_local_upload_rel(), ENT_QUOTES, 'UTF-8') ?></code>。清單以<strong>唯一 path</strong>計（同一檔案被多店引用只下載一次）；若數量低於預期，請先確認「同步店家」已完成且快照含 <code>photos_json</code>。</p>



                    <div class="sync-run" id="batch-images-panel" data-csrf="<?= htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8') ?>">

                        <label class="sync-option" for="force-images">

                            <input type="checkbox" id="force-images">

                            <span class="sync-option__box" aria-hidden="true"></span>

                            <span class="sync-option__text">

                                <strong>強制重新下載</strong>

                                <span class="sync-option__hint">略過「檔案已存在」，覆寫本站快取</span>

                            </span>

                        </label>



                        <div class="sync-run__actions">

                            <button type="button" class="sync-btn sync-btn--primary" id="btn-start-images" <?= (!$keyOk || !$snapshotTableOk) ? 'disabled title="' . htmlspecialchars(!$snapshotTableOk ? '請先建立快照表' : '金鑰未啟用或未設定', ENT_QUOTES, 'UTF-8') . '"' : '' ?>>

                                <?= fate_admin_dashboard_icon('sync') ?>

                                <span>開始同步圖片</span>

                            </button>

                            <button type="button" class="sync-btn sync-btn--danger" id="btn-cancel-images" disabled>

                                <span>停止並清除進度</span>

                            </button>

                        </div>



                        <div class="sync-run__actions sync-run__actions--secondary">

                            <button type="button" class="sync-btn sync-btn--danger" id="btn-purge-images" <?= (int) $uploadStats['count'] <= 0 ? 'disabled title="目前沒有快取檔案"' : '' ?>>

                                <span>我後悔了，刪除所有已下載相片</span>

                            </button>

                        </div>

                        <p class="sync-run__hint">僅刪除圖片同步產生的快取；會員手動上傳的相片會保留。刪除後前台改回從新手村載入。</p>



                        <div class="sync-progress">

                            <div class="sync-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="prog-bar-images">

                                <i id="prog-fill-images"></i>

                            </div>

                            <p class="sync-progress__meta" id="prog-text-images">尚未開始</p>

                        </div>



                        <div id="batch-images-msg" class="sync-run__msg"></div>



                        <details class="sync-log-wrap">

                            <summary>圖片同步日誌</summary>

                            <pre class="sync-log" id="sync-log-images" aria-live="polite"></pre>

                        </details>

                    </div>

                </section>

                </div>



                <aside class="sync-panel sync-panel--guide" aria-labelledby="sync-guide-title">

                    <header class="sync-panel__head">

                        <h2 id="sync-guide-title" class="sync-panel__title">使用流程</h2>

                    </header>



                    <ol class="sync-steps">

                        <li class="sync-steps__item<?= $snapshotTableOk ? ' sync-steps__item--done' : '' ?>">

                            <span class="sync-steps__num" aria-hidden="true">1</span>

                            <div>

                                <strong>建立快照表</strong>

                                <?php if ($snapshotTableOk): ?>

                                    <p><span class="sync-steps__status sync-steps__status--ok">已完成</span> · 資料表 <code>spa_store_snapshots</code> 已就緒。</p>

                                <?php else: ?>

                                    <p>首次必做：建立 <code>spa_store_snapshots</code> 後才能同步。<a href="setup_snapshots_table.php">前往建立快照表</a></p>

                                <?php endif; ?>

                            </div>

                        </li>

                        <li class="sync-steps__item">

                            <span class="sync-steps__num" aria-hidden="true">2</span>

                            <div>

                                <strong>確認金鑰</strong>

                                <p><a href="spa_api_key.php">新手村 API</a> 金鑰須為「啟用」，並與 <a href="template.php">網站設定</a> 一致。</p>

                            </div>

                        </li>

                        <li class="sync-steps__item">

                            <span class="sync-steps__num" aria-hidden="true">3</span>

                            <div>

                                <strong>同步店家</strong>

                                <p>按「開始分批同步」（每輪約 <?= (int) $batchDetail ?> 筆）。完成前請勿關閉此頁。</p>

                            </div>

                        </li>

                        <li class="sync-steps__item">

                            <span class="sync-steps__num" aria-hidden="true">4</span>

                            <div>

                                <strong>完成</strong>

                                <p>進度 100% 後前台即為最新資料；封面／相簿會自動從新手村載入，<strong>不必</strong>另跑圖片同步也能看圖。</p>

                            </div>

                        </li>

                        <li class="sync-steps__item sync-steps__item--optional">

                            <span class="sync-steps__num" aria-hidden="true">5</span>

                            <div>

                                <strong>同步圖片（選用）</strong>

                                <p>僅在想把圖片快取到本站 <code><?= htmlspecialchars(fate_spa_stores_local_upload_rel(), ENT_QUOTES, 'UTF-8') ?></code> 時執行；須先完成步驟 3，建議空間 ≥6GB，每輪約 <?= (int) $batchImages ?> 張。</p>

                            </div>

                        </li>

                    </ol>



                    <p class="sync-foot">

                        進階：伺服器 CLI <code>php admin/sync_spa_stores.php</code> · 資料異常時才勾「強制更新」

                    </p>

                </aside>

            </div>

        </div>



        <script>

(function () {

    var panel = document.getElementById('batch-panel');

    var csrf = panel.getAttribute('data-csrf');

    var btnStart = document.getElementById('btn-start-batch');

    var btnCancel = document.getElementById('btn-cancel-batch');

    var forceEl = document.getElementById('force-sync');

    var progBar = document.getElementById('prog-bar');

    var progFill = document.getElementById('prog-fill');

    var progText = document.getElementById('prog-text');

    var batchMsg = document.getElementById('batch-msg');

    var logEl = document.getElementById('sync-log');

    var logWrap = logEl && logEl.closest('details');

    var abortFlag = false;



    function logLine(s) {

        var t = new Date().toISOString().slice(11, 19);

        logEl.textContent += '[' + t + '] ' + s + '\n';

        logEl.scrollTop = logEl.scrollHeight;

        if (logWrap && !logWrap.open) {

            logWrap.open = true;

        }

    }



    function setProgress(pct, label) {

        var n = pct != null ? Math.max(0, Math.min(100, Number(pct))) : 0;

        progFill.style.width = n + '%';

        progBar.setAttribute('aria-valuenow', String(Math.round(n)));

        progText.textContent = label;

    }



    function setBusy(b) {

        btnStart.disabled = b;

        btnCancel.disabled = !b;

        forceEl.disabled = b;

    }



    function showMsg(text, cls) {

        if (!text) {

            batchMsg.innerHTML = '';

            return;

        }

        var box = document.createElement('div');

        box.className = 'msg ' + (cls || 'warn');

        box.textContent = text;

        batchMsg.innerHTML = '';

        batchMsg.appendChild(box);

    }



    function syncUserMessage(j) {

        if (!j || typeof j !== 'object') {

            return '無法開始同步，請稍後再試。';

        }

        if (j.message) {

            return String(j.message);

        }

        if (j.hint_zh) {

            return String(j.hint_zh);

        }

        return '無法開始同步，請至「新手村 API」確認金鑰已啟用。';

    }



    function progressLabel(j) {

        var seen = j.stats && j.stats.stores_seen != null ? j.stats.stores_seen : '?';

        var total = j.catalog_total != null ? j.catalog_total : '?';

        var round = j.processed_this_round || 0;

        if (j.progress_percent != null) {

            return '階段 ' + j.phase + ' · 約 ' + j.progress_percent + '% · 已處理 ' + seen + '／' + total + '（本輪 ' + round + ' 筆）';

        }

        return '階段 ' + j.phase + ' · 準備中… 已處理 ' + seen;

    }



    async function api(action, extra) {

        var body = Object.assign({ action: action, csrf: csrf }, extra || {});

        var apiFetch = window.fateAdminApiFetch || fetch;

        var r = await apiFetch('sync_chunk_api.php', {

            method: 'POST',

            headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },

            body: JSON.stringify(body),

            credentials: 'same-origin'

        });

        var j = await r.json().catch(function () { return { ok: false, error: 'bad_json' }; });

        if (!r.ok && j.ok !== false) {

            j = { ok: false, error: 'http_' + r.status };

        }

        return j;

    }



    btnCancel.addEventListener('click', function () {

        abortFlag = true;

        logLine('收到停止請求（本輪結束後清除進度）…');

    });



    btnStart.addEventListener('click', async function () {

        abortFlag = false;

        logEl.textContent = '';

        showMsg('');

        setBusy(true);

        setProgress(0, '準備中…');



        var st = await api('start', { force: !!forceEl.checked });

        if (!st.ok) {

            var startMsg = syncUserMessage(st);

            logLine('開始失敗: ' + startMsg);

            showMsg(startMsg, 'bad');

            setBusy(false);

            return;

        }

        if (st.api_key_gate || (st.diagnostics && st.diagnostics.api_key_gate)) {

            logLine(st.api_key_gate || st.diagnostics.api_key_gate);

        }

        logLine('已開始；目錄總數=' + (st.catalog_total != null ? st.catalog_total : '?'));



        while (!abortFlag) {

            var j = await api('step');

            if (!j.ok && j.error === 'no_job') {

                logLine('無進度狀態');

                break;

            }

            if (!j.ok) {

                var stepMsg = syncUserMessage(j);

                logLine('錯誤: ' + stepMsg);

                showMsg(stepMsg, 'bad');

                break;

            }



            setProgress(j.progress_percent, progressLabel(j));



            if (j.stats) {

                logLine(

                    'updated=' + j.stats.updated +

                    ' skipped=' + j.stats.skipped_same_revision +

                    ' errors=' + j.stats.errors +

                    ' pages=' + j.stats.fetched_pages

                );

            }



            if (j.fatal && j.hint_zh) {

                showMsg(j.hint_zh, 'bad');

            }



            if (j.finished) {

                if (j.fatal) {

                    logLine('中止');

                    showMsg(j.hint_zh || '同步因錯誤中止，請見執行日誌。', 'bad');

                } else if (j.sync_ok) {

                    showMsg('同步完成。', 'ok');

                    setProgress(100, '已完成');

                    logLine('完成');

                } else {

                    showMsg('同步結束但有錯誤，請見執行日誌。', 'warn');

                    logLine('結束（含錯誤）');

                }



                logLine(JSON.stringify({

                    stats: j.stats,

                    catalog_total: j.catalog_total,

                    diagnostics: j.diagnostics || {}

                }, null, 2));

                break;

            }



            await new Promise(function (r) { setTimeout(r, 40); });

        }



        if (abortFlag) {

            await api('cancel');

            logLine('已停止並清除進度');

            setProgress(0, '已停止');

        }



        setBusy(false);

    });

})();

(function () {

    var panel = document.getElementById('batch-images-panel');

    if (!panel) return;

    var csrf = panel.getAttribute('data-csrf');

    var btnStart = document.getElementById('btn-start-images');

    var btnCancel = document.getElementById('btn-cancel-images');

    var forceEl = document.getElementById('force-images');

    var btnPurge = document.getElementById('btn-purge-images');

    var progBar = document.getElementById('prog-bar-images');

    var progFill = document.getElementById('prog-fill-images');

    var progText = document.getElementById('prog-text-images');

    var batchMsg = document.getElementById('batch-images-msg');

    var logEl = document.getElementById('sync-log-images');

    var logWrap = logEl && logEl.closest('details');

    var abortFlag = false;



    function logLine(s) {

        var t = new Date().toISOString().slice(11, 19);

        logEl.textContent += '[' + t + '] ' + s + '\n';

        logEl.scrollTop = logEl.scrollHeight;

        if (logWrap && !logWrap.open) logWrap.open = true;

    }



    function setProgress(pct, label) {

        var n = pct != null ? Math.max(0, Math.min(100, Number(pct))) : 0;

        progFill.style.width = n + '%';

        progBar.setAttribute('aria-valuenow', String(Math.round(n)));

        progText.textContent = label;

    }



    function setBusy(b) {

        btnStart.disabled = b;

        btnCancel.disabled = !b;

        forceEl.disabled = b;

        if (btnPurge) btnPurge.disabled = b;

    }



    function showMsg(text, cls) {

        if (!text) { batchMsg.innerHTML = ''; return; }

        var box = document.createElement('div');

        box.className = 'msg ' + (cls || 'warn');

        box.textContent = text;

        batchMsg.innerHTML = '';

        batchMsg.appendChild(box);

    }



    function syncUserMessage(j) {

        if (!j || typeof j !== 'object') return '無法開始圖片同步，請稍後再試。';

        if (j.message) return String(j.message);

        if (j.hint_zh) return String(j.hint_zh);

        return '無法開始圖片同步，請確認金鑰已啟用。';

    }



    function progressLabel(j) {

        var total = j.image_total != null ? j.image_total : '?';

        var st = j.stats || {};

        var done = (st.downloaded || 0) + (st.skipped_exists || 0) + (st.skipped_invalid || 0) + (st.errors || 0);

        if (j.progress_percent != null) {

            return '約 ' + j.progress_percent + '% · 已處理 ' + done + '／' + total

                + '（新下載 ' + (st.downloaded || 0) + '，略過 ' + (st.skipped_exists || 0) + '）';

        }

        return '準備中… 已處理 ' + done;

    }



    async function api(action, extra) {

        var body = Object.assign({ action: action, csrf: csrf }, extra || {});

        var apiFetch = window.fateAdminApiFetch || fetch;

        var r = await apiFetch('sync_chunk_api.php', {

            method: 'POST',

            headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },

            body: JSON.stringify(body),

            credentials: 'same-origin'

        });

        var j = await r.json().catch(function () { return { ok: false, error: 'bad_json' }; });

        if (!r.ok && j.ok !== false) j = { ok: false, error: 'http_' + r.status };

        return j;

    }



    btnCancel.addEventListener('click', function () {

        abortFlag = true;

        logLine('收到停止請求…');

    });



    btnStart.addEventListener('click', async function () {

        abortFlag = false;

        logEl.textContent = '';

        showMsg('');

        setBusy(true);

        setProgress(0, '準備中…');



        var st = await api('images_start', { force: !!forceEl.checked });

        if (!st.ok) {

            var startMsg = syncUserMessage(st);

            logLine('開始失敗: ' + startMsg);

            showMsg(startMsg, 'bad');

            setBusy(false);

            return;

        }

        logLine(
            '待下載唯一 path=' + (st.image_total != null ? st.image_total : '?')
            + '；快照引用 ' + (st.diagnostics && st.diagnostics.photo_refs != null ? st.diagnostics.photo_refs : '?') + ' 次'
            + '；唯一檔名 ' + (st.diagnostics && st.diagnostics.unique_basenames != null ? st.diagnostics.unique_basenames : '?')
            + '；含圖店家 ' + (st.diagnostics && st.diagnostics.stores_with_photos != null ? st.diagnostics.stores_with_photos : '?')
        );



        while (!abortFlag) {

            var j = await api('images_step');

            if (!j.ok && j.error === 'no_job') {

                logLine('無進度狀態');

                break;

            }

            if (!j.ok) {

                showMsg(syncUserMessage(j), 'bad');

                logLine('錯誤: ' + syncUserMessage(j));

                break;

            }



            setProgress(j.progress_percent, progressLabel(j));

            if (j.stats) {

                logLine(

                    'downloaded=' + j.stats.downloaded +

                    ' skipped=' + j.stats.skipped_exists +

                    ' invalid=' + j.stats.skipped_invalid +

                    ' errors=' + j.stats.errors +

                    ' bytes=' + (j.stats.bytes || 0)

                );

            }



            if (j.finished) {

                if (j.sync_ok) {

                    showMsg('圖片同步完成。', 'ok');

                    setProgress(100, '已完成');

                    logLine('完成');

                } else if (j.hint_zh) {

                    showMsg(j.hint_zh, j.image_total === 0 ? 'warn' : 'bad');

                } else {

                    showMsg('圖片同步結束但有錯誤，請見日誌。', 'warn');

                }

                logLine(JSON.stringify({ stats: j.stats, image_total: j.image_total }, null, 2));

                break;

            }



            await new Promise(function (r) { setTimeout(r, 60); });

        }



        if (abortFlag) {

            await api('images_cancel');

            logLine('已停止');

            setProgress(0, '已停止');

        }



        setBusy(false);

    });



    if (btnPurge) {

        btnPurge.addEventListener('click', async function () {

            if (!btnCancel.disabled) {

                showMsg('請先停止進行中的圖片同步。', 'warn');

                return;

            }

            if (!confirm(

                '確定要刪除所有由「圖片同步」下載至本站的快取嗎？\n\n'

                + '會員手動上傳的相片會保留。\n'

                + '刪除後前台將改回從新手村載入圖片。\n\n'

                + '此操作無法復原。'

            )) {

                return;

            }

            btnPurge.disabled = true;

            showMsg('刪除中…', 'warn');

            var j = await api('images_purge');

            if (j.ok) {

                logLine(j.hint_zh || '已刪除快取');

                showMsg(j.hint_zh || '已刪除快取。', 'ok');

                setTimeout(function () { location.reload(); }, 900);

            } else {

                showMsg(j.hint_zh || '刪除失敗，請稍後再試。', 'bad');

                btnPurge.disabled = false;

            }

        });

    }

})();

        </script>

<?php

fate_admin_layout_end();


