// 中間サーバ連携基盤 — ダッシュボード（参照実装フロント、日本語/English）
const $ = (s, r = document) => r.querySelector(s);
const app = $("#app");
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
const yen = (n) => "¥" + Number(n).toLocaleString("ja-JP");
// デモ用の機関トークン（参照実装。実際の業務システムはサーバ側で保持し、ブラウザには出さない）
const DEMO_AGENCY_TOKEN = "demo-linkage-token";
const api = async (path, opts = {}) => {
  opts.headers = { "x-agency-token": DEMO_AGENCY_TOKEN, ...(opts.headers || {}) };
  const r = await fetch("/api/v1" + path, opts);
  const data = await r.json().catch(() => ({}));
  return { ok: r.ok, status: r.status, data };
};
let LINKDEFS = [];

// ── 多言語(i18n): 日本語 / English ──
const CHUUKAN_I18N = {
  ja: {
    "nav.match": "名寄せ", "nav.query": "情報照会デモ", "nav.links": "別表（連携範囲）", "nav.audit": "連携ログ",
    "brand.eyebrow": "標準準拠システム間の情報連携ハブ（参照実装）",
    "footer": "マイナンバーは保持・伝送しません。宛名番号で名寄せし、機関別符号で連携し、すべての照会・提供を記録します。",
    "match.title": "宛名番号による名寄せ",
    "match.desc": "住民を選ぶと、その人の情報を保有する機関（システム）と機関別符号、特定個人情報の保有状況を表示します。",
    "match.atena": "宛名番号", "match.name": "氏名", "match.agencyCount": "保有機関数",
    "match.pick": "左の住民を選択してください。",
    "match.birth": "生年月日", "match.addr": "住所",
    "match.agenciesHead": "保有機関と機関別符号", "match.agency": "機関", "match.code": "機関別符号（マスク）",
    "match.itemsHead": "特定個人情報の保有状況", "match.holder": "保有機関", "match.item": "情報", "match.year": "年度",
    "match.none": "登録なし",
    "match.note": "※ 値の取得は「情報照会デモ」から、別表で許可された事務として行います（生のマイナンバーは介在しません）。",
    "query.title": "情報照会デモ",
    "query.desc": "事務（照会する機関）と住民、照会したい情報を選び「照会する」を押します。別表で許可された組合せのみ提供され、範囲外は拒否されます。いずれも連携ログに記録されます。",
    "query.proc": "事務（照会元）", "query.resident": "住民（宛名番号）", "query.item": "照会する情報", "query.run": "照会する",
    "query.denyNote": "「範囲外を試す」を押すと、別表で許可されていない情報を要求し、拒否される様子を確認できます。",
    "query.deny": "範囲外を試す（拒否デモ）",
    "query.okHead": "✅ 提供されました", "query.proc2": "事務", "query.target": "対象（宛名番号）", "query.info": "情報",
    "query.holderIs": "保有機関", "query.value": "値", "query.via": "名寄せ",
    "query.codes": "照会元符号 {r} ／ 保有機関符号 {h} で突合（マイナンバー非伝送）。連携ログに記録済み。",
    "query.denied": "⛔ 拒否されました（別表範囲外）", "query.failed": "⛔ 照会できませんでした",
    "query.req": "要求", "query.logged": "この事象も連携ログに記録されます。",
    "links.title": "別表（情報連携の範囲）",
    "links.desc": "各事務が照会できる特定個人情報の範囲です。中間サーバはこの定義に基づいて照会の可否を判定します。",
    "links.proc": "事務", "links.requester": "照会元機関", "links.items": "照会できる情報",
    "audit.title": "連携ログ（照会・提供記録）",
    "audit.desc": "情報連携のすべての照会・提供・拒否を時系列で記録します（{n}件）。",
    "audit.refresh": "更新", "audit.ts": "日時", "audit.type": "種別", "audit.proc": "事務/機関",
    "audit.target": "対象", "audit.item": "情報", "audit.result": "結果", "audit.none": "記録なし",
    "type.照会": "照会", "type.提供登録": "提供登録", "type.拒否": "拒否",
  },
  en: {
    "nav.match": "Name matching", "nav.query": "Query demo", "nav.links": "Linkage table", "nav.audit": "Linkage log",
    "brand.eyebrow": "Information-linkage hub between standard-compliant systems (reference implementation)",
    "footer": "The My Number is never stored or transmitted. Residents are matched by reference number, linked via agency-specific codes, and every query/provision is recorded.",
    "match.title": "Name matching by reference number",
    "match.desc": "Select a resident to see which agencies (systems) hold their records, the agency-specific codes, and the personal-information items held.",
    "match.atena": "Ref. number", "match.name": "Name", "match.agencyCount": "Agencies",
    "match.pick": "Select a resident on the left.",
    "match.birth": "Date of birth", "match.addr": "Address",
    "match.agenciesHead": "Holding agencies & agency-specific codes", "match.agency": "Agency", "match.code": "Agency code (masked)",
    "match.itemsHead": "Held personal-information items", "match.holder": "Holder", "match.item": "Item", "match.year": "FY",
    "match.none": "None",
    "match.note": "* Values are obtained via the Query demo, as procedures permitted by the linkage table (no raw My Number involved).",
    "query.title": "Information-query demo",
    "query.desc": "Choose a procedure (requesting agency), a resident and an item, then press Query. Only combinations permitted by the linkage table are answered; out-of-scope requests are denied. Everything is recorded in the linkage log.",
    "query.proc": "Procedure (requester)", "query.resident": "Resident (ref. number)", "query.item": "Item to query", "query.run": "Query",
    "query.denyNote": "\"Try out-of-scope\" requests an item not permitted by the linkage table, so you can see it being denied.",
    "query.deny": "Try out-of-scope (denial demo)",
    "query.okHead": "✅ Provided", "query.proc2": "Procedure", "query.target": "Subject (ref. number)", "query.info": "Item",
    "query.holderIs": "holder", "query.value": "Value", "query.via": "Matching",
    "query.codes": "Matched by requester code {r} and holder code {h} (no My Number transmitted). Recorded in the linkage log.",
    "query.denied": "⛔ Denied (out of linkage-table scope)", "query.failed": "⛔ Query failed",
    "query.req": "Request", "query.logged": "This event is also recorded in the linkage log.",
    "links.title": "Linkage table (scope of information linkage)",
    "links.desc": "The personal-information items each procedure may query. The intermediary server decides permissibility based on this definition.",
    "links.proc": "Procedure", "links.requester": "Requesting agency", "links.items": "Permitted items",
    "audit.title": "Linkage log (queries & provisions)",
    "audit.desc": "Records every query, provision and denial of information linkage in order ({n} entries).",
    "audit.refresh": "Refresh", "audit.ts": "Time", "audit.type": "Type", "audit.proc": "Procedure/agency",
    "audit.target": "Subject", "audit.item": "Item", "audit.result": "Result", "audit.none": "No records",
    "type.照会": "Query", "type.提供登録": "Provision", "type.拒否": "Denied",
  },
};
let lang = localStorage.getItem("chuukan-lang") || "ja";
function t(key, vars) {
  let s = (CHUUKAN_I18N[lang] && CHUUKAN_I18N[lang][key]) || CHUUKAN_I18N.ja[key] || key;
  if (vars) for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, String(v));
  return s;
}
let currentView = "match";
function applyChrome() {
  const navMap = { match: "nav.match", query: "nav.query", links: "nav.links", audit: "nav.audit" };
  document.querySelectorAll("nav button[data-view]").forEach((b) => { const k = navMap[b.dataset.view]; if (k) b.textContent = t(k); });
  const eb = $(".brand .eyebrow"); if (eb) eb.textContent = t("brand.eyebrow");
  const ft = $(".footer small"); if (ft) ft.textContent = t("footer");
  if (!document.getElementById("lang-select")) {
    const nav = $("header.topbar nav");
    if (nav) {
      const sel = document.createElement("select");
      sel.id = "lang-select"; sel.title = "言語 / Language";
      sel.innerHTML = '<option value="ja">日本語</option><option value="en">English</option>';
      sel.addEventListener("change", (e) => setLang(e.target.value));
      nav.appendChild(sel);
    }
  }
  const ls = document.getElementById("lang-select"); if (ls) ls.value = lang;
  document.documentElement.lang = lang;
}
function setLang(l) { lang = l; localStorage.setItem("chuukan-lang", l); applyChrome(); setView(currentView); }

