// app-data.jsx — sample agenda, motion templates, parser, helpers
(function () {
  // ---- Motion boilerplate templates -------------------------------------
  // {{}} marks where the secretary types the substance; after insertion it
  // is replaced with the PLACEHOLDER token and auto-selected in the textarea.
  const PLACEHOLDER = "\u2026"; // …

  const MOTIONS = [
    {
      code: "M/S/U",
      label: "Carried — unanimous",
      hint: "Made, seconded, unanimously carried",
      template: "A motion was duly made, seconded, and unanimously carried to {{}}.",
    },
    {
      code: "M/S/C",
      label: "Carried",
      hint: "Made, seconded, and carried",
      template: "A motion was duly made, seconded, and carried to {{}}.",
    },
    {
      code: "M/S/F",
      label: "Failed",
      hint: "Made, seconded, but failed",
      template: "A motion was duly made and seconded, but failed, to {{}}.",
    },
    {
      code: "Tabled",
      label: "Tabled / deferred",
      hint: "Laid over to a future meeting",
      template: "A motion was duly made and seconded to table {{}} until the next regular meeting.",
    },
  ];

  // Appendable vote clauses (inserted before the final period of a motion)
  const CLAUSES = [
    { code: "Abstentions", label: "+ abstentions", clause: ", with {{}} abstaining," },
    { code: "Opposed", label: "+ opposed", clause: ", with {{}} opposed," },
  ];

  // map shorthand typed inline -> template
  const INLINE = {};
  MOTIONS.forEach((m) => { INLINE[m.code.toLowerCase()] = m; });

  // ---- Decision statuses -------------------------------------------------
  const STATUSES = [
    { key: "approved", label: "Approved" },
    { key: "tabled", label: "Tabled" },
    { key: "deferred", label: "Deferred" },
    { key: "noaction", label: "No action" },
    { key: "info", label: "Informational" },
  ];

  // ---- Sample agenda (realistic nonprofit board) -------------------------
  const SAMPLE_TEXT = `BOARD OF DIRECTORS — REGULAR MEETING AGENDA

1. Call to Order
2. Roll Call and Establishment of Quorum
3. Approval of the Agenda
4. Approval of Minutes — Regular Meeting of May 11, 2026
5. Consent Agenda
6. Treasurer's Report and Financial Statements
7. Executive Director's Report
8. Committee Reports
   a. Finance Committee
   b. Governance & Nominating Committee
9. Old Business
   a. Adoption of the FY2026 Operating Budget
10. New Business
   a. Renewal of Headquarters Facilities Lease
11. Public Comment
12. Executive Session
13. Adjournment`;

  const PRESENTERS = [
    "Chair", "Vice Chair", "Secretary", "Treasurer", "Executive Director",
    "Finance Committee Chair", "Governance Chair", "General Counsel", "Staff",
  ];

  // ---- Roman-numeral helpers --------------------------------------------
  const ROMAN_RE = /^(M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))$/i;
  function isRoman(s) { return !!s && ROMAN_RE.test(s); }
  function romanToInt(s) {
    const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
    const u = s.toUpperCase(); let n = 0;
    for (let i = 0; i < u.length; i++) {
      const cur = map[u[i]], next = map[u[i + 1]] || 0;
      n += cur < next ? -cur : cur;
    }
    return n;
  }
  function intToRoman(n) {
    const t = [["M", 1000], ["CM", 900], ["D", 500], ["CD", 400], ["C", 100], ["XC", 90],
      ["L", 50], ["XL", 40], ["X", 10], ["IX", 9], ["V", 5], ["IV", 4], ["I", 1]];
    let r = "";
    t.forEach(([sym, val]) => { while (n >= val) { r += sym; n -= val; } });
    return r;
  }

  const NOISE = /^(page\s*#?\s*$|agenda\s*$|continued|cont'd)/i;

  // Pull apart a single raw line into { leadingTabs, page, marker, body }.
  // page  = leading "3-5" / "47" / "76-98" column before a tab/double-space
  // marker= "I" / "A" / "1" label before a "." or ")"
  function parseLine(rawLine) {
    let s = String(rawLine).replace(/\r/g, "");
    const leadingTabs = (s.match(/^\t+/) || [""])[0].length;
    s = s.replace(/^[\t ]+/, "").replace(/[\t ]+$/, "");
    if (!s) return { empty: true };
    let page = "";
    const pm = s.match(/^(\d{1,3}(?:\s*[-\u2013]\s*\d{1,3})?)(?:\t|\u00a0|\s{2,})\s*(.*)$/);
    if (pm && pm[2]) { page = pm[1].replace(/\s/g, ""); s = pm[2]; }
    let marker = "", body = s;
    const mm = s.match(/^([A-Za-z]{1,5}|\d{1,2})[.)][\t ]+(.+)$/);
    if (mm) { marker = mm[1]; body = mm[2].replace(/^[\t ]+/, ""); }
    return { empty: false, leadingTabs, page, marker, body: body.trim() };
  }

  // ---- Title-block / meta detection -------------------------------------
  const MONTHS = "(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)";
  function detectMeta(blockLines) {
    const meta = {};
    let title = "";
    const nonEmpty = blockLines.map((l) => l.trim()).filter(Boolean);
    nonEmpty.forEach((line, i) => {
      if (/^page\s*#?$/i.test(line)) return;
      if (i === 0 && !meta.org) { meta.org = toTitleCase(line); return; }
      if (/\bagenda\b/i.test(line) && !title) { title = toTitleCase(line.replace(/\s*[-\u2013\u2014]?\s*agenda\s*$/i, "").trim()); return; }
      const tm = line.match(/(\d{1,2}:\d{2})\s*([ap])\.?\s*m\.?/i);
      if (tm && !meta.time) meta.time = tm[1] + " " + tm[2].toLowerCase() + ".m.";
      const dm = line.match(new RegExp(MONTHS + "[a-z]*\\.?\\s+(\\d{1,2}),?\\s+(\\d{4})", "i"));
      if (dm && !meta.date) {
        const parsed = new Date(dm[0]);
        if (!isNaN(parsed)) meta.date = parsed.toISOString().slice(0, 10);
      }
      if (/(center|hall|room|clubhouse|library|office|videoconference|zoom|teleconference|\d+\s+[NSEW]\.?\s|\bst\b|\bave\b|\bblvd\b|\bdr\b)/i.test(line)
          && !/\bagenda\b/i.test(line) && !tm) {
        if (!meta.location) meta.location = toTitleCase(line);
      }
    });
    return { meta, title };
  }

  // ---- Agenda parser -----------------------------------------------------
  // Builds a hierarchical outline. Top level follows whatever scheme the
  // first item uses (roman "I." or numeric "1."); everything else nests
  // beneath it as lettered / numbered / unlabelled sub-items. A leading
  // page-number column is captured per item, and wrapped lines are rejoined.
  function parseAgenda(raw) {
    const rawLines = String(raw || "").split(/\r?\n/);

    // locate first real item line
    let firstIdx = -1;
    for (let i = 0; i < rawLines.length; i++) {
      const p = parseLine(rawLines[i]);
      if (!p.empty && p.marker) { firstIdx = i; break; }
    }

    let title = "", meta = {};
    if (firstIdx > 0) {
      const det = detectMeta(rawLines.slice(0, firstIdx));
      meta = det.meta; title = det.title;
    }

    const items = [];
    let scheme = null;        // 'roman' | 'num'
    let topCount = 0;         // sequence value of last top-level item
    let lastItem = null;

    const startAt = firstIdx >= 0 ? firstIdx : 0;
    for (let i = startAt; i < rawLines.length; i++) {
      const p = parseLine(rawLines[i]);
      if (p.empty) continue;
      if (NOISE.test(p.body) && !p.marker) continue;

      const mk = p.marker;
      const isNum = /^\d+$/.test(mk);
      let isTop = false, level = 1;

      if (mk) {
        if (!scheme) {
          // first item sets the top-level scheme
          scheme = isNum ? "num" : (isRoman(mk) ? "roman" : "letter");
          isTop = true;
          topCount = isNum ? parseInt(mk, 10) : (isRoman(mk) ? romanToInt(mk) : 1);
        } else if (scheme === "num") {
          if (isNum && parseInt(mk, 10) === topCount + 1) { isTop = true; topCount++; }
        } else if (scheme === "roman") {
          if (!isNum && isRoman(mk)) {
            const val = romanToInt(mk);
            if (mk.length > 1) { isTop = true; topCount = val; }                 // II, III, IV… unambiguous
            else if (val === romanToInt(intToRoman(topCount + 1))) { isTop = true; topCount = val; } // next single
          }
        }
        if (!isTop) level = isNum ? 2 : 1; // numeric markers nest deeper than letters
      } else {
        // no marker: continuation of the previous line, or an unlabelled sub
        if (p.leadingTabs === 0 && lastItem && !p.page) {
          lastItem.title = (lastItem.title + " " + p.body).replace(/\s+/g, " ").trim();
          continue;
        }
        level = 1;
      }

      const number = isTop
        ? (scheme === "roman" ? mk.toUpperCase() : mk)
        : (mk ? (isNum ? mk : mk.toUpperCase()) : "");
      const it = makeItem(number, clean(p.body), isTop ? 0 : level, p.page);
      items.push(it);
      lastItem = it;
    }

    if (items.length >= 2) return { title, items, meta };

    // ---- fallback: heading-style (no explicit numbering) ----
    items.length = 0;
    let n = 0;
    rawLines.forEach((line) => {
      const text = clean(line.trim());
      if (!text || text.length > 90) return;
      if (NOISE.test(text)) return;
      const words = text.split(/\s+/).length;
      if (/[.!?]$/.test(text) && words > 9) return;
      if (words > 14) return;
      if (!title && /agenda|board|meeting/i.test(text)) { title = toTitleCase(text); return; }
      n += 1;
      items.push(makeItem(String(n), text, 0, ""));
    });

    return { title, items, meta };
  }

  function clean(s) {
    return String(s)
      .replace(/\s*\.{2,}\s*\d*$/, "")                 // dotted leader + trailing page
      .replace(/\t/g, " ")
      .replace(/\s+/g, " ")
      .trim();
  }

  let _seq = 0;
  function makeItem(number, rawTitle, level, page) {
    const t = guessType(rawTitle);
    return {
      id: "it_" + (Date.now().toString(36)) + "_" + (_seq++),
      number,
      title: rawTitle,
      level: level || 0,
      isSub: (level || 0) > 0,
      page: page || "",
      presenter: t.presenter || "",
      notes: "",
      status: t.status || "",
      vote: { recorded: false, for: "", against: "", abstain: "" },
      actions: [],
      collapsed: false,
    };
  }

  // light heuristics to pre-fill presenter / likely status
  function guessType(title) {
    const t = title.toLowerCase();
    if (/treasurer|financial/.test(t)) return { presenter: "Treasurer" };
    if (/executive director/.test(t)) return { presenter: "Executive Director" };
    if (/finance committee/.test(t)) return { presenter: "Finance Committee Chair" };
    if (/governance/.test(t)) return { presenter: "Governance Chair" };
    if (/call to order|roll call|adjourn|public comment/.test(t)) return { presenter: "Chair", status: "info" };
    if (/executive session/.test(t)) return { presenter: "Chair" };
    return {};
  }

  function toTitleCase(s) {
    return s.toLowerCase().replace(/\b([a-z])/g, (m) => m.toUpperCase());
  }

  window.MeetingData = {
    PLACEHOLDER, MOTIONS, CLAUSES, INLINE, STATUSES, SAMPLE_TEXT, PRESENTERS,
    parseAgenda, makeItem,
  };
})();
