/* ============================================================
   Shared helpers + components
   ============================================================ */
const { useState, useEffect, useRef, useMemo } = React;

/* ---------- Date helpers ---------- */
const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const MONTHS_SHORT = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const DOW = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];

function parseISO(s) { const [y,m,d] = s.split("-").map(Number); return new Date(y, m-1, d); }
function toISO(dt) {
  const y = dt.getFullYear(), m = String(dt.getMonth()+1).padStart(2,"0"), d = String(dt.getDate()).padStart(2,"0");
  return `${y}-${m}-${d}`;
}
function addDays(dt, n) { const c = new Date(dt); c.setDate(c.getDate()+n); return c; }
function isWeekend(dt) { const g = dt.getDay(); return g === 0 || g === 6; }
function eachDay(fromISO_, toISO_) {
  const out = []; let cur = parseISO(fromISO_); const end = parseISO(toISO_);
  while (cur <= end) { out.push(new Date(cur)); cur = addDays(cur, 1); }
  return out;
}
// working days — counts only the requester's contracted workdays; half day = 0.5
function workingDays(req) {
  if (req.type === "half") return holiday(req.from) ? 0 : 0.5;
  const person = req.person ? VP_DATA.byId[req.person] : null;
  const wd = (person && person.workdays) || [1, 2, 3, 4, 5];
  // A Kosovo public holiday is not a working day — like a weekend, it never
  // draws from the balance, even when leave is laid across it.
  return eachDay(req.from, req.to).filter((d) => wd.includes(d.getDay()) && !holiday(d)).length;
}
// Working days of a request that fall inside [loISO, hiISO] (inclusive),
// excluding weekends/non-workdays and public holidays. Used to attribute
// leave to a calendar year or to the Q1 carryover window.
function workingDaysInWindow(req, loISO, hiISO) {
  const lo = parseISO(loISO), hi = parseISO(hiISO);
  if (req.type === "half") {
    const d = parseISO(req.from);
    return (d >= lo && d <= hi && !holiday(req.from)) ? 0.5 : 0;
  }
  const person = req.person ? VP_DATA.byId[req.person] : null;
  const wd = (person && person.workdays) || [1, 2, 3, 4, 5];
  return eachDay(req.from, req.to).filter((d) => d >= lo && d <= hi && wd.includes(d.getDay()) && !holiday(d)).length;
}
function fmtRange(fromISO_, toISO_) {
  const a = parseISO(fromISO_), b = parseISO(toISO_);
  if (fromISO_ === toISO_) return `${MONTHS_SHORT[a.getMonth()]} ${a.getDate()}`;
  if (a.getMonth() === b.getMonth()) return `${MONTHS_SHORT[a.getMonth()]} ${a.getDate()}–${b.getDate()}`;
  return `${MONTHS_SHORT[a.getMonth()]} ${a.getDate()} – ${MONTHS_SHORT[b.getMonth()]} ${b.getDate()}`;
}
function fmtDateLong(iso) { const d = parseISO(iso); return `${DOW[(d.getDay()+6)%7]}, ${MONTHS_SHORT[d.getMonth()]} ${d.getDate()}`; }
function daysAgo(iso) {
  const now = new Date();
  const diff = Math.round((now - parseISO(iso)) / 86400000);
  if (diff <= 0) return "today";
  if (diff === 1) return "yesterday";
  if (diff < 7) return `${diff}d ago`;
  return `${Math.floor(diff/7)}w ago`;
}
// Real local "today" as an ISO yyyy-mm-dd string. Drives the calendar's
// current month, the today marker, and which dates count as past.
const TODAY = (function () {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
})();

// Kosovo public holiday lookup. Accepts an ISO string or a Date.
// Returns the holiday record ({ name, moveable? }) or null.
function holiday(dtOrIso) {
  const iso = typeof dtOrIso === "string" ? dtOrIso : toISO(dtOrIso);
  return (window.VP_DATA && VP_DATA.KOSOVO_HOLIDAYS && VP_DATA.KOSOVO_HOLIDAYS[iso]) || null;
}

