/* FP&A Copilot — shared primitives: Icon, Button, Card, Chip, Delta, Logo */

// Prepend the API base URL set by scripts/inject-config.js at Vercel build time.
// Empty string in local dev so all fetch calls hit the FastAPI server via relative paths.
window.apiUrl = (path) => (window.__MONTHENDIQ_API_BASE__ || '') + path;

// ── Auth plumbing ──────────────────────────────────────────────────────────
// Every browser gets a firm token (account tokens replace it on sign-in).
// All /api/ fetches carry it as an Authorization: Bearer header so sessions
// created here are owned by this browser/account and locked to it server-side.
// /api/data responses include a signed share_token for owners; it's captured
// here so share-link builders can read it synchronously via shareTokenFor().
(() => {
  try {
    if (!localStorage.getItem("meiq_firm_token")) {
      localStorage.setItem("meiq_firm_token", crypto.randomUUID());
    }
  } catch {}
  const _shareTokens = {};
  window.shareTokenFor = (sessionId) => _shareTokens[sessionId] || null;
  const _fetch = window.fetch.bind(window);
  window.fetch = (input, init) => {
    const url  = typeof input === "string" ? input : (input && input.url) || "";
    const base = window.__MONTHENDIQ_API_BASE__ || "";
    const path = base && url.startsWith(base) ? url.slice(base.length) : url;
    if (!path.startsWith("/api/")) return _fetch(input, init);
    init = init || {};
    try {
      const tok = localStorage.getItem("meiq_firm_token");
      if (tok) {
        const h = new Headers(init.headers || (typeof input !== "string" && input.headers) || undefined);
        if (!h.has("Authorization")) h.set("Authorization", "Bearer " + tok);
        init.headers = h;
      }
    } catch {}
    const p = _fetch(input, init);
    const m = path.match(/^\/api\/data\/([^/?]+)/);
    if (m) {
      p.then((res) => {
        if (!res.ok) return;
        res.clone().json().then((d) => {
          if (d && d.share_token) _shareTokens[decodeURIComponent(m[1])] = d.share_token;
        }).catch(() => {});
      }).catch(() => {});
    }
    return p;
  };
})();

// Icons are decorative by default: every one of them sits next to a text label,
// so announcing them again is noise. Pass `label` for the rare standalone icon
// that carries the whole meaning of a control.
function Icon({ name, size = 18, stroke = 1.75, color, style, className, label }) {
  const lib = (window.lucide && window.lucide.icons) || {};
  const key = String(name).split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
  const node = lib[key];
  let inner = "";
  if (node) {
    const children = Array.isArray(node) ? node : (node.iconNode || []);
    inner = children.map(([tag, attrs]) =>
      "<" + tag + " " + Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(" ") + "></" + tag + ">"
    ).join("");
  }
  const a11y = label
    ? `role="img" aria-label="${String(label).replace(/"/g, "&quot;")}"`
    : `aria-hidden="true" focusable="false"`;
  const svg =
    `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" ` +
    `fill="none" stroke="${color || "currentColor"}" stroke-width="${stroke}" ` +
    `stroke-linecap="round" stroke-linejoin="round" ${a11y}>${inner}</svg>`;
  return <span className={className} style={{ display: "inline-flex", lineHeight: 0, ...style }} dangerouslySetInnerHTML={{ __html: svg }} />;
}

function Button({ variant = "primary", size, icon, iconRight, children, onClick, style, disabled }) {
  return (
    <button className={`btn ${variant}${size ? " " + size : ""}`} onClick={onClick} style={style} disabled={disabled}>
      {icon && <Icon name={icon} size={size === "sm" ? 14 : 16} />}
      {children}
      {iconRight && <Icon name={iconRight} size={14} />}
    </button>
  );
}

function Card({ title, sub, action, children, style, className }) {
  return (
    <div className={`card${className ? " " + className : ""}`} style={style}>
      {(title || action) && (
        <div className="card-h">
          <div>
            {title && <h3>{title}</h3>}
            {sub && <div className="sub">{sub}</div>}
          </div>
          {action}
        </div>
      )}
      <div className="card-b">{children}</div>
    </div>
  );
}

function Chip({ tone = "info", icon, children }) {
  return (
    <span className={`chip ${tone}`}>
      {icon && <Icon name={icon} size={12} />}
      {children}
    </span>
  );
}

function Delta({ fav, children, up }) {
  const cls = fav === null || fav === undefined ? "neu" : fav ? "fav" : "adv";
  const arrow = up ? "↑" : up === false ? "↓" : "→";
  return <span className={`delta ${cls}`}>{arrow} {children}</span>;
}

function Logo() {
  return (
    <div className="sb-brand">
      <div className="mark">
        <svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/>
          <line x1="6" y1="20" x2="6" y2="14"/>
          <path d="M20 7l-2-2-2 2" /><circle cx="20" cy="5" r="1" fill="#fff" stroke="none"/>
        </svg>
      </div>
      <div className="wm">MonthEnd<span>IQ</span></div>
    </div>
  );
}