function setView(v) {
  currentView = v;
  document.querySelectorAll("nav button").forEach((b) => b.classList.toggle("active", b.dataset.view === v));
  ({ match: viewMatch, query: viewQuery, links: viewLinks, audit: viewAudit }[v] || viewMatch)();
}

// ── 名寄せ ──────────────────────────────────────────────
async function viewMatch() {
  const { data: residents } = await api("/residents");
  app.innerHTML = `
    <h2 class="section-title">${t("match.title")}</h2>
    <p class="muted">${t("match.desc")}</p>
    <div class="grid-2">
      <div class="card"><table class="data-table"><thead><tr><th>${t("match.atena")}</th><th>${t("match.name")}</th><th>${t("match.agencyCount")}</th></tr></thead>
        <tbody>${residents.map((r) => `<tr class="clickable" data-atena="${esc(r.atena)}"><td><code>${esc(r.atena)}</code></td><td>${esc(r.name)}<br><small class="muted">${esc(r.kana)}</small></td><td>${r.agencyCount}</td></tr>`).join("")}</tbody>
      </table></div>
      <div class="card" id="match-detail"><p class="muted">${t("match.pick")}</p></div>
    </div>`;
  app.querySelectorAll("tr.clickable").forEach((tr) => tr.addEventListener("click", () => showMatch(tr.dataset.atena)));
}
async function showMatch(atena) {
  const { ok, data } = await api("/residents/" + encodeURIComponent(atena));
  const box = $("#match-detail");
  if (!ok) { box.innerHTML = `<p class="error">${esc(data.error)}</p>`; return; }
  box.innerHTML = `
    <h3>${esc(data.name)} <small class="muted">(${esc(data.atena)})</small></h3>
    <dl class="kv"><dt>${t("match.birth")}</dt><dd>${esc(data.birthDate)}</dd><dt>${t("match.addr")}</dt><dd>${esc(data.address)}</dd></dl>
    <h4>${t("match.agenciesHead")}</h4>
    <table class="data-table"><thead><tr><th>${t("match.agency")}</th><th>${t("match.code")}</th></tr></thead>
      <tbody>${data.agencies.map((a) => `<tr><td>${esc(a.name)}</td><td><code>${esc(a.codeMasked)}</code></td></tr>`).join("")}</tbody></table>
    <h4>${t("match.itemsHead")}</h4>
    ${data.items.length ? `<table class="data-table"><thead><tr><th>${t("match.holder")}</th><th>${t("match.item")}</th><th>${t("match.year")}</th></tr></thead>
      <tbody>${data.items.map((i) => `<tr><td>${esc(i.holderName)}</td><td>${esc(i.item)}</td><td>${i.fiscalYear}</td></tr>`).join("")}</tbody></table>`
      : `<p class="muted">${t("match.none")}</p>`}
    <p class="muted small">${t("match.note")}</p>`;
}