/* ---------- Icons (stroke line icons) ---------- */
function Icon({ name, size = 18, stroke = 1.6, style }) {
  const common = { width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", style };
  const P = {
    sun: <g><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5L19 19M19 5l-1.5 1.5M6.5 17.5L5 19"/></g>,
    home: <g><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9.5 21v-6h5v6"/></g>,
    thermo: <g><path d="M10 13.5V5a2 2 0 1 1 4 0v8.5a4 4 0 1 1-4 0Z"/><path d="M12 9v5.5"/></g>,
    half: <g><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18Z" fill="currentColor" stroke="none"/></g>,
    star: <g><path d="M12 3.5l2.4 5 5.6.7-4 3.9 1 5.5L12 16l-5 2.6 1-5.5-4-3.9 5.6-.7Z"/></g>,
    calendar: <g><rect x="3" y="4.5" width="18" height="16" rx="2.5"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/></g>,
    inbox: <g><path d="M3 13h5l1.5 2.5h5L16 13h5"/><path d="M5 5h14l2 8v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5Z"/></g>,
    users: <g><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c.6-3 3-4.5 5.5-4.5S14 16 14.5 19"/><path d="M16 5.2a3 3 0 0 1 0 5.6M17 14.6c2 .5 3.6 2 4 4.4"/></g>,
    plus: <g><path d="M12 5v14M5 12h14"/></g>,
    check: <g><path d="M5 12.5 10 17 19 7"/></g>,
    x: <g><path d="M6 6l12 12M18 6 6 18"/></g>,
    arrowRight: <g><path d="M5 12h14M13 6l6 6-6 6"/></g>,
    arrowLeft: <g><path d="M19 12H5M11 18l-6-6 6-6"/></g>,
    chevDown: <g><path d="M6 9l6 6 6-6"/></g>,
    chevLeft: <g><path d="M15 6l-6 6 6 6"/></g>,
    chevRight: <g><path d="M9 6l6 6-6 6"/></g>,
    clock: <g><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></g>,
    swap: <g><path d="M7 4 3 8l4 4M3 8h12M17 20l4-4-4-4M21 16H9"/></g>,
    logout: <g><path d="M14 4h4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-4"/><path d="M9 12h11M16 8l4 4-4 4"/></g>,
    bell: <g><path d="M6 9a6 6 0 0 1 12 0c0 5 1.5 6.5 2 7H4c.5-.5 2-2 2-7Z"/><path d="M10 20a2 2 0 0 0 4 0"/></g>,
    search: <g><circle cx="11" cy="11" r="7"/><path d="m20 20-3.2-3.2"/></g>,
    info: <g><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 7.5v.5"/></g>,
    dot: <circle cx="12" cy="12" r="3" fill="currentColor" stroke="none"/>,
    leaf: <g><path d="M5 19c0-7 5-13 14-14 0 9-5 14-14 14Z"/><path d="M5 19c2-5 5-8 9-10"/></g>,
  };
  return <svg {...common} aria-hidden="true">{P[name] || P.dot}</svg>;
}

/* ---------- Mesper logo mark (M with rising growth arrow) ----------
   Recreated as SVG from the brand symbol. To use the exact original instead,
   drop mark.svg into the project and swap the <svg> for <img src="mark.svg">. */
function MMark({ size = 30, gradient = true, color }) {
  const gid = "mmark-" + React.useId().replace(/:/g, "");
  const paint = gradient ? `url(#${gid})` : (color || "var(--accent)");
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" fill="none" aria-hidden="true"
      style={{ display: "block", flex: "0 0 auto", overflow: "visible" }}>
      {gradient && (
        <defs>
          <linearGradient id={gid} x1="14" y1="86" x2="90" y2="14" gradientUnits="userSpaceOnUse">
            <stop offset="0" stopColor="#3E9DF5" />
            <stop offset="1" stopColor="#5B45DD" />
          </linearGradient>
        </defs>
      )}
      {/* right leg (drawn first so the rising arm overlaps it cleanly) */}
      <path d="M66,84 L66,50" stroke={paint} strokeWidth="15" strokeLinecap="butt" />
      {/* left leg → valley → rising arrow shaft */}
      <path d="M24,84 L24,38 L47,81 L82,24" stroke={paint} strokeWidth="15"
        strokeLinejoin="miter" strokeLinecap="butt" />
      {/* arrowhead (points up-right, overlaps the shaft tip) */}
      <polygon points="90.4,10.4 93.1,40.2 62.5,21.4" fill={paint} />
    </svg>
  );
}

/* ---------- Mesper wordmark ----------
   The MMark supplies the stylised "M"; this renders the remaining "ESPER"
   (plus the product suffix), so mark + wordmark read as "MESPER Leave". */
function Wordmark({ size = 18, light = false, sub = "Leave" }) {
  return (
    <span style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: size,
      letterSpacing: "0.04em", color: light ? "#fff" : "#403D9C", lineHeight: 1, whiteSpace: "nowrap" }}>
      ESPER{sub && <span style={{ fontWeight: 600, letterSpacing: "0.02em", color: light ? "rgba(255,255,255,0.72)" : "var(--ink-3)" }}>&nbsp;{sub}</span>}
    </span>
  );
}

