// ---------- 3. Sites ----------
let SITE_EDITOR = null;

function _siteEditorBody() {
  const current = SITE_EDITOR || { enabled: true, respect_robots: true, yaml_path: "config/sites/demo.yaml" };
  const strategy = current.default_fetch_strategy || "";
  const managedBy = current.managed_by || "ui";
  return `
    <div class="mb-4 p-3 bg-blue-50 border border-blue-100 rounded text-xs text-blue-900">
      <span class="font-semibold">managed_by = ${esc(managedBy)}</span> · UI 托管的站点可直接保存修改；YAML 托管的站点需同步修改配置文件。
    </div>
    <div class="grid grid-cols-2 gap-4">
      <div><div class="field-label">site_code</div><input id="s-code" value="${esc(current.site_code || "")}" class="field-input mono"/></div>
      <div><div class="field-label">站点名称</div><input id="s-name" value="${esc(current.site_name || "")}" class="field-input"/></div>
      <div class="col-span-2"><div class="field-label">base_url</div><input id="s-base" value="${esc(current.base_url || "")}" class="field-input mono" placeholder="https://example.gov.cn"/></div>
      <div><div class="field-label">site_role</div><input id="s-role" value="${esc(current.site_role || "")}" class="field-input" placeholder="qingyuan_local / county_local"/></div>
      <div></div>
      <!-- ─── 配置来源（cms_adapter XOR yaml_path） ─── -->
      <div class="col-span-2">
        <div class="field-label">配置来源 <span class="text-red-500">*</span> <span class="font-normal text-slate-400 text-[11px]">两者必须二选一</span></div>
        <div class="flex gap-2">
          <label class="flex-1 border rounded-md p-3 cursor-pointer ${(current.cms_adapter || !current.yaml_path) ? "border-[#1D4ED8] bg-blue-50" : "border-slate-200"}" data-src="adapter">
            <input type="radio" name="s-source" value="adapter" ${(current.cms_adapter || !current.yaml_path) ? "checked" : ""} class="mr-2" onchange="_toggleSiteSource()"/>
            <span class="text-sm font-medium">CMS 适配器</span>
            <span class="pill pill-blue ml-2">推荐</span>
            <div class="field-hint">使用内置适配器代码（如 gkmlpt），无需写选择器</div>
          </label>
          <label class="flex-1 border rounded-md p-3 cursor-pointer ${current.yaml_path ? "border-[#1D4ED8] bg-blue-50" : "border-slate-200"}" data-src="yaml">
            <input type="radio" name="s-source" value="yaml" ${current.yaml_path ? "checked" : ""} class="mr-2" onchange="_toggleSiteSource()"/>
            <span class="text-sm font-medium">YAML 选择器</span>
            <span class="pill pill-slate ml-2">手写</span>
            <div class="field-hint">写一份 config/sites/&lt;code&gt;.yaml 提供 CSS 选择器</div>
          </label>
        </div>
      </div>
      <div class="col-span-2" id="s-adapter-row">
        <div class="field-label">cms_adapter</div>
        <input id="s-adapter" value="${esc(current.cms_adapter || "")}" class="field-input mono" placeholder="gkmlpt"/>
        <div class="field-hint">已注册适配器：gkmlpt / trs / yidu …</div>
      </div>
      <div class="col-span-2" id="s-yaml-row" style="display:none">
        <div class="field-label">yaml_path</div>
        <input id="s-yaml" value="${esc(current.yaml_path || "")}" class="field-input mono" placeholder="config/sites/<code>.yaml"/>
        <div class="field-hint">相对项目根；容器里会从 /app/config/sites/* 读取</div>
      </div>
      <div class="col-span-2">
        <div class="field-label">抓取策略</div>
        <div class="flex gap-2">
          <label class="flex-1 border rounded-md p-3 cursor-pointer ${strategy === "playwright" ? "border-[#1D4ED8] bg-blue-50" : "border-slate-200"}">
            <input type="radio" name="s-strategy" value="playwright" ${strategy === "playwright" ? "checked" : ""} class="mr-2"/>
            <span class="text-sm font-medium">playwright</span>
            <span class="pill pill-blue ml-2">推荐</span>
            <div class="field-hint">适合动态 JS 渲染站点</div>
          </label>
          <label class="flex-1 border rounded-md p-3 cursor-pointer ${strategy === "httpx" || !strategy ? "border-[#1D4ED8] bg-blue-50" : "border-slate-200"}">
            <input type="radio" name="s-strategy" value="httpx" ${strategy === "httpx" || !strategy ? "checked" : ""} class="mr-2"/>
            <span class="text-sm font-medium">httpx</span>
            <span class="pill pill-slate ml-2">默认</span>
            <div class="field-hint">静态 HTML / JSON API</div>
          </label>
        </div>
      </div>
      <div class="col-span-2"><div class="field-label">备注</div><input id="s-remark" value="${esc(current.remark || "")}" class="field-input"/></div>
      <div class="col-span-2 flex gap-6 pt-1">
        <label class="flex items-center gap-2 text-sm"><input id="s-enabled" type="checkbox" ${current.enabled ? "checked" : ""} /> <span>启用站点</span></label>
        <label class="flex items-center gap-2 text-sm"><input id="s-robots" type="checkbox" ${current.respect_robots ? "checked" : ""} /> <span>遵守 robots.txt</span></label>
      </div>
      <div class="col-span-2 mt-3 mb-1 text-xs font-semibold text-slate-500 uppercase tracking-wider">抓取计划（防风控）</div>
      <div class="col-span-2 text-[11px] text-slate-500 -mt-1">空 = 不限制；窗口外的 cron 触发会被调度器跳过；单日上限达到后剩余 target 顺延到次日。</div>
      <div>
        <div class="field-label">抓取窗口 · 开始</div>
        <input id="s-win-start" type="time" value="${esc(current.crawl_window_start || "")}" class="field-input"/>
      </div>
      <div>
        <div class="field-label">抓取窗口 · 结束</div>
        <input id="s-win-end" type="time" value="${esc(current.crawl_window_end || "")}" class="field-input"/>
      </div>
      <div>
        <div class="field-label">单日最大请求数</div>
        <input id="s-daily-max" type="number" min="0" value="${current.daily_max_requests ?? ""}" class="field-input" placeholder="不限"/>
      </div>
      <div class="flex items-end pb-1">
        <label class="flex items-center gap-2 text-sm"><input id="s-weekend" type="checkbox" ${current.weekend_enabled !== false ? "checked" : ""} /> <span>周末允许抓取</span></label>
      </div>
      <div>
        <div class="field-label">退避最小（秒）</div>
        <input id="s-backoff-min" type="number" min="0" value="${current.backoff_min_sec ?? ""}" class="field-input" placeholder="0"/>
      </div>
      <div>
        <div class="field-label">退避最大（秒）</div>
        <input id="s-backoff-max" type="number" min="0" value="${current.backoff_max_sec ?? ""}" class="field-input" placeholder="0"/>
      </div>
      <div class="col-span-2 mt-2 pt-2 border-t border-[#E5EBF1]">
        <div class="field-label">站点级 cron 计划（覆盖默认 daily 02:00；留空走默认）</div>
        <input id="s-cron" value="${esc(current.schedule_cron || "")}"
               class="field-input mono"
               placeholder="例如 */30 * * * *  (每 30 分钟)  或  0 7 * * *  (每天 07:00)"/>
        <div class="text-[11px] text-slate-500 mt-1">
          5 字段 cron。优先级：target.schedule_cron → yaml column.schedule → yaml default_column.schedule → <span class="mono">站点级</span> → 内置默认 <span class="mono">0 2 * * *</span>。
        </div>
      </div>
    </div>`;
}

