// data.jsx — async content loader and logic constants.
// Fetches content/*.yml, parses them with js-yaml, and assigns to window.
// Logic-only constants and lookup helpers live here; all prose lives in YAML.

const EFFORT_ORDER = ["low", "medium", "high"];
const SKILL_ORDER  = ["basic", "intermediate", "advanced"];

const EFFORT_LABEL = { low: "Low effort", medium: "Medium effort", high: "High effort" };
const SKILL_LABEL  = { basic: "Basic", intermediate: "Intermediate", advanced: "Advanced" };

function methodById(id)  { return window.METHODS.find(m => m.id === id); }
function phaseById(id)   { return window.PHASES.find(p => p.id === id); }
function actorById(id)   { return window.ACTORS.find(a => a.id === id); }
function refByKey(key)   { return window.REFERENCES[String(key).toLowerCase()]; }

// Parse content/references.bib into a { lowercased-key -> entry } map.
// References are supplementary: a missing/empty file or absent parser yields {}
// rather than blocking app boot.
async function loadReferences() {
  const out = {};
  try {
    const r = await fetch("content/references.bib");
    if (!r.ok) return out;
    const text = await r.text();
    if (!text.trim() || !window.bibtexParse) return out;
    const entries = window.bibtexParse.toJSON(text) || [];
    for (const e of entries) {
      if (!e || !e.citationKey) continue;
      const fields = {};
      const tags = e.entryTags || {};
      // bibtex-parse-js tag casing varies — normalise field names to lowercase.
      for (const k of Object.keys(tags)) fields[k.toLowerCase()] = tags[k];
      const key = e.citationKey.toLowerCase();
      out[key] = { key, type: String(e.entryType || "").toLowerCase(), fields };
    }
  } catch (e) {
    console.warn(`Could not load content/references.bib — ${e.message}`);
  }
  return out;
}

// Decode the common LaTeX/BibTeX escapes that reference managers (Zotero,
// Mendeley) emit — accented letters, special glyphs, and dashes — into Unicode,
// so e.g. "Sch{\"a}pke" displays as "Schäpke". Accents map to combining-mark
// codepoints applied to the base letter, then the result is NFC-normalised.
function deTeX(s) {
  s = String(s || "");
  s = s.replace(/---/g, "—").replace(/--/g, "–")   // em / en dash
       .replace(/``/g, "“").replace(/''/g, "”")    // curly quotes
       .replace(/\\&/g, "&").replace(/\\%/g, "%").replace(/\\\$/g, "$")
       .replace(/\\#/g, "#").replace(/\\_/g, "_");
  // accent command -> Unicode combining-mark codepoint (kept as numbers so this
  // source stays pure-ASCII; the mark is built at runtime via fromCharCode)
  const ACC = {
    '"': 0x0308, "'": 0x0301, "`": 0x0300, "^": 0x0302,  // umlaut acute grave circumflex
    "~": 0x0303, "=": 0x0304, ".": 0x0307, "u": 0x0306,  // tilde macron dot breve
    "v": 0x030C, "H": 0x030B, "c": 0x0327, "k": 0x0328,  // caron dbl-acute cedilla ogonek
    "r": 0x030A, "d": 0x0323, "b": 0x0331,               // ring dot-below macron-below
  };
  s = s.replace(/\\([`'^"~=.uvHckrdb])\s*\{?([A-Za-z])\}?/g, (m, a, ch) => ACC[a] ? ch + String.fromCharCode(ACC[a]) : ch);
  // standalone special letters (control words gobble one trailing space)
  s = s.replace(/\\ss\b ?/g, "ß")
       .replace(/\\AE\b ?/g, "Æ").replace(/\\ae\b ?/g, "æ")
       .replace(/\\OE\b ?/g, "Œ").replace(/\\oe\b ?/g, "œ")
       .replace(/\\AA\b ?/g, "Å").replace(/\\aa\b ?/g, "å")
       .replace(/\\O\b ?/g, "Ø").replace(/\\o\b ?/g, "ø")
       .replace(/\\L\b ?/g, "Ł").replace(/\\l\b ?/g, "ł");
  s = s.replace(/[{}]/g, "").replace(/~/g, " ");              // strip braces, ~ = nbsp tie
  if (s.normalize) s = s.normalize("NFC");
  return s.replace(/\s+/g, " ").trim();
}

// Render a parsed BibTeX entry as { text, href } for display.
// Format: "Family et al. (Year). Title. Venue." with a DOI/URL link when present.
function formatReference(ref) {
  const f = (ref && ref.fields) || {};
  const stripBraces = s => String(s || "").replace(/[{}]/g, "").trim();

  let authors = "";
  if (f.author) {
    const families = deTeX(f.author).split(/\s+and\s+/i).map(a => {
      a = a.trim();
      if (a.includes(",")) return a.split(",")[0].trim();        // "Family, Given"
      const parts = a.split(/\s+/).filter(Boolean);              // "Given Family"
      return parts.length ? parts[parts.length - 1] : "";
    }).filter(Boolean);
    if (families.length === 1)      authors = families[0];
    else if (families.length === 2) authors = `${families[0]} & ${families[1]}`;
    else if (families.length >= 3)  authors = `${families[0]} et al.`;
  }

  const year  = stripBraces(f.year);
  const title = deTeX(f.title);
  const venue = deTeX(f.journal || f.booktitle || f.publisher);

  const parts = [];
  if (authors)   parts.push(year ? `${authors} (${year})` : authors);
  else if (year) parts.push(`(${year})`);
  if (title)     parts.push(title);
  if (venue)     parts.push(venue);
  const text = parts.length ? parts.join(". ") + "." : (ref?.key || "");

  let href = null;
  if (f.doi)      href = "https://doi.org/" + stripBraces(f.doi).replace(/^https?:\/\/(dx\.)?doi\.org\//i, "");
  else if (f.url) href = stripBraces(f.url);

  return { text, href };
}

async function loadContent() {
  const files = [
    { file: "phases",         key: "PHASES"         },
    { file: "methods",        key: "METHODS"        },
    { file: "actors",         key: "ACTORS"         },
    { file: "context-layers", key: "CONTEXT_LAYERS" },
    { file: "site",           key: "SITE"           },
  ];
  for (const { file, key } of files) {
    let text;
    try {
      const r = await fetch(`content/${file}.yml`);
      if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
      text = await r.text();
    } catch (e) {
      throw new Error(`Could not load content/${file}.yml — ${e.message}`);
    }
    try {
      window[key] = window.jsyaml.load(text);
    } catch (e) {
      throw new Error(`YAML error in content/${file}.yml — ${e.message}`);
    }
  }
  window.REFERENCES = await loadReferences();
}

Object.assign(window, {
  EFFORT_ORDER, SKILL_ORDER, EFFORT_LABEL, SKILL_LABEL,
  methodById, phaseById, actorById, refByKey, formatReference, loadContent,
});