/* ---------- Brand assets (real artwork) ----------
   BrandMark   = the square arrow-M (mark.png)
   BrandLockup = the full "MESPER" lockup (logo.png) + optional product suffix.
   Both fall back to the hand-drawn SVG (MMark/Wordmark) if the image is missing. */
function BrandMark({ size = 30 }) {
  const [err, setErr] = React.useState(false);
  if (err) return <MMark size={size} />;
  return (
    <img src="mark.png" width={size} height={size} alt="Mesper"
      onError={() => setErr(true)}
      style={{ display: "block", flex: "0 0 auto", objectFit: "contain" }} />
  );
}
function BrandLockup({ height = 26, sub = "Leave", light = false }) {
  const [err, setErr] = React.useState(false);
  const suffix = sub && (
    <span style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: height * 0.74,
      letterSpacing: "0.01em", color: light ? "rgba(255,255,255,0.72)" : "var(--ink-3)", lineHeight: 1 }}>{sub}</span>
  );
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: sub ? height * 0.36 : 0, whiteSpace: "nowrap" }}>
      {err
        ? <span style={{ display: "inline-flex", alignItems: "flex-end", gap: 8 }}><MMark size={height * 1.45} /><Wordmark size={height * 0.8} sub="" light={light} /></span>
        : <img src="logo.png" alt="Mesper" onError={() => setErr(true)}
            style={{ height, width: "auto", display: "block", filter: light ? "brightness(0) invert(1)" : "none" }} />}
      {suffix}
    </span>
  );
}

/* ---------- Avatar ---------- */
function initials(p) { return (p.first[0] + p.last[0]).toUpperCase(); }
function Avatar({ person, size = 36, ring = false }) {
  if (!person) return null;
  return (
    <div style={{
      width: size, height: size, borderRadius: "50%", flex: "0 0 auto",
      display: "grid", placeItems: "center",
      background: person.color, color: "#fff",
      fontFamily: "var(--font-display)", fontWeight: 600,
      fontSize: size * 0.38, letterSpacing: "-0.02em",
      boxShadow: ring ? "0 0 0 3px var(--surface), 0 0 0 4px var(--line-2)" : "none",
      userSelect: "none",
    }}>{initials(person)}</div>
  );
}

/* ---------- Type tag / pill ---------- */
function TypeTag({ type, withLabel = true, size = "md" }) {
  const lt = VP_DATA.leaveTypes[type];
  const pad = size === "sm" ? "3px 9px 3px 7px" : "5px 12px 5px 9px";
  const fs = size === "sm" ? 12 : 13;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      background: lt.soft, color: lt.color, padding: pad,
      borderRadius: "var(--r-pill)", fontWeight: 600, fontSize: fs,
      lineHeight: 1, whiteSpace: "nowrap",
    }}>
      <Icon name={lt.icon} size={size === "sm" ? 13 : 14} stroke={1.8} />
      {withLabel && lt.label}
    </span>
  );
}

/* ---------- Status pill ---------- */
function StatusPill({ status }) {
  const map = {
    pending:  { c: "var(--st-pending)",  b: "var(--st-pending-soft)",  t: "Pending" },
    approved: { c: "var(--st-approved)", b: "var(--st-approved-soft)", t: "Approved" },
    declined: { c: "var(--st-declined)", b: "var(--st-declined-soft)", t: "Declined" },
  };
  const s = map[status];
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      background: s.b, color: s.c, padding: "4px 11px 4px 9px",
      borderRadius: "var(--r-pill)", fontWeight: 600, fontSize: 12.5, lineHeight: 1,
    }}>
      <span style={{ width: 6, height: 6, borderRadius: "50%", background: "currentColor" }} />
      {s.t}
    </span>
  );
}

