/* ============================================================
   IRONLOG — Shared UI primitives + icon set.
   Exported to window for cross-file use.
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

/* ---------------- Icons (stroke, 24 grid) ---------------- */
function Icon({ d, fill, size = 18, sw = 1.7, ...rest }) {
  return (
    <svg viewBox="0 0 24 24" width={size} height={size} fill={fill ? "currentColor" : "none"}
         stroke={fill ? "none" : "currentColor"} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" {...rest}>
      {Array.isArray(d) ? d.map((p, i) => <path key={i} d={p} />) : <path d={d} />}
    </svg>
  );
}
const Icons = {
  Pen:    (p) => <Icon {...p} d={["M12 20h9", "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"]} />,
  Plus:   (p) => <Icon {...p} d={["M12 5v14", "M5 12h14"]} />,
  Close:  (p) => <Icon {...p} d={["M18 6 6 18", "M6 6l12 12"]} />,
  Chevron:(p) => <Icon {...p} d="M9 6l6 6-6 6" />,
  ChevL:  (p) => <Icon {...p} d="M15 6l-6 6 6 6" />,
  Lock:   (p) => <Icon {...p} d={["M5 11h14v10H5z", "M8 11V7a4 4 0 0 1 8 0v4"]} />,
  Chat:   (p) => <Icon {...p} d="M21 11.5a8.38 8.38 0 0 1-8.5 8.5 9 9 0 0 1-4-1L3 20l1-4.5a8.5 8.5 0 0 1-1-4A8.38 8.38 0 0 1 11.5 3h.5a8.38 8.38 0 0 1 9 8.5Z" />,
  Send:   (p) => <Icon {...p} d={["M22 2 11 13", "M22 2l-7 20-4-9-9-4Z"]} />,
  Image:  (p) => <Icon {...p} d={["M3 3h18v18H3z", "M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z", "M21 15l-5-5L5 21"]} />,
  Flame:  (p) => <Icon {...p} fill d="M12 2c1 3 4 4.5 4 8a4 4 0 0 1-8 0c0-1.2.4-2 1-3-.2 2 1 3 1.5 3 .8 0 1.2-.8 1-1.8C11.2 5.5 11 3.6 12 2Z" />,
  Download:(p)=> <Icon {...p} d={["M12 3v12", "M7 11l5 5 5-5", "M5 21h14"]} />,
  Trash:  (p) => <Icon {...p} d={["M4 7h16", "M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2", "M6 7l1 13h10l1-13"]} />,
  Arrow:  (p) => <Icon {...p} d={["M5 12h14", "M13 6l6 6-6 6"]} />,
  Calendar:(p)=> <Icon {...p} d={["M3 5h18v16H3z", "M3 9h18", "M8 3v4", "M16 3v4"]} />,
  Check:  (p) => <Icon {...p} d="M4 12l5 5L20 6" />,
  Dot:    (p) => <Icon {...p} fill d="M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z" />,
  Logout: (p) => <Icon {...p} d={["M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4", "M16 17l5-5-5-5", "M21 12H9"]} />,
  Maximize:(p)=> <Icon {...p} d={["M8 3H5a2 2 0 0 0-2 2v3", "M21 8V5a2 2 0 0 0-2-2h-3", "M3 16v3a2 2 0 0 0 2 2h3", "M16 21h3a2 2 0 0 0 2-2v-3"]} />,
  Play:   (p) => <Icon {...p} fill d="M8 5v14l11-7z" />,
  Camera: (p) => <Icon {...p} d={["M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z", "M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"]} />,
  Utensils:(p)=> <Icon {...p} d={["M3 2v7a3 3 0 0 0 6 0V2", "M6 9v13", "M18 2c-2 0-3 2-3 5s1 4 3 4 3-1 3-4-1-5-3-5Z", "M18 11v11"]} />,
};

/* ---------------- Pen / edit button ---------------- */
function Pen({ onClick, label = "Edit", small }) {
  return (
    <button className={"pen" + (small ? " pen-sm" : "")} onClick={onClick} aria-label={label} title={label}>
      <Icons.Pen />
    </button>
  );
}

/* Wraps an editable region: shows pen only in admin mode. */
function Editable({ admin, onEdit, label, children, className = "", penPos = "tr" }) {
  const pos = {
    tr: { top: -10, right: -10 }, tl: { top: -10, left: -10 },
    br: { bottom: -10, right: -10 }, inline: null,
  }[penPos];
  return (
    <div className={"editable " + (admin ? "hl " : "") + className} style={{ position: "relative" }}>
      {children}
      {admin && (
        <div style={pos ? { position: "absolute", zIndex: 5, ...pos } : { display: "inline-flex", marginLeft: 8 }}>
          <Pen onClick={onEdit} label={label} small />
        </div>
      )}
    </div>
  );
}