// Switch between cms_adapter and yaml_path inputs based on the radio choice.
// Highlights the picked card and hides the other input row.
function _toggleSiteSource() {
  const src = document.querySelector('input[name="s-source"]:checked')?.value || "adapter";
  const adapterRow = document.getElementById("s-adapter-row");
  const yamlRow = document.getElementById("s-yaml-row");
  if (adapterRow) adapterRow.style.display = src === "adapter" ? "" : "none";
  if (yamlRow) yamlRow.style.display = src === "yaml" ? "" : "none";
  // Visual highlight on the radio cards
  document.querySelectorAll('label[data-src]').forEach(el => {
    const pick = el.dataset.src === src;
    el.classList.toggle("border-[#1D4ED8]", pick);
    el.classList.toggle("bg-blue-50", pick);
    el.classList.toggle("border-slate-200", !pick);
  });
}

function _siteEditorFooter() {
  return `
    <button onclick="closeModal()" class="btn-ghost">取消</button>
    <button onclick="saveSiteEditor()" class="btn-primary">保存站点</button>`;
}

function _openSiteModal() {
  openModal({
    title: SITE_EDITOR && SITE_EDITOR.site_code ? `编辑站点 · ${SITE_EDITOR.site_code}` : "新增站点",
    subtitle: "站点即一个 CMS 网站：base_url + 抓取策略 + 适配器绑定",
    body: _siteEditorBody(),
    footer: _siteEditorFooter(),
    onClose: () => { SITE_EDITOR = null; },
  });
  _toggleSiteSource();  // sync show/hide on first paint
}

