// Shared utilities: Buddhist-era dates, calendar DateField, baht formatting.
(function () {
  const TH_FULL = ["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"];

  window.todayISO = function () { const d = new Date(); return d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2,"0") + "-" + String(d.getDate()).padStart(2,"0"); };

  // ISO "2026-06-04" → "04 มิถุนายน 2569" (Buddhist Era, zero-padded day, full Thai month)
  window.isoToBE = function (iso) { if (!iso) return ""; const d = new Date(iso + "T00:00:00"); if (isNaN(d)) return iso; return String(d.getDate()).padStart(2,"0") + " " + TH_FULL[d.getMonth()] + " " + (d.getFullYear() + 543); };

  // day offset from a timeline start ISO  ↔  ISO
  window.isoToDay = function (iso, startISO) { try { const g = new Date((startISO || window.DATA.timeline.startISO) + "T00:00:00").getTime(); const d = new Date(iso + "T00:00:00").getTime(); return Math.round((d - g) / 86400000); } catch (e) { return 0; } };
  window.dayToISO = function (day, startISO) { try { const g = new Date((startISO || window.DATA.timeline.startISO) + "T00:00:00").getTime(); const d = new Date(g + (day || 0) * 86400000); return d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2,"0") + "-" + String(d.getDate()).padStart(2,"0"); } catch (e) { return window.todayISO(); } };

  // budget stored internally in BAHT → "7,000,000 บาท"
  window.fmtBaht = function (baht) { const n = Math.round(parseFloat(baht) || 0); return n.toLocaleString("en-US") + " บาท"; };

  // ── Timeline item type derivation (single source of truth) ──
  // Default: auto-classify by dates (same-day → milestone, multi-day → task).
  // The user can manually override in the Item modal; the stored type wins.
  window.autoTypeFromDates = function (sISO, eISO) {
    const s = sISO || ""; const e = eISO || s;
    if (!e || s === e) return "milestone";
    return "task";
  };
  window.effectiveType = function (t) {
    if (!t) return "task";
    // Honor explicit stored type when valid
    if (t.type === "milestone" || t.type === "task") return t.type;
    // Fall back to date-based auto-classify
    return window.autoTypeFromDates(t.dateISO, t.dateISOEnd);
  };
  window.isMilestone = function (t) { return window.effectiveType(t) === "milestone"; };
  // Display labels used everywhere in the UI:
  window.typeLabel = function (t) { return window.isMilestone(t) ? "เหตุการณ์สำคัญ" : "งานดำเนินการ"; };
  window.typeIcon  = function (t) { return window.isMilestone(t) ? "♦" : "🟢"; };

  // Real CSV download (UTF-8 BOM for Excel/Thai)
  window.exportCSV = function (filename, rows) {
    const csv = rows.map((r) => r.map((c) => { const s = String(c == null ? "" : c); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; }).join(",")).join("\n");
    const blob = new Blob(["﻿" + csv], { type: "text/csv;charset=utf-8;" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a"); a.href = url; a.download = filename;
    document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
  };
  // Build a real projects CSV from current state
  window.exportProjectsCSV = function () {
    const D = window.DATA;
    const rows = [["รหัส", "โครงการ", "ประเภท", "สถานะ", "ความคืบหน้า (%)", "เจ้าของ", "งบประมาณ (บาท)", "เบิกจ่าย (บาท)", "วันเริ่ม", "วันสิ้นสุด"]];
    (D.projects || []).forEach((p) => rows.push([p.code, p.name, p.type, (D.statusMeta[p.status] || {}).th || p.status, p.progress, p.owner, Math.round(p.budget || 0), Math.round(p.spent || 0), p.start || "", p.end || ""]));
    window.exportCSV("projects-report.csv", rows);
    window.toast("ดาวน์โหลด CSV แล้ว");
  };

  // Per-project Gantt scale — derives the timeline window from the project's own
  // start/end (supports historical & multi-year spans) and maps phase/item ISO dates.
  window.ganttScale = function (p, det) {
    const tstart = p.start || window.DATA.timeline.startISO;
    const tendISO = p.end || window.dayToISO(window.DATA.timeline.totalDays, tstart);
    const total = Math.max(30, window.isoToDay(tendISO, tstart));
    const clamp = (d) => Math.max(0, Math.min(total, d));
    const today = clamp(window.isoToDay(window.todayISO(), tstart));
    const phases = (det.phases || []).map((ph) => {
      const sISO = ph.startISO || window.dayToISO(ph.s);
      const eISO = ph.endISO || window.dayToISO(ph.e);
      const s = clamp(window.isoToDay(sISO, tstart));
      const e = Math.max(s, clamp(window.isoToDay(eISO, tstart)));
      return Object.assign({}, ph, { s, e, tasks: (ph.tasks || []).map((t) => {
        const tISOs = t.dateISO || window.dayToISO(t.s);
        const tISOe = t.dateISOEnd || tISOs;
        const ds = clamp(window.isoToDay(tISOs, tstart));
        const de = Math.max(ds, clamp(window.isoToDay(tISOe, tstart)));
        return Object.assign({}, t, { s: ds, e: de });
      }) });
    });
    return { phases, startISO: tstart, todayDay: today, totalDays: total };
  };

  // ── Form-safety helpers ────────────────────────────────────
  // Overlay click handler: no-op (outside click never closes a form).
  window.NOOP = function () {};

  // Subscribe a component to the global data-change event so its DOM
  // re-renders whenever any DB.* mutation runs anywhere in the app.
  window.useLiveData = function () {
    const { useState, useEffect } = React;
    const [, set] = useState(0);
    useEffect(() => {
      const fn = () => set((n) => n + 1);
      window.addEventListener("data-change", fn);
      return () => window.removeEventListener("data-change", fn);
    }, []);
  };
  // Confirm dialog when there are unsaved changes.
  window.confirmDiscard = function (dirty) {
    if (!dirty) return true;
    return confirm("มีข้อมูลที่ยังไม่ได้บันทึก ต้องการออกจากหน้านี้หรือไม่?\n\n[ตกลง] = ออกจากหน้านี้\n[ยกเลิก] = กลับไปแก้ไข");
  };
  // useDirty — wraps useState with dirty tracking + a safeClose that prompts on unsaved changes.
  //   const [f, setF, safeClose] = window.useDirty(initial, onClose);
  //   const [f, setF, safeClose] = window.useDirty(initial, onClose, { draftKey: "project-create" });
  //
  //   With draftKey: auto-saves to localStorage every change (debounced 5s) and restores on next mount.
  //   On successful save, callers should invoke window.clearDraft(draftKey) to drop the recovered draft.
  window.useDirty = function (initial, onClose, opts) {
    const { useState, useRef, useEffect } = React;
    const draftKey = opts && opts.draftKey;
    // Restore draft if present (otherwise use initial)
    const start = (() => {
      if (!draftKey) return initial;
      try {
        const raw = localStorage.getItem("bp-draft-" + draftKey);
        if (raw) return Object.assign({}, initial, JSON.parse(raw));
      } catch (e) {}
      return initial;
    })();
    const [f, setRaw] = useState(start);
    const dirty = useRef(false);
    const restored = useRef(draftKey && start !== initial);
    const saveTimer = useRef(null);
    const setF = (next) => {
      dirty.current = true;
      setRaw(next);
      if (draftKey) {
        // Debounced save every 5 seconds
        if (saveTimer.current) clearTimeout(saveTimer.current);
        saveTimer.current = setTimeout(() => {
          try { const cur = (typeof next === "function") ? next(f) : next; localStorage.setItem("bp-draft-" + draftKey, JSON.stringify(cur)); } catch (e) {}
        }, 5000);
      }
    };
    useEffect(() => () => { if (saveTimer.current) clearTimeout(saveTimer.current); }, []);
    const safeClose = () => { if (window.confirmDiscard(dirty.current)) onClose(); };
    return [f, setF, safeClose, restored.current];
  };
  window.clearDraft = function (draftKey) {
    try { localStorage.removeItem("bp-draft-" + draftKey); } catch (e) {}
  };

  // Reusable calendar date field (native picker + Buddhist-era preview). Supports historical & future dates.
  window.DateField = function DateField({ label, value, onChange }) {
    return (
      <label className="login-field"><span>{label}</span>
        <div className="login-input"><input type="date" lang="th" value={value || ""} onChange={(e) => onChange(e.target.value)} style={{ border:0, background:"none", outline:"none", font:"inherit", fontSize:13.5, flex:1, color:"var(--ink)" }} /></div>
        <span style={{ fontSize:12, color:"var(--muted)", marginTop:4 }}>{value ? window.isoToBE(value) : "—"}</span>
      </label>
    );
  };

  // ── 30-min increment time picker (HH:mm) ─────────────────────
  function buildTimes() {
    const arr = []; for (let h = 0; h < 24; h++) for (const m of [0, 30]) arr.push(String(h).padStart(2,"0") + ":" + String(m).padStart(2,"0"));
    return arr;
  }
  const TIME_OPTS = buildTimes();
  window.TimeField = function TimeField({ label, value, onChange }) {
    return (
      <label className="login-field"><span>{label}</span>
        <div className="login-input">
          <input list="bp-time-opts" value={value || ""} onChange={(e) => onChange(e.target.value)} placeholder="--:--" pattern="[0-9]{2}:[0-9]{2}" maxLength={5}
            style={{ border:0, background:"none", outline:"none", font:"inherit", fontSize:13.5, flex:1, color:"var(--ink)" }} />
          <datalist id="bp-time-opts">{TIME_OPTS.map((t) => <option key={t} value={t} />)}</datalist>
        </div>
      </label>
    );
  };
  // Add minutes to "HH:mm" → "HH:mm" (wraps within a single day; 24:00 clamps to 23:59)
  window.addMinutes = function (hhmm, mins) {
    if (!hhmm || !/^\d{2}:\d{2}$/.test(hhmm)) return hhmm;
    const [h, m] = hhmm.split(":").map(Number);
    let total = h * 60 + m + (mins || 0);
    if (total >= 24 * 60) total = 24 * 60 - 1;
    if (total < 0) total = 0;
    return String(Math.floor(total / 60)).padStart(2,"0") + ":" + String(total % 60).padStart(2,"0");
  };
  // Quick-duration buttons that update an end-time from a start-time
  window.QuickDurationButtons = function QuickDurationButtons({ start, onEnd }) {
    const opts = [["+30 นาที", 30],["+1 ชม.", 60],["+2 ชม.", 120],["ครึ่งวัน", 240],["เต็มวัน", 480]];
    return (
      <div style={{ display:"flex", gap:6, flexWrap:"wrap", marginTop:6 }}>
        {opts.map(([label, mins]) => (
          <button type="button" key={label} className="chip"
            onClick={() => { if (start) onEnd(window.addMinutes(start, mins)); }}
            style={{ padding:"5px 11px", fontSize:12, height:"auto", minHeight:0 }}>{label}</button>
        ))}
      </div>
    );
  };

  // ── Markdown-lite (bold/bullet/numbered/link) — input toolbar + display renderer ──
  // Toolbar inserts plain-text syntax around the textarea selection so data stays as strings.
  window.RichToolbar = function RichToolbar({ textareaRef }) {
    const wrap = (open, close) => () => {
      const ta = textareaRef && textareaRef.current; if (!ta) return;
      const s = ta.selectionStart, e = ta.selectionEnd, val = ta.value;
      const before = val.slice(0, s), sel = val.slice(s, e), after = val.slice(e);
      ta.value = before + open + sel + close + after;
      ta.selectionStart = s + open.length; ta.selectionEnd = e + open.length;
      ta.dispatchEvent(new Event("input", { bubbles: true }));
      ta.focus();
    };
    const linePrefix = (prefix) => () => {
      const ta = textareaRef && textareaRef.current; if (!ta) return;
      const s = ta.selectionStart, e = ta.selectionEnd, val = ta.value;
      const lineStart = val.lastIndexOf("\n", s - 1) + 1;
      const lineEnd = val.indexOf("\n", e); const end = lineEnd === -1 ? val.length : lineEnd;
      const block = val.slice(lineStart, end);
      const linesArr = block.split("\n");
      let counter = 1;
      const out = linesArr.map((ln) => {
        if (prefix === "1. ") { const r = counter + ". " + ln.replace(/^\s*(\d+\.\s|-\s|\*\s)/, ""); counter++; return r; }
        return prefix + ln.replace(/^\s*(\d+\.\s|-\s|\*\s)/, "");
      }).join("\n");
      ta.value = val.slice(0, lineStart) + out + val.slice(end);
      ta.dispatchEvent(new Event("input", { bubbles: true }));
      ta.focus();
    };
    const link = () => {
      const url = prompt("URL ของลิงก์:"); if (!url) return;
      const ta = textareaRef && textareaRef.current; if (!ta) return;
      const s = ta.selectionStart, e = ta.selectionEnd, val = ta.value;
      const sel = val.slice(s, e) || "ลิงก์";
      ta.value = val.slice(0, s) + "[" + sel + "](" + url + ")" + val.slice(e);
      ta.dispatchEvent(new Event("input", { bubbles: true }));
      ta.focus();
    };
    const btn = { padding:"3px 8px", fontSize:12, fontWeight:600, border:"1px solid var(--line)", background:"var(--surface)", color:"var(--ink2)", borderRadius:6, cursor:"pointer", height:26 };
    return (
      <div style={{ display:"flex", gap:5, marginBottom:5, flexWrap:"wrap" }}>
        <button type="button" style={{ ...btn, fontWeight:800 }} onClick={wrap("**","**")} title="ตัวหนา">B</button>
        <button type="button" style={btn} onClick={linePrefix("- ")} title="รายการ bullet">•</button>
        <button type="button" style={btn} onClick={linePrefix("1. ")} title="รายการตัวเลข">1.</button>
        <button type="button" style={btn} onClick={link} title="แทรกลิงก์">🔗</button>
      </div>
    );
  };

  // Render Markdown-lite to safe HTML (escapes everything first; only our tokens become tags).
  function escHtml(s) { return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"); }
  window.renderRich = function renderRich(text) {
    if (!text) return "";
    let s = escHtml(text);
    // links: [text](url)  — url limited to http(s)/mailto/relative
    s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+|mailto:[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:underline">$1</a>');
    // bold: **text**
    s = s.replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>");
    // lists: per-line
    const lines = s.split("\n"); const out = []; let mode = null;
    for (const ln of lines) {
      const ul = /^- (.*)$/.exec(ln), ol = /^\d+\.\s(.*)$/.exec(ln);
      if (ul) { if (mode !== "ul") { if (mode) out.push("</"+mode+">"); out.push('<ul style="margin:4px 0 4px 18px">'); mode = "ul"; } out.push("<li>"+ul[1]+"</li>"); }
      else if (ol) { if (mode !== "ol") { if (mode) out.push("</"+mode+">"); out.push('<ol style="margin:4px 0 4px 18px">'); mode = "ol"; } out.push("<li>"+ol[1]+"</li>"); }
      else { if (mode) { out.push("</"+mode+">"); mode = null; } out.push(ln); }
    }
    if (mode) out.push("</"+mode+">");
    return out.join("\n");
  };

  // RichDisplay — renders markdown-lite text, preserving line breaks.
  window.RichDisplay = function RichDisplay({ text, style }) {
    return <div style={Object.assign({ whiteSpace:"pre-wrap", wordBreak:"break-word", overflowWrap:"anywhere" }, style || {})}
      dangerouslySetInnerHTML={{ __html: window.renderRich(text || "") }} />;
  };

  // ── Auto-resizing textarea ───────────────────────────────────
  // Grows with content, scrolls only past a max height. Use everywhere we want
  // unlimited-feel long-form input. Always wraps in a column div so toolbar (if any)
  // stacks ABOVE the textarea even when the parent (.login-input) is a flex row.
  window.AutoTextarea = function AutoTextarea({ value, onChange, placeholder, rows = 3, maxHeight = 320, textareaRef, withToolbar = false, ...rest }) {
    const { useRef, useEffect } = React;
    const internalRef = useRef(null);
    const ref = textareaRef || internalRef;
    const resize = () => {
      const ta = ref.current; if (!ta) return;
      ta.style.height = "auto";
      ta.style.height = Math.min(ta.scrollHeight, maxHeight) + "px";
      ta.style.overflowY = ta.scrollHeight > maxHeight ? "auto" : "hidden";
    };
    useEffect(() => { resize(); }, [value]);
    // Walk up to the nearest .login-input and tag it as multiline so the CSS
    // can swap the locked-48px height + padding for textarea-friendly values.
    useEffect(() => {
      const ta = ref.current; if (!ta) return;
      let p = ta.parentElement;
      while (p && !(p.classList && p.classList.contains("login-input"))) p = p.parentElement;
      if (p) p.classList.add("bp-multiline");
      return () => { if (p) p.classList.remove("bp-multiline"); };
    }, []);
    return (
      <div style={{ display:"flex", flexDirection:"column", width:"100%", minWidth:0 }}>
        {withToolbar && <window.RichToolbar textareaRef={ref} />}
        <textarea ref={ref} value={value || ""} onChange={onChange} rows={rows} placeholder={placeholder}
          style={Object.assign({
            border:0, background:"none", outline:"none", font:"inherit", fontSize:13.5,
            color:"var(--ink)", width:"100%", resize:"none", lineHeight:1.55,
            overflowY:"hidden", wordBreak:"break-word", overflowWrap:"anywhere", padding:0, margin:0,
          }, rest.style || {})}
          {...rest} />
      </div>
    );
  };

  // ── ExpandableText: 3-line clamp with "ดูเพิ่มเติม" → opens full content modal ──
  window.ExpandableText = function ExpandableText({ text, lines = 3, title }) {
    const { useState, useRef, useEffect } = React;
    const ref = useRef(null);
    const [overflowing, setOverflowing] = useState(false);
    const [open, setOpen] = useState(false);
    useEffect(() => {
      const el = ref.current; if (!el) return;
      // After render, detect if content exceeds the clamp
      const lineH = parseFloat(getComputedStyle(el).lineHeight) || 18;
      setOverflowing(el.scrollHeight > lineH * lines + 2);
    }, [text, lines]);
    if (!text) return null;
    return (
      <>
        <div ref={ref} style={{
          display:"-webkit-box", WebkitBoxOrient:"vertical", WebkitLineClamp:lines,
          overflow:"hidden", whiteSpace:"pre-wrap", wordBreak:"break-word", overflowWrap:"anywhere",
          lineHeight:1.55,
        }} dangerouslySetInnerHTML={{ __html: window.renderRich(text) }} />
        {overflowing && (
          <button type="button" onClick={(e) => { e.stopPropagation(); setOpen(true); }}
            style={{ background:"none", border:0, color:"var(--accent)", cursor:"pointer", padding:"4px 0", fontSize:12.5, fontWeight:600 }}>
            ดูเพิ่มเติม
          </button>
        )}
        {open && (
          <div className="login-overlay" onClick={window.NOOP}>
            <div className="login-card" style={{ maxWidth:640, width:"94vw", maxHeight:"86vh", overflowY:"auto" }} onClick={(e) => e.stopPropagation()}>
              <button type="button" className="login-x icon-btn" onClick={() => setOpen(false)}>{window.IC ? window.IC.x : "×"}</button>
              {title && <h2 style={{ marginRight:32 }}>{title}</h2>}
              <window.RichDisplay text={text} style={{ marginTop:10, fontSize:14, color:"var(--ink2)" }} />
            </div>
          </div>
        )}
      </>
    );
  };
})();
