/* import.jsx — read a Markdown file produced by the Markdown export (export.jsx)
   back into campaign/session data. The mapping is the inverse of
   buildExportMarkdown: campaign heading + blurb/tone, "## Session N — Name"
   headings, and "### Step" sections whose cards are parsed per STEP_DEFS.

   Markdown is a lossy round-trip (images and exact formatting are dropped on
   export), so the parser is deliberately tolerant: unknown headings are skipped,
   the export footer is stripped, and anything it can't place is ignored rather
   than throwing. Reuses window.RPG (data.js) and window.Modal / Icon (ui.jsx). */

// ── Parser ────────────────────────────────────────────────────────────────────

// Build the reverse of export's heading text → step def. Export titles a step
// with step.name capitalized (e.g. "rewards" → "Rewards", "NPCs" → "NPCs").
function buildHeadingMap() {
  const map = {};
  (window.RPG.STEP_DEFS || []).forEach((def) => {
    const heading = def.name.charAt(0).toUpperCase() + def.name.slice(1);
    map[heading.toLowerCase()] = def;
  });
  return map;
}

// Trim leading/trailing blank lines but preserve internal ones (body blocks).
function trimBlankLines(lines) {
  const out = lines.slice();
  while (out.length && !out[0].trim()) out.shift();
  while (out.length && !out[out.length - 1].trim()) out.pop();
  return out;
}

// A list step with no "title" field (secrets) exports as a bullet list, where a
// multi-line body is continued on indented lines and a revealed item is tagged
// with a trailing " _(revealed)_".
function parseBulletCards(def, lines) {
  const items = [];
  let cur = null;
  for (const ln of lines) {
    const bm = ln.match(/^[-*]\s+(.*)$/);
    if (bm) {
      if (cur) items.push(cur);
      cur = { id: window.RPG.uid(), body: bm[1] };
    } else if (cur) {
      // continuation line — export indents these by two spaces
      cur.body += "\n" + ln.replace(/^\s{0,2}/, "");
    }
  }
  if (cur) items.push(cur);

  return items.map((it) => {
    const rm = it.body.match(/\s*_\(revealed\)_\s*$/);
    if (rm) { it.body = it.body.slice(0, rm.index).replace(/\s+$/, ""); it.revealed = true; }
    else if (def.revealable) it.revealed = false;
    it.body = it.body.trim();
    return it;
  }).filter((it) => it.body);
}

// One title card group → an item shaped by the step's fields. The group's first
// line is the title line ("**Title** — _sub_"); for steps with an aspects field
// the very next line may be an aspects line ("_a · b · c_"); the rest is body.
function parseTitleCard(def, lines) {
  const hasAspects = def.fields.some((f) => f.key === "aspects");
  const first = (lines[0] || "").trim();
  const tm = first.match(/^\*\*(.+?)\*\*\s*(?:[—–-]\s*_(.+?)_\s*)?$/);
  const title = tm ? tm[1].trim() : first.replace(/\*\*/g, "").trim();
  const sub = tm && tm[2] ? tm[2].trim() : "";

  let rest = lines.slice(1);
  let aspects = [];
  if (hasAspects && rest[0] && rest[0].trim() && /^_(.+)_$/.test(rest[0].trim())) {
    aspects = rest[0].trim().replace(/^_|_$/g, "").split(/\s*·\s*/).map((s) => s.trim()).filter(Boolean);
    rest = rest.slice(1);
  }
  const body = trimBlankLines(rest).join("\n").trim();

  const card = { id: window.RPG.uid() };
  def.fields.forEach((f) => {
    if (f.key === "title") card.title = title;
    else if (f.key === "sub") card.sub = sub;
    else if (f.key === "aspects") card.aspects = aspects;
    else if (f.key === "body") card.body = body;
  });
  const hasContent = card.title || card.body || (card.aspects && card.aspects.length);
  return hasContent ? card : null;
}

function parseStepBlock(def, text) {
  const lines = String(text || "").split("\n");

  // Single free-text step (Strong Start): the whole block is one body.
  if (def.kind === "single") {
    const body = trimBlankLines(lines).join("\n").trim();
    return body ? [{ id: window.RPG.uid(), body }] : [];
  }

  // List step with no title (secrets) → bullets.
  if (!def.fields.some((f) => f.key === "title")) {
    return parseBulletCards(def, lines);
  }

  // Title cards — a new card begins at each "**…" line. Any leading text before
  // the first such line is treated as a single titleless card (rare).
  const groups = [];
  let cur = null;
  for (const ln of lines) {
    if (/^\*\*/.test(ln.trim())) { if (cur) groups.push(cur); cur = [ln]; }
    else if (cur) cur.push(ln);
    else { (groups.lead = groups.lead || []).push(ln); }
  }
  const cards = [];
  if (groups.lead && trimBlankLines(groups.lead).length) {
    const c = parseTitleCard(def, ["", ...groups.lead]); // no title line → all body
    if (c) cards.push(c);
  }
  if (cur) groups.push(cur);
  groups.forEach((g) => { const c = parseTitleCard(def, g); if (c) cards.push(c); });
  return cards;
}