/* ---------------- Count-up number (fires when visible) ---------------- */
function CountUp({ value, duration = 1300, className = "", format = (n) => Math.round(n).toLocaleString() }) {
  const [n, setN] = useState(0);
  const ref = useRef(null);
  const started = useRef(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) { setN(value); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started.current) {
          started.current = true;
          const t0 = performance.now();
          const tick = (t) => {
            const p = Math.min(1, (t - t0) / duration);
            const eased = 1 - Math.pow(1 - p, 3);
            setN(value * eased);
            if (p < 1) requestAnimationFrame(tick);
            else setN(value);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.4 });
    io.observe(el);
    return () => io.disconnect();
  }, [value]);
  return <span ref={ref} className={"tnum " + className}>{format(n)}</span>;
}

/* ---------------- Reveal on scroll ---------------- */
function Reveal({ children, delay = 0, as: Tag = "div", className = "", ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let done = false;
    const show = () => { if (!done) { done = true; el.classList.add("in"); } };
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setTimeout(show, delay); io.disconnect(); } });
    }, { threshold: 0.12 });
    io.observe(el);
    return () => io.disconnect();
  }, [delay]);
  return <Tag ref={ref} className={"reveal " + className} {...rest}>{children}</Tag>;
}

/* ---------------- Sparkline ---------------- */
function Sparkline({ data, w = 92, h = 30, color = "var(--ox-bright)", showDot = true }) {
  if (!data || data.length < 2) return null;
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * (w - 4) + 2;
    const y = h - 3 - ((v - min) / span) * (h - 6);
    return [x, y];
  });
  const dPath = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
  const area = dPath + ` L${pts[pts.length-1][0].toFixed(1)} ${h} L${pts[0][0].toFixed(1)} ${h} Z`;
  const last = pts[pts.length - 1];
  const gid = "sg" + Math.round(min*1000 + max);
  return (
    <svg width={w} height={h} style={{ display: "block", overflow: "visible" }} aria-hidden="true">
      <defs>
        <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={color} stopOpacity="0.22" />
          <stop offset="100%" stopColor={color} stopOpacity="0" />
        </linearGradient>
      </defs>
      <path d={area} fill={`url(#${gid})`} />
      <path d={dPath} fill="none" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
      {showDot && <circle cx={last[0]} cy={last[1]} r="2.6" fill={color} />}
    </svg>
  );
}

/* ---------------- Modal ---------------- */
function Modal({ title, onClose, children, footer, maxWidth }) {
  const ref = useRef(null);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.activeElement;
    // focus first focusable
    const first = ref.current && ref.current.querySelector("input,textarea,select,button");
    if (first) setTimeout(() => first.focus(), 30);
    return () => { document.removeEventListener("keydown", onKey); if (prev && prev.focus) prev.focus(); };
  }, [onClose]);
  return (
    <div className="scrim" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }} role="dialog" aria-modal="true" aria-label={title}>
      <div className="modal" ref={ref} style={maxWidth ? { maxWidth } : null}>
        <div className="modal-head">
          <h3 className="head" style={{ margin: 0, fontSize: 17, letterSpacing: "0.1em", textTransform: "uppercase" }}>{title}</h3>
          <button className="pen" onClick={onClose} aria-label="Close"><Icons.Close /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div style={{ padding: "0 24px 24px", display: "flex", gap: 12, justifyContent: "flex-end" }}>{footer}</div>}
      </div>
    </div>
  );
}