function catChip(cat) {
  if (!cat) return null;
  const lc = cat.toLowerCase();
  if (lc.includes("revenue") || lc.includes("turnover") || lc.includes("sales") || lc.includes("income"))
    return { bg: "var(--favourable-soft)", color: "var(--favourable-text)" };
  if (lc.includes("staff") || lc.includes("payroll") || lc.includes("wage") || lc.includes("salary") || lc.includes("hr"))
    return { bg: "var(--chip-staff-bg)", color: "var(--chip-staff-text)" };
  if (lc.includes("direct") || lc.includes("cogs") || lc.includes("material") || lc.includes("production"))
    return { bg: "var(--chip-direct-bg)", color: "var(--chip-direct-text)" };
  if (lc.includes("market") || lc.includes("advertis"))
    return { bg: "var(--chip-marketing-bg)", color: "var(--chip-marketing-text)" };
  if (lc.includes("admin") || lc.includes("overhead") || lc.includes("general"))
    return { bg: "var(--surface-2)", color: "var(--fg-2)" };
  if (lc.includes("finance") || lc.includes("interest") || lc.includes("bank"))
    return { bg: "var(--chip-finance-bg)", color: "var(--chip-finance-text)" };
  // Unknown categories get a theme-safe neutral chip (a light-only hsl formula
  // used to produce glaring light blobs in dark mode)
  return { bg: "var(--surface-3)", color: "var(--fg-2)" };
}

// Reads currency symbol from localStorage; falls back to £.
// Components call fmtCurrency() so changing the symbol in Settings refreshes on next render.
function fmtCurrency(v, { signed = false, compact = false } = {}) {
  if (v == null || isNaN(v)) return "—";
  let sym = "£";
  try { sym = localStorage.getItem("meiq_currency_sym") || "£"; } catch {}
  const abs = Math.abs(v);
  const pfx = (signed ? (v > 0 ? "+" : v < 0 ? "-" : "") : (v < 0 ? "-" : "")) + sym;
  if (compact) {
    if (abs >= 1e6) return `${pfx}${(abs / 1e6).toFixed(1)}m`;
    if (abs >= 1e3) return `${pfx}${Math.round(abs / 1e3)}k`;
    return `${pfx}${Math.round(abs)}`;
  }
  return `${pfx}${Math.round(abs).toLocaleString()}`;
}

// ── RAG threshold system ──────────────────────────────────────────────────────

const _RAG_DEFAULTS = {
  revenue_var_pct: { green: 5,  amber: 0,  hib: true,  enabled: false },
  profit_var_pct:  { green: 5,  amber: 0,  hib: true,  enabled: false },
  op_margin:       { green: 15, amber: 10, hib: true,  enabled: false },
  payroll_pct:     { green: 55, amber: 65, hib: false, enabled: false },
};

function loadRagThresholds() {
  try {
    const raw = localStorage.getItem("meiq_rag_thresholds");
    return raw ? { ..._RAG_DEFAULTS, ...JSON.parse(raw) } : { ..._RAG_DEFAULTS };
  } catch { return { ..._RAG_DEFAULTS }; }
}

function ragStatus(value, threshold) {
  if (!threshold?.enabled || value == null || isNaN(value)) return null;
  if (threshold.hib) {
    return value >= threshold.green ? "green" : value >= threshold.amber ? "amber" : "red";
  }
  return value <= threshold.green ? "green" : value <= threshold.amber ? "amber" : "red";
}

function RagBadge({ status }) {
  if (!status) return null;
  const MAP = {
    green: { bg: "var(--favourable-soft)", color: "var(--favourable-text)", label: "On track"  },
    amber: { bg: "var(--caution-soft)",    color: "var(--caution-text)",    label: "Monitor"   },
    red:   { bg: "var(--adverse-soft)",    color: "var(--adverse-text)",    label: "Off track" },
  };
  const { bg, color, label } = MAP[status] || {};
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      padding: "2px 8px", borderRadius: "var(--radius-pill)",
      background: bg, color,
      font: "var(--text-label)", fontSize: 10.5, flexShrink: 0,
    }}>
      <span style={{ fontSize: 7, lineHeight: 1 }}>●</span>{label}
    </span>
  );
}


/* apiErrorText: flatten an API error body into readable text. The upload
   endpoints return structured detail ({message, issues:[{severity,message}]})
   for row-level diagnostics; older endpoints return a plain string. */
function apiErrorText(j, fallback) {
  const d = j && j.detail;
  if (!d) return fallback;
  if (typeof d === "string") return d;
  const lines = [d.message || fallback];
  for (const it of (d.issues || [])) {
    lines.push((it.severity === "error" ? "\u2715 " : "\u26A0 ") + it.message);
  }
  return lines.join("\n");
}

Object.assign(window, { Icon, Button, Card, Chip, Delta, Logo, catChip, fmtCurrency,
  loadRagThresholds, ragStatus, RagBadge, apiErrorText });