function editSite(code) {
  const row = window.__siteRows?.find(s => s.site_code === code);
  if (!row) return;
  SITE_EDITOR = { ...row };
  _openSiteModal();
}

function newSite() {
  SITE_EDITOR = null;
  _openSiteModal();
}

async function saveSiteEditor() {
  // Honor the "配置来源" radio: only one of cms_adapter / yaml_path is sent.
  // The other is forced to null so the DB CHECK (cms_adapter IS NULL) <>
  // (yaml_path IS NULL) accepts the row.
  const source = document.querySelector('input[name="s-source"]:checked')?.value || "adapter";
  const adapterVal = document.getElementById("s-adapter").value.trim() || null;
  const yamlVal    = document.getElementById("s-yaml").value.trim() || null;
  const payload = {
    site_code: document.getElementById("s-code").value.trim(),
    site_name: document.getElementById("s-name").value.trim() || null,
    base_url: document.getElementById("s-base").value.trim(),
    site_role: document.getElementById("s-role").value.trim() || null,
    cms_adapter: source === "adapter" ? adapterVal : null,
    yaml_path:   source === "yaml"    ? yamlVal    : null,
    default_fetch_strategy: (document.querySelector('input[name="s-strategy"]:checked')?.value) || null,
    enabled: document.getElementById("s-enabled").checked,
    respect_robots: document.getElementById("s-robots").checked,
    remark: document.getElementById("s-remark").value.trim() || null,
    crawl_window_start: document.getElementById("s-win-start").value || null,
    crawl_window_end:   document.getElementById("s-win-end").value || null,
    daily_max_requests: document.getElementById("s-daily-max").value === "" ? null : Number(document.getElementById("s-daily-max").value),
    weekend_enabled:    document.getElementById("s-weekend").checked,
    backoff_min_sec:    document.getElementById("s-backoff-min").value === "" ? null : Number(document.getElementById("s-backoff-min").value),
    backoff_max_sec:    document.getElementById("s-backoff-max").value === "" ? null : Number(document.getElementById("s-backoff-max").value),
    schedule_cron:      document.getElementById("s-cron")?.value.trim() || null,
  };
  try {
    if (SITE_EDITOR && SITE_EDITOR.site_code) {
      await api(`/api/sites/${encodeURIComponent(SITE_EDITOR.site_code)}`, {
        method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),
      });
      toast(`已更新 ${payload.site_code}`);
    } else {
      await api("/api/sites", {
        method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),
      });
      toast(`已新增 ${payload.site_code}`);
    }
    SITE_EDITOR = null;
    closeModal();
    PAGES.sites();
  } catch (e) { toast(e.message, "err"); }
}

const SITES_STATE = {
  page: 1,
  pageSize: 20,
  rows: [],
  q: "",
  sortKey: "site_code",
  sortDir: "asc",
};

