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

const cls = (...xs) => xs.filter(Boolean).join(" ");

// Animated number count-up
function useCountUp(target, deps = []) {
  const [val, setVal] = useState(0);
  const motion = (document.documentElement.getAttribute("data-motion") !== "off");
  useEffect(() => {
    if (!motion) { setVal(target); return; }
    let raf, start;
    const dur = 900;
    const from = 0;
    const step = (ts) => {
      if (!start) start = ts;
      const p = Math.min(1, (ts - start) / dur);
      const e = 1 - Math.pow(1 - p, 3);
      setVal(from + (target - from) * e);
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    const fallback = setTimeout(() => setVal(target), dur + 250);
    return () => { cancelAnimationFrame(raf); clearTimeout(fallback); };
  }, deps);
  return val;
}

function CountUp({ value, decimals = 0, suffix = "" }) {
  const v = useCountUp(value, [value]);
  return <span className="count-up">{v.toFixed(decimals)}{suffix}</span>;
}

function Toggle({ on, onChange }) {
  return <button className={cls("toggle", on && "on")} onClick={(e) => { e.stopPropagation(); onChange(!on); }} aria-pressed={on} />;
}

function StatusDot({ status, live }) {
  return <span className={cls("status-dot", status, live && "live")} />;
}

const HEALTH = {
  healthy: { label: "Healthy", cls: "ok" },
  idle: { label: "Idle", cls: "" },
  attention: { label: "Needs attention", cls: "bad" },
  provisioning: { label: "Provisioning", cls: "warn" },
  paused: { label: "Paused", cls: "" },
};

function HealthBadge({ health }) {
  const h = HEALTH[health] || HEALTH.idle;
  return <span className={cls("badge", h.cls)}><StatusDot status={health} live={health === "healthy" || health === "provisioning"} />{h.label}</span>;
}

// deterministic bars for sparkline
function Spark({ seed = 1, n = 16, hotLast = 4 }) {
  const bars = [];
  let x = seed * 9301 + 49297;
  for (let i = 0; i < n; i++) {
    x = (x * 9301 + 49297) % 233280;
    const h = 28 + (x / 233280) * 72;
    bars.push(h);
  }
  return (
    <div className="spark">
      {bars.map((h, i) => <i key={i} className={i >= n - hotLast ? "hot" : ""} style={{ height: h + "%" }} />)}
    </div>
  );
}

function Meter({ value, tone }) {
  const [w, setW] = useState(0);
  useEffect(() => { const t = setTimeout(() => setW(value), 60); return () => clearTimeout(t); }, [value]);
  return <div className={cls("meter", tone)}><i style={{ width: Math.round(w * 100) + "%" }} /></div>;
}

// model id -> short label + provider
function modelMeta(id) {
  if (id.startsWith("azure-openai/")) return { prov: "Azure", short: id.replace("azure-openai/hermes-", "") };
  if (id.startsWith("openrouter/")) return { prov: "OpenRouter", short: id.split("/").slice(-1)[0] };
  if (id === "claude-cli") return { prov: "Claude", short: "claude-cli" };
  if (id === "claude-cli-chat") return { prov: "Claude", short: "claude (Max)" };
  if (id.startsWith("claude-cli-chat/")) {
    const alias = id.split("/")[1];
    return { prov: "Claude", short: `${alias[0].toUpperCase()}${alias.slice(1)} (Max)` };
  }
  if (id === "codex-cli") return { prov: "Codex", short: "codex (Pro)" };
  if (id.startsWith("codex-cli/")) {
    const alias = id.split("/")[1];
    const match = /^gpt-(\d+\.\d+)-(sol|terra|luna)$/.exec(alias);
    const label = match ? `${match[1]} ${match[2][0].toUpperCase()}${match[2].slice(1)}` : alias;
    return { prov: "Codex", short: `${label} (Pro)` };
  }
  if (id === "gemini-cli") return { prov: "Gemini", short: "gemini-cli" };
  return { prov: "", short: id };
}

function ProviderLogo({ id }) {
  const map = {
    "azure-openai": { t: "Az", c: "#3b6ef6" },
    "openrouter": { t: "OR", c: "#a78bfa" },
    "claude-cli": { t: "Cl", c: "#d97757" },
    "claude-cli-chat": { t: "Cl", c: "#d97757" },
    "codex-cli": { t: "Cx", c: "#10a37f" },
    "gemini-cli": { t: "Gm", c: "#4285f4" },
  };
  const m = map[id] || { t: "?", c: "var(--muted)" };
  return <div className="prov-logo" style={{ background: `color-mix(in oklab, ${m.c} 18%, var(--surface-2))`, color: m.c, border: "1px solid var(--line)" }}>{m.t}</div>;
}

function langColor(lang) {
  const m = { TypeScript: "#3178c6", Go: "#00add8", Swift: "#f05138", Python: "#3776ab", Astro: "#ff5d01", Rust: "#dea584", HCL: "#844fba", Shell: "#89e051", MDX: "#fcb32c" };
  return m[lang] || "var(--faint)";
}

const AGENT_PERSONAS = {
  workspace: { emoji: "🦊", color: "#f97316", title: "Mission Orchestrator", zone: "orchestrator", order: 0, blurb: "Routes goals, delegates work, and keeps the whole team aligned." },
  default: { emoji: "🦊", color: "#f97316", title: "Mission Orchestrator", zone: "orchestrator", order: 0, blurb: "Routes goals, delegates work, and keeps the whole team aligned." },
  cto: { emoji: "🏛️", color: "#8b5cf6", title: "Chief Technology Officer", zone: "leadership", order: 0, blurb: "Owns architecture, technical strategy, and risk." },
  pm: { emoji: "📋", color: "#3b82f6", title: "Product Manager", zone: "product", order: 0, blurb: "Leads discovery, prioritization, and product direction." },
  po: { emoji: "🧭", color: "#06b6d4", title: "Product Owner", zone: "product", order: 1, blurb: "Turns product direction into stories and acceptance criteria." },
  designer: { emoji: "✨", color: "#f472b6", title: "UI/UX Designer", zone: "product", order: 2, blurb: "Owns flows, interaction design, and the design system." },
  aiengineer: { emoji: "🧠", color: "#a855f7", title: "AI Engineer", zone: "engineering", order: 0, blurb: "Builds model-powered features, agents, and evaluation pipelines." },
  backend: { emoji: "⚙️", color: "#f59e0b", title: "Backend Engineer", zone: "engineering", order: 1, blurb: "Owns APIs, data, queues, and integrations." },
  frontend: { emoji: "🖥️", color: "#10b981", title: "Frontend Engineer", zone: "engineering", order: 2, blurb: "Builds accessible, polished web experiences." },
  mobile: { emoji: "📱", color: "#ec4899", title: "Mobile Engineer", zone: "engineering", order: 3, blurb: "Builds and ships iOS and Android applications." },
  iot: { emoji: "📡", color: "#14b8a6", title: "IoT Engineer", zone: "engineering", order: 4, blurb: "Owns devices, firmware, telemetry, and fleet connectivity." },
  qa: { emoji: "🧪", color: "#22c55e", title: "QA Engineer", zone: "quality", order: 0, blurb: "Verifies acceptance criteria and guards against regressions." },
  reviewer: { emoji: "🔍", color: "#eab308", title: "Code Reviewer", zone: "quality", order: 1, blurb: "Reviews correctness, security, performance, and maintainability." },
  gatekeeper: { emoji: "🚦", color: "#94a3b8", title: "Release Gatekeeper", zone: "quality", order: 2, blurb: "Owns the final evidence gate before release." },
  devops: { emoji: "🚀", color: "#ef4444", title: "DevOps Engineer", zone: "quality", order: 3, blurb: "Owns delivery, Azure infrastructure, and observability." },
};

const agentSlug = (id) => String(id || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
function agentVisual(id) {
  const key = agentSlug(id);
  const persona = AGENT_PERSONAS[key] || { emoji: "🤖", color: "#64748b", title: "Agent", zone: "engineering", order: 99, blurb: "Configured workspace agent." };
  let custom = {};
  try { custom = JSON.parse(localStorage.getItem("operations:agents:" + id) || "null") || {}; } catch (e) { /* use shared persona */ }
  return { ...persona, ...(custom.emoji ? { emoji: custom.emoji } : {}), ...(custom.color ? { color: custom.color } : {}) };
}

const agentById = (id) => {
  const found = (window.DATA.agents || []).find((a) => a.id === id);
  if (found) return found;
  const visual = agentVisual(id);
  return { id, name: id || "Agent", role: visual.title, ...visual };
};

// Simple friendly robot face avatar in the agent's color
function AgentBot({ agent, size = 44, ring }) {
  const a = typeof agent === "string" ? agentById(agent) : agent;
  const c = a.color || "var(--primary)";
  return (
    <div style={{ width: size, height: size, position: "relative", flexShrink: 0 }}>
      {ring && <div style={{ position: "absolute", inset: -3, borderRadius: "50%", border: `2px solid ${a.status === "active" ? "var(--success)" : a.status === "break" ? "var(--warn)" : "var(--faint)"}` }} />}
      <div style={{ width: "100%", height: "100%", borderRadius: ring ? "50%" : Math.round(size * 0.27), background: `color-mix(in oklab, ${c} 26%, var(--surface-2))`, display: "grid", placeItems: "center", border: `1px solid ${c}` }}>
        <svg width={size * 0.56} height={size * 0.56} viewBox="0 0 24 24" style={{ display: "block" }}>
          <rect x="5" y="8" width="14" height="11" rx="3" fill="none" stroke={c} strokeWidth="1.7" />
          <path d="M12 4v4M12 4h-1.5M12 4h1.5" stroke={c} strokeWidth="1.7" strokeLinecap="round" />
          <circle cx="9.5" cy="13" r="1.4" fill={c} />
          <circle cx="14.5" cy="13" r="1.4" fill={c} />
        </svg>
      </div>
      {a.emoji && size >= 36 && <span aria-hidden="true" style={{ position: "absolute", right: -5, bottom: -5, width: Math.max(19, size * .42), height: Math.max(19, size * .42), borderRadius: "50%", display: "grid", placeItems: "center", fontSize: Math.max(11, size * .23), background: "var(--surface)", border: `1px solid ${c}`, boxShadow: "0 2px 7px rgba(0,0,0,.28)" }}>{a.emoji}</span>}
    </div>
  );
}

// number formatting
function fmt(n) {
  if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
  if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
  return Math.round(n).toString();
}

// The BFF prepends a <workspace_context .../> block to user turns; session
// titles derived from that first turn leak the raw XML. Strip it for display —
// including a TRUNCATED opening tag (server-side titles are clipped to ~60 chars,
// so the tag often has no closing '>' left to anchor on).
function stripInjectedContext(text) {
  return String(text || "")
    .replace(/^\s*<workspace_context\b[\s\S]*?<\/workspace_context>\s*/i, "") // full block
    .replace(/^\s*<workspace_context\b[^>]*\/?>\s*/i, "")                     // self-closing / open tag
    .replace(/^\s*<workspace_context\b[^>]*$/i, "")                           // truncated open tag (no '>')
    .trim();
}

// SVG area + line chart
function AreaChart({ data, color = "var(--primary)", height = 200, fill = true }) {
  // A single point can't form a line (step would divide by zero) — pad it so
  // an empty/fresh backend renders a flat line instead of a NaN path.
  if (!Array.isArray(data) || data.length < 2) data = [data && data[0] || 0, data && data[0] || 0];
  const w = 1000, h = height, pad = 8;
  const max = Math.max(...data, 1);
  const step = (w - pad * 2) / (data.length - 1);
  const pts = data.map((v, i) => [pad + i * step, h - pad - (v / max) * (h - pad * 2)]);
  const line = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
  const area = `${line} L ${pts[pts.length - 1][0].toFixed(1)} ${h} L ${pts[0][0].toFixed(1)} ${h} Z`;
  const gid = "g" + Math.random().toString(36).slice(2, 7);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: "100%", height, display: "block" }}>
      <defs>
        <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={color} stopOpacity="0.28" />
          <stop offset="100%" stopColor={color} stopOpacity="0" />
        </linearGradient>
      </defs>
      {fill && <path d={area} fill={`url(#${gid})`} />}
      <path d={line} fill="none" stroke={color} strokeWidth="2.4" strokeLinejoin="round" strokeLinecap="round" vectorEffect="non-scaling-stroke" />
    </svg>
  );
}

Object.assign(window, { cls, useCountUp, CountUp, Toggle, StatusDot, HealthBadge, Spark, Meter, modelMeta, ProviderLogo, langColor, HEALTH, AGENT_PERSONAS, agentVisual, agentById, AgentBot, fmt, AreaChart, stripInjectedContext });
