/* loader.jsx — fetches pure-markdown documents at runtime and parses them
   into the data model the app renders. No backend; works on any static host. */
(function () {
  // ---- tiny YAML front-matter parser (sufficient for our front-matter) ----
  function parseScalar(v) {
    v = v.trim();
    if (v.startsWith('"') && v.endsWith('"')) return v.slice(1, -1);
    if (v.startsWith("[") && v.endsWith("]")) {
      return v.slice(1, -1).split(",").map((s) => parseScalar(s)).filter((s) => s !== "");
    }
    if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
    return v;
  }
  function parseFrontMatter(text) {
    text = text.replace(/\r/g, "");
    const m = text.match(/^---\n([\s\S]*?)\n---\n?/);
    if (!m) return { data: {}, body: text };
    const data = {};
    const lines = m[1].split("\n");
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (!line.trim()) continue;
      const kv = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
      if (!kv) continue;
      const key = kv[1];
      let val = kv[2];
      if (val === "") {
        // block list following (indented "- ")
        const list = [];
        while (i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
          list.push(parseScalar(lines[++i].replace(/^\s+-\s/, "")));
        }
        data[key] = list;
      } else {
        data[key] = parseScalar(val);
      }
    }
    return { data, body: text.slice(m[0].length) };
  }

  // ---- amount parsing for the serving scaler ----
  function parseAmount(s) {
    s = (s || "").trim();
    const m = s.match(/^(\d+\s+\d+\/\d+|\d+\/\d+|\d*\.?\d+)\s*(.*)$/);
    if (!m) return { qty: null, unit: "", taste: true, note: s };
    let qty, num = m[1];
    if (num.includes("/")) {
      const parts = num.split(/\s+/);
      if (parts.length === 2) { const [a, b] = parts[1].split("/"); qty = Number(parts[0]) + Number(a) / Number(b); }
      else { const [a, b] = num.split("/"); qty = Number(a) / Number(b); }
    } else qty = Number(num);
    return { qty, unit: m[2].trim(), taste: false, note: "" };
  }

  // ---- split a markdown body into ## sections ----
  function splitSections(body) {
    const lines = body.split("\n");
    const sections = { _intro: [] };
    let cur = "_intro";
    for (const line of lines) {
      const h = line.match(/^##\s+(.*)$/);
      if (h) { cur = h[1].trim().toLowerCase(); sections[cur] = []; }
      else sections[cur].push(line);
    }
    return sections;
  }
  function parseOrdered(lines) {
    return lines.filter((l) => /^\d+\.\s/.test(l.trim())).map((l) => l.trim().replace(/^\d+\.\s/, ""));
  }

  // ---- one "| name | amount |" row -> ingredient (or null) ----
  function parseIngredientRow(line) {
    const t = line.trim();
    if (!t.startsWith("|")) return null;
    const cells = t.split("|").slice(1, -1).map((c) => c.trim());
    if (cells.length < 2) return null;
    if (/^:?-+:?$/.test(cells[0].replace(/\s/g, ""))) return null;   // table separator
    if (/^(item|ingredient)s?$/i.test(cells[0])) return null;        // optional header row
    let name = cells[0], opt = false;
    if (/\*$/.test(name)) { opt = true; name = name.replace(/\s*\*$/, "").trim(); }
    const amt = parseAmount(cells[1]);
    return { name, qty: amt.qty, unit: amt.unit, opt, taste: amt.taste, note: amt.note };
  }

  // ---- Method section -> [{ text, ingredients:[…] }] ----
  // Each numbered item is a step; indented "| name | amount |" rows beneath it
  // are the ingredients that step introduces.
  function parseMethod(lines) {
    const steps = [];
    let cur = null;
    for (const raw of lines) {
      const m = raw.match(/^\s*\d+\.\s+(.*)$/);
      if (m) { cur = { text: m[1].trim(), ingredients: [] }; steps.push(cur); continue; }
      if (!cur) continue;
      const ing = parseIngredientRow(raw);
      if (ing) { cur.ingredients.push(ing); continue; }
      if (raw.trim()) cur.text += " " + raw.trim();   // wrapped instruction line
    }
    return steps;
  }

  // ---- combine per-step ingredients into one list for the recipe page ----
  // Same name + unit are summed; an ingredient used as required in any step is
  // required overall; first-seen order is preserved.
  function combineIngredients(steps) {
    const order = [], map = new Map();
    for (const st of steps) {
      for (const ing of st.ingredients) {
        const kind = ing.qty == null ? "t:" + (ing.note || "") : "q";
        const key = ing.name.toLowerCase() + "|" + (ing.unit || "") + "|" + kind;
        if (!map.has(key)) {
          map.set(key, { ...ing });
          order.push(key);
        } else {
          const agg = map.get(key);
          if (agg.qty != null && ing.qty != null) agg.qty += ing.qty;
          agg.opt = agg.opt && ing.opt;
        }
      }
    }
    return order.map((k) => map.get(k));
  }

  function buildRecipe(slug, fm, body) {
    const sec = splitSections(body);
    const findSec = (name) => sec[Object.keys(sec).find((k) => k.includes(name)) || ""] || [];
    const steps = parseMethod(findSec("method"));
    return {
      slug,
      title: fm.title, summary: fm.summary, date: fm.date, category: fm.category,
      image: fm.image, gallery: fm.gallery,
      active: fm.active, total: fm.total, difficulty: fm.difficulty,
      servings: fm.servings || 1, servingsUnit: fm.servingsUnit,
      equipment: fm.equipment || [],
      intro: (sec._intro || []).join("\n").trim(),
      steps,
      ingredients: combineIngredients(steps),
      notes: parseOrdered(findSec("note")),
    };
  }
  function buildArticle(slug, fm, body) {
    return { slug, section: fm.section, title: fm.title, summary: fm.summary, date: fm.date, readMins: fm.readMins, body: body.trim() };
  }

  async function fetchText(path) {
    const res = await fetch(path);
    if (!res.ok) throw new Error("Failed to load " + path + " (" + res.status + ")");
    return res.text();
  }

  async function loadContent() {
    const manifest = JSON.parse(await fetchText("content/manifest.json"));
    const recipes = await Promise.all((manifest.recipes || []).map(async (slug) => {
      const { data, body } = parseFrontMatter(await fetchText("content/recipes/" + slug + ".md"));
      return buildRecipe(slug, data, body);
    }));
    const articles = [];
    for (const section of ["techniques", "ingridients"]) {
      for (const slug of (manifest[section] || [])) {
        const { data, body } = parseFrontMatter(await fetchText("content/" + section + "/" + slug + ".md"));
        data.section = data.section || section;
        articles.push(buildArticle(slug, data, body));
      }
    }
    window.RECIPES = recipes;
    window.ARTICLES = articles;
    return { recipes, articles };
  }

  Object.assign(window, { loadContent, parseFrontMatter, parseAmount });
})();