// ── 情報照会デモ ────────────────────────────────────────
async function viewQuery() {
  if (!LINKDEFS.length) LINKDEFS = (await api("/link-definitions")).data;
  const { data: residents } = await api("/residents");
  app.innerHTML = `
    <h2 class="section-title">${t("query.title")}</h2>
    <p class="muted">${t("query.desc")}</p>
    <div class="card"><form id="q-form" class="form-grid">
      <label>${t("query.proc")}
        <select id="q-proc">${LINKDEFS.map((d, i) => `<option value="${i}">${esc(d.procedure)}（${esc(d.requesterName)}）</option>`).join("")}</select></label>
      <label>${t("query.resident")}
        <select id="q-atena">${residents.map((r) => `<option value="${esc(r.atena)}">${esc(r.atena)} ${esc(r.name)}</option>`).join("")}</select></label>
      <label>${t("query.item")}
        <select id="q-item"></select></label>
      <label>　<button class="primary" type="submit">${t("query.run")}</button></label>
    </form>
    <p class="muted small">${t("query.denyNote")}</p>
    <button id="q-deny" type="button">${t("query.deny")}</button>
    </div>
    <div id="q-result"></div>`;
  const procSel = $("#q-proc"), itemSel = $("#q-item");
  const fillItems = () => {
    const def = LINKDEFS[procSel.value];
    itemSel.innerHTML = def.allowed.map((a, i) => `<option value="${i}">${esc(a.item)}（${esc(a.holder)}）</option>`).join("");
  };
  procSel.addEventListener("change", fillItems); fillItems();
  $("#q-form").addEventListener("submit", (e) => { e.preventDefault(); runQuery(false); });
  $("#q-deny").addEventListener("click", () => runQuery(true));
}
async function runQuery(forceDeny) {
  const def = LINKDEFS[$("#q-proc").value];
  const atena = $("#q-atena").value;
  let holder, item;
  if (forceDeny) { holder = "juki"; item = "世帯人数"; if (def.allowed.some((a) => a.holder === holder && a.item === item)) { holder = "tax"; item = "課税額"; } }
  else { const a = def.allowed[$("#q-item").value]; holder = a.holder; item = a.item; }
  const { ok, status, data } = await api("/query", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ requester: def.requester, atena, holder, item, purpose: def.procedure }) });
  const box = $("#q-result");
  if (ok) {
    box.innerHTML = `<div class="card ok"><h3>${t("query.okHead")}</h3>
      <dl class="kv"><dt>${t("query.proc2")}</dt><dd>${esc(def.procedure)}</dd><dt>${t("query.target")}</dt><dd>${esc(atena)}</dd>
      <dt>${t("query.info")}</dt><dd>${esc(item)}（${t("query.holderIs")}：${esc(data.holder)}）</dd>
      <dt>${t("query.value")}</dt><dd><strong>${item.includes("額") || item.includes("所得") ? yen(data.value) : esc(data.value)}</strong></dd>
      <dt>${t("query.via")}</dt><dd>${esc(data.via)}</dd></dl>
      <p class="muted small">${t("query.codes", { r: esc(data.audit.requesterCodeMasked), h: esc(data.audit.holderCodeMasked) })}</p></div>`;
  } else {
    box.innerHTML = `<div class="card error-box"><h3>${status === 403 ? t("query.denied") : t("query.failed")}</h3>
      <p>${esc(data.error)}</p><p class="muted small">${t("query.req")}: ${esc(def.procedure)} → ${esc(item)}（${esc(holder)}）。${t("query.logged")}</p></div>`;
  }
}