PAGES.sites = async () => {
  const ws = document.getElementById("workspace");
  ws.innerHTML = `<div class="text-slate-400 text-sm">加载中…</div>`;
  try {
    const { sites } = await api("/api/sites");
    window.__siteRows = sites;
    SITES_STATE.rows = sites;
    SITES_STATE.page = 1;
    ws.innerHTML = `<div id="sites-box"></div>`;
    _renderSitesTable();
  } catch (e) {
    ws.innerHTML = `<div class="card p-6 text-sm text-red-600">加载失败：${esc(e.message)}</div>`;
  }
};

function _renderSitesTable() {
  const { rows, page, pageSize } = SITES_STATE;
  const filteredRows = _filteredSiteRows(rows);
  const sortedRows = _sortedSiteRows(filteredRows);
  const total = sortedRows.length;
  const slice = sortedRows.slice((page - 1) * pageSize, page * pageSize);
  const sortableTh = (key, label, cls = "text-left") => `
    <th class="${cls} px-4 py-2 whitespace-nowrap">
      <button type="button"
              class="inline-flex items-center gap-1 hover:text-slate-800"
              onclick="_sortSitesBy('${key}')">
        <span>${label}</span>
        <span class="text-[10px] ${SITES_STATE.sortKey === key ? "text-blue-600" : "text-slate-300"}">${_siteSortIcon(key)}</span>
      </button>
    </th>`;
  document.getElementById("sites-box").innerHTML = card({
    title: "站点列表",
    actions: `<button onclick="newSite()" class="text-xs px-3 py-1 rounded bg-[#1D4ED8] text-white">+ 新增站点</button>`,
    body: `
      <div class="mb-3 flex items-center gap-2">
        <input id="sites-q"
               value="${esc(SITES_STATE.q)}"
               class="field-input max-w-md"
               placeholder="搜索 site_code / 名称 / base_url / 适配器 / 备注"
               onkeydown="_siteSearchKeydown(event)" />
        <button class="btn-primary" onclick="_applySiteFilter()">搜索</button>
        ${SITES_STATE.q ? `<button class="btn-outline" onclick="_clearSiteFilter()">清空</button>` : ""}
        <div class="text-xs text-slate-400">共 ${total} / ${(rows || []).length} 个站点</div>
      </div>
      <table class="w-full text-sm">
        <thead class="bg-slate-50 text-slate-500 text-xs uppercase">
          <tr>
            ${sortableTh("site_code", "site_code")}
            ${sortableTh("site_name", "名称")}
            ${sortableTh("base_url", "base_url")}
            ${sortableTh("adapter", "适配器", "text-center")}
            ${sortableTh("target_count", "目标数", "text-center")}
            ${sortableTh("enabled", "状态", "text-center")}
            <th class="px-4 py-2 text-right">操作</th>
          </tr>
        </thead>
        <tbody>
          ${slice.map(site => `
            <tr class="border-t border-slate-100 hover:bg-slate-50/60">
              <td class="px-4 py-2 mono text-xs">${esc(site.site_code)}</td>
              <td class="px-4 py-2">${esc(site.site_name || "")}</td>
              <td class="px-4 py-2 text-xs text-slate-500 mono max-w-[340px] truncate" title="${esc(site.base_url || "")}">${esc(site.base_url || "")}</td>
              <td class="px-4 py-2 text-center text-xs">${site.cms_adapter ? pill(site.cms_adapter, "blue") : pill("yaml", "slate")} ${site.managed_by === "ui" ? pill("UI托管", "amber") : ""}</td>
              <td class="px-4 py-2 text-center text-xs">${(site.targets || []).length}</td>
              <td class="px-4 py-2 text-center">${site.enabled ? pill("启用", "green") : pill("停用", "slate")}</td>
              <td class="px-4 py-2 text-right whitespace-nowrap">
                <button onclick="editSite('${esc(site.site_code)}')" class="text-xs px-2 py-1 rounded border border-slate-200 text-slate-700 hover:bg-slate-50">编辑</button>
                <button onclick="toggleSite('${esc(site.site_code)}', ${!site.enabled})" class="text-xs px-2 py-1 rounded border ${site.enabled ? "border-slate-200 text-slate-600" : "bg-emerald-600 text-white border-emerald-600"}">${site.enabled ? "停用站点" : "启用站点"}</button>
                <button onclick="deleteSite('${esc(site.site_code)}')" class="text-xs px-2 py-1 rounded border border-red-200 text-red-600 hover:bg-red-50">删除</button>
              </td>
            </tr>`).join("") || `<tr><td colspan="7" class="p-8 text-center text-slate-400 text-sm">暂无站点</td></tr>`}
        </tbody>
      </table>
      <div id="sites-pager"></div>`
  });
  renderPager({
    container: document.getElementById("sites-pager"),
    total, page, pageSize,
    summary: `共 ${total} 个站点`,
    onPage: p => { SITES_STATE.page = p; _renderSitesTable(); },
  });
}