/* ---------- Button ---------- */
function Button({ children, variant = "primary", size = "md", icon, iconRight, onClick, disabled, style, type, full }) {
  const [hover, setHover] = useState(false);
  const sizes = {
    sm: { padding: "7px 13px", fontSize: 13.5, gap: 6 },
    md: { padding: "10px 17px", fontSize: 14.5, gap: 8 },
    lg: { padding: "13px 22px", fontSize: 15.5, gap: 9 },
  };
  const variants = {
    primary: { background: "var(--accent)", color: "var(--accent-contrast)", border: "1px solid transparent",
      boxShadow: hover ? "var(--sh-md)" : "var(--sh-sm)", filter: hover ? "brightness(1.06)" : "none" },
    ghost: { background: hover ? "var(--paper-2)" : "transparent", color: "var(--ink-2)", border: "1px solid transparent" },
    outline: { background: hover ? "var(--surface-2)" : "var(--surface)", color: "var(--ink)",
      border: "1px solid var(--line-2)", boxShadow: hover ? "var(--sh-sm)" : "none" },
    approve: { background: hover ? "var(--st-approved)" : "var(--st-approved-soft)", color: hover ? "#fff" : "var(--st-approved)",
      border: "1px solid transparent" },
    decline: { background: hover ? "var(--st-declined)" : "var(--surface)", color: hover ? "#fff" : "var(--st-declined)",
      border: "1px solid var(--st-declined-soft)" },
  };
  return (
    <button type={type || "button"} onClick={disabled ? undefined : onClick} disabled={disabled}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      className="vp-focusable"
      style={{
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        gap: sizes[size].gap, ...sizes[size], ...variants[variant],
        width: full ? "100%" : "auto",
        borderRadius: "var(--r-sm)", fontFamily: "var(--font-body)", fontWeight: 600,
        cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.5 : 1,
        whiteSpace: "nowrap",
        transition: "all 0.16s ease", letterSpacing: "-0.01em", ...style,
      }}>
      {icon && <Icon name={icon} size={sizes[size].fontSize + 2} stroke={1.9} />}
      {children}
      {iconRight && <Icon name={iconRight} size={sizes[size].fontSize + 2} stroke={1.9} />}
    </button>
  );
}

/* ---------- Card ---------- */
function Card({ children, style, pad = "var(--pad-card)", className = "" }) {
  return (
    <div className={className} style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: "var(--r-lg)", padding: pad, boxShadow: "var(--sh-sm)", ...style,
    }}>{children}</div>
  );
}

/* ---------- Section label ---------- */
function Eyebrow({ children, style }) {
  return <div style={{
    fontSize: 11.5, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase",
    color: "var(--ink-4)", ...style,
  }}>{children}</div>;
}

/* ---------- Responsive hook ---------- */
function useIsNarrow(bp = 820) {
  const [narrow, setNarrow] = useState(typeof window !== "undefined" && window.innerWidth < bp);
  useEffect(() => {
    const onResize = () => setNarrow(window.innerWidth < bp);
    onResize();
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [bp]);
  return narrow;
}

/* ---------- Rich text (Markdown → safe HTML) ----------
   Stores raw Markdown; renders via marked + DOMPurify (both CDN globals).
   Links are forced to open in a new tab with rel=noopener. If the CDNs are
   unavailable, falls back to escaped plain text — never raw/unsanitized HTML. */
const RT_PURIFY_CFG = {
  ALLOWED_TAGS: ["p", "br", "strong", "em", "b", "i", "del", "mark", "h1", "h2", "h3", "ul", "ol", "li", "blockquote", "code", "pre", "a", "hr", "input"],
  ALLOWED_ATTR: ["href", "target", "rel", "type", "checked", "disabled", "class", "start"],
  ALLOWED_URI_REGEXP: /^(https?:|mailto:)/i,
};
let _rtReady = false;
function rtSetup() {
  if (_rtReady) return;
  if (window.DOMPurify) {
    DOMPurify.addHook("afterSanitizeAttributes", (node) => {
      if (node.tagName === "A") { node.setAttribute("target", "_blank"); node.setAttribute("rel", "noopener noreferrer nofollow"); }
    });
  }
  if (window.marked && marked.setOptions) marked.setOptions({ gfm: true, breaks: true });
  _rtReady = true;
}
function RichText({ md, style }) {
  const html = useMemo(() => {
    const text = (md || "").trim();
    if (!text || !window.marked || !window.DOMPurify) return null;
    try {
      rtSetup();
      const withMark = text.replace(/==([^=\n]+)==/g, "<mark>$1</mark>"); // ==highlight==
      return DOMPurify.sanitize(marked.parse(withMark), RT_PURIFY_CFG);
    } catch (e) { return null; }
  }, [md]);
  if (html == null) return <div className="rt" style={{ whiteSpace: "pre-wrap", ...style }}>{md || ""}</div>;
  return <div className="rt" style={style} dangerouslySetInnerHTML={{ __html: html }} />;
}

/* export */
Object.assign(window, {
  RichText,
  MONTHS, MONTHS_SHORT, DOW, parseISO, toISO, addDays, isWeekend, eachDay,
  workingDays, workingDaysInWindow, fmtRange, fmtDateLong, daysAgo, TODAY, holiday,
  Icon, initials, Avatar, TypeTag, StatusPill, Button, Card, Eyebrow, MMark, Wordmark,
  BrandMark, BrandLockup,
  useIsNarrow,
});