// Parse a full export-format Markdown document. Returns
// { ok: true, campaign:{name,blurb,tone}, sessions:[…] } or { ok:false, error }.
function parseImportMarkdown(raw) {
  if (!raw || !String(raw).trim()) return { ok: false, error: "The file is empty." };

  const headingMap = buildHeadingMap();
  const lines = String(raw).replace(/\r\n/g, "\n").split("\n")
    // drop the export footer (a horizontal rule + "_Exported from Emberwright…_")
    .filter((ln) => !/^---+\s*$/.test(ln) && !/^_Exported from Emberwright.*_\s*$/.test(ln.trim()));

  const result = { ok: true, campaign: { name: "", blurb: "", tone: "" }, sessions: [] };
  let context = null;          // "campaign" | "session" | "step"
  let curSession = null;
  let curStep = null;
  let buf = [];

  const flush = () => {
    const block = buf; buf = [];
    if (context === "campaign") {
      for (const ln of block) {
        const t = ln.trim();
        if (!t) continue;
        let m;
        if (!result.campaign.blurb && (m = t.match(/^_(.+)_$/))) result.campaign.blurb = m[1].trim();
        else if ((m = t.match(/^\*\*Tone & themes:\*\*\s*(.*)$/))) result.campaign.tone = m[1].trim();
      }
    } else if (context === "session" && curSession) {
      for (const ln of block) {
        const m = ln.trim().match(/^_(.+)_$/);
        if (m) { curSession.date = m[1].trim(); break; }
      }
    } else if (context === "step" && curSession && curStep) {
      const items = parseStepBlock(curStep, block.join("\n"));
      if (items.length) curSession.data[curStep.id] = items;
    }
  };

  for (const line of lines) {
    const hm = line.match(/^(#{1,6})\s+(.*)$/);
    if (!hm) { buf.push(line); continue; }
    flush();
    const level = hm[1].length;
    const titleText = hm[2].trim();
    if (level === 1) {
      result.campaign.name = titleText;
      context = "campaign"; curSession = null; curStep = null;
    } else if (level === 2) {
      const sm = titleText.match(/^Session\s+(\d+)\s*[—–-]\s*(.*)$/);
      const number = sm ? parseInt(sm[1], 10) : null;
      const name = sm ? sm[2].trim() : titleText;
      curSession = window.RPG.blankSession(number || result.sessions.length + 1);
      curSession.name = name || "Untitled session";
      if (number != null) curSession.number = number;
      curSession.date = "";
      result.sessions.push(curSession);
      context = "session"; curStep = null;
    } else {
      const def = headingMap[titleText.toLowerCase()];
      if (def && curSession) { curStep = def; context = "step"; }
      else { curStep = null; context = null; } // unknown sub-heading → ignore its body
    }
  }
  flush();

  if (!result.sessions.length) {
    return { ok: false, error: "No sessions found. Import expects a Markdown file exported from Emberwright." };
  }
  return result;
}

// ── Import dialog ─────────────────────────────────────────────────────────────
function ImportRadio({ name, value, current, onPick, title, hint, disabled }) {
  return (
    <label className="cf-row" style={{ flexDirection: "row", alignItems: "flex-start", gap: 10, opacity: disabled ? 0.5 : 1 }}>
      <input type="radio" name={name} checked={current === value} disabled={disabled}
        onChange={() => onPick(value)} style={{ marginTop: 4 }} />
      <span>
        <strong>{title}</strong>
        <span className="cf-hint" style={{ display: "block" }}>{hint}</span>
      </span>
    </label>
  );
}

function sessionLabelFor(s) {
  return (s.number != null ? "Session " + s.number + " — " : "") + (s.name || "Untitled");
}

function ImportModal({ camp, setState, onClose }) {
  const [parsed, setParsed] = React.useState(null);
  const [error, setError] = React.useState("");
  const [fileName, setFileName] = React.useState("");
  const [scope, setScope] = React.useState("new");
  const fileRef = React.useRef(null);

  const readFile = (file) => {
    if (!file) return;
    setFileName(file.name);
    const reader = new FileReader();
    reader.onload = () => {
      let res;
      try { res = parseImportMarkdown(String(reader.result || "")); }
      catch (e) { res = { ok: false, error: "Couldn't parse this file." }; }
      if (res.ok) { setParsed(res); setError(""); }
      else { setParsed(null); setError(res.error); }
    };
    reader.onerror = () => { setParsed(null); setError("Couldn't read this file."); };
    reader.readAsText(file);
  };

  const doImport = () => {
    if (!parsed) return;
    const sessions = parsed.sessions;
    if (scope === "new") {
      const newCamp = {
        id: window.RPG.uid(),
        name: parsed.campaign.name || "Imported campaign",
        blurb: parsed.campaign.blurb || "",
        tone: parsed.campaign.tone || "",
        sessions: sessions.map((s, i) => ({ ...s, number: i + 1 })),
      };
      if (!newCamp.sessions.length) newCamp.sessions = [window.RPG.blankSession(1)];
      setState((st) => ({
        ...st,
        campaigns: [...st.campaigns, newCamp],
        activeCampaign: newCamp.id,
        activeSession: newCamp.sessions[0].id,
      }));
    } else {
      const base = camp.sessions.length;
      const added = sessions.map((s, i) => ({ ...s, number: base + i + 1 }));
      const firstId = added[0] && added[0].id;
      setState((st) => ({
        ...st,
        campaigns: st.campaigns.map((c) =>
          c.id === camp.id ? { ...c, sessions: [...c.sessions, ...added] } : c),
        activeSession: firstId || st.activeSession,
      }));
    }
    onClose();
  };

  const count = parsed ? parsed.sessions.length : 0;

  return (
    <window.Modal title="Import" onClose={onClose} width={460}>
      <div className="camp-form">
        <span className="cf-label">Markdown file</span>
        <input ref={fileRef} type="file" accept=".md,.markdown,.txt,text/markdown,text/plain"
          style={{ display: "none" }}
          onChange={(e) => readFile(e.target.files && e.target.files[0])} />
        <button type="button" className="btn btn-sm" style={{ alignSelf: "flex-start" }}
          onClick={() => fileRef.current && fileRef.current.click()}>
          <window.Icon name="import" size={15} />{fileName ? "Choose a different file" : "Choose a .md file"}
        </button>
        {fileName && <span className="cf-hint">{fileName}</span>}
        <span className="cf-hint">
          Reads a Markdown file exported from Emberwright. (Images aren't included in Markdown exports, so they won't be restored.)
        </span>

        {error && <span className="cf-hint" style={{ color: "var(--danger, #b3433a)" }}>{error}</span>}

        {parsed && (
          <>
            <div style={{ border: "1px solid var(--line)", borderRadius: 10, padding: "10px 14px", background: "var(--paper-2)" }}>
              <strong style={{ fontFamily: "var(--font-display)" }}>{parsed.campaign.name || "Untitled campaign"}</strong>
              <span className="cf-hint" style={{ display: "block", marginTop: 2 }}>
                {count} session{count !== 1 ? "s" : ""} found
              </span>
              <ul style={{ margin: "6px 0 0", paddingLeft: 18, fontSize: 13, color: "var(--ink-soft)" }}>
                {parsed.sessions.slice(0, 6).map((s) => <li key={s.id}>{sessionLabelFor(s)}</li>)}
                {count > 6 && <li>…and {count - 6} more</li>}
              </ul>
            </div>

            <span className="cf-label" style={{ marginTop: 6 }}>Import as</span>
            <ImportRadio name="ew-import-scope" value="new" current={scope} onPick={setScope}
              title="A new campaign"
              hint={"Creates “" + (parsed.campaign.name || "Imported campaign") + "” with all " + count + " session" + (count !== 1 ? "s" : "") + "."} />
            <ImportRadio name="ew-import-scope" value="current" current={scope} onPick={setScope}
              title="Sessions in this campaign"
              hint={"Adds the " + count + " session" + (count !== 1 ? "s" : "") + " to “" + camp.name + "” after its existing ones."} />
          </>
        )}

        <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
          <button type="button" className="btn btn-ghost btn-sm" onClick={onClose}>Cancel</button>
          <button type="button" className="btn btn-accent btn-sm" onClick={doImport} disabled={!parsed}>
            <window.Icon name="import" size={15} />Import
          </button>
        </div>
      </div>
    </window.Modal>
  );
}

window.parseImportMarkdown = parseImportMarkdown;
window.ImportModal = ImportModal;