/* ---------------- Inline formatter: **bold** / *bold*, strip stray * ---------------- */
function formatInline(text, keyPrefix) {
  // Normalize bold markers first, then strip any leftover single asterisks.
  const parts = String(text).split(/(\*\*[^*]+\*\*|__[^_]+__|\*[^*\n]+\*)/g);
  return parts.map((p, i) => {
    const key = keyPrefix + "-" + i;
    if (/^\*\*[^*]+\*\*$/.test(p)) return <b key={key} className="rt-b">{p.slice(2, -2)}</b>;
    if (/^__[^_]+__$/.test(p)) return <b key={key} className="rt-b">{p.slice(2, -2)}</b>;
    if (/^\*[^*\n]+\*$/.test(p)) return <b key={key} className="rt-b">{p.slice(1, -1)}</b>;
    // strip any orphan asterisks/underscores left behind
    return <span key={key}>{p.replace(/[*_`]+/g, "")}</span>;
  });
}

/* ---------------- RichText: turns AI markdown-ish text into clean JSX ----------------
   - **bold** / *bold* -> bold (never shows raw asterisks)
   - "- ", "* ", "• ", "1." lines -> bullet/numbered lists
   - blank line -> paragraph break
*/
function RichText({ text, className = "" }) {
  const raw = String(text || "").replace(/\r/g, "");
  const lines = raw.split("\n");
  const blocks = [];
  let list = null; // { ordered, items: [] }
  const flush = () => { if (list) { blocks.push(list); list = null; } };

  lines.forEach((ln) => {
    const t = ln.trim();
    const bullet = t.match(/^[-*•]\s+(.*)$/);
    const numbered = t.match(/^(\d+)[.)]\s+(.*)$/);
    if (bullet) {
      if (!list || list.ordered) { flush(); list = { ordered: false, items: [] }; }
      list.items.push(bullet[1]);
    } else if (numbered) {
      if (!list || !list.ordered) { flush(); list = { ordered: true, items: [] }; }
      list.items.push(numbered[2]);
    } else if (t === "") {
      flush();
      blocks.push({ blank: true });
    } else {
      flush();
      // strip markdown headers (#) — we don't want raw markup
      blocks.push({ p: t.replace(/^#+\s*/, "") });
    }
  });
  flush();

  return (
    <div className={"richtext " + className}>
      {blocks.map((b, i) => {
        if (b.blank) return null;
        if (b.p !== undefined) return <p key={i} className="rt-p">{formatInline(b.p, "p" + i)}</p>;
        const Tag = b.ordered ? "ol" : "ul";
        return (
          <Tag key={i} className="rt-list">
            {b.items.map((it, j) => <li key={j}>{formatInline(it, "l" + i + "-" + j)}</li>)}
          </Tag>
        );
      })}
    </div>
  );
}

/* ---------------- DayChart: day-per-day line chart with hover tooltips ---------------- */
function DayChart({ points, unit = "", height = 260 }) {
  const [hover, setHover] = useState(null); // index
  const wrapRef = useRef(null);
  const [w, setW] = useState(640);
  useEffect(() => {
    const measure = () => { if (wrapRef.current) setW(wrapRef.current.clientWidth || 640); };
    measure();
    window.addEventListener("resize", measure);
    return () => window.removeEventListener("resize", measure);
  }, []);

  if (!points || points.length === 0) return <div className="daychart-empty muted">No data points yet.</div>;
  const pad = { l: 44, r: 16, t: 18, b: 34 };
  const h = height;
  const vals = points.map((p) => p.value);
  let min = Math.min(...vals), max = Math.max(...vals);
  if (min === max) { min -= 1; max += 1; }
  const span = max - min;
  const innerW = Math.max(10, w - pad.l - pad.r);
  const innerH = h - pad.t - pad.b;
  const x = (i) => pad.l + (points.length === 1 ? innerW / 2 : (i / (points.length - 1)) * innerW);
  const y = (v) => pad.t + innerH - ((v - min) / span) * innerH;

  const linePath = points.map((p, i) => (i ? "L" : "M") + x(i).toFixed(1) + " " + y(p.value).toFixed(1)).join(" ");
  const areaPath = linePath + ` L${x(points.length - 1).toFixed(1)} ${pad.t + innerH} L${x(0).toFixed(1)} ${pad.t + innerH} Z`;
  const ticks = 4;
  const gridVals = Array.from({ length: ticks + 1 }, (_, i) => min + (span * i) / ticks);

  return (
    <div className="daychart" ref={wrapRef} style={{ position: "relative" }}>
      <svg width={w} height={h} style={{ display: "block", overflow: "visible" }}
        onMouseLeave={() => setHover(null)}>
        <defs>
          <linearGradient id="dc-grad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--ox-bright)" stopOpacity="0.28" />
            <stop offset="100%" stopColor="var(--ox-bright)" stopOpacity="0" />
          </linearGradient>
        </defs>
        {gridVals.map((gv, i) => (
          <g key={i}>
            <line x1={pad.l} x2={w - pad.r} y1={y(gv)} y2={y(gv)} stroke="var(--line)" strokeWidth="1" />
            <text x={pad.l - 8} y={y(gv) + 4} textAnchor="end" className="dc-axis">{Math.round(gv)}</text>
          </g>
        ))}
        <path d={areaPath} fill="url(#dc-grad)" />
        <path d={linePath} fill="none" stroke="var(--ox-bright)" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
        {points.map((p, i) => (
          <g key={i}>
            <circle cx={x(i)} cy={y(p.value)} r={hover === i ? 5.5 : 3.4} fill={hover === i ? "var(--chalk)" : "var(--ox-bright)"} stroke="var(--ink)" strokeWidth="1.5" />
            {/* fat invisible hit target */}
            <rect x={x(i) - innerW / (points.length * 2) - 6} y={pad.t} width={innerW / points.length + 12} height={innerH}
              fill="transparent" onMouseEnter={() => setHover(i)} style={{ cursor: "crosshair" }} />
            {i % Math.ceil(points.length / 6 || 1) === 0 && (
              <text x={x(i)} y={h - pad.b + 18} textAnchor="middle" className="dc-axis">{(p.date || "").replace(/\/\d{4}$/, "")}</text>
            )}
          </g>
        ))}
      </svg>
      {hover != null && (
        <div className="dc-tip" style={{ left: Math.min(Math.max(x(hover), 60), w - 60), top: y(points[hover].value) }}>
          <div className="dc-tip-val tnum">{points[hover].value}{unit}</div>
          <div className="dc-tip-date tnum">{points[hover].date}</div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { Icon, Icons, Pen, Editable, CountUp, Reveal, Sparkline, Modal, RichText, DayChart, formatInline });