// ── 別表 ────────────────────────────────────────────────
async function viewLinks() {
  const { data } = await api("/link-definitions");
  app.innerHTML = `
    <h2 class="section-title">${t("links.title")}</h2>
    <p class="muted">${t("links.desc")}</p>
    <div class="card"><table class="data-table"><thead><tr><th>${t("links.proc")}</th><th>${t("links.requester")}</th><th>${t("links.items")}</th></tr></thead>
      <tbody>${data.map((d) => `<tr><td>${esc(d.procedure)}</td><td>${esc(d.requesterName)}</td><td>${d.allowed.map((a) => `${esc(a.item)}<small class="muted">（${esc(a.holder)}）</small>`).join("、")}</td></tr>`).join("")}</tbody>
    </table></div>`;
}

// ── 連携ログ ────────────────────────────────────────────
const TYPE_BADGE = { "照会": "badge-ok", "提供登録": "badge-info", "拒否": "badge-err" };
async function viewAudit() {
  const { data } = await api("/audit?limit=200");
  app.innerHTML = `
    <h2 class="section-title">${t("audit.title")}</h2>
    <p class="muted">${t("audit.desc", { n: data.length })} <button id="a-refresh" type="button">${t("audit.refresh")}</button></p>
    <div class="card"><table class="data-table"><thead><tr><th>${t("audit.ts")}</th><th>${t("audit.type")}</th><th>${t("audit.proc")}</th><th>${t("audit.target")}</th><th>${t("audit.item")}</th><th>${t("audit.result")}</th></tr></thead>
      <tbody>${data.map((r) => `<tr><td><small>${esc(r.ts.replace("T", " ").slice(0, 19))}</small></td>
        <td><span class="badge ${TYPE_BADGE[r.type] || ""}">${esc(t("type." + r.type))}</span></td>
        <td>${esc(r.purpose || r.requesterName || "")}</td><td><code>${esc(r.atena)}</code></td>
        <td>${esc(r.item || "")}${r.holderName ? `<small class="muted">（${esc(r.holderName)}）</small>` : ""}</td>
        <td>${esc(r.result || "")}</td></tr>`).join("") || `<tr><td colspan="6" class="muted">${t("audit.none")}</td></tr>`}</tbody>
    </table></div>`;
  $("#a-refresh")?.addEventListener("click", viewAudit);
}

document.querySelectorAll("nav button").forEach((b) => b.addEventListener("click", () => setView(b.dataset.view)));
applyChrome();
setView("match");
