/* @ttt/ui — Component Library
   All components attach to window so other Babel <script> tags can use them.
   Tokens come from tokens.css via CSS vars — components stay theme-aware
   without prop-drilling a context for color values.
*/

const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

/* ──────────────────────────────────────────────────────────────
   Section header     /// SECTION NAME
   ────────────────────────────────────────────────────────────── */
function Sec({ title, children, dense, extra }) {
  return (
    <section style={{ marginBottom: dense ? 24 : 40 }}>
      <header style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        paddingBottom: 6, borderBottom: "1px solid var(--line)", marginBottom: 14,
      }}>
        <h3 style={{
          margin: 0, fontFamily: "var(--fn-mono)", fontSize: "var(--fs-xxs)",
          color: "var(--teal)", textTransform: "uppercase", letterSpacing: 2.5,
          fontWeight: 700, display: "flex", alignItems: "center", gap: 6,
        }}>
          <span style={{ color: "var(--tx-4)" }}>///</span> {title}
        </h3>
        {extra}
      </header>
      {children}
    </section>
  );
}

/* ──────────────────────────────────────────────────────────────
   LogoMark — the shield + check + indicator dot
   ────────────────────────────────────────────────────────────── */
function LogoMark({ size = 28, mono }) {
  const c = mono ? "currentColor" : "var(--teal)";
  return (
    <div style={{
      width: size, height: size, borderRadius: "var(--r-sm)",
      border: `2px solid ${c}`, display: "inline-flex",
      alignItems: "center", justifyContent: "center", position: "relative",
      flexShrink: 0,
    }}>
      <svg width={size * 0.5} height={size * 0.5} viewBox="0 0 16 16" fill="none">
        <path d="M3 8.5L6.5 12L13 4" stroke={c} strokeWidth="2.2"
          strokeLinecap="round" strokeLinejoin="round" />
      </svg>
      <span style={{
        position: "absolute", top: -3, right: -3,
        width: Math.max(5, size * 0.22), height: Math.max(5, size * 0.22),
        borderRadius: "50%", background: c,
        boxShadow: mono ? "none" : "var(--teal-glow)",
      }} />
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Logo — mark + wordmark
   ────────────────────────────────────────────────────────────── */
function Logo({ compact, size = 28, mono }) {
  return (
    <div style={{ display: "inline-flex", alignItems: "center", gap: compact ? 8 : 10 }}>
      <LogoMark size={size} mono={mono} />
      {!compact && (
        <div style={{ lineHeight: 1.15 }}>
          <div style={{
            fontFamily: "var(--fn-mono)", fontSize: "var(--fs-base)",
            fontWeight: 700, letterSpacing: -0.3,
            color: mono ? "currentColor" : "var(--tx)",
          }}>
            <span style={{ color: mono ? "currentColor" : "var(--teal)" }}>terra</span>
            <span>check</span>
          </div>
          <div style={{
            fontFamily: "var(--fn-mono)", fontSize: 9,
            color: mono ? "currentColor" : "var(--tx-3)",
            textTransform: "uppercase", letterSpacing: 1.6, marginTop: 1,
            opacity: mono ? 0.6 : 1,
          }}>
            terraform · test · toolkit
          </div>
        </div>
      )}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Btn — primary | outline | ghost | danger × xs sm md lg
   ────────────────────────────────────────────────────────────── */
const BTN_VAR = {
  primary: {
    bg: "var(--teal)", bgH: "var(--teal-hover)", fg: "var(--tx-inv)",
    bd: "1px solid var(--teal)", sh: "var(--teal-glow)",
  },
  outline: {
    bg: "transparent", bgH: "var(--bg-raised)", fg: "var(--tx)",
    bd: "1px solid var(--line)", sh: "none",
  },
  ghost: {
    bg: "transparent", bgH: "var(--bg-raised)", fg: "var(--tx-2)",
    bd: "1px solid transparent", sh: "none",
  },
  danger: {
    bg: "var(--status-fail)", bgH: "#be123c", fg: "#fff",
    bd: "1px solid var(--status-fail)", sh: "0 0 20px rgba(244,63,94,0.18)",
  },
};
const BTN_SZ = {
  xs: { px: 8,  py: 4,  fs: "var(--fs-xxs)", gap: 4 },
  sm: { px: 10, py: 5,  fs: "var(--fs-xs)",  gap: 5 },
  md: { px: 14, py: 7,  fs: "var(--fs-sm)",  gap: 6 },
  lg: { px: 20, py: 10, fs: "var(--fs-base)",gap: 8 },
};
function Btn({ children, variant = "primary", size = "md", icon, loading, disabled, full, onClick, type = "button", title }) {
  const [h, setH] = useState(false);
  const v = BTN_VAR[variant], s = BTN_SZ[size];
  const dis = disabled || loading;
  return (
    <button
      type={type}
      title={title}
      onClick={dis ? undefined : onClick}
      onMouseEnter={() => setH(true)}
      onMouseLeave={() => setH(false)}
      disabled={dis}
      style={{
        display: "inline-flex", alignItems: "center", justifyContent: "center", gap: s.gap,
        padding: `${s.py}px ${s.px}px`,
        width: full ? "100%" : undefined,
        background: dis ? "var(--bg-input)" : (h ? v.bgH : v.bg),
        color: dis ? "var(--tx-3)" : v.fg,
        border: v.bd, borderRadius: "var(--r-sm)",
        fontSize: s.fs, fontFamily: "var(--fn-mono)", fontWeight: 600,
        cursor: dis ? "default" : "pointer",
        transition: "all 150ms",
        boxShadow: (h && !dis) ? v.sh : "none",
        opacity: dis && !loading ? 0.45 : 1, outline: "none",
        letterSpacing: 0.3, whiteSpace: "nowrap",
        textTransform: "lowercase",
      }}>
      {loading && (
        <span style={{
          width: "1em", height: "1em", border: "1.5px solid currentColor",
          borderTopColor: "transparent", borderRadius: "50%",
          animation: "ttt-spin 800ms linear infinite",
        }} />
      )}
      {!loading && icon && <span style={{ display: "inline-flex", lineHeight: 1 }}>{icon}</span>}
      {children}
    </button>
  );
}

/* ──────────────────────────────────────────────────────────────
   Chip — soft | solid | outline + optional dot/pulse + removable
   ────────────────────────────────────────────────────────────── */
function Chip({ children, color = "var(--tx-3)", variant = "soft", dot, removable, onRemove, size = "md" }) {
  // hex with alpha — color must be hex for soft/outline alpha tricks; else fallback
  const isHex = typeof color === "string" && /^#([0-9a-f]{6})$/i.test(color);
  const tint = (alpha) => isHex ? `${color}${alpha}` : color;

  const bg = variant === "solid" ? color :
             variant === "outline" ? "transparent" :
             isHex ? tint("18") : "var(--teal-soft)";
  const fg = variant === "solid" ? "#fff" : color;
  const bd = variant === "outline" ? `1px solid ${isHex ? tint("55") : color}` :
             variant === "soft"    ? `1px solid ${isHex ? tint("22") : "transparent"}` :
             "none";

  const sz = size === "sm"
    ? { px: 5, py: 1, fs: 9 }
    : { px: 7, py: 2, fs: "var(--fs-xxs)" };

  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      padding: `${sz.py}px ${sz.px}px`,
      borderRadius: "var(--r-xs)",
      fontSize: sz.fs, fontFamily: "var(--fn-mono)",
      fontWeight: 500, lineHeight: 1.5,
      background: bg, color: fg, border: bd,
      textTransform: "lowercase",
    }}>
      {dot && (
        <span style={{
          width: 5, height: 5, borderRadius: "50%", background: color,
          boxShadow: isHex ? `0 0 5px ${tint("77")}` : "none",
          animation: dot === "pulse" ? "ttt-pulse 1.8s infinite" : "none",
        }} />
      )}
      {children}
      {removable && (
        <button onClick={onRemove} style={{
          background: "none", border: "none", padding: 0, marginLeft: 2,
          cursor: "pointer", color: "inherit", opacity: 0.6, fontSize: "1.1em", lineHeight: 1,
        }}>×</button>
      )}
    </span>
  );
}

/* ──────────────────────────────────────────────────────────────
   Panel — surface with optional left accent + hover glow
   ────────────────────────────────────────────────────────────── */
function Panel({ children, padding = 16, accent, glow, glowColor, onClick, hoverable, style = {}, className }) {
  const [h, setH] = useState(false);
  const interactive = !!onClick || hoverable;
  return (
    <div
      className={className}
      onClick={onClick}
      onMouseEnter={() => setH(true)}
      onMouseLeave={() => setH(false)}
      style={{
        background: "var(--bg-panel)",
        borderRadius: "var(--r-md)",
        border: `1px solid ${interactive && h ? "var(--line-2)" : "var(--line)"}`,
        borderLeft: accent ? `3px solid ${accent}` : undefined,
        padding,
        cursor: onClick ? "pointer" : "default",
        transition: "all 180ms",
        boxShadow: glow && h ? (glowColor ? `0 0 24px ${glowColor}26` : "var(--teal-glow)") : "none",
        ...style,
      }}>
      {children}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Metric — big number + label + optional sub/trend
   ────────────────────────────────────────────────────────────── */
function Metric({ label, value, color, sub, trend }) {
  const trendColor = trend?.startsWith("+") ? "var(--layer-unit)" :
                     trend?.startsWith("-") ? "var(--status-fail)" : "var(--tx-3)";
  return (
    <Panel padding={14}>
      <div style={{
        fontSize: "var(--fs-xxl)", fontWeight: 800, fontFamily: "var(--fn-sans)",
        color: color || "var(--tx)", lineHeight: 1, letterSpacing: -1,
        display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap",
      }}>
        {value}
        {trend && (
          <span style={{
            fontFamily: "var(--fn-mono)", fontSize: "var(--fs-xxs)",
            color: trendColor, fontWeight: 600,
          }}>{trend}</span>
        )}
      </div>
      <div style={{
        fontSize: "var(--fs-xxs)", color: "var(--tx-3)",
        fontFamily: "var(--fn-mono)", marginTop: 6,
        textTransform: "uppercase", letterSpacing: 1.5,
      }}>{label}</div>
      {sub && (
        <div style={{
          fontSize: "var(--fs-xxs)", color: "var(--tx-3)",
          fontFamily: "var(--fn-mono)", marginTop: 2,
        }}>{sub}</div>
      )}
    </Panel>
  );
}

/* ──────────────────────────────────────────────────────────────
   Ind — status dot + optional label
   ────────────────────────────────────────────────────────────── */
function Ind({ status = "pend", label, size = 7 }) {
  const colorMap = {
    pass: "var(--status-pass)", fail: "var(--status-fail)",
    run:  "var(--status-run)",  pend: "var(--status-pend)",
    skip: "var(--status-skip)",
  };
  const c = colorMap[status] || colorMap.pend;
  const glowMap = {
    pass: "var(--glow-pass)", fail: "var(--glow-fail)", run: "var(--glow-run)",
  };
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
      <span style={{
        width: size, height: size, borderRadius: "50%", background: c,
        boxShadow: glowMap[status] || "none",
        animation: status === "run" ? "ttt-pulse 1.8s infinite" : "none",
        flexShrink: 0,
      }} />
      {label && (
        <span style={{
          fontSize: "var(--fs-xxs)", color: "var(--tx-2)",
          fontFamily: "var(--fn-mono)", textTransform: "uppercase",
          letterSpacing: 0.5,
        }}>{label}</span>
      )}
    </span>
  );
}

/* ──────────────────────────────────────────────────────────────
   LayerRow — the workhorse component
   ────────────────────────────────────────────────────────────── */
const LAYER_META = {
  unit:  { name: "UNIT",        color: "var(--layer-unit)",  icon: "⚡" },
  sec:   { name: "SECURITY",    color: "var(--layer-sec)",   icon: "🔒" },
  integ: { name: "INTEGRATION", color: "var(--layer-integ)", icon: "☁" },
  e2e:   { name: "E2E",         color: "var(--layer-e2e)",   icon: "🌐" },
};
function LayerRow({ layer, status = "pend", count, duration, onClick, expanded, children }) {
  const m = LAYER_META[layer];
  const [h, setH] = useState(false);
  return (
    <div>
      <div
        onClick={onClick}
        onMouseEnter={() => setH(true)}
        onMouseLeave={() => setH(false)}
        style={{
          display: "flex", alignItems: "center",
          padding: "8px 12px", borderRadius: "var(--r-sm)",
          background: "var(--bg-well)",
          border: `1px solid ${h && onClick ? "var(--line-2)" : "var(--line)"}`,
          cursor: onClick ? "pointer" : "default",
          transition: "border-color 150ms",
        }}>
        <span style={{
          width: 3, height: 20, borderRadius: 2,
          background: m.color, marginRight: 10, flexShrink: 0,
        }} />
        <span style={{ fontSize: "var(--fs-xs)", marginRight: 6 }}>{m.icon}</span>
        <span style={{
          fontSize: "var(--fs-xxs)", color: "var(--tx-2)",
          fontFamily: "var(--fn-mono)", fontWeight: 600,
          letterSpacing: 1, minWidth: 88,
        }}>{m.name}</span>
        <span style={{ flex: 1 }} />
        <span style={{
          fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
          color: "var(--tx)", fontWeight: 600,
          minWidth: 56, textAlign: "right",
        }}>{count || "—"}</span>
        {duration && (
          <span style={{
            fontFamily: "var(--fn-mono)", fontSize: "var(--fs-xxs)",
            color: "var(--tx-3)", marginLeft: 10,
            minWidth: 44, textAlign: "right",
          }}>{duration}</span>
        )}
        <span style={{ marginLeft: 10 }}><Ind status={status} size={6} /></span>
        {onClick && (
          <span style={{
            marginLeft: 8, color: "var(--tx-3)", fontSize: 11,
            transform: expanded ? "rotate(90deg)" : "none",
            transition: "transform 150ms",
          }}>▸</span>
        )}
      </div>
      {expanded && children && (
        <div style={{
          marginTop: 2, padding: "10px 12px",
          background: "var(--bg-well)", border: "1px solid var(--line)",
          borderTop: "none", borderRadius: "0 0 var(--r-sm) var(--r-sm)",
        }}>{children}</div>
      )}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Pipeline — QUEUE → SCAN → GEN → FAST → GATE → CLOUD → DONE
   ────────────────────────────────────────────────────────────── */
const PIPE_STEPS = [
  { key: "pending",      label: "QUEUE" },
  { key: "analyzing",    label: "SCAN" },
  { key: "generating",   label: "GEN" },
  { key: "testing_fast", label: "FAST" },
  { key: "gated",        label: "GATE" },
  { key: "testing_cloud",label: "CLOUD" },
  { key: "completed",    label: "DONE" },
];
function Pipeline({ current = "pending", compact }) {
  const fail = current === "failed";
  const ci = fail ? -1 : PIPE_STEPS.findIndex(s => s.key === current);
  const failAt = fail ? (PIPE_STEPS.findIndex(s => s.key === current.replace("failed_at_","")) ?? -1) : -1;

  return (
    <div style={{ display: "flex", alignItems: "center", gap: 2, overflowX: "auto", paddingBottom: 2 }}>
      {PIPE_STEPS.map((s, i) => {
        const past = ci >= 0 && i < ci;
        const now  = i === ci;
        const failedHere = fail && i === Math.max(0, failAt);
        const color = failedHere ? "var(--status-fail)"
                    : past ? "var(--layer-unit)"
                    : now  ? "var(--teal)"
                    : "var(--tx-4)";
        const filled = past || now || failedHere;
        return (
          <React.Fragment key={s.key}>
            <div style={{
              padding: compact ? "3px 6px" : "5px 9px",
              borderRadius: "var(--r-xs)",
              background: filled ? `color-mix(in srgb, ${color} 12%, transparent)` : "transparent",
              border: `1px solid ${filled ? `color-mix(in srgb, ${color} 35%, transparent)` : "var(--line)"}`,
              transition: "all 200ms",
            }}>
              <span style={{
                fontSize: compact ? 8 : 9, fontFamily: "var(--fn-mono)",
                fontWeight: now ? 700 : 500,
                color: filled ? color : "var(--tx-3)",
                letterSpacing: 1,
                animation: now && !fail ? "ttt-pulse 2s infinite" : "none",
              }}>{s.label}</span>
            </div>
            {i < PIPE_STEPS.length - 1 && (
              <div style={{
                width: compact ? 8 : 14, height: 1,
                background: past ? "color-mix(in srgb, var(--layer-unit) 50%, transparent)" : "var(--line)",
              }} />
            )}
          </React.Fragment>
        );
      })}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Input — labeled, monospace, optional error
   ────────────────────────────────────────────────────────────── */
function Input({ label, value, onChange, placeholder, type = "text", mono = true, error, icon, hint, disabled, suffix }) {
  const [f, setF] = useState(false);
  const border = error ? "var(--status-fail)" : f ? "var(--line-focus)" : "var(--line)";
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      {label && (
        <label style={{
          fontSize: "var(--fs-xxs)", color: "var(--tx-3)",
          fontFamily: "var(--fn-mono)", fontWeight: 600,
          textTransform: "uppercase", letterSpacing: 1.5,
        }}>{label}</label>
      )}
      <div style={{
        position: "relative", display: "flex", alignItems: "center",
        background: "var(--bg-input)",
        border: `1px solid ${border}`,
        borderRadius: "var(--r-xs)",
        opacity: disabled ? 0.5 : 1,
        transition: "border-color 150ms",
      }}>
        {icon && (
          <span style={{
            paddingLeft: 9, color: "var(--tx-3)",
            display: "inline-flex", alignItems: "center",
          }}>{icon}</span>
        )}
        <input
          type={type}
          value={value ?? ""}
          onChange={e => onChange?.(e.target.value)}
          onFocus={() => setF(true)}
          onBlur={() => setF(false)}
          placeholder={placeholder}
          disabled={disabled}
          style={{
            flex: 1, padding: "7px 10px", paddingLeft: icon ? 6 : 10,
            background: "transparent", border: "none",
            color: "var(--tx)",
            fontFamily: mono ? "var(--fn-mono)" : "var(--fn-sans)",
            fontSize: "var(--fs-sm)", outline: "none", minWidth: 0,
          }} />
        {suffix && (
          <span style={{
            paddingRight: 9, paddingLeft: 4,
            color: "var(--tx-3)", fontSize: "var(--fs-xxs)",
            fontFamily: "var(--fn-mono)",
          }}>{suffix}</span>
        )}
      </div>
      {(error || hint) && (
        <span style={{
          fontSize: "var(--fs-xxs)",
          color: error ? "var(--status-fail)" : "var(--tx-3)",
          fontFamily: "var(--fn-mono)",
        }}>{error || hint}</span>
      )}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Select
   ────────────────────────────────────────────────────────────── */
function Select({ label, value, onChange, options = [], placeholder, error, disabled }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      {label && (
        <label style={{
          fontSize: "var(--fs-xxs)", color: "var(--tx-3)",
          fontFamily: "var(--fn-mono)", fontWeight: 600,
          textTransform: "uppercase", letterSpacing: 1.5,
        }}>{label}</label>
      )}
      <div style={{ position: "relative" }}>
        <select
          value={value}
          onChange={e => onChange?.(e.target.value)}
          disabled={disabled}
          style={{
            width: "100%", padding: "7px 28px 7px 10px",
            background: "var(--bg-input)",
            border: `1px solid ${error ? "var(--status-fail)" : "var(--line)"}`,
            borderRadius: "var(--r-xs)",
            color: value ? "var(--tx)" : "var(--tx-3)",
            fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
            outline: "none", appearance: "none",
            cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
          }}>
          {placeholder && <option value="">{placeholder}</option>}
          {options.map(o => {
            const v = typeof o === "string" ? o : o.value;
            const l = typeof o === "string" ? o : o.label;
            return <option key={v} value={v}>{l}</option>;
          })}
        </select>
        <span style={{
          position: "absolute", right: 10, top: "50%",
          transform: "translateY(-50%)", color: "var(--tx-3)",
          pointerEvents: "none", fontSize: 9,
        }}>▼</span>
      </div>
      {error && (
        <span style={{
          fontSize: "var(--fs-xxs)", color: "var(--status-fail)",
          fontFamily: "var(--fn-mono)",
        }}>{error}</span>
      )}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Toggle
   ────────────────────────────────────────────────────────────── */
function Toggle({ label, checked, onChange, description, disabled }) {
  return (
    <label style={{
      display: "flex", alignItems: "flex-start", gap: 10,
      cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
    }}>
      <button
        type="button"
        role="switch"
        aria-checked={checked}
        onClick={() => !disabled && onChange?.(!checked)}
        style={{
          width: 32, height: 18, borderRadius: 999,
          background: checked ? "var(--teal)" : "var(--bg-input)",
          border: `1px solid ${checked ? "var(--teal)" : "var(--line)"}`,
          padding: 0, cursor: disabled ? "default" : "pointer",
          position: "relative", flexShrink: 0,
          transition: "all 150ms", marginTop: 1,
        }}>
        <span style={{
          position: "absolute", top: 1, left: checked ? 15 : 1,
          width: 14, height: 14, borderRadius: "50%",
          background: checked ? "#fff" : "var(--tx-3)",
          transition: "left 150ms, background 150ms",
        }} />
      </button>
      {(label || description) && (
        <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
          {label && (
            <span style={{
              fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
              color: "var(--tx)", fontWeight: 500, lineHeight: 1.3,
            }}>{label}</span>
          )}
          {description && (
            <span style={{
              fontFamily: "var(--fn-mono)", fontSize: "var(--fs-xxs)",
              color: "var(--tx-3)", lineHeight: 1.4,
            }}>{description}</span>
          )}
        </div>
      )}
    </label>
  );
}

/* ──────────────────────────────────────────────────────────────
   Tabs — with optional count badges
   ────────────────────────────────────────────────────────────── */
function Tabs({ tabs = [], active, onChange }) {
  return (
    <div style={{
      display: "flex", gap: 0, borderBottom: "1px solid var(--line)",
    }}>
      {tabs.map(t => {
        const key = typeof t === "string" ? t : t.key;
        const label = typeof t === "string" ? t : t.label;
        const count = typeof t === "object" ? t.count : null;
        const on = active === key;
        return (
          <button
            key={key}
            onClick={() => onChange?.(key)}
            style={{
              padding: "8px 14px", background: "transparent",
              border: "none", borderBottom: `2px solid ${on ? "var(--teal)" : "transparent"}`,
              marginBottom: -1,
              color: on ? "var(--tx)" : "var(--tx-3)",
              fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
              fontWeight: 600, letterSpacing: 0.3, cursor: "pointer",
              display: "inline-flex", alignItems: "center", gap: 6,
              textTransform: "lowercase",
              transition: "color 150ms, border-color 150ms",
            }}>
            {label}
            {count != null && (
              <span style={{
                background: on ? "var(--teal-soft)" : "var(--bg-raised)",
                color: on ? "var(--teal)" : "var(--tx-3)",
                fontSize: 9, padding: "1px 5px",
                borderRadius: "var(--r-xs)", fontWeight: 600,
              }}>{count}</span>
            )}
          </button>
        );
      })}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Modal
   ────────────────────────────────────────────────────────────── */
function Modal({ open, onClose, title, children, footer, width = 480 }) {
  useEffect(() => {
    if (!open) return;
    const h = e => e.key === "Escape" && onClose?.();
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,0.6)",
      backdropFilter: "blur(2px)", zIndex: 100,
      display: "flex", alignItems: "center", justifyContent: "center",
      animation: "ttt-fade-up 180ms",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width, maxWidth: "94vw", maxHeight: "90vh",
        background: "var(--bg-panel)", border: "1px solid var(--line-2)",
        borderRadius: "var(--r-md)", boxShadow: "0 18px 60px rgba(0,0,0,0.5)",
        display: "flex", flexDirection: "column",
      }}>
        {title && (
          <div style={{
            padding: "12px 16px", borderBottom: "1px solid var(--line)",
            display: "flex", justifyContent: "space-between", alignItems: "center",
          }}>
            <div style={{
              fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
              fontWeight: 600, color: "var(--tx)", letterSpacing: 0.3,
            }}>{title}</div>
            <button onClick={onClose} style={{
              background: "none", border: "none", color: "var(--tx-3)",
              cursor: "pointer", padding: 4, fontSize: 16, lineHeight: 1,
            }}>×</button>
          </div>
        )}
        <div style={{ padding: 16, overflow: "auto", flex: 1 }}>{children}</div>
        {footer && (
          <div style={{
            padding: "10px 16px", borderTop: "1px solid var(--line)",
            display: "flex", justifyContent: "flex-end", gap: 6,
          }}>{footer}</div>
        )}
      </div>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Code — block with language label + copy
   ────────────────────────────────────────────────────────────── */
function Code({ code, language = "text", maxHeight = 360, showLineNumbers, copyable = true, accent }) {
  const [cp, setCp] = useState(false);
  const lines = code.split("\n");
  return (
    <div style={{
      borderRadius: "var(--r-md)", overflow: "hidden",
      border: "1px solid var(--line)",
      borderLeft: accent ? `3px solid ${accent}` : `1px solid var(--line)`,
    }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        padding: "5px 12px", background: "var(--bg-raised)",
        borderBottom: "1px solid var(--line)",
      }}>
        <span style={{
          fontFamily: "var(--fn-mono)", fontSize: "var(--fs-xxs)",
          color: "var(--tx-3)", textTransform: "uppercase", letterSpacing: 1.5,
        }}>{language}</span>
        {copyable && (
          <Btn variant="ghost" size="xs" onClick={() => {
            navigator.clipboard?.writeText(code);
            setCp(true); setTimeout(() => setCp(false), 1500);
          }}>{cp ? "✓ copied" : "copy"}</Btn>
        )}
      </div>
      <pre style={{
        margin: 0, padding: showLineNumbers ? "12px 12px 12px 0" : 14,
        background: "var(--bg-well)", maxHeight, overflow: "auto",
        fontSize: "var(--fs-sm)", lineHeight: 1.7,
        fontFamily: "var(--fn-mono)", color: "var(--tx)",
      }}>
        {showLineNumbers ? (
          <code style={{ display: "block" }}>
            {lines.map((l, i) => (
              <div key={i} style={{ display: "flex", gap: 12 }}>
                <span style={{
                  display: "inline-block", minWidth: 32, textAlign: "right",
                  color: "var(--tx-4)", userSelect: "none",
                  paddingLeft: 8,
                }}>{i + 1}</span>
                <span>{l || " "}</span>
              </div>
            ))}
          </code>
        ) : code}
      </pre>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Table — sortable, row click, loading
   ────────────────────────────────────────────────────────────── */
function Table({ columns = [], data = [], onRowClick, loading, sortKey, sortDir, onSort, empty }) {
  return (
    <div style={{
      border: "1px solid var(--line)", borderRadius: "var(--r-md)",
      overflow: "hidden", background: "var(--bg-panel)",
    }}>
      <table style={{
        width: "100%", borderCollapse: "collapse",
        fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
      }}>
        <thead>
          <tr style={{ background: "var(--bg-raised)" }}>
            {columns.map(c => {
              const sortable = c.sortable !== false && onSort;
              const on = sortKey === c.key;
              return (
                <th key={c.key}
                  onClick={() => sortable && onSort(c.key)}
                  style={{
                    padding: "8px 12px", textAlign: c.align || "left",
                    fontSize: "var(--fs-xxs)", color: "var(--tx-3)",
                    fontWeight: 600, textTransform: "uppercase", letterSpacing: 1.5,
                    borderBottom: "1px solid var(--line)",
                    cursor: sortable ? "pointer" : "default",
                    userSelect: "none", whiteSpace: "nowrap",
                    width: c.width,
                  }}>
                  {c.label}
                  {on && <span style={{ marginLeft: 4, color: "var(--teal)" }}>
                    {sortDir === "desc" ? "↓" : "↑"}
                  </span>}
                </th>
              );
            })}
          </tr>
        </thead>
        <tbody>
          {loading ? (
            Array.from({ length: 4 }).map((_, i) => (
              <tr key={i}>
                {columns.map(c => (
                  <td key={c.key} style={{ padding: "10px 12px", borderBottom: "1px solid var(--line)" }}>
                    <Skeleton height={12} />
                  </td>
                ))}
              </tr>
            ))
          ) : data.length === 0 ? (
            <tr><td colSpan={columns.length} style={{ padding: 32, textAlign: "center", color: "var(--tx-3)" }}>
              {empty || "no data"}
            </td></tr>
          ) : data.map((row, i) => (
            <tr key={row.id ?? i}
              onClick={() => onRowClick?.(row)}
              style={{
                cursor: onRowClick ? "pointer" : "default",
                transition: "background 100ms",
              }}
              onMouseEnter={e => e.currentTarget.style.background = "var(--bg-raised)"}
              onMouseLeave={e => e.currentTarget.style.background = "transparent"}
            >
              {columns.map(c => (
                <td key={c.key} style={{
                  padding: "10px 12px",
                  borderBottom: i < data.length - 1 ? "1px solid var(--line)" : "none",
                  textAlign: c.align || "left",
                  color: c.muted ? "var(--tx-3)" : "var(--tx)",
                  whiteSpace: c.nowrap ? "nowrap" : undefined,
                }}>{c.render ? c.render(row[c.key], row) : row[c.key]}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Skeleton
   ────────────────────────────────────────────────────────────── */
function Skeleton({ width = "100%", height = 14, variant = "rect", count = 1 }) {
  const radius = variant === "circle" ? "50%" : variant === "text" ? 2 : "var(--r-xs)";
  return (
    <>
      {Array.from({ length: count }).map((_, i) => (
        <div key={i} style={{
          width, height, borderRadius: radius,
          background: "linear-gradient(90deg, var(--bg-raised) 0%, var(--bg-input) 50%, var(--bg-raised) 100%)",
          backgroundSize: "200% 100%",
          animation: "ttt-skeleton 1.5s ease-in-out infinite",
          marginBottom: count > 1 && i < count - 1 ? 6 : 0,
        }} />
      ))}
    </>
  );
}

/* ──────────────────────────────────────────────────────────────
   EmptyState
   ────────────────────────────────────────────────────────────── */
function EmptyState({ title, description, icon, action }) {
  return (
    <div style={{
      display: "flex", flexDirection: "column", alignItems: "center",
      textAlign: "center", padding: 40, gap: 12,
      background: "var(--bg-well)", border: "1px dashed var(--line)",
      borderRadius: "var(--r-md)",
    }}>
      {icon && (
        <div style={{
          width: 44, height: 44, borderRadius: "var(--r-sm)",
          background: "var(--teal-soft)", border: "1px solid var(--teal-border)",
          display: "flex", alignItems: "center", justifyContent: "center",
          color: "var(--teal)", fontSize: 20,
        }}>{icon}</div>
      )}
      <div>
        <div style={{
          fontFamily: "var(--fn-mono)", fontSize: "var(--fs-md)",
          fontWeight: 600, color: "var(--tx)", marginBottom: 4,
        }}>{title}</div>
        {description && (
          <div style={{
            fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
            color: "var(--tx-3)", lineHeight: 1.5, maxWidth: 360,
          }}>{description}</div>
        )}
      </div>
      {action}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Toast — useToast hook + <ToastHost>
   ────────────────────────────────────────────────────────────── */
const ToastCtx = createContext(null);
function useToast() {
  return useContext(ToastCtx);
}
function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((kind, message, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, kind, message, ...opts }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), opts.duration || 3200);
  }, []);
  const api = {
    success: (m, o) => push("pass", m, o),
    error:   (m, o) => push("fail", m, o),
    info:    (m, o) => push("info", m, o),
    warning: (m, o) => push("run",  m, o),
  };
  return (
    <ToastCtx.Provider value={api}>
      {children}
      <ToastHost toasts={toasts} />
    </ToastCtx.Provider>
  );
}
function ToastHost({ toasts }) {
  const kindColor = {
    pass: "var(--status-pass)", fail: "var(--status-fail)",
    run:  "var(--status-run)",  info: "var(--teal)",
  };
  return (
    <div style={{
      position: "fixed", right: 16, bottom: 16, zIndex: 200,
      display: "flex", flexDirection: "column", gap: 6,
      pointerEvents: "none",
    }}>
      {toasts.map(t => (
        <div key={t.id} style={{
          minWidth: 260, maxWidth: 360,
          background: "var(--bg-panel)", border: "1px solid var(--line-2)",
          borderLeft: `3px solid ${kindColor[t.kind] || "var(--teal)"}`,
          borderRadius: "var(--r-sm)", padding: "10px 12px",
          display: "flex", alignItems: "center", gap: 10,
          fontFamily: "var(--fn-mono)", fontSize: "var(--fs-sm)",
          color: "var(--tx)", boxShadow: "0 10px 30px rgba(0,0,0,0.4)",
          animation: "ttt-fade-up 200ms ease-out",
          pointerEvents: "auto",
        }}>
          <Ind status={t.kind === "info" ? "pass" : t.kind} size={7} />
          <span style={{ flex: 1 }}>{t.message}</span>
        </div>
      ))}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────
   Theme toggle button (utility)
   ────────────────────────────────────────────────────────────── */
function ThemeToggle() {
  const [mode, setMode] = useState(() =>
    document.documentElement.getAttribute("data-theme") || "dark"
  );
  const flip = () => {
    const next = mode === "dark" ? "light" : "dark";
    setMode(next);
    document.documentElement.setAttribute("data-theme", next);
    try { localStorage.setItem("ttt-theme", next); } catch {}
  };
  return (
    <Btn variant="outline" size="sm" onClick={flip}
      icon={<span style={{ fontSize: 11 }}>{mode === "dark" ? "☾" : "☀"}</span>}>
      {mode === "dark" ? "light" : "dark"}
    </Btn>
  );
}

/* ──────────────────────────────────────────────────────────────
   Inject component-only keyframes (skeleton uses one)
   ────────────────────────────────────────────────────────────── */
(function () {
  if (document.getElementById("ttt-component-keyframes")) return;
  const s = document.createElement("style");
  s.id = "ttt-component-keyframes";
  s.textContent = `
    @keyframes ttt-skeleton { 0% { background-position: 100% 0 } 100% { background-position: -100% 0 } }
  `;
  document.head.appendChild(s);
})();

/* ──────────────────────────────────────────────────────────────
   Export to window for cross-script use
   ────────────────────────────────────────────────────────────── */
Object.assign(window, {
  Sec, Logo, LogoMark,
  Btn, Chip, Panel, Metric, Ind,
  LayerRow, Pipeline, LAYER_META, PIPE_STEPS,
  Input, Select, Toggle, Tabs, Modal, Code,
  Table, Skeleton, EmptyState,
  ToastProvider, useToast, ThemeToggle,
});