function _filteredSiteRows(rows) {
  const q = (SITES_STATE.q || "").trim().toLowerCase();
  if (!q) return rows;
  return rows.filter(site => {
    const hay = [
      site.site_code,
      site.site_name,
      site.base_url,
      site.cms_adapter || "yaml",
      site.managed_by,
      site.default_fetch_strategy,
      site.remark,
    ].filter(v => v != null).join(" ").toLowerCase();
    return hay.includes(q);
  });
}

function _siteSortIcon(key) {
  if (SITES_STATE.sortKey !== key) return "↕";
  return SITES_STATE.sortDir === "asc" ? "↑" : "↓";
}

function _siteSortValue(site, key) {
  if (key === "adapter") return site.cms_adapter || "yaml";
  if (key === "target_count") return (site.targets || []).length;
  if (key === "enabled") return site.enabled ? 1 : 0;
  return site[key] ?? "";
}

function _sortedSiteRows(rows) {
  const { sortKey, sortDir } = SITES_STATE;
  const dir = sortDir === "desc" ? -1 : 1;
  return [...rows].sort((a, b) => {
    const av = _siteSortValue(a, sortKey);
    const bv = _siteSortValue(b, sortKey);
    if (typeof av === "number" || typeof bv === "number") {
      return ((Number(av) || 0) - (Number(bv) || 0)) * dir;
    }
    return String(av).localeCompare(String(bv), "zh-CN", {
      numeric: true,
      sensitivity: "base",
    }) * dir;
  });
}

function _sortSitesBy(key) {
  if (SITES_STATE.sortKey === key) {
    SITES_STATE.sortDir = SITES_STATE.sortDir === "asc" ? "desc" : "asc";
  } else {
    SITES_STATE.sortKey = key;
    SITES_STATE.sortDir = "asc";
  }
  SITES_STATE.page = 1;
  _renderSitesTable();
}

function _applySiteFilter() {
  const input = document.getElementById("sites-q");
  SITES_STATE.q = input ? input.value : "";
  SITES_STATE.page = 1;
  _renderSitesTable();
}

function _siteSearchKeydown(event) {
  if (event.key !== "Enter") return;
  event.preventDefault();
  _applySiteFilter();
}

function _clearSiteFilter() {
  SITES_STATE.q = "";
  SITES_STATE.page = 1;
  _renderSitesTable();
}

async function toggleSite(code, enabled) {
  try {
    await api(`/api/sites/${encodeURIComponent(code)}/toggle?enabled=${enabled}`, { method: "POST" });
    toast(`已${enabled ? "启用" : "停用"} ${code}`);
    PAGES.sites();
  } catch (e) { toast(e.message, "err"); }
}
async function deleteSite(code) {
  if (!confirm(`确认永久删除站点 ${code}？\n将级联删除其部门/目标。若已有历史文章将拒绝（请先停用）。`)) return;
  try {
    await api(`/api/sites/${encodeURIComponent(code)}`, { method: "DELETE" });
    toast(`已删除 ${code}`);
    PAGES.sites();
  } catch (e) { toast(e.message, "err"); }
}
