(function () {
  'use strict';

  var API = '/blog/leaderboard.php';
  var options = null;
  var currentMode = '';
  var csrf = '';
  var currentUser = null;
  var root = null;
  var runs = Object.create(null);
  var readyResolve;
  var ready = new Promise(function (resolve) { readyResolve = resolve; });

  function escapeHtml(value) {
    return String(value == null ? '' : value).replace(/[&<>"']/g, function (char) {
      return { '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#039;' }[char];
    });
  }

  function formatTime(ms) {
    ms = Math.max(0, Number(ms) || 0);
    var total = ms / 1000;
    if (total < 60) return total.toFixed(2) + 's';
    var minutes = Math.floor(total / 60);
    var seconds = (total - minutes * 60).toFixed(2).padStart(5, '0');
    return minutes + ':' + seconds;
  }

  function formatScore(value) {
    var n = Number(value) || 0;
    return Number.isInteger(n) ? String(n) : n.toFixed(1);
  }

  function styleText() {
    return `
      :host{all:initial;font-family:Inter,ui-sans-serif,system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;color:#eff5ff}
      *{box-sizing:border-box}
      button,a{font:inherit}
      .trigger{position:fixed;right:max(16px,env(safe-area-inset-right));bottom:max(16px,env(safe-area-inset-bottom));z-index:2147483000;display:flex;align-items:center;gap:8px;min-height:46px;padding:0 15px;border:1px solid rgba(255,255,255,.23);border-radius:4px;background:#111722;color:#fff;font-size:13px;font-weight:850;letter-spacing:.03em;cursor:pointer;box-shadow:5px 5px 0 #2447d8,0 14px 40px rgba(0,0,0,.28)}
      .trigger:hover,.trigger:focus-visible{outline:2px solid #d5ff52;outline-offset:2px;transform:translate(-1px,-1px)}
      .live{width:7px;height:7px;border-radius:50%;background:#d5ff52;box-shadow:0 0 10px #d5ff52}
      .backdrop{position:fixed;inset:0;z-index:2147482999;display:none;align-items:center;justify-content:center;padding:18px;background:rgba(3,7,14,.72);backdrop-filter:blur(8px)}
      .backdrop.open{display:flex}
      .panel{width:min(680px,100%);max-height:min(760px,calc(100vh - 36px));overflow:hidden;border:1px solid rgba(255,255,255,.16);border-radius:6px;background:#111722;color:#eff5ff;box-shadow:10px 10px 0 #2447d8,0 30px 90px rgba(0,0,0,.5)}
      .head{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;padding:22px 22px 18px;border-bottom:1px solid rgba(255,255,255,.13);background:linear-gradient(120deg,rgba(36,71,216,.22),transparent 55%)}
      .kicker{display:block;margin-bottom:5px;color:#d5ff52;font:800 10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.14em;text-transform:uppercase}
      h2{margin:0;font-family:ui-serif,Georgia,"Noto Serif SC","Songti SC",serif;font-size:29px;line-height:1.1;letter-spacing:-.04em}
      .close{width:38px;height:38px;border:1px solid rgba(255,255,255,.18);border-radius:0;background:transparent;color:#fff;font-size:22px;cursor:pointer}
      .close:hover{background:#d5ff52;color:#111722}
      .account{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 22px;border-bottom:1px solid rgba(255,255,255,.1);color:#9aa8bd;font-size:12px}
      .account b{color:#fff}.account a{color:#d5ff52;text-decoration:none;font-weight:800}
      .modes{display:flex;gap:7px;overflow:auto;padding:13px 22px;border-bottom:1px solid rgba(255,255,255,.1)}
      .mode{flex:0 0 auto;min-height:32px;padding:0 10px;border:1px solid rgba(255,255,255,.15);border-radius:2px;background:transparent;color:#aab6c8;font-size:11px;font-weight:800;cursor:pointer}
      .mode.active{border-color:#4d6dff;background:#2447d8;color:#fff}
      .mine{display:none;margin:14px 22px 0;padding:12px 13px;border-left:3px solid #d5ff52;background:rgba(213,255,82,.07);font-size:12px}
      .mine.show{display:flex;justify-content:space-between;gap:14px}.mine strong{color:#d5ff52}
      .table-wrap{max-height:450px;overflow:auto;padding:10px 22px 20px}
      table{width:100%;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums}
      th,td{padding:11px 7px;border-bottom:1px solid rgba(255,255,255,.09);text-align:left}
      th{position:sticky;top:0;background:#111722;color:#7f8da3;font:800 10px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.1em;text-transform:uppercase}
      th:first-child,td:first-child{width:48px;text-align:center}
      th:last-child,td:last-child{text-align:right}
      tr.me td{background:rgba(36,71,216,.18)}
      .rank{font-weight:900;color:#8c9bb0}.rank.top{color:#d5ff52}
      .name{font-weight:800}.badge{display:inline-flex;margin-left:5px;padding:2px 4px;border:1px solid #4d6dff;color:#8fa4ff;font:800 9px/1 ui-monospace,monospace;vertical-align:2px}.vip{border-color:#d5ff52;background:#d5ff52;color:#111722}
      .value{font-size:15px;font-weight:900}.sub{display:block;margin-top:2px;color:#78869b;font-size:10px}
      .empty,.loading{padding:38px 10px;text-align:center;color:#7f8da3}
      .foot{padding:11px 22px;border-top:1px solid rgba(255,255,255,.1);color:#6f7e94;font-size:10px;line-height:1.5}
      .toast{position:fixed;left:50%;bottom:max(76px,calc(env(safe-area-inset-bottom) + 76px));z-index:2147483001;max-width:min(420px,calc(100vw - 32px));padding:11px 15px;border:1px solid rgba(255,255,255,.18);border-radius:3px;background:#111722;color:#fff;font-size:12px;font-weight:750;box-shadow:0 12px 35px rgba(0,0,0,.35);opacity:0;pointer-events:none;transform:translate(-50%,8px);transition:.2s ease}
      .toast.show{opacity:1;transform:translate(-50%,0)}.toast.good{border-color:#d5ff52}.toast.bad{border-color:#f87171}
      @media(max-width:560px){.backdrop{padding:0;align-items:flex-end}.panel{width:100%;max-height:88vh;border-radius:10px 10px 0 0;box-shadow:0 -5px 0 #2447d8}.head{padding:19px 16px 15px}.account,.modes{padding-left:16px;padding-right:16px}.mine{margin-left:16px;margin-right:16px}.table-wrap{padding-left:10px;padding-right:10px}.trigger{right:12px;bottom:max(12px,env(safe-area-inset-bottom));min-height:43px;padding:0 12px}.badge{display:none}}
      @media(prefers-reduced-motion:reduce){.trigger,.toast{transition:none}}
    `;
  }

  function mount() {
    var host = document.createElement('div');
    host.id = 'adnovel-arena';
    document.body.appendChild(host);
    root = host.attachShadow({ mode: 'open' });
    root.innerHTML = '<style>' + styleText() + '</style>' +
      '<button class="trigger" type="button" aria-haspopup="dialog"><span class="live"></span>全站排行</button>' +
      '<div class="backdrop"><section class="panel" role="dialog" aria-modal="true" aria-labelledby="arena-title">' +
      '<header class="head"><div><span class="kicker">Adnovel Arena / Live</span><h2 id="arena-title">全站排行榜</h2></div><button class="close" type="button" aria-label="关闭">×</button></header>' +
      '<div class="account">正在连接全站成绩…</div><div class="modes"></div><div class="mine"></div>' +
      '<div class="table-wrap"><div class="loading">读取排行榜…</div></div>' +
      '<div class="foot">每个网站账号、每种模式仅保留个人最佳成绩；计时榜越快越靠前，积分榜高分优先、同分时用时更短优先。</div>' +
      '</section></div><div class="toast"></div>';

    root.querySelector('.trigger').addEventListener('click', openPanel);
    root.querySelector('.close').addEventListener('click', closePanel);
    root.querySelector('.backdrop').addEventListener('click', function (event) {
      if (event.target === this) closePanel();
    });
    document.addEventListener('keydown', function (event) {
      if (event.key === 'Escape') closePanel();
    });
  }

  function openPanel() {
    root.querySelector('.trigger').style.display = 'none';
    root.querySelector('.backdrop').classList.add('open');
    root.querySelector('.close').focus();
    loadBoard(currentMode);
  }

  function closePanel() {
    if (!root) return;
    root.querySelector('.backdrop').classList.remove('open');
    root.querySelector('.trigger').style.removeProperty('display');
  }

  var toastTimer = 0;
  function toast(message, good) {
    if (!root) return;
    var el = root.querySelector('.toast');
    el.textContent = message;
    el.className = 'toast show ' + (good ? 'good' : 'bad');
    clearTimeout(toastTimer);
    toastTimer = setTimeout(function () { el.className = 'toast'; }, 3200);
  }

  function accountHtml() {
    if (currentUser) {
      return '<span>站点账号 <b>' + escapeHtml(currentUser.username) + '</b> · LV.' + currentUser.level + (currentUser.vip ? ' · VIP' : '') + '</span><span>成绩自动上榜</span>';
    }
    var next = encodeURIComponent(location.pathname + location.search);
    return '<span>当前未登录，仍可查看排行榜</span><a href="/blog/login.php?next=' + next + '">登录后参赛 →</a>';
  }

  function renderModes(modes) {
    var box = root.querySelector('.modes');
    box.innerHTML = '';
    Object.keys(modes).forEach(function (key) {
      var button = document.createElement('button');
      button.type = 'button';
      button.className = 'mode' + (key === currentMode ? ' active' : '');
      button.textContent = modes[key];
      button.addEventListener('click', function () { setMode(key); });
      box.appendChild(button);
    });
  }

  function renderBoard(data) {
    currentUser = data.user;
    csrf = data.csrf || csrf;
    root.querySelector('.account').innerHTML = accountHtml();
    root.querySelector('#arena-title').textContent = data.title + ' · 全站榜';
    renderModes(data.modes || {});

    var mine = root.querySelector('.mine');
    if (data.me) {
      var myValue = data.metric === 'time' ? formatTime(data.me.duration_ms) : formatScore(data.me.score);
      mine.className = 'mine show';
      mine.innerHTML = '<span>我的最佳 <strong>' + myValue + '</strong></span><span>全站第 <strong>' + data.me.rank + '</strong> 名</span>';
    } else {
      mine.className = 'mine';
      mine.innerHTML = '';
    }

    var wrap = root.querySelector('.table-wrap');
    if (!data.rows.length) {
      wrap.innerHTML = '<div class="empty">这个模式还没有成绩。<br>完成第一局，成为榜首。</div>';
      return;
    }
    var rows = data.rows.map(function (row) {
      var primary = data.metric === 'time' ? formatTime(row.duration_ms) : formatScore(row.score);
      var secondary = data.metric === 'time'
        ? (options.game === 'schulte' ? '错点 ' + formatScore(row.score) : '完成记录')
        : '用时 ' + formatTime(row.duration_ms);
      var mineClass = currentUser && row.username === currentUser.username ? ' class="me"' : '';
      return '<tr' + mineClass + '><td><span class="rank ' + (row.rank <= 3 ? 'top' : '') + '">' + String(row.rank).padStart(2, '0') + '</span></td>' +
        '<td><span class="name">' + escapeHtml(row.username) + '</span><span class="badge">LV.' + row.level + '</span>' + (row.vip ? '<span class="badge vip">VIP</span>' : '') + '</td>' +
        '<td><span class="value">' + primary + '</span><span class="sub">' + secondary + '</span></td></tr>';
    }).join('');
    wrap.innerHTML = '<table><thead><tr><th>#</th><th>玩家</th><th>' + (data.metric === 'time' ? '最佳用时' : '最高成绩') + '</th></tr></thead><tbody>' + rows + '</tbody></table>';
  }

  async function loadBoard(mode) {
    if (!options || !root) return null;
    currentMode = mode || currentMode || options.defaultMode || Object.keys(options.modes || {})[0];
    root.querySelector('.table-wrap').innerHTML = '<div class="loading">读取排行榜…</div>';
    try {
      var response = await fetch(API + '?game=' + encodeURIComponent(options.game) + '&mode=' + encodeURIComponent(currentMode), { credentials:'same-origin', cache:'no-store' });
      var data = await response.json();
      if (!response.ok || !data.ok) throw new Error(data.error || '排行榜暂时不可用');
      renderBoard(data);
      return data;
    } catch (error) {
      root.querySelector('.table-wrap').innerHTML = '<div class="empty">' + escapeHtml(error.message) + '</div>';
      return null;
    }
  }

  function setMode(mode) {
    if (!options || !options.modes[mode]) return;
    currentMode = mode;
    loadBoard(mode);
  }

  async function post(payload) {
    var response = await fetch(API, {
      method:'POST', credentials:'same-origin',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify(payload)
    });
    var data = await response.json().catch(function () { return { ok:false, error:'服务器响应异常' }; });
    if (!response.ok || !data.ok) throw new Error(data.error || '请求失败');
    return data;
  }

  async function start(mode) {
    await ready;
    mode = mode || currentMode;
    currentMode = mode;
    if (!currentUser) {
      runs[mode] = Promise.resolve(null);
      return null;
    }
    runs[mode] = post({ action:'start', game:options.game, mode:mode, csrf:csrf })
      .then(function (data) { return data.run_token; })
      .catch(function (error) { toast(error.message, false); return null; });
    return runs[mode];
  }

  async function submit(result) {
    await ready;
    var mode = result.mode || currentMode;
    if (!currentUser) {
      toast('已完成本局；登录网站账号后，成绩才会进入全站榜。', false);
      return null;
    }
    var runPromise = runs[mode];
    runs[mode] = null;
    var runToken = runPromise ? await runPromise : null;
    if (!runToken) {
      toast('本局没有有效的开局记录，未写入全站榜。', false);
      return null;
    }
    try {
      var data = await post({
        action:'submit', game:options.game, mode:mode, csrf:csrf, run_token:runToken,
        score:Number(result.score) || 0,
        duration_ms:Math.max(0, Math.round(Number(result.durationMs) || 0)),
        meta:result.meta || {}
      });
      toast(data.message + ' · 第 ' + data.rank + ' 名', !!data.saved);
      await loadBoard(mode);
      return data;
    } catch (error) {
      toast(error.message, false);
      return null;
    }
  }

  function init(config) {
    if (options) return ready;
    options = config || {};
    options.modes = options.modes || {};
    currentMode = options.defaultMode || Object.keys(options.modes)[0] || '';
    mount();
    loadBoard(currentMode).then(function (data) {
      readyResolve(data);
    });
    return ready;
  }

  window.AdnovelArena = { init:init, start:start, submit:submit, setMode:setMode, open:openPanel, ready:ready };
})();
