// Căutarea, fișa cuvântului și comutatorul coloanei.
//
// Textul paginii e complet fără JavaScript; ce adaugă fișierul ăsta e căutarea.
// Dicționarul se ia pe felii, o literă pe cerere, ca prima încărcare să nu care
// opt megabytes.

const LANG = document.body.dataset.lang || "ro";
const $ = (s, r = document) => r.querySelector(s);

function wireBackToTop() {
  const link = document.querySelector(".back-to-top");
  const target = document.getElementById("top");
  const footer = document.querySelector(".foot");
  if (!link || !target || !footer) return;
  link.classList.add("enhanced");
  const update = () => {
    const footerTop = footer.getBoundingClientRect().top;
    const lift = Math.max(16, window.innerHeight - footerTop + 16);
    link.style.setProperty("--btt-bottom", `${lift}px`);
    link.classList.toggle("shown", window.scrollY > Math.max(640, window.innerHeight * .85));
  };
  link.addEventListener("click", (event) => {
    event.preventDefault();
    window.scrollTo(0, 0);
    target.focus({ preventScroll: true });
  });
  addEventListener("scroll", update, { passive: true });
  addEventListener("resize", update);
  update();
}

wireBackToTop();

const FLAT = { "ă": "a", "î": "i", "â": "a", "ș": "s", "ț": "t", "ş": "s", "ţ": "t" };
const norm = (s) => s.toLowerCase().normalize("NFC")
  .replace(/ş/g, "ș").replace(/ţ/g, "ț").replace(/â/g, "î")
  .replace(/[^a-zăîșț'-]/g, "");
const flat = (s) => norm(s).replace(/[ăîâșțşţ]/g, (c) => FLAT[c] || c);

let C = null, INDEX = null, INDEX_FLAT = null;
const shards = new Map();
const formShards = new Map();

async function boot() {
  const [client, index] = await Promise.all([
    fetch("/data/client.json").then((r) => r.json()),
    fetch("/data/index.json").then((r) => r.json()),
  ]);
  C = client[LANG];
  INDEX = index;
  INDEX_FLAT = index.map(flat);
  return C;
}

async function shard(letter) {
  if (!letter) return null;
  if (shards.has(letter)) return shards.get(letter);
  const p = fetch(`/data/words/${encodeURIComponent(letter)}.json`)
    .then((r) => (r.ok ? r.json() : { w: {} }))
    .catch(() => ({ w: {} }));
  shards.set(letter, p);
  return p;
}

/* Formele flexionare stau în fișiere separate și se aduc numai dacă litera
   scrisă nu e deja o lemă. */
async function formShard(letter) {
  if (!letter) return null;
  if (formShards.has(letter)) return formShards.get(letter);
  const p = fetch(`/data/forms/${encodeURIComponent(letter)}.json`)
    .then((r) => (r.ok ? r.json() : { f: {} }))
    .catch(() => ({ f: {} }));
  formShards.set(letter, p);
  return p;
}

/* Rezolvă ce a scris cititorul: lema însăși, o formă flexionară a ei, sau
   aceeași scriere fără diacritice. */
async function resolve(raw) {
  const n = norm(raw);
  if (!n) return null;
  const tries = [n, flat(n)];
  for (const t of tries) {
    const sh = await shard(t[0]);
    if (sh && sh.w[t]) return { word: t, rec: sh.w[t] };
  }
  for (const t of tries) {
    const fs = await formShard(t[0]);
    const lemma = fs && fs.f[t];
    if (lemma) {
      const sh2 = await shard(lemma[0]);
      if (sh2 && sh2.w[lemma]) return { word: lemma, rec: sh2.w[lemma], from: raw.trim() };
    }
  }
  // ultima încercare: potrivire fără diacritice în index
  const f = flat(n);
  const i = INDEX_FLAT.indexOf(f);
  if (i >= 0) {
    const w = INDEX[i];
    const sh = await shard(w[0]);
    if (sh && sh.w[w]) return { word: w, rec: sh.w[w] };
  }
  return null;
}

// ——— formatare ———————————————————————————————————————————————————————————
const dec = (n, d = 1) => {
  const s = Number(n).toFixed(d);
  return LANG === "ro" ? s.replace(".", ",") : s;
};
const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;")
  .replace(/>/g, "&gt;").replace(/"/g, "&quot;");
const t = (s, vars = {}) =>
  String(s).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? vars[k] : m));

function langName(code) {
  return C.langs[code] || code;
}

// ——— fișa ————————————————————————————————————————————————————————————————
function chainHtml(rec) {
  if (!rec.c || !rec.c.length) return "";
  const items = rec.c.map(([kind, lang, word, tr]) => {
    const k = C.result.kinds[kind] || kind;
    const etymon = word
      ? ` <span class="e">${esc(word)}</span>${tr ? ` <span class="tr">(${esc(tr)})</span>` : ""}`
      : "";
    return `<li><span class="k">${esc(k)}</span> <span class="l">${esc(langName(lang))}</span>${etymon}</li>`;
  }).join("");
  return `<ol class="chain">${items}</ol>`;
}

function sparkHtml(rec, stratum) {
  const bins = C.bins;
  const W = 420, H = 118, PL = 30, PR = 46, PT = 12, PB = 24;
  const own = rec.cv || null;
  const series = own || stratum.curve;
  const nowVals = [rec.f ? rec.f.vorbit : 0, rec.f ? rec.f.scris : 0];
  const scaleVals = own ? series.concat(nowVals) : series;
  const vmax = Math.max(...scaleVals, 0.0001) * 1.15;
  const n = series.length;
  const innerW = W - PL - PR;
  const histW = innerW * 0.68;
  const sx = (i) => PL + (n === 1 ? histW / 2 : (i / (n - 1)) * histW);
  const sy = (v) => H - PB - (v / vmax) * (H - PT - PB);
  const d = series.map((v, i) => `${i ? "L" : "M"}${sx(i).toFixed(1)},${sy(v).toFixed(1)}`).join("");
  const dots = series.map((v, i) =>
    `<circle class="dot" cx="${sx(i).toFixed(1)}" cy="${sy(v).toFixed(1)}" r="3"><title>${bins[i][0]}–${bins[i][1]}: ${dec(v, 2)}</title></circle>`).join("");
  const gapX = PL + histW + innerW * 0.14;
  const nowX = PL + innerW + 2;
  const nowMarks = own ? nowVals.map((v, i) =>
    `<circle class="now" cx="${(nowX).toFixed(1)}" cy="${sy(v).toFixed(1)}" r="3"/>
<text x="${(nowX + 6).toFixed(1)}" y="${(sy(v) + 3).toFixed(1)}">${i === 0 ? esc(C.result.spoken) : esc(C.result.written)}</text>`).join("") : "";
  return `<svg class="spark" viewBox="0 0 ${W} ${H}" role="img"
 aria-label="${esc(C.result.curveLabel)}">
  <line class="axis" x1="${PL}" y1="${H - PB}" x2="${PL + innerW}" y2="${H - PB}"/>
  <line class="gap" x1="${gapX.toFixed(1)}" y1="${PT}" x2="${gapX.toFixed(1)}" y2="${H - PB}"/>
  <path class="line" d="${d}"/>
  ${dots}
  ${nowMarks}
  <text x="${PL}" y="${H - 8}">${bins[0][0]}</text>
  <text x="${(PL + histW).toFixed(1)}" y="${H - 8}" text-anchor="middle">${bins[n - 1][1]}</text>
  <text x="${PL - 4}" y="${PT + 8}" text-anchor="end">${dec(vmax, vmax < 10 ? 1 : 0)}</text>
</svg>`;
}

function registerVerdict(rec) {
  const f = rec.f || {};
  const sp = f.vorbit || 0, wr = f.scris || 0;
  if (!sp && !wr) return "";
  if (!wr || sp / wr >= 1.35)
    return `<p class="reg-verdict"><b>${t(C.result.registerSpoken, { n: dec(wr ? sp / wr : 0, 1) })}</b></p>`;
  if (!sp || wr / sp >= 1.35)
    return `<p class="reg-verdict"><b>${t(C.result.registerWritten, { n: dec(sp ? wr / sp : 0, 1) })}</b></p>`;
  return `<p class="reg-verdict">${esc(C.result.registerEven)}</p>`;
}

function card(word, rec) {
  const st = C.strata[rec.l];
  const R = C.result;
  const f = rec.f || {};
  const flags = [];
  if (rec.dx) flags.push(t(R.dexDiffers, { layer: (C.strata[rec.dx] || {}).name || rec.dx }));
  if (rec.af) flags.push(t(R.affix, { parts: rec.af.join(" + ") }));
  const firstTxt = rec.fa
    ? (rec.fa === C.bins[0][0] ? R.firstNone : `${rec.fa}–${rec.fa + 24}`)
    : R.firstNever;
  const thin = !rec.cv
    ? `<p class="curve-note">${t(R.curveThin, { n: dec(rec.cvn || 0, 0) })}</p>` : "";
  return `<article class="card">
  <div class="card-head">
    <h2>${esc(word)}</h2>
    ${rec.p ? `<span class="pos">${esc(R.pos[rec.p] || rec.p)}</span>` : ""}
    ${rec.g ? `<p class="gloss">${esc(rec.g)}</p>` : ""}
  </div>
  <div class="card-body">
    <div class="cell">
      <h3>${esc(R.layerLabel)}</h3>
      <p class="stratum-name">${esc(st.name)}</p>
      <p class="stratum-when">${esc(st.when)} · ${esc(st.who)}</p>
      ${flags.map((x) => `<span class="flag">${esc(x)}</span>`).join(" ")}
    </div>
    <div class="cell">
      <h3>${esc(R.chainLabel)}</h3>
      ${chainHtml(rec) || `<p class="stratum-note">${esc(R.disputed)}</p>`}
    </div>
    <div class="cell">
      <h3>${esc(R.freqLabel)}</h3>
      <dl class="freqs">
        <div><dt>${esc(R.spoken)}</dt><dd>${dec(f.vorbit || 0, 1)} <small>${esc(R.perMillion)}</small></dd></div>
        <div><dt>${esc(R.written)}</dt><dd>${dec(f.scris || 0, 1)}</dd></div>
        <div><dt>${esc(R.historical)}</dt><dd>${dec(f.istoric || 0, 1)}</dd></div>
      </dl>
      ${registerVerdict(rec)}
    </div>
    <div class="cell">
      <h3>${esc(R.curveLabel)}</h3>
      ${sparkHtml(rec, st)}
      <p class="curve-note">${esc(R.firstLabel)}: ${esc(firstTxt)}</p>
      ${thin}
    </div>
  </div>
</article>`;
}

function missHtml(raw) {
  const R = C.result;
  const f = flat(raw);
  const near = INDEX.filter((w, i) => INDEX_FLAT[i].startsWith(f.slice(0, 3)))
    .slice(0, 8);
  return `<div class="miss">
  <p><b>${esc(R.notFound)}</b></p>
  <p>${esc(R.notFoundHint)}</p>
  ${near.length ? `<p class="hint">${esc(R.suggestions)}</p>
  <div class="sug">${near.map((w) => `<button type="button" class="chip" data-word="${esc(w)}">${esc(w)}</button>`).join("")}</div>` : ""}
</div>`;
}

// ——— coloana ————————————————————————————————————————————————————————————
function litColumn(stratum) {
  document.querySelectorAll(".band, .leg").forEach((b) => {
    b.classList.toggle("lit", b.dataset.stratum === stratum);
  });
}
function columnMode(mode) {
  const key = { types: "types", spoken: "spoken", written: "written" }[mode];
  document.querySelectorAll(".band, .leg").forEach((b) => {
    const v = parseFloat(b.dataset[key]);
    b.style.setProperty("--h", v.toFixed(3));
    const val = b.querySelector(".leg-val");
    if (val) val.textContent = `${dec(v, 1)}%`;
    if (b.classList.contains("band")) {
      const name = C.strata[b.dataset.stratum].name;
      b.setAttribute("aria-label", `${name}: ${dec(v, 1)}%`);
    }
  });
  document.querySelectorAll(".col-controls button").forEach((btn) => {
    btn.classList.toggle("on", btn.dataset.mode === mode);
    btn.setAttribute("aria-pressed", String(btn.dataset.mode === mode));
  });
}

// ——— căutarea ————————————————————————————————————————————————————————————
let acItems = [], acPos = -1;

function closeAc(input, ac) {
  ac.hidden = true; ac.innerHTML = ""; acItems = []; acPos = -1;
  input.setAttribute("aria-expanded", "false");
}

function suggest(q) {
  const n = norm(q), f = flat(q);
  if (n.length < 2) return [];
  const pre = [], mid = [];
  for (let i = 0; i < INDEX.length; i++) {
    const w = INDEX[i], wf = INDEX_FLAT[i];
    if (wf.startsWith(f) || w.startsWith(n)) pre.push(w);
    else if (mid.length < 40 && wf.includes(f)) mid.push(w);
    if (pre.length >= 10) break;
  }
  return pre.concat(mid).slice(0, 10);
}

async function show(word, opts = {}) {
  const out = $("#result");
  const r = await resolve(word);
  if (!r) {
    out.innerHTML = missHtml(word);
    litColumn(null);
    return;
  }
  out.innerHTML = card(r.word, r.rec);
  litColumn(r.rec.l);
  if (opts.hash !== false) {
    const h = `#${encodeURIComponent(r.word)}`;
    if (location.hash !== h) history.replaceState(null, "", h);
  }
  if (opts.scroll) {
    out.scrollIntoView({ behavior: "smooth", block: "nearest" });
  }
}

function wire() {
  const form = $(".searchbox");
  if (!form) return;
  const input = $("#q"), ac = $("#ac");

  const render = () => {
    if (!acItems.length) { ac.hidden = true; ac.innerHTML = ""; return; }
    ac.innerHTML = acItems.map((w, i) => {
      const rec = null;
      return `<li role="option" id="ac-${i}" aria-selected="${i === acPos}" data-word="${esc(w)}">
        <span class="w">${esc(w)}</span></li>`;
    }).join("");
    ac.hidden = false;
    input.setAttribute("aria-expanded", "true");
  };

  input.addEventListener("input", () => {
    acItems = suggest(input.value); acPos = -1; render();
  });
  input.addEventListener("keydown", (e) => {
    if (e.key === "ArrowDown" || e.key === "ArrowUp") {
      if (!acItems.length) return;
      e.preventDefault();
      acPos = (acPos + (e.key === "ArrowDown" ? 1 : -1) + acItems.length) % acItems.length;
      render();
      input.setAttribute("aria-activedescendant", `ac-${acPos}`);
    } else if (e.key === "Enter") {
      e.preventDefault();
      const w = acPos >= 0 ? acItems[acPos] : input.value;
      if (acPos >= 0) input.value = w;
      closeAc(input, ac);
      show(w, { scroll: true });
    } else if (e.key === "Escape") {
      closeAc(input, ac);
    }
  });
  ac.addEventListener("mousedown", (e) => {
    const li = e.target.closest("li[data-word]");
    if (!li) return;
    e.preventDefault();
    input.value = li.dataset.word;
    closeAc(input, ac);
    show(li.dataset.word, { scroll: true });
  });
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    closeAc(input, ac);
    show(input.value, { scroll: true });
  });
  document.addEventListener("click", (e) => {
    if (!form.contains(e.target)) closeAc(input, ac);
  });

  $("[data-random]")?.addEventListener("click", () => {
    const w = INDEX[Math.floor(Math.random() * INDEX.length)];
    input.value = w;
    show(w, { scroll: true });
  });

  document.addEventListener("click", (e) => {
    const chip = e.target.closest(".chip[data-word]");
    if (!chip) return;
    input.value = chip.dataset.word;
    show(chip.dataset.word, { scroll: true });
    document.getElementById("lookup")?.scrollIntoView({ behavior: "smooth", block: "start" });
  });

  document.querySelectorAll(".col-controls button").forEach((btn) => {
    btn.addEventListener("click", () => columnMode(btn.dataset.mode));
  });

  window.addEventListener("hashchange", () => {
    const h = decodeURIComponent(location.hash.slice(1));
    if (h) { input.value = h; show(h, { hash: false }); }
  });
}

boot().then(() => {
  wire();
  const h = decodeURIComponent(location.hash.slice(1));
  if (h) {
    const input = $("#q");
    if (input) input.value = h;
    show(h, { hash: false });
  }
}).catch((err) => {
  console.error("straturi: nu s-au putut încărca datele", err);
});
