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

// ============================================================================
// TOAST NOTIFICATION SYSTEM — replaces all alert() calls
// ============================================================================

const ToastContext = createContext(null);

function useToast() {
  return useContext(ToastContext);
}

function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const addToast = useCallback((message, type = "info", duration = 4000) => {
    const id = Date.now() + Math.random();
    setToasts(prev => [...prev, { id, message, type, exiting: false }]);
    if (duration > 0) {
      setTimeout(() => {
        setToasts(prev => prev.map(t => t.id === id ? { ...t, exiting: true } : t));
        setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 250);
      }, duration);
    }
  }, []);
  const dismissToast = useCallback((id) => {
    setToasts(prev => prev.map(t => t.id === id ? { ...t, exiting: true } : t));
    setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 250);
  }, []);
  return (
    <ToastContext.Provider value={addToast}>
      {children}
      <div className="fixed top-2 right-2 sm:top-4 sm:right-4 z-[9999] flex flex-col gap-2 max-w-[calc(100vw-1rem)] sm:max-w-sm" role="alert" aria-live="polite">
        {toasts.map(t => {
          const colors = {
            success: "bg-green-800 border-green-600 text-green-100",
            error: "bg-red-900 border-red-700 text-red-100",
            warning: "bg-amber-900 border-amber-600 text-amber-100",
            info: "bg-gray-800 border-gray-600 text-gray-100",
          };
          return (
            <div key={t.id} className={`${colors[t.type] || colors.info} border rounded-lg px-4 py-3 shadow-lg flex items-start gap-2 ${t.exiting ? "toast-exit" : "toast-enter"}`}>
              <span className="flex-1 text-sm">{t.message}</span>
              <button onClick={() => dismissToast(t.id)} className="text-gray-400 hover:text-white ml-2 flex-shrink-0" aria-label="Dismiss notification">&times;</button>
            </div>
          );
        })}
      </div>
    </ToastContext.Provider>
  );
}

// ============================================================================
// CONFIRM / PROMPT MODALS — replaces window.confirm() and window.prompt()
// ============================================================================

function ConfirmModal({ open, title, message, onConfirm, onCancel, confirmText = "Confirm", danger = false }) {
  const btnRef = useRef(null);
  useEffect(() => { if (open && btnRef.current) btnRef.current.focus(); }, [open]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9998]" role="dialog" aria-modal="true" aria-label={title}>
      <div className="bg-gray-800 border border-gray-700 rounded-lg p-6 max-w-sm w-full mx-4 shadow-xl">
        <h3 className="text-white font-semibold mb-2">{title}</h3>
        <p className="text-gray-300 text-sm mb-4">{message}</p>
        <div className="flex justify-end gap-2">
          <button onClick={onCancel} className="px-4 py-2 rounded text-sm text-gray-300 hover:bg-gray-700 transition">Cancel</button>
          <button ref={btnRef} onClick={onConfirm}
            className={`px-4 py-2 rounded text-sm font-medium text-white transition ${danger ? "bg-red-600 hover:bg-red-700" : "bg-amber-600 hover:bg-amber-700"}`}>
            {confirmText}
          </button>
        </div>
      </div>
    </div>
  );
}

function PromptModal({ open, title, message, placeholder, onSubmit, onCancel, required = false }) {
  const [value, setValue] = useState("");
  const inputRef = useRef(null);
  useEffect(() => { if (open) { setValue(""); setTimeout(() => inputRef.current?.focus(), 50); } }, [open]);
  if (!open) return null;
  const handleSubmit = () => { if (required && !value.trim()) return; onSubmit(value); };
  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9998]" role="dialog" aria-modal="true" aria-label={title}>
      <div className="bg-gray-800 border border-gray-700 rounded-lg p-6 max-w-sm w-full mx-4 shadow-xl">
        <h3 className="text-white font-semibold mb-2">{title}</h3>
        <p className="text-gray-300 text-sm mb-3">{message}</p>
        <input ref={inputRef} type="text" value={value} onChange={e => setValue(e.target.value)}
          onKeyDown={e => e.key === "Enter" && handleSubmit()}
          placeholder={placeholder || ""}
          className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none mb-4" />
        <div className="flex justify-end gap-2">
          <button onClick={onCancel} className="px-4 py-2 rounded text-sm text-gray-300 hover:bg-gray-700 transition">Cancel</button>
          <button onClick={handleSubmit} disabled={required && !value.trim()}
            className="px-4 py-2 rounded text-sm font-medium bg-amber-600 hover:bg-amber-700 text-white transition disabled:opacity-50">
            Submit
          </button>
        </div>
      </div>
    </div>
  );
}

// ============================================================================
// COOKIE HELPERS — session persistence across page refreshes
// ============================================================================

function setCookie(name, value, hours) {
  const expires = new Date(Date.now() + hours * 3600000).toUTCString();
  document.cookie = name + "=" + encodeURIComponent(JSON.stringify(value)) + ";expires=" + expires + ";path=/;SameSite=Strict";
}

function getCookie(name) {
  const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
  if (!match) return null;
  try { return JSON.parse(decodeURIComponent(match[2])); } catch (e) { return null; }
}

function deleteCookie(name) {
  document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Strict";
}

// ============================================================================
// TYPING INDICATOR — animated dots shown while waiting for LLM response
// ============================================================================

function TypingIndicator({ visible }) {
  if (!visible) return null;
  return (
    <div className="flex justify-start">
      <div className="bg-gray-800 border border-gray-700 rounded-lg rounded-bl-none px-4 py-3 flex items-center gap-1.5">
        <span className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{ animationDelay: "0ms", animationDuration: "1s" }} />
        <span className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{ animationDelay: "200ms", animationDuration: "1s" }} />
        <span className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{ animationDelay: "400ms", animationDuration: "1s" }} />
      </div>
    </div>
  );
}

// ============================================================================
// TOOL LINE STRIPPING — hide [Executing: ...] lines for non-admin users
// ============================================================================

function stripToolLines(content) {
  if (!content) return "";
  // Remove lines like [Executing: ...], [Tool completed ...], [Export started: ...]
  return content.replace(/^\[(?:Executing|Tool completed|Export started)[^\]]*\].*$/gm, "").trimStart();
}

// Markdown rendering for chat messages (sanitized with DOMPurify)
marked.setOptions({ breaks: true, gfm: true });

// DOMPurify config — allow safe HTML subset
const PURIFY_CONFIG = typeof DOMPurify !== "undefined" ? {
  ALLOWED_TAGS: ["p", "br", "strong", "em", "b", "i", "code", "pre", "ul", "ol", "li", "a", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "table", "thead", "tbody", "tr", "th", "td", "hr", "div", "span", "sup", "sub", "del", "img"],
  ALLOWED_ATTR: ["href", "title", "class", "target", "rel", "src", "alt", "width", "height"],
  ADD_ATTR: ["target"],
} : {};

function MarkdownMessage({ content, className }) {
  const containerRef = useRef(null);
  const html = React.useMemo(() => {
    if (!content) return "";
    try {
      let parsed = marked.parse(content)
        .replace(/<table>/g, '<div class="table-wrap"><table>')
        .replace(/<\/table>/g, '</table></div>');
      // Add copy button to code blocks
      parsed = parsed.replace(/<pre>/g, '<pre><button class="code-copy-btn" title="Copy to clipboard">Copy</button>');
      // Sanitize HTML
      if (typeof DOMPurify !== "undefined") {
        return DOMPurify.sanitize(parsed, { ...PURIFY_CONFIG, ALLOWED_TAGS: [...PURIFY_CONFIG.ALLOWED_TAGS, "button"] });
      }
      return parsed;
    } catch (e) {
      return content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
    }
  }, [content]);

  // Attach copy handlers to code block buttons after render
  useEffect(() => {
    if (!containerRef.current) return;
    const buttons = containerRef.current.querySelectorAll(".code-copy-btn");
    buttons.forEach(btn => {
      btn.onclick = () => {
        const pre = btn.closest("pre");
        if (!pre) return;
        const code = pre.querySelector("code");
        const text = (code || pre).textContent.replace(/^Copy\n?/, "");
        navigator.clipboard.writeText(text).then(() => {
          btn.textContent = "Copied!";
          setTimeout(() => { btn.textContent = "Copy"; }, 1500);
        }).catch(() => {});
      };
    });
  }, [html]);

  return React.createElement("div", {
    ref: containerRef,
    className: "markdown-message " + (className || ""),
    dangerouslySetInnerHTML: { __html: html }
  });
}

// ============================================================================
// MOCK DATA
// ============================================================================

const MOCK_FLEET = [
  { id: "fleet-001", name: "Production (US-East)", agents: 24, status: "healthy", lastSeen: "now", utilization: 87 },
  { id: "fleet-002", name: "Staging (US-West)", agents: 8, status: "healthy", lastSeen: "1m", utilization: 42 },
  { id: "fleet-003", name: "Development", agents: 12, status: "degraded", lastSeen: "3m", utilization: 56 },
];

const MOCK_ALERTS = [
  { id: "ALR-1001", severity: "critical", message: "Model inference latency exceeded 2s on fleet-001", timestamp: "14:32", agent: "ml-node-07", resolved: false },
  { id: "ALR-1002", severity: "warning", message: "Agent node fleet-002-04 memory usage at 89%", timestamp: "14:15", agent: "fleet-002-04", resolved: false },
  { id: "ALR-1003", severity: "info", message: "Daily backup completed successfully", timestamp: "13:45", agent: "backup-job-01", resolved: true },
];

const MOCK_COST = [
  { period: "Jan", compute: 45000, storage: 8200, network: 3100 },
  { period: "Feb", compute: 52000, storage: 9100, network: 3400 },
  { period: "Mar", compute: 48500, storage: 8900, network: 3200 },
];

const MOCK_TASK_LOG = [
  { id: "TASK-2847", status: "completed", title: "Process order batch #521", agent: "order-processor-03", duration: "2m 14s", timestamp: "14:28" },
  { id: "TASK-2846", status: "in_progress", title: "Analyze customer feedback", agent: "sentiment-analyzer-01", duration: "4m 32s", timestamp: "14:12" },
  { id: "TASK-2845", status: "queued", title: "Generate daily report", agent: "report-gen-02", duration: "pending", timestamp: "14:05" },
];

const MOCK_PROJECTS = [
  { id: "PRJ-88", name: "Q1 Automation Initiative", status: "in_progress", progress: 65, owner: "alex.kim", team: 7 },
  { id: "PRJ-87", name: "Model Fine-tuning Sprint", status: "in_progress", progress: 42, owner: "jordan.lee", team: 5 },
  { id: "PRJ-86", name: "Infrastructure Upgrade", status: "on_hold", progress: 30, owner: "alex.kim", team: 3 },
];

const MOCK_AGENTS = [
  { id: "agent-001", name: "Order Processor", tasks: 247, status: "idle", model: "claude-opus-4", efficiency: 94 },
  { id: "agent-002", name: "Sentiment Analyzer", tasks: 183, status: "busy", model: "claude-sonnet-4", efficiency: 88 },
  { id: "agent-003", name: "Report Generator", tasks: 92, status: "idle", model: "claude-haiku-4", efficiency: 96 },
];

// Mock data removed — business tabs now use live analytics via chat

// ============================================================================
// UTILITY COMPONENTS
// ============================================================================

function StatusDot({ status }) {
  const colorMap = {
    healthy: "bg-green-500",
    degraded: "bg-amber-500",
    critical: "bg-red-500",
    idle: "bg-teal-500",
    busy: "bg-blue-500",
    active: "bg-green-500",
    inactive: "bg-gray-400",
    complete: "bg-green-500",
    in_progress: "bg-amber-500",
    pending: "bg-gray-400",
    queued: "bg-blue-500",
    in_service: "bg-green-500",
    awaiting_parts: "bg-amber-500",
    shipped_vendor: "bg-blue-500",
    complete_service: "bg-green-500",
    processing: "bg-amber-500",
    quoted: "bg-gray-400",
    invoiced: "bg-blue-500",
    tagging: "bg-amber-500",
    qc_pass: "bg-green-500",
    qc_fail: "bg-red-500",
    config: "bg-amber-500",
    proposal: "bg-gray-400",
    negotiation: "bg-amber-500",
    discovery: "bg-blue-500",
    closed_won: "bg-green-500",
    discrepancy: "bg-red-500",
  };
  return <div className={`w-2 h-2 rounded-full ${colorMap[status] || "bg-gray-400"}`} role="status" aria-label={status || "unknown"} title={status || "unknown"} />;
}

function MetricBar({ label, value, total, color = "bg-amber-500" }) {
  const percentage = Math.round((value / total) * 100);
  return (
    <div className="mb-4">
      <div className="flex justify-between text-xs mb-1">
        <span className="text-gray-300">{label}</span>
        <span className="text-gray-400">{percentage}%</span>
      </div>
      <div className="w-full bg-gray-700 rounded-full h-2 overflow-hidden">
        <div className={`${color} h-full`} style={{ width: `${percentage}%` }} />
      </div>
    </div>
  );
}

function SeverityBadge({ severity }) {
  const classMap = {
    critical: "bg-red-900 text-red-200",
    warning: "bg-amber-900 text-amber-200",
    info: "bg-blue-900 text-blue-200",
  };
  return (
    <span className={`px-2 py-1 rounded text-xs font-medium ${classMap[severity] || "bg-gray-700 text-gray-200"}`}>
      {severity.charAt(0).toUpperCase() + severity.slice(1)}
    </span>
  );
}

function ModelBadge({ model }) {
  const colors = {
    "claude-opus-4": "bg-violet-900 text-violet-200",
    "claude-sonnet-4": "bg-purple-900 text-purple-200",
    "claude-haiku-4": "bg-teal-900 text-teal-200",
  };
  return (
    <span className={`px-2 py-1 rounded text-xs font-medium ${colors[model] || "bg-gray-700 text-gray-200"}`}>
      {model}
    </span>
  );
}

function TaskStatusIcon({ status }) {
  const icons = {
    completed: "✓",
    in_progress: "◐",
    queued: "→",
  };
  const colors = {
    completed: "text-green-400",
    in_progress: "text-amber-400",
    queued: "text-blue-400",
  };
  return <span className={`text-lg ${colors[status] || "text-gray-400"}`}>{icons[status] || "·"}</span>;
}

// ============================================================================
// HELPER FUNCTIONS
// ============================================================================

function hasRole(user, ...roles) {
  if (!user?.roles) return false;
  if (user.roles.includes("admin")) return true;
  return roles.some(r => user.roles.includes(r));
}

// ============================================================================
// MAIN COMPONENTS
// ============================================================================

// ============================================================================
// METRIC PARSERS — extract numbers from MCP tool output
// ============================================================================

function parseTopCPU(topOutput) {
  if (!topOutput) return 0;
  const match = topOutput.match(/%Cpu\(s\):\s+([\d.]+)\s+us,\s+([\d.]+)\s+sy/);
  if (match) return Math.round(parseFloat(match[1]) + parseFloat(match[2]));
  // Fallback: try to find idle percentage
  const idleMatch = topOutput.match(/([\d.]+)\s+id/);
  if (idleMatch) return Math.round(100 - parseFloat(idleMatch[1]));
  return 0;
}

function parseFreeMemory(freeOutput) {
  if (!freeOutput) return { total: "0", used: "0", percent: 0 };
  const lines = freeOutput.split("\n");
  const memLine = lines.find((l) => l.trim().startsWith("Mem:"));
  if (!memLine) return { total: "0", used: "0", percent: 0 };
  const parts = memLine.trim().split(/\s+/);
  // Normalize values to bytes to handle mixed units (e.g., 31G vs 5200M)
  const toBytes = (s) => {
    const n = parseFloat(s) || 0;
    const u = (s || "").slice(-1).toUpperCase();
    if (u === "T") return n * 1e12;
    if (u === "G") return n * 1e9;
    if (u === "M") return n * 1e6;
    if (u === "K") return n * 1e3;
    return n;
  };
  const total = toBytes(parts[1]) || 1;
  const used = toBytes(parts[2]) || 0;
  return { total: parts[1], used: parts[2], percent: Math.round((used / total) * 100) };
}

function parseDfDisk(dfOutput) {
  if (!dfOutput) return { total: "0", used: "0", percent: 0 };
  const lines = dfOutput.split("\n");
  const rootLine = lines.find((l) => l.trim().endsWith(" /") || l.includes("/$"));
  if (!rootLine) {
    // fallback: first non-header line
    const dataLine = lines.find((l, i) => i > 0 && l.trim().length > 0);
    if (!dataLine) return { total: "0", used: "0", percent: 0 };
    const parts = dataLine.trim().split(/\s+/);
    return { total: parts[1] || "0", used: parts[2] || "0", percent: parseInt(parts[4]) || 0 };
  }
  const parts = rootLine.trim().split(/\s+/);
  return { total: parts[1] || "0", used: parts[2] || "0", percent: parseInt(parts[4]) || 0 };
}

function parseGPUMetrics(gpuString) {
  if (!gpuString) return null;
  const parts = gpuString.split(",").map((s) => s.trim());
  return {
    utilization: parseInt(parts[0]) || 0,
    vramUsed: parseInt(parts[1]) || 0,
    vramTotal: parseInt(parts[2]) || 0,
    temperature: parseInt(parts[3]) || 0,
  };
}

function parseServiceList(servicesText) {
  if (!servicesText) return [];
  return servicesText.split("\n")
    .filter((line) => line.includes(".service"))
    .map((line) => {
      const parts = line.trim().split(/\s+/);
      const name = (parts[0] || "").replace(".service", "");
      return {
        name,
        loadState: parts[1] || "unknown",
        activeState: parts[2] || "unknown",
        subState: parts[3] || "",
        description: parts.slice(4).join(" "),
      };
    })
    .filter((s) => s.name);
}

function parseMetricsResult(resultData) {
  // MCP tool returns either a JSON dict or a text array
  let data = {};
  if (typeof resultData === "string") {
    try { data = JSON.parse(resultData); } catch { return { cpu: 0, memory: { percent: 0 }, disk: { percent: 0 }, gpu: null }; }
  } else if (Array.isArray(resultData)) {
    // Try parsing first element as JSON
    try { data = JSON.parse(resultData[0]); } catch { data = { raw: resultData.join("\n") }; }
  } else if (typeof resultData === "object") {
    data = resultData;
  }
  return {
    cpu: parseTopCPU(data.cpu || ""),
    memory: parseFreeMemory(data.memory || ""),
    disk: parseDfDisk(data.disk || ""),
    gpu: parseGPUMetrics(data.gpu),
    network: data.network_sockets || "",
    hostname: data.hostname || "",
  };
}

// ============================================================================
// AGENTS TAB COMPONENTS
// ============================================================================

function MetricGauge({ label, value, thresholds = { warn: 70, danger: 90 } }) {
  const color = value > thresholds.danger ? "text-red-400"
    : value > thresholds.warn ? "text-amber-400"
    : "text-green-400";
  const bgColor = value > thresholds.danger ? "bg-red-500"
    : value > thresholds.warn ? "bg-amber-500"
    : "bg-green-500";
  return (
    <div className="text-center">
      <div className="relative w-16 h-16 mx-auto mb-1">
        <svg className="w-16 h-16 transform -rotate-90" viewBox="0 0 36 36">
          <path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
            fill="none" stroke="#374151" strokeWidth="3" />
          <path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
            fill="none" className={bgColor.replace("bg-", "stroke-")} strokeWidth="3"
            strokeDasharray={`${value}, 100`} strokeLinecap="round" />
        </svg>
        <span className={`absolute inset-0 flex items-center justify-center text-sm font-bold ${color}`}>
          {value}%
        </span>
      </div>
      <div className="text-xs text-gray-400">{label}</div>
    </div>
  );
}

function TemperatureIndicator({ temp }) {
  const color = temp > 80 ? "text-red-400" : temp > 60 ? "text-amber-400" : "text-green-400";
  return <span className={`text-sm font-mono ${color}`}>{temp}°C</span>;
}

const PROFILE_TO_AGENT_TYPE_MAP = {
  "ollama-only": "monitoring",
  "os-coding": "coding",
  "claude-code": "coding",
};

function AgentTypeBadge({ agentType }) {
  const colors = {
    monitoring: "bg-gray-700 text-gray-200",
    inference: "bg-blue-900 text-blue-200",
    coding: "bg-violet-900 text-violet-200",
    triage: "bg-amber-900 text-amber-200",
    analytics: "bg-emerald-900 text-emerald-200",
    "analytics-secure": "bg-red-900 text-red-200",
  };
  const labels = {
    monitoring: "monitoring",
    inference: "inference",
    coding: "coding",
    triage: "triage",
    analytics: "analytics",
    "analytics-secure": "analytics-secure",
  };
  return (
    <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[agentType] || "bg-gray-700 text-gray-200"}`}>
      {labels[agentType] || agentType || "unknown"}
    </span>
  );
}

// ── Tab Icon Library ────────────────────────────────────────────────
// Store as path data arrays — rendered in TabIcon component
const TAB_ICON_PATHS = {
  truck: [{t:"path",d:"M1 3h15v13H1z"},{t:"path",d:"M16 8h4l3 3v5h-7V8z"},{t:"circle",cx:5.5,cy:18.5,r:2.5},{t:"circle",cx:18.5,cy:18.5,r:2.5}],
  package: [{t:"path",d:"M16.5 9.4l-9-5.19M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"},{t:"path",d:"M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12"}],
  warehouse: [{t:"path",d:"M22 8.35V20a2 2 0 01-2 2H4a2 2 0 01-2-2V8.35A2 2 0 013.26 6.5l8-3.2a2 2 0 011.48 0l8 3.2A2 2 0 0122 8.35z"},{t:"path",d:"M6 18h12M6 14h12"}],
  wrench: [{t:"path",d:"M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"}],
  settings: [{t:"circle",cx:12,cy:12,r:3},{t:"path",d:"M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"}],
  cog: [{t:"circle",cx:12,cy:12,r:8},{t:"circle",cx:12,cy:12,r:2},{t:"path",d:"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"}],
  "chart-bar": [{t:"path",d:"M12 20V10M18 20V4M6 20v-4"}],
  "chart-line": [{t:"path",d:"M23 6l-9.5 9.5-5-5L1 18"},{t:"path",d:"M17 6h6v6"}],
  "chart-pie": [{t:"path",d:"M21.21 15.89A10 10 0 118 2.83"},{t:"path",d:"M22 12A10 10 0 0012 2v10z"}],
  users: [{t:"path",d:"M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"},{t:"circle",cx:9,cy:7,r:4},{t:"path",d:"M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"}],
  user: [{t:"path",d:"M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"},{t:"circle",cx:12,cy:7,r:4}],
  briefcase: [{t:"rect",x:2,y:7,width:20,height:14,rx:2},{t:"path",d:"M16 21V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v16"}],
  dollar: [{t:"path",d:"M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"}],
  "credit-card": [{t:"rect",x:1,y:4,width:22,height:16,rx:2},{t:"path",d:"M1 10h22"}],
  receipt: [{t:"path",d:"M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1-2-1z"},{t:"path",d:"M8 10h8M8 14h4"}],
  shield: [{t:"path",d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"}],
  lock: [{t:"rect",x:3,y:11,width:18,height:11,rx:2},{t:"path",d:"M7 11V7a5 5 0 0110 0v4"}],
  key: [{t:"path",d:"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"}],
  server: [{t:"rect",x:2,y:2,width:20,height:8,rx:2},{t:"rect",x:2,y:14,width:20,height:8,rx:2},{t:"path",d:"M6 6h.01M6 18h.01"}],
  database: [{t:"ellipse",cx:12,cy:5,rx:9,ry:3},{t:"path",d:"M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"},{t:"path",d:"M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"}],
  globe: [{t:"circle",cx:12,cy:12,r:10},{t:"path",d:"M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"}],
  clipboard: [{t:"path",d:"M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"},{t:"rect",x:8,y:2,width:8,height:4,rx:1}],
  folder: [{t:"path",d:"M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"}],
  search: [{t:"circle",cx:11,cy:11,r:8},{t:"path",d:"M21 21l-4.35-4.35"}],
  bell: [{t:"path",d:"M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"},{t:"path",d:"M13.73 21a2 2 0 01-3.46 0"}],
  lightning: [{t:"path",d:"M13 2L3 14h9l-1 8 10-12h-9l1-8z"}],
  refresh: [{t:"path",d:"M23 4v6h-6M1 20v-6h6"},{t:"path",d:"M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"}],
  home: [{t:"path",d:"M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"},{t:"path",d:"M9 22V12h6v10"}],
  "brain-chip": [{t:"rect",x:5,y:5,width:14,height:14,rx:2},{t:"path",d:"M9 5V2M15 5V2M9 19v3M15 19v3M5 9H2M5 15H2M19 9h3M19 15h3"},{t:"circle",cx:9,cy:5,r:0.5},{t:"circle",cx:15,cy:5,r:0.5},{t:"circle",cx:9,cy:19,r:0.5},{t:"circle",cx:15,cy:19,r:0.5},{t:"circle",cx:5,cy:9,r:0.5},{t:"circle",cx:5,cy:15,r:0.5},{t:"circle",cx:19,cy:9,r:0.5},{t:"circle",cx:19,cy:15,r:0.5},{t:"path",d:"M9.5 8.5v7M14.5 8.5v7M9.5 12h5"}],
  star: [{t:"path",d:"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"}],
  flag: [{t:"path",d:"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"},{t:"path",d:"M4 22v-7"}],
  inbox: [{t:"path",d:"M22 12h-6l-2 3H10l-2-3H2"},{t:"path",d:"M5.45 5.11L2 12v6a2 2 0 002 2h16a2 2 0 002-2v-6l-3.45-6.89A2 2 0 0016.76 4H7.24a2 2 0 00-1.79 1.11z"}],
  layers: [{t:"path",d:"M12 2L2 7l10 5 10-5-10-5z"},{t:"path",d:"M2 17l10 5 10-5"},{t:"path",d:"M2 12l10 5 10-5"}],
  monitor: [{t:"rect",x:2,y:3,width:20,height:14,rx:2},{t:"path",d:"M8 21h8M12 17v4"}],
  "file-text": [{t:"path",d:"M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"},{t:"path",d:"M14 2v6h6M16 13H8M16 17H8M10 9H8"}],
  terminal: [{t:"path",d:"M4 17l6-5-6-5"},{t:"path",d:"M12 19h8"}],
};

function ICPLogo({ className = "h-10", invert = true }) {
  return <img src="/static/icp-logo-blue.png" alt="ICP" className={className}
    style={{ objectFit: "contain", filter: invert ? "brightness(0) invert(1)" : "none" }} />;
}

function TabIcon({ name, className = "w-4 h-4" }) {
  const shapes = TAB_ICON_PATHS[name];
  if (!shapes) return null;
  return (
    <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24"
      strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
      {shapes.map((s, i) => {
        if (s.t === "path") return <path key={i} d={s.d} />;
        if (s.t === "circle") return <circle key={i} cx={s.cx} cy={s.cy} r={s.r} />;
        if (s.t === "rect") return <rect key={i} x={s.x} y={s.y} width={s.width} height={s.height} rx={s.rx} ry={s.ry} />;
        if (s.t === "ellipse") return <ellipse key={i} cx={s.cx} cy={s.cy} rx={s.rx} ry={s.ry} />;
        return null;
      })}
    </svg>
  );
}

function HostMetricCard({ host, metrics, sessionToken, onHealthCheck }) {
  const m = metrics || { cpu: 0, memory: { percent: 0 }, disk: { percent: 0 }, gpu: null };
  const [checking, setChecking] = useState(false);
  const [showFindings, setShowFindings] = useState(false);
  const [findings, setFindings] = useState([]);
  const [findingsCount, setFindingsCount] = useState(0);

  const runHealthCheck = async () => {
    setChecking(true);
    try {
      const res = await fetch("/api/console/exec", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ host: host.id, tool: "get_host_info", args: {} }),
      });
      if (res.ok) {
        const data = await res.json();
        const result = data.results?.[host.id];
        if (onHealthCheck) onHealthCheck(host.id, result);
      }
    } catch {} finally { setChecking(false); }
  };

  // Poll findings count for this agent
  useEffect(() => {
    const poll = async () => {
      try {
        const res = await fetch(`/api/findings?source_host=${host.id}&status=active&limit=50`, {
          headers: { "X-Session-Token": sessionToken },
        });
        if (res.ok) {
          const data = await res.json();
          setFindingsCount(data.total || 0);
          if (showFindings) setFindings(data.findings || []);
        }
      } catch {}
    };
    poll();
    const iv = setInterval(poll, 30000);
    return () => clearInterval(iv);
  }, [host.id, sessionToken, showFindings]);

  const handleDismiss = async (id) => { await fetch(`/api/findings/${id}/dismiss`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); };
  const handleStar = async (id) => { await fetch(`/api/findings/${id}/star`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); };
  const handleResolve = async (id) => { await fetch(`/api/findings/${id}/resolve`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); };

  return (
    <div className={`bg-gray-800 rounded-lg p-4 border transition ${showFindings && findingsCount > 0 ? "border-amber-600" : "border-gray-700 hover:border-amber-500"}`}>
      <div className="flex items-center justify-between mb-3">
        <div className="flex items-center gap-2">
          <StatusDot status={host.status} />
          <h3 className="text-sm font-medium text-white">{host.name}</h3>
          <AgentTypeBadge agentType={host.agent_type} />
        </div>
        <div className="flex items-center gap-2">
          {host.ip && <span className="text-xs text-gray-500 font-mono">{host.ip}</span>}
          <button onClick={() => setShowFindings(!showFindings)}
            className={`relative text-xs px-2 py-1 rounded border transition ${showFindings ? "text-amber-400 border-amber-600 bg-amber-900/20" : "text-gray-400 hover:text-amber-400 border-gray-700 hover:border-amber-500"}`}
            title={findingsCount > 0 ? `${findingsCount} active finding${findingsCount !== 1 ? "s" : ""}` : "No findings"}>
            Findings
            {findingsCount > 0 && (
              <span className="absolute -top-1.5 -right-1.5 bg-amber-600 text-white text-[10px] font-bold rounded-full w-4 h-4 flex items-center justify-center">
                {findingsCount}
              </span>
            )}
          </button>
          <button onClick={runHealthCheck} disabled={checking}
            className="text-xs text-gray-400 hover:text-amber-400 px-2 py-1 rounded border border-gray-700 hover:border-amber-500 transition disabled:opacity-50">
            {checking ? "..." : "Check"}
          </button>
        </div>
      </div>
      <div className="flex items-center justify-around">
        <MetricGauge label="CPU" value={m.cpu || 0} />
        <MetricGauge label="Memory" value={m.memory?.percent || 0} />
        <MetricGauge label="Disk" value={m.disk?.percent || 0} />
        {m.gpu && <MetricGauge label="GPU" value={m.gpu.utilization || 0} />}
      </div>
      <div className="flex items-center justify-between mt-3 text-xs text-gray-500">
        <span>{host.uptime || ""}</span>
        {(host.model || host.ollama_model) && <span className="text-gray-600">{host.model || host.ollama_model}</span>}
      </div>
      {/* Inline findings panel */}
      {showFindings && (
        <div className="mt-3 pt-3 border-t border-gray-700">
          {findings.length === 0 ? (
            <p className="text-xs text-gray-500 text-center py-2">No active findings for this agent.</p>
          ) : (
            <div className="space-y-2 max-h-64 overflow-y-auto">
              {findings.map((f) => (
                <FindingCard key={f.id} finding={f} sessionToken={sessionToken} onDismiss={handleDismiss} onStar={handleStar} onResolve={handleResolve} />
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function FleetSubTab({ hosts, hostMetrics, sessionToken }) {
  const [healthDetail, setHealthDetail] = useState(null);

  const handleHealthCheck = (hostId, result) => {
    setHealthDetail({ hostId, result, timestamp: new Date() });
  };

  return (
    <div className="space-y-4">
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        {hosts.map((host) => (
          <HostMetricCard key={host.id} host={host} metrics={hostMetrics[host.id]}
            sessionToken={sessionToken} onHealthCheck={handleHealthCheck} />
        ))}
      </div>
      {hosts.length === 0 && (
        <div className="text-center text-gray-500 py-8">No agents registered in fleet</div>
      )}
      {healthDetail && (
        <div className="bg-gray-800 rounded-lg p-4 border border-green-700">
          <div className="flex items-center justify-between mb-2">
            <h3 className="text-sm font-semibold text-white">Health Check: {healthDetail.hostId}</h3>
            <button onClick={() => setHealthDetail(null)} className="text-gray-400 hover:text-white text-xs">Close</button>
          </div>
          <pre className="text-xs text-gray-300 font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-gray-900 rounded p-3">
            {healthDetail.result?.ok
              ? (Array.isArray(healthDetail.result.result) ? healthDetail.result.result.join("\n") : JSON.stringify(healthDetail.result.result, null, 2))
              : `Error: ${healthDetail.result?.error || "Unknown error"}`}
          </pre>
        </div>
      )}
    </div>
  );
}

function GPUSubTab({ hosts, hostMetrics }) {
  const gpuHosts = hosts.filter((h) => h.has_gpu || hostMetrics[h.id]?.gpu);
  if (gpuHosts.length === 0) {
    return <div className="text-center text-gray-500 py-8">No GPU agents in fleet</div>;
  }
  return (
    <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
      {gpuHosts.map((host) => {
        const gpu = hostMetrics[host.id]?.gpu;
        return (
          <div key={host.id} className="bg-gray-800 rounded-lg p-4 border border-gray-700">
            <div className="flex items-center gap-2 mb-4">
              <StatusDot status={host.status} />
              <h3 className="text-sm font-medium text-white">{host.name}</h3>
              <span className="text-xs bg-green-900 text-green-200 px-2 py-0.5 rounded">GPU</span>
            </div>
            {gpu ? (
              <div className="space-y-4">
                <div className="flex items-center justify-around">
                  <MetricGauge label="Utilization" value={gpu.utilization} thresholds={{ warn: 80, danger: 95 }} />
                  <div className="text-center">
                    <div className="text-2xl font-bold text-white">{gpu.vramUsed}</div>
                    <div className="text-xs text-gray-400">/ {gpu.vramTotal} MiB VRAM</div>
                    <MetricBar label="VRAM" value={gpu.vramUsed} total={gpu.vramTotal} color="bg-violet-500" />
                  </div>
                  <div className="text-center">
                    <TemperatureIndicator temp={gpu.temperature} />
                    <div className="text-xs text-gray-400 mt-1">Temp</div>
                  </div>
                </div>
              </div>
            ) : (
              <div className="text-center text-gray-500 py-4">Waiting for GPU metrics...</div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function ServicesSubTab({ hosts, hostServices, sessionToken, user }) {
  const [actionResult, setActionResult] = useState(null);
  const [acting, setActing] = useState(null);

  const handleServiceAction = async (hostname, serviceName, action) => {
    setActing(`${hostname}:${serviceName}:${action}`);
    setActionResult(null);
    try {
      const res = await fetch("/api/console/exec", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ host: hostname, tool: "manage_service", args: { name: serviceName, action } }),
      });
      if (res.ok) {
        const data = await res.json();
        const result = data.results?.[hostname];
        setActionResult({ hostname, service: serviceName, action, ok: result?.ok, message: result?.ok ? `${action} successful` : (result?.error || "Failed") });
      }
    } catch (e) { setActionResult({ hostname, service: serviceName, action, ok: false, message: e.message }); }
    finally { setActing(null); }
  };

  const canManage = hasRole(user, "fleet_ops", "admin");

  return (
    <div className="space-y-4">
      {actionResult && (
        <div className={`p-3 rounded text-sm ${actionResult.ok ? "bg-green-900 text-green-200" : "bg-red-900 text-red-200"}`}>
          [{actionResult.hostname}] {actionResult.service}: {actionResult.message}
        </div>
      )}
      {hosts.map((host) => {
        const services = hostServices[host.id] || [];
        return (
          <div key={host.id} className="bg-gray-800 rounded-lg p-4 border border-gray-700">
            <div className="flex items-center gap-2 mb-3">
              <StatusDot status={host.status} />
              <h3 className="text-sm font-medium text-white">{host.name}</h3>
              <span className="text-xs text-gray-500">{services.length} services</span>
            </div>
            {services.length > 0 ? (
              <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
                {services.map((svc) => {
                  const isRunning = svc.activeState === "running";
                  const isFailed = svc.activeState === "failed";
                  const statusColor = isFailed ? "border-red-600 bg-red-900/20" : isRunning ? "border-gray-600" : "border-gray-700 bg-gray-900/30";
                  const isActing = acting === `${host.id}:${svc.name}:restart` || acting === `${host.id}:${svc.name}:stop`;
                  return (
                    <div key={svc.name} className={`p-2 rounded border ${statusColor} flex items-center justify-between`}>
                      <div className="flex items-center gap-2 min-w-0">
                        <div className={`w-2 h-2 rounded-full flex-shrink-0 ${isFailed ? "bg-red-500" : isRunning ? "bg-green-500" : "bg-gray-500"}`} />
                        <span className="text-xs text-gray-200 truncate" title={svc.name}>{svc.name}</span>
                      </div>
                      {canManage && (
                        <div className="flex gap-1 flex-shrink-0 ml-2">
                          <button onClick={() => handleServiceAction(host.id, svc.name, "restart")} disabled={isActing}
                            className="text-xs text-amber-400 hover:text-amber-300 disabled:opacity-50" title="Restart">↻</button>
                          {isRunning && (
                            <button onClick={() => handleServiceAction(host.id, svc.name, "stop")} disabled={isActing}
                              className="text-xs text-red-400 hover:text-red-300 disabled:opacity-50" title="Stop">■</button>
                          )}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            ) : (
              <div className="text-xs text-gray-500">Loading services...</div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function AlertsSubTab({ alerts, sessionToken, user }) {
  const canAck = hasRole(user, "fleet_ops", "admin");

  const handleAcknowledge = async (alertId) => {
    try {
      await fetch(`/api/alerts/${alertId}/acknowledge`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      });
    } catch {}
  };

  return (
    <div className="space-y-3">
      {alerts.length === 0 && (
        <div className="text-center text-gray-500 py-8">No alerts</div>
      )}
      {alerts.map((alert) => (
        <div key={alert.id} className={`flex items-start gap-3 p-3 rounded border ${
          alert.acknowledged ? "bg-gray-800 border-gray-700 opacity-60" : "bg-gray-800 border-gray-600"
        }`}>
          <SeverityBadge severity={alert.severity} />
          <div className="flex-1 min-w-0">
            <p className="text-sm text-gray-100">{alert.message}</p>
            <p className="text-xs text-gray-400 mt-1">
              {alert.source_host} • {new Date(alert.timestamp).toLocaleString()}
              {alert.acknowledged && <span className="ml-2 text-green-400">✓ {alert.acknowledged_by}</span>}
            </p>
          </div>
          {canAck && !alert.acknowledged && (
            <button onClick={() => handleAcknowledge(alert.id)}
              className="text-xs text-gray-400 hover:text-green-400 px-2 py-1 rounded border border-gray-700 hover:border-green-500 transition flex-shrink-0">
              Ack
            </button>
          )}
        </div>
      ))}
    </div>
  );
}

function ActivitySubTab({ hosts, agentStatus, sessionToken, user, onChatAgent, distVersion }) {
  const canManage = hasRole(user, "fleet_ops", "admin");
  const isAdmin = hasRole(user, "admin");
  const [acting, setActing] = useState(null);
  const toast = useToast();

  const handleRestartAgent = async (hostname, service) => {
    setActing(`${hostname}:${service}`);
    try {
      await fetch("/api/console/exec", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ host: hostname, tool: "manage_service", args: { name: service, action: "restart" } }),
      });
    } catch {} finally { setActing(null); }
  };

  const handleForceUpdate = async (hostname) => {
    setActing(`${hostname}:update`);
    try {
      const res = await fetch(`/api/agent/${hostname}/update`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      });
      if (res.ok) {
        toast.success(`Update triggered for ${hostname} — agent restarting`);
        // Keep button disabled for 30s while agent downloads, deploys, and restarts
        setTimeout(() => setActing((prev) => prev === `${hostname}:update` ? null : prev), 30000);
        return;
      } else {
        const err = await res.json().catch(() => ({}));
        toast.error(err.detail || `Update failed: ${res.status}`);
      }
    } catch (e) {
      toast.error(`Update failed: ${e.message}`);
    }
    setActing(null);
  };

  // Merge host info with agent status
  const agents = hosts.map((h) => {
    const status = (agentStatus || []).find((a) => a.hostname === h.id);
    return { ...h, agentOnline: status?.status || "unknown", lastHeartbeat: status?.last_heartbeat, version: status?.version || h.version || "unknown" };
  });

  return (
    <div className="space-y-4">
      <div className="bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
        <table className="w-full text-xs">
          <thead className="bg-gray-900">
            <tr className="text-left text-gray-400">
              <th className="px-4 py-2">Agent</th>
              <th className="px-4 py-2">Type</th>
              <th className="px-4 py-2">Provider / Model</th>
              <th className="px-4 py-2">Version</th>
              <th className="px-4 py-2">Status</th>
              <th className="px-4 py-2">Last Heartbeat</th>
              {canManage && <th className="px-4 py-2 text-right">Actions</th>}
            </tr>
          </thead>
          <tbody>
            {agents.map((agent) => (
              <tr key={agent.id} className="border-t border-gray-700 hover:bg-gray-700/50">
                <td className="px-4 py-2">
                  <div className="flex items-center gap-2">
                    <StatusDot status={agent.agentOnline === "online" ? "healthy" : agent.agentOnline === "degraded" ? "degraded" : "inactive"} />
                    <span className="text-gray-200 font-medium">{agent.name}</span>
                    {agent.hostname && agent.hostname !== agent.name && <span className="text-gray-500 font-mono text-xs">{agent.hostname}</span>}
                    {agent.ip && <span className="text-gray-500 font-mono">{agent.ip}</span>}
                  </div>
                </td>
                <td className="px-4 py-2"><AgentTypeBadge agentType={agent.agent_type} /></td>
                <td className="px-4 py-2"><span className="text-gray-300">{agent.provider ? `${agent.provider}/${agent.model || "?"}` : agent.ollama_model || "—"}</span></td>
                <td className="px-4 py-2 text-gray-400 font-mono text-xs">{agent.version || "—"}</td>
                <td className="px-4 py-2"><span className={`${agent.agentOnline === "online" ? "text-green-400" : agent.agentOnline === "degraded" ? "text-amber-400" : "text-gray-500"}`}>{agent.agentOnline}</span></td>
                <td className="px-4 py-2 text-gray-400">{agent.lastHeartbeat ? new Date(agent.lastHeartbeat).toLocaleTimeString() : "—"}</td>
                {canManage && (
                  <td className="px-4 py-2 text-right">
                    <div className="flex gap-2 justify-end">
                      {onChatAgent && (
                        <button onClick={() => onChatAgent({ hostname: agent.id, agent_type: agent.agent_type, provider: agent.provider, model: agent.model })}
                          className="text-blue-400 hover:text-blue-300" title="Direct chat with agent LLM">Chat</button>
                      )}
                      {distVersion && agent.version && agent.version !== "unknown" && distVersion !== "0.0.0" && agent.version < distVersion && (
                        <button onClick={() => handleForceUpdate(agent.id)}
                          disabled={acting === `${agent.id}:update`}
                          className={`font-medium disabled:cursor-not-allowed ${acting === `${agent.id}:update` ? "text-amber-400 animate-pulse" : "text-green-400 hover:text-green-300"}`}
                          title={acting === `${agent.id}:update` ? "Update in progress..." : `Update from ${agent.version} to ${distVersion}`}>
                          {acting === `${agent.id}:update` ? "Updating…" : "Update"}
                        </button>
                      )}
                      <button onClick={() => handleRestartAgent(agent.id, "hal-mcp")}
                        disabled={acting === `${agent.id}:hal-mcp`}
                        className="text-amber-400 hover:text-amber-300 disabled:opacity-50" title="Restart MCP">↻ MCP</button>
                      <button onClick={() => handleRestartAgent(agent.id, "hal-local-agent")}
                        disabled={acting === `${agent.id}:hal-local-agent`}
                        className="text-amber-400 hover:text-amber-300 disabled:opacity-50" title="Restart Agent">↻ Agent</button>
                    </div>
                  </td>
                )}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ── Direct Agent Chat Panel ─────────────────────────────────
function AgentChatPanel({ hostname, agentType, provider, model, sessionToken, onClose }) {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [mode, setMode] = useState("agent");
  const [elapsed, setElapsed] = useState(0);
  const abortRef = useRef(null);
  const scrollRef = useRef(null);
  const inputRef = useRef(null);
  const elapsedRef = useRef(null);

  // Resizable panel
  const [panelWidth, setPanelWidth] = useState(() => {
    try { return parseInt(localStorage.getItem("hal_agent_chat_width")) || 384; } catch { return 384; }
  });
  const resizingRef = useRef(false);
  useEffect(() => {
    const onMove = (e) => {
      if (!resizingRef.current) return;
      const newW = Math.max(320, Math.min(800, window.innerWidth - e.clientX));
      setPanelWidth(newW);
    };
    const onUp = () => {
      if (resizingRef.current) {
        resizingRef.current = false;
        document.body.style.cursor = "";
        document.body.style.userSelect = "";
        try { localStorage.setItem("hal_agent_chat_width", String(panelWidth)); } catch {}
      }
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    return () => { window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", onUp); };
  }, [panelWidth]);

  // Elapsed time counter while loading
  useEffect(() => {
    if (isLoading) {
      setElapsed(0);
      elapsedRef.current = setInterval(() => setElapsed((e) => e + 1), 1000);
    } else {
      if (elapsedRef.current) clearInterval(elapsedRef.current);
      setElapsed(0);
    }
    return () => { if (elapsedRef.current) clearInterval(elapsedRef.current); };
  }, [isLoading]);

  // Auto-scroll to bottom on new messages
  useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages]);

  // Focus input on mount and when agent changes
  useEffect(() => {
    if (inputRef.current) inputRef.current.focus();
    setMessages([]);
    setInput("");
    setIsLoading(false);
  }, [hostname]);

  const handleSend = async (e) => {
    e.preventDefault();
    const text = input.trim();
    if (!text || isLoading) return;

    const userMsg = { id: Date.now(), role: "user", content: text };
    const assistantId = Date.now() + 1;
    const assistantMsg = { id: assistantId, role: "assistant", content: "" };

    setMessages((prev) => [...prev, userMsg, assistantMsg]);
    setInput("");
    setIsLoading(true);

    const controller = new AbortController();
    abortRef.current = controller;

    // Build conversation for API (all messages in order)
    const apiMessages = [...messages, userMsg].map((m) => ({ role: m.role, content: m.content }));

    try {
      const res = await fetch(`/api/agent/${hostname}/chat`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ messages: apiMessages, mode }),
        signal: controller.signal,
      });

      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || `Error: ${res.status}`);
      }

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          try {
            const data = JSON.parse(line.slice(6));
            if (data.type === "chunk" || data.type === "error") {
              setMessages((prev) =>
                prev.map((msg) =>
                  msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
                )
              );
            } else if (data.type === "done") {
              break;
            }
          } catch {}
        }
      }
    } catch (err) {
      if (err.name !== "AbortError") {
        setMessages((prev) =>
          prev.map((msg) =>
            msg.id === assistantId
              ? { ...msg, content: msg.content || `*Error: ${err.message}*` }
              : msg
          )
        );
      }
    } finally {
      setIsLoading(false);
      abortRef.current = null;
      setTimeout(() => inputRef.current?.focus(), 50);
    }
  };

  const handleStop = () => {
    if (abortRef.current) abortRef.current.abort();
  };

  const handleClear = () => {
    if (abortRef.current) abortRef.current.abort();
    setMessages([]);
    setIsLoading(false);
  };

  return (
    <div className="fixed inset-0 z-50 md:relative md:inset-auto md:z-auto w-full flex-shrink-0 border-l border-gray-700 bg-gray-900 flex flex-col h-full"
      style={{width: window.innerWidth < 768 ? '100%' : `${panelWidth}px`}}>
      {/* Resize handle — desktop only */}
      <div className="hidden md:block absolute left-0 top-0 bottom-0 w-2 cursor-col-resize hover:bg-amber-500/40 transition-colors z-10"
        style={{marginLeft: "-4px"}}
        onMouseDown={(e) => { e.preventDefault(); resizingRef.current = true; document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; }} />
      {/* Header */}
      <div className="px-3 py-2 border-b border-gray-700 space-y-1">
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2 min-w-0">
            <span className="text-gray-200 font-medium text-sm truncate">{hostname}</span>
            <AgentTypeBadge agentType={agentType} />
          </div>
          <button onClick={onClose} className="text-gray-500 hover:text-gray-300 ml-2 flex-shrink-0" title="Close">
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
          </button>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-gray-500 text-xs">{provider}/{model}</span>
          <div className="flex items-center gap-2">
            <button onClick={handleClear} className="text-xs text-gray-500 hover:text-gray-300" title="Clear conversation">Clear</button>
            <button
              onClick={() => setMode((m) => (m === "agent" ? "raw" : "agent"))}
              className={`text-xs px-2 py-0.5 rounded border transition ${
                mode === "agent"
                  ? "border-amber-600 text-amber-400 bg-amber-900/30"
                  : "border-blue-600 text-blue-400 bg-blue-900/30"
              }`}
              title={mode === "agent" ? "Using agent's configured prompt" : "No system prompt — raw LLM access"}>
              {mode === "agent" ? "Agent Prompt" : "Raw"}
            </button>
          </div>
        </div>
      </div>

      {/* Messages */}
      <div ref={scrollRef} className="flex-1 overflow-y-auto p-3 space-y-3">
        {messages.length === 0 && (
          <div className="text-center text-gray-600 text-xs mt-8">
            <p>Direct chat with <span className="text-gray-400">{hostname}</span></p>
            <p className="mt-1">{mode === "agent" ? "Agent prompt active" : "Raw mode — no system prompt"}</p>
          </div>
        )}
        {messages.filter((msg) => !(msg.role === "assistant" && !msg.content && isLoading)).map((msg) => (
          <div key={msg.id} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
            <div
              className={`max-w-[90%] md:max-w-[85%] rounded-lg px-3 py-2 text-sm ${
                msg.role === "user"
                  ? "bg-amber-700/60 text-gray-100"
                  : "bg-gray-800 text-gray-200 border border-gray-700"
              }`}>
              {msg.role === "assistant" && msg.content ? (
                <MarkdownMessage content={msg.content} />
              ) : (
                <span className="whitespace-pre-wrap">{msg.content}</span>
              )}
            </div>
          </div>
        ))}
        {isLoading && (
          <div className="flex items-center gap-2">
            <TypingIndicator visible={true} />
            {elapsed > 0 && (
              <span className="text-gray-500 text-xs">
                {elapsed}s{elapsed > 10 && provider === "ollama" ? " · CPU inference may be slow" : ""}
              </span>
            )}
          </div>
        )}
      </div>

      {/* Input */}
      <div className="border-t border-gray-700 p-3">
        <form onSubmit={handleSend} className="flex gap-2">
          <input
            ref={inputRef}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder={`Chat with ${hostname}...`}
            className="flex-1 bg-gray-800 text-gray-200 rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
            disabled={isLoading}
            autoComplete="off"
          />
          {isLoading ? (
            <button type="button" onClick={handleStop}
              className="px-3 py-2 rounded bg-red-700 text-white text-sm hover:bg-red-600 transition">Stop</button>
          ) : (
            <button type="submit" disabled={!input.trim()}
              className="px-3 py-2 rounded bg-amber-600 text-white text-sm hover:bg-amber-500 disabled:opacity-50 disabled:cursor-not-allowed transition">Send</button>
          )}
        </form>
      </div>
    </div>
  );
}

function FileAttachmentInput({ sessionToken, files, onFilesChange, disabled }) {
  const [uploading, setUploading] = useState(false);
  const fileInputRef = useRef(null);
  const toast = useToast();

  const ALLOWED_EXT = [".csv", ".json", ".xml", ".txt", ".tsv", ".yaml", ".yml", ".log"];
  const MAX_SIZE = 50 * 1024 * 1024;

  const handleFileSelect = async (e) => {
    const selected = Array.from(e.target.files || []);
    if (!selected.length) return;

    const valid = [];
    for (const file of selected) {
      const ext = "." + file.name.split(".").pop().toLowerCase();
      if (!ALLOWED_EXT.includes(ext)) {
        toast("File type " + ext + " not allowed. Allowed: " + ALLOWED_EXT.join(", "), "warning");
        continue;
      }
      if (file.size > MAX_SIZE) {
        toast(file.name + " exceeds 50MB limit.", "warning");
        continue;
      }
      valid.push(file);
    }
    if (!valid.length) return;

    setUploading(true);
    const uploaded = [...files];
    for (const file of valid) {
      const formData = new FormData();
      formData.append("file", file);
      try {
        const res = await fetch("/api/chat/upload", {
          method: "POST",
          headers: { "X-Session-Token": sessionToken },
          body: formData,
        });
        if (!res.ok) {
          const err = await res.json().catch(() => ({}));
          toast("Upload failed: " + file.name + " — " + (err.detail || res.status), "error");
          continue;
        }
        const data = await res.json();
        uploaded.push({ file_id: data.file_id, filename: data.filename, size: data.size });
      } catch (err) {
        toast("Upload error: " + file.name + " — " + err.message, "error");
      }
    }
    onFilesChange(uploaded);
    setUploading(false);
    if (fileInputRef.current) fileInputRef.current.value = "";
  };

  const removeFile = (file_id) => {
    onFilesChange(files.filter((f) => f.file_id !== file_id));
  };

  const clearAll = () => {
    onFilesChange([]);
  };

  const formatSize = (bytes) => {
    if (bytes < 1024) return bytes + "B";
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + "KB";
    return (bytes / (1024 * 1024)).toFixed(1) + "MB";
  };

  return (
    <div>
      {files.length > 0 && (
        <div className="flex flex-wrap gap-1 px-3 py-1.5 border-b border-gray-700 bg-gray-800/50">
          {files.map((f) => (
            <span key={f.file_id} className="inline-flex items-center gap-1 bg-gray-700 text-gray-200 text-xs px-2 py-1 rounded">
              <svg className="w-3 h-3 text-amber-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
              </svg>
              <span className="truncate max-w-[120px]">{f.filename}</span>
              <span className="text-gray-400">({formatSize(f.size)})</span>
              <button onClick={() => removeFile(f.file_id)} className="text-gray-400 hover:text-red-400 ml-0.5 font-bold">&times;</button>
            </span>
          ))}
          <button onClick={clearAll} className="text-xs text-gray-500 hover:text-gray-300 ml-1">Clear all</button>
        </div>
      )}
      <input
        ref={fileInputRef}
        type="file"
        multiple
        accept={ALLOWED_EXT.join(",")}
        onChange={handleFileSelect}
        className="hidden"
        disabled={disabled || uploading}
      />
      <button
        onClick={() => fileInputRef.current?.click()}
        disabled={disabled || uploading}
        className="text-gray-400 hover:text-amber-400 transition disabled:opacity-30"
        title="Attach file (CSV, JSON, XML, TXT, TSV, YAML, LOG — max 50MB)"
      >
        {uploading ? (
          <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
            <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
            <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
          </svg>
        ) : (
          <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
          </svg>
        )}
      </button>
    </div>
  );
}

// ── WorkflowCard Component ─────────────────────────────────
function WorkflowCard({ workflow, sessionToken, user }) {
  const [wf, setWf] = React.useState(workflow);
  const [approving, setApproving] = React.useState(false);
  const [promptModal, setPromptModal] = React.useState(null); // {type, stepIndex}
  const toast = useToast();

  const stepStatusColors = {
    completed: "bg-green-900/60 text-green-200",
    approved: "bg-green-900/60 text-green-200",
    in_progress: "bg-amber-900/60 text-amber-200",
    awaiting_approval: "bg-violet-900/60 text-violet-200",
    pending: "bg-gray-700/60 text-gray-400",
    failed: "bg-red-900/60 text-red-200",
    rejected: "bg-red-900/60 text-red-200",
    skipped: "bg-gray-700/60 text-gray-500",
  };

  const stepDotColors = {
    completed: "bg-green-500",
    approved: "bg-green-500",
    in_progress: "bg-amber-500 animate-pulse",
    awaiting_approval: "bg-violet-500 animate-pulse",
    pending: "bg-gray-600",
    failed: "bg-red-500",
    rejected: "bg-red-500",
    skipped: "bg-gray-600",
  };

  const wfStatusColors = {
    in_progress: "bg-amber-900/60 text-amber-200",
    completed: "bg-green-900/60 text-green-200",
    failed: "bg-red-900/60 text-red-200",
    cancelled: "bg-gray-700/60 text-gray-400",
    pending: "bg-blue-900/60 text-blue-200",
  };

  const canApproveStep = (step) => {
    if (step.status !== "awaiting_approval") return false;
    const userRoles = user?.roles || [];
    return userRoles.includes(step.required_role) || userRoles.includes("admin");
  };

  const doApprove = async (stepIndex, comment) => {
    setApproving(true);
    try {
      const res = await fetch(`/api/workflows/${wf.id}/steps/${stepIndex}/approve`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ comment: comment || "" }),
      });
      if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || "Approval failed"); }
      const data = await res.json();
      if (data.workflow) setWf(data.workflow);
      toast("Step approved successfully", "success");
    } catch (err) {
      toast("Failed to approve: " + err.message, "error");
    } finally { setApproving(false); }
  };

  const doReject = async (stepIndex, reason) => {
    setApproving(true);
    try {
      const res = await fetch(`/api/workflows/${wf.id}/steps/${stepIndex}/reject`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ comment: reason }),
      });
      if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.detail || "Rejection failed"); }
      const data = await res.json();
      if (data.workflow) setWf(data.workflow);
      toast("Step rejected", "warning");
    } catch (err) {
      toast("Failed to reject: " + err.message, "error");
    } finally { setApproving(false); }
  };

  const handleApprove = (stepIndex) => setPromptModal({ type: "approve", stepIndex });
  const handleReject = (stepIndex) => setPromptModal({ type: "reject", stepIndex });

  return (
    <div className="bg-gray-800 rounded-lg p-4 border border-gray-700 my-2">
      <div className="flex items-center justify-between mb-3">
        <div>
          <p className="font-medium text-white text-sm">{wf.title}</p>
          <p className="text-xs text-gray-500 font-mono">{wf.id}</p>
        </div>
        <span className={`px-2 py-0.5 rounded text-xs font-medium ${wfStatusColors[wf.status] || "bg-gray-700 text-gray-400"}`}>
          {(wf.status || "").replace(/_/g, " ")}
        </span>
      </div>
      <div className="space-y-1.5 ml-1 border-l-2 border-gray-600 pl-3">
        {(wf.steps || []).map((step, i) => (
          <div key={i} className="relative">
            <div className={`absolute -left-[17px] top-1.5 w-2.5 h-2.5 rounded-full border-2 border-gray-800 ${stepDotColors[step.status] || "bg-gray-600"}`} />
            <div className="flex items-center gap-2 flex-wrap">
              <span className={`text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded font-medium ${stepStatusColors[step.status] || "bg-gray-700 text-gray-400"}`}>
                {step.type}
              </span>
              <span className="text-sm text-gray-200">{step.title}</span>
              {step.status === "completed" && step.approved_by && (
                <span className="text-[10px] text-green-400">✓ {step.approved_by}</span>
              )}
              {step.status === "rejected" && (
                <span className="text-[10px] text-red-400">✗ rejected</span>
              )}
            </div>
            {canApproveStep(step) && (
              <div className="flex gap-2 mt-1.5 ml-0">
                <button disabled={approving} onClick={() => handleApprove(i)}
                  className="px-3 py-1 rounded text-xs font-medium bg-green-700 hover:bg-green-600 text-white transition disabled:opacity-50">
                  Approve
                </button>
                <button disabled={approving} onClick={() => handleReject(i)}
                  className="px-3 py-1 rounded text-xs font-medium bg-red-700 hover:bg-red-600 text-white transition disabled:opacity-50">
                  Reject
                </button>
              </div>
            )}
          </div>
        ))}
      </div>
      {wf.context && Object.keys(wf.context).length > 0 && (
        <div className="mt-2 pt-2 border-t border-gray-700 flex flex-wrap gap-2">
          {Object.entries(wf.context).map(([k, v]) => (
            <span key={k} className="text-[10px] bg-gray-700 text-gray-300 px-1.5 py-0.5 rounded">
              {k}: {v}
            </span>
          ))}
        </div>
      )}
      <PromptModal
        open={!!promptModal}
        title={promptModal?.type === "approve" ? "Approve Step" : "Reject Step"}
        message={promptModal?.type === "approve" ? "Add an optional comment for this approval." : "Provide a reason for rejecting this step."}
        placeholder={promptModal?.type === "approve" ? "Comment (optional)" : "Reason for rejection"}
        required={promptModal?.type === "reject"}
        onSubmit={(val) => {
          const { type, stepIndex } = promptModal;
          setPromptModal(null);
          if (type === "approve") doApprove(stepIndex, val);
          else doReject(stepIndex, val);
        }}
        onCancel={() => setPromptModal(null)}
      />
    </div>
  );
}

// ── Inject CSS animations if not already present ──────────
if (!document.getElementById("hal-animations")) {
  const style = document.createElement("style");
  style.id = "hal-animations";
  style.textContent = `
    @keyframes hal-spin { to { transform: rotate(360deg); } }
    @keyframes hal-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
  `;
  document.head.appendChild(style);
}

// ── ExportStatusCard Component ────────────────────────────
function ExportStatusCard({ jobId, format, sessionToken }) {
  const [status, setStatus] = React.useState("pending");
  const [filename, setFilename] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [rowCount, setRowCount] = React.useState(null);
  const intervalRef = React.useRef(null);

  React.useEffect(() => {
    let mounted = true;
    const poll = () => {
      fetch(`/api/exports/${jobId}`, {
        headers: { "X-Session-Token": sessionToken },
      })
        .then((r) => r.ok ? r.json() : Promise.reject())
        .then((data) => {
          if (!mounted) return;
          setStatus(data.status);
          setFilename(data.filename);
          setRowCount(data.row_count);
          if (data.error) setError(data.error);
          if (data.status === "completed" || data.status === "failed") {
            clearInterval(intervalRef.current);
          }
        })
        .catch(() => {});
    };
    poll();
    intervalRef.current = setInterval(poll, 3000);
    return () => { mounted = false; clearInterval(intervalRef.current); };
  }, [jobId, sessionToken]);

  const handleDownload = () => {
    fetch(`/api/exports/${jobId}/download`, {
      headers: { "X-Session-Token": sessionToken },
    })
      .then((r) => {
        if (!r.ok) throw new Error("Download failed");
        return r.blob();
      })
      .then((blob) => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = filename || `export.${format}`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      })
      .catch((err) => console.error("Download error:", err));
  };

  const formatIcons = { xlsx: "\u{1F4CA}", csv: "\u{1F4CB}", pdf: "\u{1F4C4}", docx: "\u{1F4C4}" };
  const formatLabels = { xlsx: "Excel", csv: "CSV", pdf: "PDF", docx: "Word" };
  const icon = formatIcons[format] || "\u{1F4C1}";
  const label = formatLabels[format] || format?.toUpperCase();

  return (
    <div style={{
      background: status === "pending" || status === "running"
        ? "rgba(30, 58, 95, 0.6)"
        : "rgba(30, 58, 95, 0.5)",
      border: "1px solid rgba(59, 130, 246, 0.3)",
      borderRadius: "8px",
      padding: "12px 16px",
      margin: "8px 0",
      display: "flex",
      alignItems: "center",
      gap: "12px",
      maxWidth: "400px",
      animation: status === "pending" || status === "running"
        ? "hal-pulse 2s ease-in-out infinite" : "none",
    }}>
      <span style={{ fontSize: "24px" }}>{icon}</span>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: "13px", fontWeight: 600, color: "#e2e8f0" }}>
          {label} Export
        </div>
        {status === "pending" || status === "running" ? (
          <div style={{ fontSize: "12px", color: "#94a3b8", display: "flex", alignItems: "center", gap: "6px" }}>
            <span style={{
              display: "inline-block", width: "12px", height: "12px",
              border: "2px solid rgba(148,163,184,0.3)", borderTopColor: "#60a5fa",
              borderRadius: "50%",
              animation: "hal-spin 1s linear infinite",
            }}></span>
            Preparing file...
          </div>
        ) : status === "completed" ? (
          <div style={{ fontSize: "12px", color: "#86efac" }}>
            {rowCount != null ? `${rowCount.toLocaleString()} rows` : "Ready"}
            {filename && <span style={{ color: "#94a3b8" }}> — {filename}</span>}
          </div>
        ) : (
          <div style={{ fontSize: "12px", color: "#fca5a5" }}>
            Failed{error ? `: ${error}` : ""}
          </div>
        )}
      </div>
      {status === "completed" && (
        <button
          onClick={handleDownload}
          style={{
            background: "linear-gradient(135deg, #2563eb, #1d4ed8)",
            color: "white",
            border: "none",
            borderRadius: "6px",
            padding: "6px 16px",
            fontSize: "12px",
            fontWeight: 600,
            cursor: "pointer",
            whiteSpace: "nowrap",
          }}
          onMouseOver={(e) => e.target.style.background = "linear-gradient(135deg, #3b82f6, #2563eb)"}
          onMouseOut={(e) => e.target.style.background = "linear-gradient(135deg, #2563eb, #1d4ed8)"}
        >
          Download
        </button>
      )}
    </div>
  );
}

// ── FindingCard Component ──────────────────────────────────

const SEVERITY_COLORS = {
  CRITICAL: { border: "border-red-500", bg: "bg-red-900/20", text: "text-red-400", dot: "bg-red-500" },
  HIGH: { border: "border-orange-500", bg: "bg-orange-900/20", text: "text-orange-400", dot: "bg-orange-500" },
  MEDIUM: { border: "border-amber-500", bg: "bg-amber-900/20", text: "text-amber-400", dot: "bg-amber-500" },
  LOW: { border: "border-blue-500", bg: "bg-blue-900/20", text: "text-blue-400", dot: "bg-blue-500" },
};

function FindingCard({ finding, sessionToken, onDismiss, onStar, onResolve }) {
  const [expanded, setExpanded] = useState(false);
  const sev = SEVERITY_COLORS[finding.severity] || SEVERITY_COLORS.LOW;
  const isResolved = finding.status === "resolved";
  const isRecurring = (finding.occurrence_count || 1) >= 3;

  const timeAgo = (ts) => {
    if (!ts) return "";
    const diff = Date.now() - new Date(ts).getTime();
    const mins = Math.floor(diff / 60000);
    if (mins < 1) return "just now";
    if (mins < 60) return `${mins}m ago`;
    const hrs = Math.floor(mins / 60);
    if (hrs < 24) return `${hrs}h ago`;
    return `${Math.floor(hrs / 24)}d ago`;
  };

  const handleDismiss = async () => {
    try {
      await fetch(`/api/findings/${finding.id}/dismiss`, {
        method: "POST", headers: { "X-Session-Token": sessionToken },
      });
      onDismiss?.(finding.id);
    } catch {}
  };

  const handleStar = async () => {
    try {
      await fetch(`/api/findings/${finding.id}/star`, {
        method: "POST", headers: { "X-Session-Token": sessionToken },
      });
      onStar?.(finding.id);
    } catch {}
  };

  const handleResolve = async () => {
    try {
      await fetch(`/api/findings/${finding.id}/resolve`, {
        method: "POST", headers: { "X-Session-Token": sessionToken },
      });
      onResolve?.(finding.id);
    } catch {}
  };

  return (
    <div className={`border-l-4 ${sev.border} ${sev.bg} rounded-r p-3 mb-2 w-full ${isResolved ? "opacity-50" : ""}`}>
      <div className="flex items-center justify-between gap-1 mb-1">
        <div className="flex items-center gap-2 flex-wrap flex-1 min-w-0">
          <span className={`w-2 h-2 rounded-full ${isResolved ? "bg-green-500" : sev.dot}`}></span>
          <span className={`text-xs font-semibold uppercase ${sev.text}`}>{finding.severity}</span>
          <span className="text-xs text-gray-500 bg-gray-800 px-1.5 py-0.5 rounded">{finding.category}</span>
          {(finding.occurrence_count || 1) > 1 && (
            <span className="text-xs text-amber-400 bg-amber-900/30 px-1.5 py-0.5 rounded font-mono">{"\u00d7"}{finding.occurrence_count}</span>
          )}
          {isRecurring && <span className="text-xs text-orange-400 bg-orange-900/30 px-1 py-0.5 rounded">Recurring</span>}
          {finding.escalated && <span className="text-xs text-red-400 bg-red-900/30 px-1 py-0.5 rounded">Escalated</span>}
          {isResolved && <span className="text-xs text-green-400 bg-green-900/30 px-1 py-0.5 rounded">Resolved</span>}
          {finding.watchdog_check_id && <span className="text-xs text-gray-500" title="Automated watchdog check">{"\u23f1"}</span>}
        </div>
        <div className="flex items-center gap-0.5 flex-shrink-0">
          <button onClick={handleStar} className="text-gray-500 hover:text-amber-400 p-0.5" title={finding.starred ? "Unstar" : "Star"} aria-label={finding.starred ? "Unstar finding" : "Star finding"}>
            {finding.starred ? "\u2605" : "\u2606"}
          </button>
          {!isResolved && (
            <button onClick={handleResolve} className="text-gray-500 hover:text-green-400 p-0.5" title="Resolve" aria-label="Resolve finding">{"\u2713"}</button>
          )}
          <button onClick={handleDismiss} className="text-gray-500 hover:text-red-400 p-0.5" title="Dismiss" aria-label="Dismiss finding">
            {"\u2715"}
          </button>
        </div>
      </div>
      <p className={`text-sm leading-snug ${isResolved ? "text-gray-400 line-through" : "text-gray-200"}`}>{finding.finding}</p>
      <div className="flex items-center gap-3 mt-1.5 text-xs text-gray-500">
        <span>{timeAgo(finding.last_seen || finding.timestamp)}</span>
        {finding.first_seen && finding.first_seen !== finding.last_seen && (
          <span>First: {timeAgo(finding.first_seen)}</span>
        )}
        {finding.source_host && <span>{finding.source_host}</span>}
        {isResolved && finding.resolved_at && <span>Resolved {timeAgo(finding.resolved_at)}</span>}
      </div>
      {finding.details && (
        <button onClick={() => setExpanded(!expanded)} className="text-xs text-gray-500 hover:text-gray-300 mt-1.5 underline">
          {expanded ? "Hide details" : "Show details"}
        </button>
      )}
      {expanded && finding.details && (() => {
        const lines = finding.details.split("\n");
        // Detect pipe-delimited table: header row with |, separator row with -+-
        const sepIdx = lines.findIndex(l => /^[-+]+$/.test(l.replace(/\s/g, "")));
        if (sepIdx > 0 && lines[sepIdx - 1].includes("|")) {
          const headers = lines[sepIdx - 1].split("|").map(h => h.trim()).filter(Boolean);
          const dataRows = lines.slice(sepIdx + 1).filter(l => l.includes("|")).map(l => l.split("|").map(c => c.trim()).filter(Boolean));
          const footer = lines.slice(sepIdx + 1).filter(l => !l.includes("|") && l.trim()).join("\n");
          return (
            <div className="mt-2 overflow-x-auto max-w-full" style={{scrollbarWidth: "thin"}}>
              <table className="text-xs border-collapse min-w-max">
                <thead>
                  <tr>{headers.map((h, i) => <th key={i} className="text-left text-gray-400 bg-gray-800 px-2 py-1 border-b border-gray-700 font-medium">{h}</th>)}</tr>
                </thead>
                <tbody>
                  {dataRows.map((row, ri) => (
                    <tr key={ri} className={ri % 2 === 0 ? "bg-gray-900/30" : "bg-gray-900/60"}>
                      {row.map((cell, ci) => <td key={ci} className="px-2 py-1 text-gray-300 border-b border-gray-800 whitespace-nowrap">{cell}</td>)}
                    </tr>
                  ))}
                </tbody>
              </table>
              {footer && <p className="text-xs text-gray-500 mt-1">{footer}</p>}
            </div>
          );
        }
        return <pre className="mt-2 text-xs text-gray-400 bg-gray-900/50 p-2 rounded overflow-x-auto whitespace-pre" style={{scrollbarWidth: "thin"}}>{finding.details}</pre>;
      })()}
    </div>
  );
}

// ── FindingsSidebar Component ─────────────────────────────

function FindingsSidebar({ tabId, sessionToken, isOpen, onToggle }) {
  const [findings, setFindings] = useState([]);
  const [total, setTotal] = useState(0);
  const [statusFilter, setStatusFilter] = useState("active");
  const [severityFilter, setSeverityFilter] = useState("");
  const knownIdsRef = useRef(new Set());
  const toast = useToast();
  const [sidebarWidth, setSidebarWidth] = useState(() => {
    try { const w = parseInt(localStorage.getItem("hal-findings-width")); return w >= 350 && w <= 900 ? w : 480; } catch { return 480; }
  });
  const resizingRef = useRef(false);

  // Persist width to localStorage
  useEffect(() => {
    try { localStorage.setItem("hal-findings-width", String(sidebarWidth)); } catch {}
  }, [sidebarWidth]);

  useEffect(() => {
    const handleMouseMove = (e) => {
      if (!resizingRef.current) return;
      const newWidth = window.innerWidth - e.clientX;
      setSidebarWidth(Math.max(350, Math.min(900, newWidth)));
    };
    const handleMouseUp = () => { resizingRef.current = false; document.body.style.cursor = ""; document.body.style.userSelect = ""; };
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);
    return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); };
  }, []);

  const fetchFindings = () => {
    if (!sessionToken || !tabId) return;
    const statusParam = statusFilter === "all" ? "" : `&status=${statusFilter}`;
    const sevParam = severityFilter ? `&severity=${severityFilter}` : "";
    fetch(`/api/findings?tab_id=${tabId}&limit=50${statusParam}${sevParam}`, {
      headers: { "X-Session-Token": sessionToken },
    })
      .then((r) => r.ok ? r.json() : Promise.reject())
      .then((data) => {
        const newFindings = data.findings || [];
        setFindings(newFindings);
        setTotal(data.total || 0);
        // Toast for new findings
        newFindings.forEach((f) => {
          if (f.is_new && !knownIdsRef.current.has(f.id)) {
            toast(`New finding: ${f.finding.substring(0, 80)}`, f.severity === "CRITICAL" || f.severity === "HIGH" ? "error" : "warning", 6000);
          }
        });
        knownIdsRef.current = new Set(newFindings.map((f) => f.id));
      })
      .catch(() => {});
  };

  useEffect(() => {
    if (!isOpen) return;
    fetchFindings();
    const interval = setInterval(fetchFindings, 30000);
    return () => clearInterval(interval);
  }, [sessionToken, tabId, isOpen, statusFilter, severityFilter]);

  const handleDismiss = (id) => setFindings((prev) => prev.filter((f) => f.id !== id));
  const handleStar = (id) => setFindings((prev) => prev.map((f) => f.id === id ? { ...f, starred: !f.starred } : f));
  const handleResolve = (id) => setFindings((prev) => prev.map((f) => f.id === id ? { ...f, status: "resolved" } : f));

  if (!isOpen) return null;

  const filterBtn = (label, value) => (
    <button onClick={() => setStatusFilter(value)}
      className={`px-2 py-0.5 text-xs rounded ${statusFilter === value ? "bg-amber-600 text-white" : "bg-gray-800 text-gray-400 hover:bg-gray-700"}`}>
      {label}
    </button>
  );

  return (
    <div className="fixed inset-0 z-40 md:relative md:inset-auto md:z-auto flex-shrink-0 bg-gray-900 flex flex-col h-full relative border-l border-gray-700" style={{width: window.innerWidth < 768 ? '100%' : `${sidebarWidth}px`}}>
      {/* Resize handle — desktop only */}
      <div className="hidden md:block absolute left-0 top-0 bottom-0 w-2 cursor-col-resize hover:bg-amber-500/40 transition-colors z-10"
        style={{marginLeft: "-4px"}}
        onMouseDown={(e) => { e.preventDefault(); resizingRef.current = true; document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; }} />
      <div className="flex items-center justify-between px-3 py-2 border-b border-gray-700">
        <div className="flex items-center gap-2">
          <span className="text-sm font-semibold text-gray-300">Findings</span>
          {total > 0 && (
            <span className="text-xs bg-amber-600 text-white px-1.5 py-0.5 rounded-full">{total}</span>
          )}
        </div>
        <button onClick={onToggle} className="text-gray-500 hover:text-gray-300 text-lg" aria-label="Close findings">{"\u2715"}</button>
      </div>
      <div className="flex gap-1 px-3 py-2 border-b border-gray-800">
        {filterBtn("Active", "active")}
        {filterBtn("Resolved", "resolved")}
        {filterBtn("All", "all")}
      </div>
      <div className="flex gap-1 px-3 py-1.5 border-b border-gray-800">
        <span className="text-[10px] text-gray-500 mr-0.5 self-center">Sev:</span>
        {[{ k: "", l: "All" }, { k: "CRITICAL", l: "Crit", c: "red" }, { k: "HIGH", l: "High", c: "orange" }, { k: "MEDIUM", l: "Med", c: "amber" }, { k: "LOW", l: "Low", c: "blue" }].map((b) => (
          <button key={b.k} onClick={() => setSeverityFilter(b.k)}
            className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${severityFilter === b.k
              ? (b.c ? `bg-${b.c}-900/40 text-${b.c}-400 ring-1 ring-${b.c}-500` : "bg-gray-700 text-gray-200")
              : "text-gray-500 hover:text-gray-300"}`}>{b.l}</button>
        ))}
      </div>
      <div className="flex-1 overflow-y-auto p-3">
        {findings.length === 0 ? (
          <div className="text-center text-gray-600 text-sm mt-8">
            <div className="text-3xl mb-2">{"\uD83D\uDD0D"}</div>
            {statusFilter === "active" ? "No active findings" : statusFilter === "resolved" ? "No resolved findings" : "No findings yet"}
          </div>
        ) : (
          findings.map((f) => (
            <FindingCard key={f.id} finding={f} sessionToken={sessionToken} onDismiss={handleDismiss} onStar={handleStar} onResolve={handleResolve} />
          ))
        )}
      </div>
    </div>
  );
}

// ── Insights Tab ──────────────────────────────────────────
// Consolidated findings view — all findings from business tabs + infrastructure agents

function InsightsTab({ sessionToken, user }) {
  const [findings, setFindings] = useState([]);
  const [total, setTotal] = useState(0);
  const [statusFilter, setStatusFilter] = useState("active");
  const [severityFilter, setSeverityFilter] = useState("");
  const [loading, setLoading] = useState(true);
  const lastFindingIdsRef = useRef(new Set());

  const fetchFindings = useCallback(async () => {
    try {
      const params = new URLSearchParams({ limit: "100" });
      if (statusFilter) params.set("status", statusFilter);
      if (severityFilter) params.set("severity", severityFilter);
      const res = await fetch(`/api/findings?${params}`, { headers: { "X-Session-Token": sessionToken } });
      if (!res.ok) return;
      const data = await res.json();
      const newFindings = data.findings || [];
      // Toast for new HIGH/CRITICAL findings
      const currentIds = new Set(newFindings.map((f) => f.id));
      if (lastFindingIdsRef.current.size > 0) {
        newFindings.forEach((f) => {
          if (f.is_new && !lastFindingIdsRef.current.has(f.id) && (f.severity === "CRITICAL" || f.severity === "HIGH")) {
            if (typeof toast !== "undefined") toast.error(`New ${f.severity}: ${f.finding.slice(0, 80)}`);
          }
        });
      }
      lastFindingIdsRef.current = currentIds;
      setFindings(newFindings);
      setTotal(data.total || 0);
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, [sessionToken, statusFilter, severityFilter]);

  useEffect(() => { fetchFindings(); const iv = setInterval(fetchFindings, 30000); return () => clearInterval(iv); }, [fetchFindings]);

  const handleDismiss = async (id) => {
    await fetch(`/api/findings/${id}/dismiss`, { method: "POST", headers: { "X-Session-Token": sessionToken } });
    fetchFindings();
  };
  const handleStar = async (id) => {
    await fetch(`/api/findings/${id}/star`, { method: "POST", headers: { "X-Session-Token": sessionToken } });
    fetchFindings();
  };
  const handleResolve = async (id) => {
    await fetch(`/api/findings/${id}/resolve`, { method: "POST", headers: { "X-Session-Token": sessionToken } });
    fetchFindings();
  };

  // Summary counts
  const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
  findings.forEach((f) => { if (counts[f.severity] !== undefined) counts[f.severity]++; });

  const sevBtns = [
    { key: "", label: "All" },
    { key: "CRITICAL", label: "Critical", color: "red" },
    { key: "HIGH", label: "High", color: "orange" },
    { key: "MEDIUM", label: "Medium", color: "amber" },
    { key: "LOW", label: "Low", color: "blue" },
  ];
  const statusBtns = [
    { key: "active", label: "Active" },
    { key: "resolved", label: "Resolved" },
    { key: "", label: "All" },
  ];

  return (
    <div className="flex flex-col h-full bg-gray-900">
      {/* Header */}
      <div className="px-6 py-4 border-b border-gray-700">
        <div className="flex items-center justify-between mb-3">
          <h2 className="text-xl font-semibold text-gray-100">Insights</h2>
          <div className="flex items-center gap-3 text-sm">
            {counts.CRITICAL > 0 && <span className="text-red-400 font-medium">{counts.CRITICAL} Critical</span>}
            {counts.HIGH > 0 && <span className="text-orange-400 font-medium">{counts.HIGH} High</span>}
            {counts.MEDIUM > 0 && <span className="text-amber-400 font-medium">{counts.MEDIUM} Medium</span>}
            {counts.LOW > 0 && <span className="text-blue-400 font-medium">{counts.LOW} Low</span>}
            {total === 0 && !loading && <span className="text-gray-500">No findings</span>}
          </div>
        </div>
        {/* Filters */}
        <div className="flex items-center gap-4">
          <div className="flex items-center gap-1">
            <span className="text-xs text-gray-500 mr-1">Severity:</span>
            {sevBtns.map((b) => (
              <button key={b.key} onClick={() => setSeverityFilter(b.key)}
                className={`px-2 py-0.5 rounded text-xs font-medium transition-colors ${severityFilter === b.key
                  ? (b.color ? `bg-${b.color}-900/40 text-${b.color}-400 ring-1 ring-${b.color}-500` : "bg-gray-700 text-gray-200")
                  : "text-gray-400 hover:text-gray-200"}`}>
                {b.label}
              </button>
            ))}
          </div>
          <div className="flex items-center gap-1">
            <span className="text-xs text-gray-500 mr-1">Status:</span>
            {statusBtns.map((b) => (
              <button key={b.key} onClick={() => setStatusFilter(b.key)}
                className={`px-2 py-0.5 rounded text-xs font-medium transition-colors ${statusFilter === b.key ? "bg-gray-700 text-gray-200" : "text-gray-400 hover:text-gray-200"}`}>
                {b.label}
              </button>
            ))}
          </div>
          <span className="text-xs text-gray-500 ml-auto">{total} total</span>
        </div>
      </div>
      {/* Findings list */}
      <div className="flex-1 overflow-y-auto p-4 space-y-3">
        {loading && <p className="text-center text-gray-500 mt-8">Loading...</p>}
        {!loading && findings.length === 0 && (
          <div className="text-center text-gray-500 mt-16">
            <div className="text-4xl mb-3">&#x2714;&#xFE0F;</div>
            <p className="text-lg">All clear</p>
            <p className="text-sm mt-1">No findings match the current filters.</p>
          </div>
        )}
        {findings.map((f) => (
          <FindingCard key={f.id} finding={f} sessionToken={sessionToken} onDismiss={handleDismiss} onStar={handleStar} onResolve={handleResolve} />
        ))}
      </div>
    </div>
  );
}

// ── Agent Findings Sub-Tab ───────────────────────────────────
// Per-agent infrastructure findings in the Agents/Dashboard tab

function AgentFindingsSubTab({ sessionToken }) {
  const [findings, setFindings] = useState([]);
  const [severityFilter, setSeverityFilter] = useState("");
  const [statusFilter, setStatusFilter] = useState("active");

  const fetchFindings = useCallback(async () => {
    try {
      const params = new URLSearchParams({ category: "infrastructure", limit: "100" });
      if (statusFilter) params.set("status", statusFilter);
      if (severityFilter) params.set("severity", severityFilter);
      const res = await fetch(`/api/findings?${params}`, { headers: { "X-Session-Token": sessionToken } });
      if (!res.ok) return;
      const data = await res.json();
      setFindings(data.findings || []);
    } catch (e) { /* ignore */ }
  }, [sessionToken, statusFilter, severityFilter]);

  useEffect(() => { fetchFindings(); const iv = setInterval(fetchFindings, 30000); return () => clearInterval(iv); }, [fetchFindings]);

  const handleDismiss = async (id) => { await fetch(`/api/findings/${id}/dismiss`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); fetchFindings(); };
  const handleStar = async (id) => { await fetch(`/api/findings/${id}/star`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); fetchFindings(); };
  const handleResolve = async (id) => { await fetch(`/api/findings/${id}/resolve`, { method: "POST", headers: { "X-Session-Token": sessionToken } }); fetchFindings(); };

  // Group by source_host
  const grouped = {};
  findings.forEach((f) => {
    const host = f.source_host || "unknown";
    if (!grouped[host]) grouped[host] = [];
    grouped[host].push(f);
  });

  const sevBtns = [
    { key: "", label: "All" },
    { key: "CRITICAL", label: "Critical", color: "red" },
    { key: "HIGH", label: "High", color: "orange" },
    { key: "MEDIUM", label: "Medium", color: "amber" },
    { key: "LOW", label: "Low", color: "blue" },
  ];

  return (
    <div>
      <div className="flex items-center gap-4 mb-4">
        <div className="flex items-center gap-1">
          <span className="text-xs text-gray-500 mr-1">Severity:</span>
          {sevBtns.map((b) => (
            <button key={b.key} onClick={() => setSeverityFilter(b.key)}
              className={`px-2 py-0.5 rounded text-xs font-medium ${severityFilter === b.key
                ? (b.color ? `bg-${b.color}-900/40 text-${b.color}-400 ring-1 ring-${b.color}-500` : "bg-gray-700 text-gray-200")
                : "text-gray-400 hover:text-gray-200"}`}>
              {b.label}
            </button>
          ))}
        </div>
        <div className="flex items-center gap-1">
          <span className="text-xs text-gray-500 mr-1">Status:</span>
          {["active", "resolved", ""].map((s) => (
            <button key={s} onClick={() => setStatusFilter(s)}
              className={`px-2 py-0.5 rounded text-xs font-medium ${statusFilter === s ? "bg-gray-700 text-gray-200" : "text-gray-400 hover:text-gray-200"}`}>
              {s || "All"}
            </button>
          ))}
        </div>
      </div>
      {Object.keys(grouped).length === 0 && (
        <p className="text-gray-500 text-center mt-8">No infrastructure findings.</p>
      )}
      {Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b)).map(([host, hostFindings]) => (
        <div key={host} className="mb-6">
          <h3 className="text-sm font-medium text-gray-300 mb-2 flex items-center gap-2">
            <span className="w-2 h-2 rounded-full bg-green-500 inline-block"></span>
            {host}
            <span className="text-gray-500 font-normal">({hostFindings.length})</span>
          </h3>
          <div className="space-y-2 ml-4">
            {hostFindings.map((f) => (
              <FindingCard key={f.id} finding={f} sessionToken={sessionToken} onDismiss={handleDismiss} onStar={handleStar} onResolve={handleResolve} />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

// ── Slash Command Helpers ──────────────────────────────────

const SLASH_COMMANDS = [
  { cmd: "/clear", desc: "Reset conversation" },
  { cmd: "/memory", desc: "View/manage saved notes" },
  { cmd: "/status", desc: "Context and system status" },
  { cmd: "/summarize", desc: "Compress conversation history" },
];

function parseSlashCommand(input) {
  const trimmed = input.trim();
  if (!trimmed.startsWith("/")) return null;
  const spaceIdx = trimmed.indexOf(" ");
  let command, args;
  if (spaceIdx === -1) {
    command = trimmed.toLowerCase();
    args = "";
  } else {
    command = trimmed.slice(0, spaceIdx).toLowerCase();
    args = trimmed.slice(spaceIdx + 1).trim();
  }
  // Validate it's a known command root
  const knownRoots = SLASH_COMMANDS.map(c => c.cmd);
  if (!knownRoots.includes(command)) return null;
  return { command, args };
}

function SlashCommandMenu({ input, onSelect }) {
  if (!input.startsWith("/") || input.includes(" ")) return null;
  const filter = input.toLowerCase();
  const matches = SLASH_COMMANDS.filter(c => c.cmd.startsWith(filter));
  if (matches.length === 0) return null;
  return (
    <div className="absolute bottom-full left-0 mb-1 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-lg overflow-hidden z-50" role="listbox" aria-label="Slash commands">
      {matches.map(c => (
        <button
          key={c.cmd}
          role="option"
          onClick={() => onSelect(c.cmd)}
          className="w-full text-left px-3 py-2 hover:bg-gray-700 transition flex items-center gap-2 min-h-[44px]"
        >
          <span className="text-amber-400 font-mono text-sm">{c.cmd}</span>
          <span className="text-gray-400 text-xs">{c.desc}</span>
        </button>
      ))}
    </div>
  );
}

function formatCommandResponse(data, setMessages, user, sessionToken) {
  const sysMsg = (content) => ({
    id: Date.now(),
    role: "system",
    content,
    timestamp: new Date(),
    isCommand: true,
  });

  if (data.type === "clear") {
    setMessages([{
      id: 1, role: "assistant",
      content: `Chat cleared. What would you like to do, ${user?.display_name || "User"}?`,
      timestamp: new Date(),
    }]);
    return;
  }

  if (data.type === "status") {
    const lines = [
      `Context: ${data.context_used}/${data.context_limit} messages (${data.history_total} total stored)`,
      `Memories: ${data.memory_count} saved notes`,
      `Active workflows: ${data.active_workflows}`,
      `Connected agents: ${data.connected_agents}`,
    ];
    setMessages(prev => [...prev, sysMsg(lines.join("\n"))]);
    return;
  }

  if (data.type === "memory_list") {
    if (!data.memories || data.memories.length === 0) {
      setMessages(prev => [...prev, sysMsg("No memories saved yet. Use /memory add <note> to save one.")]);
      return;
    }
    const lines = data.memories.map(m =>
      `[${m.category}] ${m.content}  (id: ${m.id}, ${m.source})`
    );
    setMessages(prev => [...prev, sysMsg("Saved memories:\n" + lines.join("\n"))]);
    return;
  }

  if (data.type === "memory_added") {
    setMessages(prev => [...prev, sysMsg(`Saved: ${data.memory.content}`)]);
    return;
  }

  if (data.type === "memory_removed") {
    setMessages(prev => [...prev, sysMsg(data.message)]);
    return;
  }

  if (data.type === "summarized") {
    setMessages(prev => [
      sysMsg(`Conversation compressed. ${data.context_freed} messages freed.`),
    ]);
    return;
  }

  if (data.type === "error") {
    setMessages(prev => [...prev, sysMsg(`Error: ${data.message}`)]);
    return;
  }
}

function MainChat({ user, sessionToken }) {
  const isAdmin = user?.roles?.includes("admin") || user?.roles?.includes("fleet_ops");
  const toast = useToast();
  const [showClearConfirm, setShowClearConfirm] = useState(false);
  const [messages, setMessages] = useState([
    {
      id: 1,
      role: "assistant",
      content: `Welcome, ${user?.display_name || "User"}! I'm HAL, your business intelligence assistant. I can help you manage your fleet, process orders, coordinate services, and more. What would you like to do?`,
      timestamp: new Date(),
    },
  ]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [attachedFiles, setAttachedFiles] = useState([]);
  const messagesEndRef = useRef(null);
  const abortRef = useRef(null);
  const chatInputRef = useRef(null);
  useEffect(() => { setTimeout(() => chatInputRef.current?.focus(), 100); }, []);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  // Fetch chat history on mount
  useEffect(() => {
    const fetchChatHistory = async () => {
      if (!sessionToken) return;

      try {
        const response = await fetch("/api/chat/history", {
          headers: { "X-Session-Token": sessionToken },
        });

        if (response.ok) {
          const data = await response.json();
          if (data.messages && data.messages.length > 0) {
            const parsedMessages = data.messages.map((msg, idx) => ({
              id: idx + 1,
              role: msg.role,
              content: msg.content,
              timestamp: new Date(msg.timestamp || Date.now()),
              workflows: msg.workflows || undefined,
              exports: msg.exports || undefined,
            }));
            setMessages(parsedMessages);
          }
        }
      } catch (err) {
        console.error("Failed to fetch chat history:", err);
      }
    };

    fetchChatHistory();
  }, [sessionToken]);

  const handleSend = async () => {
    if ((!input.trim() && !attachedFiles.length) || isLoading) return;

    // Check for slash commands
    const parsed = parseSlashCommand(input);
    if (parsed) {
      const cmdMsg = { id: messages.length + 1, role: "user", content: input, timestamp: new Date(), isCommand: true };
      setMessages(prev => [...prev, cmdMsg]);
      setInput("");
      setIsLoading(true);
      try {
        const res = await fetch("/api/chat/command", {
          method: "POST",
          headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
          body: JSON.stringify({ command: parsed.command, args: parsed.args }),
        });
        const data = await res.json();
        formatCommandResponse(data, setMessages, user, sessionToken);
      } catch (err) {
        setMessages(prev => [...prev, { id: Date.now(), role: "system", content: `Command failed: ${err.message}`, timestamp: new Date(), isCommand: true }]);
      } finally {
        setIsLoading(false);
        setTimeout(() => chatInputRef.current?.focus(), 50);
      }
      return;
    }

    const userMessage = {
      id: messages.length + 1,
      role: "user",
      content: input || "[Attached file(s)]",
      timestamp: new Date(),
      files: attachedFiles.length > 0 ? [...attachedFiles] : undefined,
    };

    const fileIds = attachedFiles.map((f) => f.file_id);
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setAttachedFiles([]);
    setIsLoading(true);

    const assistantId = messages.length + 2;
    const controller = new AbortController();
    abortRef.current = controller;

    try {
      const res = await fetch("/api/chat/send", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Session-Token": sessionToken,
        },
        body: JSON.stringify({ content: input, file_ids: fileIds.length ? fileIds : undefined }),
        signal: controller.signal,
      });

      if (!res.ok) {
        const errBody = await res.json().catch(() => ({}));
        throw new Error(errBody.detail || `API error: ${res.status}`);
      }

      // Create placeholder assistant message
      setMessages((prev) => [...prev, { id: assistantId, role: "assistant", content: "", timestamp: new Date() }]);

      // Stream SSE response
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          try {
            const data = JSON.parse(line.slice(6));
            // Tool execution events — only show to admin/fleet_ops users
            if (data.type === "tool_start" || data.type === "tool_result") {
              if (isAdmin) {
                setMessages((prev) =>
                  prev.map((msg) =>
                    msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
                  )
                );
              }
            } else if (data.type === "chunk" || data.type === "error") {
              setMessages((prev) =>
                prev.map((msg) =>
                  msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
                )
              );
            } else if (data.type === "workflow_update" && data.workflow) {
              setMessages((prev) =>
                prev.map((msg) =>
                  msg.id === assistantId
                    ? { ...msg, workflows: [...(msg.workflows || []), data.workflow] }
                    : msg
                )
              );
            } else if (data.type === "export_started" && data.job_id) {
              setMessages((prev) =>
                prev.map((msg) =>
                  msg.id === assistantId
                    ? { ...msg, exports: [...(msg.exports || []), { job_id: data.job_id, format: data.format }] }
                    : msg
                )
              );
            } else if (data.type === "done") {
              break;
            }
          } catch {}
        }
      }
    } catch (err) {
      if (err.name === "AbortError") {
        // User cancelled — keep what was streamed so far
        setMessages((prev) =>
          prev.map((msg) =>
            msg.id === assistantId && !msg.content ? { ...msg, content: "(cancelled)" } : msg
          )
        );
      } else {
        setMessages((prev) => [...prev, {
          id: assistantId,
          role: "assistant",
          content: `Sorry, I encountered an error: ${err.message}`,
          timestamp: new Date(),
        }]);
      }
    } finally {
      abortRef.current = null;
      setIsLoading(false);
      setTimeout(() => chatInputRef.current?.focus(), 50);
    }
  };

  const handleStop = () => {
    if (abortRef.current) abortRef.current.abort();
  };

  const handleClearHistory = async () => {
    if (sessionToken) {
      try {
        await fetch("/api/chat/history", {
          method: "DELETE",
          headers: { "X-Session-Token": sessionToken },
        });
      } catch (err) {
        console.error("Failed to clear chat history:", err);
      }
    }

    setMessages([
      {
        id: 1,
        role: "assistant",
        content: `Welcome, ${user?.display_name || "User"}! I'm HAL, your business intelligence assistant. I can help you manage your fleet, process orders, coordinate services, and more. What would you like to do?`,
        timestamp: new Date(),
      },
    ]);
  };

  return (
    <div className="flex flex-col h-full bg-gray-900">
      <ConfirmModal
        open={showClearConfirm}
        title="Clear Chat History"
        message="Are you sure you want to clear your chat history? This cannot be undone."
        confirmText="Clear History"
        danger={true}
        onConfirm={() => { setShowClearConfirm(false); handleClearHistory(); }}
        onCancel={() => setShowClearConfirm(false)}
      />
      <div className="border-b border-gray-700 px-4 py-2 flex items-center justify-between">
        <h3 className="text-sm font-medium text-gray-300">Chat with HAL</h3>
        <button
          onClick={() => setShowClearConfirm(true)}
          className="text-xs text-gray-400 hover:text-gray-200 transition"
        >
          Clear History
        </button>
      </div>

      <div className="flex-1 overflow-y-auto p-2 sm:p-3 md:p-4 space-y-4">
        {messages.filter((msg) => {
          // Hide empty assistant messages (typing indicator covers this state)
          const visibleContent = isAdmin ? (msg.content || "").trim() : stripToolLines(msg.content);
          if (msg.role === "assistant" && !visibleContent && !(msg.workflows?.length) && !(msg.exports?.length)) return false;
          return true;
        }).map((msg) => (
          <div
            key={msg.id}
            className={`flex ${msg.role === "system" || msg.isCommand && msg.role !== "user" ? "justify-center" : msg.role === "user" ? "justify-end" : "justify-start"}`}
          >
            <div
              className={`${msg.role === "user" ? "max-w-xs lg:max-w-md" : "max-w-2xl"} px-4 py-2 rounded-lg ${
                msg.role === "system" || (msg.isCommand && msg.role !== "user")
                  ? "bg-gray-700/50 text-gray-300 border border-gray-600 text-xs"
                  : msg.role === "user"
                    ? "bg-amber-600 text-white rounded-br-none"
                    : "bg-gray-800 text-gray-100 border border-gray-700 rounded-bl-none"
              }`}
            >
              {msg.role === "user"
                ? <p className="text-sm whitespace-pre-wrap">{msg.content}</p>
                : <MarkdownMessage content={isAdmin ? (msg.content || "").trimStart() : stripToolLines(msg.content)} />
              }
              {msg.files && msg.files.length > 0 && (
                <div className="flex flex-wrap gap-1 mt-1">
                  {msg.files.map((f) => (
                    <span key={f.file_id} className="inline-flex items-center gap-1 bg-black/20 text-xs px-1.5 py-0.5 rounded">
                      <svg className="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
                      </svg>
                      {f.filename}
                    </span>
                  ))}
                </div>
              )}
              {msg.workflows && msg.workflows.length > 0 && msg.workflows.map((wf) => (
                <WorkflowCard key={wf.id} workflow={wf} sessionToken={sessionToken} user={user} />
              ))}
              {msg.exports && msg.exports.length > 0 && msg.exports.map((exp) => (
                <ExportStatusCard key={exp.job_id} jobId={exp.job_id} format={exp.format} sessionToken={sessionToken} />
              ))}
              <p className="text-xs mt-1 opacity-70">{msg.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</p>
            </div>
          </div>
        ))}
        <TypingIndicator visible={isLoading} />
        <div ref={messagesEndRef} />
      </div>

      <div className="border-t border-gray-700 p-4">
        <FileAttachmentInput sessionToken={sessionToken} files={attachedFiles} onFilesChange={setAttachedFiles} disabled={isLoading} />
        <div className="flex gap-2 mt-1 relative">
          <SlashCommandMenu input={input} onSelect={(cmd) => setInput(cmd + " ")} />
          <input
            ref={chatInputRef}
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={(e) => e.key === "Enter" && !isLoading && handleSend()}
            placeholder={attachedFiles.length > 0 ? "Describe what to do with the files..." : "Ask HAL anything... (/ for commands)"}
            disabled={isLoading}
            className="flex-1 bg-gray-800 text-white rounded px-3 py-2 text-sm border border-gray-700 focus:border-amber-500 focus:outline-none disabled:opacity-50"
          />
          {isLoading ? (
            <button
              onClick={handleStop}
              className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded text-sm font-medium transition"
            >
              Stop
            </button>
          ) : (
            <button
              onClick={handleSend}
              disabled={!input.trim() && !attachedFiles.length}
              className="bg-amber-600 hover:bg-amber-700 text-white px-4 py-2 rounded text-sm font-medium transition disabled:opacity-50"
            >
              Send
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function TabChatPanel({ tabId, tabLabel, user, sessionToken, accessLevel }) {
  const isAdmin = user?.roles?.includes("admin") || user?.roles?.includes("fleet_ops");
  const [isOpen, setIsOpen] = useState(false);
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [attachedFiles, setAttachedFiles] = useState([]);
  const messagesEndRef = useRef(null);
  const abortRef = useRef(null);
  const chatInputRef = useRef(null);
  useEffect(() => { if (isOpen) setTimeout(() => chatInputRef.current?.focus(), 100); }, [isOpen]);

  // Fetch tab-specific chat history when opened
  useEffect(() => {
    if (!sessionToken || !isOpen) return;
    fetch(`/api/chat/history?tab=${tabId}`, {
      headers: { "X-Session-Token": sessionToken },
    })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => {
        if (data.messages && data.messages.length > 0) {
          setMessages(data.messages.map((msg, idx) => ({
            id: idx + 1, role: msg.role, content: msg.content,
            timestamp: new Date(msg.timestamp || Date.now()),
          })));
        } else {
          setMessages([{
            id: 1, role: "assistant",
            content: `Hi ${user?.display_name || "there"}! I'm HAL, ready to help with ${tabLabel}.${accessLevel === "restricted" ? " (You have read-only access.)" : ""} What do you need?`,
            timestamp: new Date(),
          }]);
        }
      })
      .catch(() => {
        setMessages([{
          id: 1, role: "assistant",
          content: `Hi! I'm HAL, assisting with ${tabLabel}. How can I help?`,
          timestamp: new Date(),
        }]);
      });
  }, [sessionToken, isOpen, tabId]);

  useEffect(() => {
    if (isOpen) messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages, isOpen]);

  const handleSend = async () => {
    if ((!input.trim() && !attachedFiles.length) || isLoading) return;

    // Check for slash commands
    const parsed = parseSlashCommand(input);
    if (parsed) {
      const cmdMsg = { id: messages.length + 1, role: "user", content: input, timestamp: new Date(), isCommand: true };
      setMessages(prev => [...prev, cmdMsg]);
      setInput("");
      setIsLoading(true);
      try {
        const res = await fetch("/api/chat/command", {
          method: "POST",
          headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
          body: JSON.stringify({ command: parsed.command, args: parsed.args, tab_id: tabId }),
        });
        const data = await res.json();
        formatCommandResponse(data, setMessages, user, sessionToken);
      } catch (err) {
        setMessages(prev => [...prev, { id: Date.now(), role: "system", content: `Command failed: ${err.message}`, timestamp: new Date(), isCommand: true }]);
      } finally {
        setIsLoading(false);
        setTimeout(() => chatInputRef.current?.focus(), 50);
      }
      return;
    }

    const fileIds = attachedFiles.map((f) => f.file_id);
    const userMessage = {
      id: messages.length + 1, role: "user",
      content: input || "[Attached file(s)]", timestamp: new Date(),
      files: attachedFiles.length > 0 ? [...attachedFiles] : undefined,
    };
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setAttachedFiles([]);
    setIsLoading(true);
    const assistantId = messages.length + 2;
    const controller = new AbortController();
    abortRef.current = controller;

    // Add placeholder assistant message
    setMessages((prev) => [...prev, { id: assistantId, role: "assistant", content: "", timestamp: new Date() }]);

    try {
      const res = await fetch("/api/chat/send", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ content: input, tab_id: tabId, file_ids: fileIds.length ? fileIds : undefined }),
        signal: controller.signal,
      });
      if (!res.ok) {
        const errBody = await res.json().catch(() => ({}));
        throw new Error(errBody.detail || `Error ${res.status}`);
      }
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          try {
            const data = JSON.parse(line.slice(6));
            if (data.type === "tool_start" || data.type === "tool_result") {
              if (isAdmin) {
                setMessages((prev) => prev.map((msg) =>
                  msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
                ));
              }
            } else if (data.type === "chunk" || data.type === "error") {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
              ));
            } else if (data.type === "workflow_update" && data.workflow) {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId
                  ? { ...msg, workflows: [...(msg.workflows || []), data.workflow] }
                  : msg
              ));
            } else if (data.type === "export_started" && data.job_id) {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId
                  ? { ...msg, exports: [...(msg.exports || []), { job_id: data.job_id, format: data.format }] }
                  : msg
              ));
            }
          } catch {}
        }
      }
    } catch (err) {
      if (err.name !== "AbortError") {
        setMessages((prev) => prev.map((msg) =>
          msg.id === assistantId ? { ...msg, content: `Error: ${err.message}` } : msg
        ));
      }
    } finally {
      abortRef.current = null;
      setIsLoading(false);
      setTimeout(() => chatInputRef.current?.focus(), 50);
    }
  };

  const handleStop = () => { if (abortRef.current) abortRef.current.abort(); };

  if (!isOpen) {
    return (
      <button
        onClick={() => setIsOpen(true)}
        className="fixed bottom-4 right-4 bg-amber-600 hover:bg-amber-500 text-white rounded-full w-12 h-12 flex items-center justify-center shadow-lg transition z-50"
        title={`Chat with HAL about ${tabLabel}`}
      >
        <span className="text-lg">💬</span>
      </button>
    );
  }

  return (
    <div className="fixed bottom-4 right-4 w-96 h-[28rem] bg-gray-800 border border-gray-700 rounded-lg shadow-xl flex flex-col z-50">
      {/* Header */}
      <div className="flex items-center justify-between px-3 py-2 border-b border-gray-700 bg-gray-750 rounded-t-lg">
        <div className="flex items-center gap-2">
          <span className="text-amber-500 text-sm font-semibold">HAL</span>
          <span className="text-gray-400 text-xs">· {tabLabel}</span>
          {accessLevel === "restricted" && (
            <span className="text-xs bg-yellow-900/50 text-yellow-400 px-1.5 py-0.5 rounded">Read-only</span>
          )}
        </div>
        <button onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-white text-sm transition">✕</button>
      </div>

      {/* Messages */}
      <div className="flex-1 overflow-y-auto p-3 space-y-2">
        {messages.filter((msg) => {
          const visibleContent = isAdmin ? (msg.content || "").trim() : stripToolLines(msg.content);
          if (msg.role === "assistant" && !visibleContent && !(msg.workflows?.length) && !(msg.exports?.length)) return false;
          return true;
        }).map((msg) => (
          <div key={msg.id} className={`flex ${msg.role === "system" || (msg.isCommand && msg.role !== "user") ? "justify-center" : msg.role === "user" ? "justify-end" : "justify-start"}`}>
            <div className={`max-w-[85%] px-3 py-2 rounded-lg text-sm ${
              msg.role === "system" || (msg.isCommand && msg.role !== "user")
                ? "bg-gray-700/50 text-gray-400 border border-gray-600 text-xs"
                : msg.role === "user"
                  ? "bg-amber-600 text-white"
                  : "bg-gray-700 text-gray-200"
            }`}>
              {msg.role === "user"
                ? <span style={{ whiteSpace: "pre-wrap" }}>{msg.content}</span>
                : <MarkdownMessage content={isAdmin ? (msg.content || "").trimStart() : stripToolLines(msg.content)} />
              }
              {msg.files && msg.files.length > 0 && (
                <div className="flex flex-wrap gap-1 mt-1">
                  {msg.files.map((f) => (
                    <span key={f.file_id} className="inline-flex items-center gap-1 bg-black/20 text-xs px-1.5 py-0.5 rounded">
                      <svg className="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
                      </svg>
                      {f.filename}
                    </span>
                  ))}
                </div>
              )}
              {msg.workflows && msg.workflows.length > 0 && msg.workflows.map((wf) => (
                <WorkflowCard key={wf.id} workflow={wf} sessionToken={sessionToken} user={user} />
              ))}
              {msg.exports && msg.exports.length > 0 && msg.exports.map((exp) => (
                <ExportStatusCard key={exp.job_id} jobId={exp.job_id} format={exp.format} sessionToken={sessionToken} />
              ))}
              {msg.timestamp && <p className="text-xs mt-1 opacity-70">{msg.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</p>}
            </div>
          </div>
        ))}
        <TypingIndicator visible={isLoading} />
        <div ref={messagesEndRef} />
      </div>

      {/* Input */}
      <div className="p-2 border-t border-gray-700">
        <FileAttachmentInput sessionToken={sessionToken} files={attachedFiles} onFilesChange={setAttachedFiles} disabled={isLoading} />
        <div className="flex gap-2 mt-1 relative">
          <SlashCommandMenu input={input} onSelect={(cmd) => setInput(cmd + " ")} />
          <input
            ref={chatInputRef}
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }}
            placeholder={attachedFiles.length > 0 ? "Describe what to do with the files..." : `Ask about ${tabLabel}... (/ for commands)`}
            className="flex-1 bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white focus:outline-none focus:border-amber-500"
            disabled={isLoading}
          />
          {isLoading ? (
            <button onClick={handleStop} className="bg-red-600 hover:bg-red-500 text-white px-3 py-1.5 rounded text-sm font-medium transition">
              Stop
            </button>
          ) : (
            <button onClick={handleSend} disabled={!input.trim() && !attachedFiles.length} className="bg-amber-600 hover:bg-amber-500 text-white px-3 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
              Send
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function ConsoleTab({ fleet: mockFleet, agents: mockAgents, sessionToken, user }) {
  const isAdmin = true; // ConsoleTab is only visible to admin/fleet_ops
  // Restore session history from localStorage
  const savedSession = (() => {
    try {
      const raw = localStorage.getItem("hal_console_history");
      return raw ? JSON.parse(raw) : null;
    } catch { return null; }
  })();

  const [history, setHistory] = useState(() => {
    const welcome = { type: "system", text: "HAL Fleet Console v1.0 — Type 'help' for available commands.", timestamp: new Date() };
    if (savedSession?.terminal?.length) {
      return [...savedSession.terminal, { type: "system", text: "— session restored —", timestamp: new Date() }];
    }
    return [welcome];
  });
  const [inputValue, setInputValue] = useState("");
  const [commandHistory, setCommandHistory] = useState(savedSession?.commands || []);
  const [historyIndex, setHistoryIndex] = useState(-1);
  const [targetHost, setTargetHost] = useState("all");
  const [isExecuting, setIsExecuting] = useState(false);
  const [liveHosts, setLiveHosts] = useState([]);
  const terminalRef = useRef(null);
  const inputRef = useRef(null);

  // Persist session history to localStorage
  useEffect(() => {
    try {
      localStorage.setItem("hal_console_history", JSON.stringify({
        commands: commandHistory.slice(-100),
        terminal: history.slice(-500),
      }));
    } catch {}
  }, [commandHistory, history]);

  // Fetch live hosts on mount (same pattern as DashboardTab)
  useEffect(() => {
    fetch("/api/hosts", { headers: { "X-Session-Token": sessionToken } })
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(data => {
        const hosts = data.hosts || data || [];
        if (hosts.length > 0) setLiveHosts(hosts);
      })
      .catch(() => {});
  }, [sessionToken]);

  // Build fleet list: prefer live hosts, fall back to mock
  const fleet = liveHosts.length > 0
    ? liveHosts.map(h => ({
        id: h.hostname || h.id,
        name: h.display_name || h.hostname || h.name || h.id,
        status: h.status === "online" || h.status === "healthy" ? "healthy" : h.status || "unknown",
        utilization: h.cpu_percent || 0,
        agents: 1,
        lastSeen: h.last_seen || "unknown",
      }))
    : mockFleet;
  const isLive = liveHosts.length > 0;

  useEffect(() => {
    if (terminalRef.current) {
      terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
    }
  }, [history]);

  const getTargetLabel = () => targetHost === "all" ? "all" : fleet.find(f => f.id === targetHost)?.name || targetHost;

  // Map console commands to MCP tool + args
  const mapCommandToTool = (command, args) => {
    switch (command) {
      case "status":     return { tool: "get_host_info", args: {} };
      case "metrics":    return { tool: "get_metrics", args: {} };
      case "uptime":     return { tool: "run_command", args: { command: "uptime" } };
      case "ps":         return { tool: "list_services", args: {} };
      case "services":   return { tool: "list_services", args: { filter_pattern: args[0] || "" } };
      case "restart":    return args[0] ? { tool: "manage_service", args: { name: args[0], action: "restart" } } : null;
      case "stop":       return args[0] ? { tool: "manage_service", args: { name: args[0], action: "stop" } } : null;
      case "logs":       return { tool: "tail_logs", args: { service: args[0] || "", lines: 50 } };
      case "df":
      case "disk":       return { tool: "run_command", args: { command: "df -h" } };
      case "ping":       return { tool: "run_command", args: { command: "echo pong" } };
      default:           return null;
    }
  };

  // Call the real API, return formatted output entries
  // Non-streaming API call (single host or fallback)
  const callConsoleAPI = async (host, tool, toolArgs) => {
    const res = await fetch("/api/console/exec", {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      body: JSON.stringify({ host, tool, args: toolArgs }),
    });
    if (!res.ok) {
      const body = await res.json().catch(() => ({}));
      throw new Error(body.detail || `API error: ${res.status}`);
    }
    const data = await res.json();
    const entries = [];
    for (const [hostname, result] of Object.entries(data.results || {})) {
      const label = fleet.find(f => f.id === hostname)?.name || hostname;
      if (result.ok) {
        const text = Array.isArray(result.result) ? result.result.join("\n") : String(result.result);
        entries.push({ type: "output", text: `[${label}] ${text}` });
      } else {
        entries.push({ type: "error", text: `[${label}] Error: ${result.error}` });
      }
    }
    return entries;
  };

  // Streaming API call for multi-host — results appear host-by-host
  const callConsoleStream = async (host, tool, toolArgs) => {
    const res = await fetch("/api/console/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      body: JSON.stringify({ host, tool, args: toolArgs }),
    });
    if (!res.ok) {
      const body = await res.json().catch(() => ({}));
      throw new Error(body.detail || `API error: ${res.status}`);
    }
    const reader = res.body.getReader();
    const decoder = new TextDecoder();
    let buffer = "";
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop() || "";
      for (const line of lines) {
        if (!line.startsWith("data: ")) continue;
        try {
          const data = JSON.parse(line.slice(6));
          if (data.done) return;
          const now = new Date();
          if (data.ok) {
            const text = Array.isArray(data.result) ? data.result.join("\n") : String(data.result);
            setHistory(prev => [...prev.filter(e => e.text !== "Executing..."),
              { type: "output", text: `[${data.display_name || data.hostname}] ${text}`, timestamp: now }]);
          } else {
            setHistory(prev => [...prev.filter(e => e.text !== "Executing..."),
              { type: "error", text: `[${data.display_name || data.hostname}] Error: ${data.error}`, timestamp: now }]);
          }
        } catch {}
      }
    }
  };

  // Mock fallback for when API is unreachable
  const mockExecute = (command, args, targetFleet) => {
    const now = new Date();
    switch (command) {
      case "status":
      case "uptime":
        return [{ type: "output", text: targetFleet.map(h =>
          `[${h.name}] ${h.status.padEnd(10)} CPU: ${h.utilization}%  Agents: ${h.agents}  Last seen: ${h.lastSeen}`
        ).join("\n") || "No hosts match target." }];
      case "agents":
        return [{ type: "output", text: mockAgents.map(a =>
          `${a.name.padEnd(22)} ${a.status.padEnd(8)} Model: ${a.model || "n/a"}  Tasks: ${a.tasks}`
        ).join("\n") || "No agents found." }];
      case "restart":
      case "stop": {
        const svc = args[0];
        if (!svc) return [{ type: "error", text: `Usage: ${command} <service-name>` }];
        const lines = targetFleet.map(h => ({ type: "output", text: `[${h.name}] ${command === "restart" ? "Restarting" : "Stopping"} ${svc}...` }));
        lines.push({ type: "system", text: `${command === "restart" ? "Restart" : "Stop"} signal sent to ${targetFleet.length} host(s) for service '${svc}'.` });
        return lines;
      }
      case "logs": {
        const svc = args[0] || "system";
        const host = targetFleet[0]?.name || "unknown";
        return [{ type: "output", text: [
          `[${host}] ${new Date(now - 30000).toLocaleTimeString()} ${svc}[1842]: Started successfully`,
          `[${host}] ${new Date(now - 25000).toLocaleTimeString()} ${svc}[1842]: Listening on port 8080`,
          `[${host}] ${new Date(now - 12000).toLocaleTimeString()} ${svc}[1842]: Request processed in 42ms`,
          `[${host}] ${new Date(now - 5000).toLocaleTimeString()} ${svc}[1842]: Health check OK`,
        ].join("\n") }];
      }
      case "df":
      case "disk":
        return [{ type: "output", text: ["Filesystem      Size   Used  Avail  Use%  Mounted on",
          ...targetFleet.map(h => `/dev/sda1       100G   ${Math.round(h.utilization * 0.8)}G    ${100 - Math.round(h.utilization * 0.8)}G   ${Math.round(h.utilization * 0.8)}%   /          [${h.name}]`)
        ].join("\n") }];
      case "ps":
      case "services":
        return [{ type: "output", text: "PID    USER          CPU%  MEM%  COMMAND\n1      root          0.0   0.1   /sbin/init\n842    claude-agent  2.1   3.4   /opt/hal/agent/hal-agent\n1203   claude-agent  0.8   1.2   ollama serve\n1544   root          0.3   0.5   /usr/sbin/nginx\n1891   claude-agent  1.5   2.8   python3 mcp_server.py" }];
      case "metrics":
        return [{ type: "output", text: targetFleet.map(h => `[${h.name}] CPU: ${h.utilization}%  Status: ${h.status}`).join("\n") }];
      case "ping":
        if (targetFleet.length === 0) return [{ type: "error", text: "No hosts match target." }];
        return targetFleet.map(h => ({ type: "output", text: `[${h.name}] PING OK — ${Math.floor(Math.random() * 15) + 1}ms` }));
      default:
        return null; // Not a known command
    }
  };

  const executeCommand = async (raw) => {
    const cmd = raw.trim();
    if (!cmd) return;

    const now = new Date();
    const prompt = `hal@${getTargetLabel()}> `;

    setCommandHistory(prev => [...prev, cmd]);
    setHistoryIndex(-1);
    setInputValue("");

    // Add the input line immediately
    setHistory(prev => [...prev, { type: "input", text: `${prompt}${cmd}`, timestamp: now }]);

    const parts = cmd.split(/\s+/);
    const command = parts[0].toLowerCase();
    const cmdArgs = parts.slice(1);

    // Client-only commands
    if (command === "clear") {
      setHistory([{ type: "system", text: "Terminal cleared.", timestamp: now }]);
      return;
    }
    if (command === "help") {
      setHistory(prev => [...prev, { type: "output", text: [
        "Available commands:",
        "  status          Show fleet host info",
        "  metrics         Show host CPU/memory/disk metrics",
        "  agents          List active agents",
        "  restart <svc>   Restart a service on target host(s)",
        "  stop <svc>      Stop a service on target host(s)",
        "  logs [svc]      Tail recent logs (optional service filter)",
        "  services [pat]  List systemd services (optional filter)",
        "  df              Show disk usage",
        "  uptime          Show host uptime",
        "  ps              List services",
        "  ping            Ping target host(s)",
        "  !<cmd>          Run a raw shell command on target host(s)",
        "  history         Show command history",
        "  clear           Clear terminal",
        "  help            Show this message",
        "",
        "Use the host selector above to target specific hosts.",
      ].join("\n"), timestamp: now }]);
      return;
    }
    if (command === "history") {
      const cmds = commandHistory.length
        ? commandHistory.map((c, i) => `  ${String(i + 1).padStart(4)}  ${c}`).join("\n")
        : "  (no history)";
      setHistory(prev => [...prev, { type: "output", text: cmds, timestamp: now }]);
      return;
    }
    if (command === "agents") {
      const agentList = (isLive ? fleet : mockAgents).map(a =>
        `${(a.name || "unknown").padEnd(22)} ${(a.status || "unknown").padEnd(8)}`
      ).join("\n") || "No agents found.";
      setHistory(prev => [...prev, { type: "output", text: agentList, timestamp: now }]);
      return;
    }

    // Validate service commands
    if ((command === "restart" || command === "stop") && !cmdArgs[0]) {
      setHistory(prev => [...prev, { type: "error", text: `Usage: ${command} <service-name>`, timestamp: now }]);
      return;
    }

    // Determine MCP tool mapping
    const isRawCommand = cmd.startsWith("!");
    const mapped = isRawCommand
      ? { tool: "run_command", args: { command: cmd.slice(1).trim() } }
      : mapCommandToTool(command, cmdArgs);

    if (!mapped) {
      setHistory(prev => [...prev, { type: "error", text: `Unknown command: '${command}'. Type 'help' for available commands.`, timestamp: now }]);
      return;
    }

    // Resolve target host ID for the API
    const apiHost = targetHost === "all" ? "all" : targetHost;

    // Show executing indicator
    setIsExecuting(true);
    setHistory(prev => [...prev, { type: "system", text: "Executing...", timestamp: now }]);

    // Use streaming for multi-host, regular API for single-host
    const useStream = apiHost === "all" && fleet.length > 1;

    try {
      if (useStream) {
        await callConsoleStream(apiHost, mapped.tool, mapped.args);
      } else {
        const entries = await callConsoleAPI(apiHost, mapped.tool, mapped.args);
        setHistory(prev => {
          const withoutExec = prev.filter(e => e.text !== "Executing...");
          return [...withoutExec, ...entries.map(e => ({ ...e, timestamp: new Date() }))];
        });
      }
    } catch (e) {
      // API unreachable — fall back to mock
      const targetFleet = targetHost === "all" ? fleet : fleet.filter(f => f.id === targetHost);
      const mockEntries = mockExecute(command, cmdArgs, targetFleet);
      setHistory(prev => {
        const withoutExec = prev.filter(e => e.text !== "Executing...");
        if (mockEntries) {
          return [...withoutExec,
            { type: "system", text: "(mock — API unreachable)", timestamp: new Date() },
            ...mockEntries.map(e => ({ ...e, timestamp: new Date() })),
          ];
        }
        return [...withoutExec, { type: "error", text: `Failed: ${e.message}`, timestamp: new Date() }];
      });
    } finally {
      setIsExecuting(false);
      setTimeout(() => inputRef.current?.focus(), 50);
    }
  };

  const handleKeyDown = (e) => {
    if (e.key === "Enter" && !isExecuting) {
      executeCommand(inputValue);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      if (commandHistory.length === 0) return;
      const newIndex = historyIndex === -1 ? commandHistory.length - 1 : Math.max(0, historyIndex - 1);
      setHistoryIndex(newIndex);
      setInputValue(commandHistory[newIndex]);
    } else if (e.key === "ArrowDown") {
      e.preventDefault();
      if (historyIndex === -1) return;
      const newIndex = historyIndex + 1;
      if (newIndex >= commandHistory.length) {
        setHistoryIndex(-1);
        setInputValue("");
      } else {
        setHistoryIndex(newIndex);
        setInputValue(commandHistory[newIndex]);
      }
    }
  };

  const quickCommands = [
    { label: "System Status", cmd: "status" },
    { label: "Tail Logs", cmd: "logs" },
    { label: "List Services", cmd: "services" },
    { label: "Disk Usage", cmd: "df" },
    { label: "Ping", cmd: "ping" },
  ];

  const typeColors = {
    input: "text-green-400",
    output: "text-gray-300",
    error: "text-red-400",
    system: "text-amber-400",
  };

  return (
    <div className="h-full flex flex-col p-2 sm:p-4 md:p-6 bg-gray-900">
      {/* Header row */}
      <div className="flex items-center justify-between mb-4">
        <div className="flex items-center gap-3">
          <h2 className="text-sm font-semibold text-white">Fleet Console</h2>
          {isLive ? (
            <span className="text-xs bg-green-900 text-green-300 px-2 py-1 rounded">{fleet.length} live</span>
          ) : (
            <span className="text-xs bg-gray-700 text-gray-400 px-2 py-1 rounded">demo mode</span>
          )}
          <div className="flex items-center gap-2">
            <label className="text-xs text-gray-400">Target:</label>
            <select
              value={targetHost}
              onChange={(e) => setTargetHost(e.target.value)}
              className="bg-gray-700 text-white text-xs rounded px-2 py-1 border border-gray-600 focus:border-amber-500 focus:outline-none"
            >
              <option value="all">All Hosts</option>
              {fleet.map((f) => (
                <option key={f.id} value={f.id}>{f.name}</option>
              ))}
            </select>
          </div>
        </div>
        <div className="flex gap-2">
          {quickCommands.map((qc) => (
            <button
              key={qc.cmd}
              onClick={() => executeCommand(qc.cmd)}
              className="px-3 py-1 rounded text-xs font-medium transition bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white"
            >
              {qc.label}
            </button>
          ))}
        </div>
      </div>

      {/* Terminal area */}
      <div
        ref={terminalRef}
        onClick={() => inputRef.current?.focus()}
        className="flex-1 bg-gray-950 rounded-lg border border-gray-700 p-4 overflow-y-auto font-mono text-sm cursor-text min-h-0"
      >
        {history.map((entry, i) => (
          <div key={i} className={`${typeColors[entry.type]} whitespace-pre-wrap mb-1`}>
            {entry.text}
          </div>
        ))}

        {/* Input line */}
        <div className="flex items-center text-green-400 mt-1">
          <span className="mr-2 shrink-0">hal@{getTargetLabel()}&gt;</span>
          <input
            ref={inputRef}
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onKeyDown={handleKeyDown}
            disabled={isExecuting}
            className="flex-1 bg-transparent text-green-400 font-mono text-sm outline-none caret-green-400 disabled:opacity-50"
            spellCheck={false}
            autoFocus
          />
        </div>
      </div>
    </div>
  );
}

function DashboardTab({ sessionToken, user }) {
  const [activeSubTab, setActiveSubTab] = useState("fleet");
  const [hosts, setHosts] = useState([]);
  const [hostMetrics, setHostMetrics] = useState({});
  const [hostServices, setHostServices] = useState({});
  const [alerts, setAlerts] = useState([]);
  const [agentStatus, setAgentStatus] = useState([]);
  const [loading, setLoading] = useState(true);
  const [lastRefresh, setLastRefresh] = useState(null);
  const [fetchError, setFetchError] = useState(null);
  const [chatAgent, setChatAgent] = useState(null);
  const [distVersion, setDistVersion] = useState(null);
  const activeSubTabRef = useRef("fleet");

  // Keep ref in sync
  useEffect(() => { activeSubTabRef.current = activeSubTab; }, [activeSubTab]);

  // Fetch host list from /api/hosts
  const fetchHosts = useCallback(async () => {
    try {
      const res = await fetch("/api/hosts", { headers: { "X-Session-Token": sessionToken } });
      if (res.ok) {
        setFetchError(null);
        const data = await res.json();
        const rawHosts = data.hosts || data || [];
        const mapped = rawHosts.map((h) => ({
          id: h.hostname || h.id,
          name: h.display_name || h.hostname || h.name || h.id,
          hostname: h.hostname || h.id,
          status: h.status === "online" || h.status === "healthy" ? "healthy" : h.status === "degraded" ? "degraded" : "offline",
          profile: h.profile || "unknown",
          agent_type: h.agent_type || PROFILE_TO_AGENT_TYPE_MAP[h.profile] || "monitoring",
          provider: h.provider || "ollama",
          model: h.model || h.ollama_model || "",
          ip: h.ip || h.address || "",
          ollama_model: h.ollama_model || "",
          uptime: h.uptime || "",
          has_gpu: h.has_gpu || false,
        }));
        setHosts(mapped);
        setLastRefresh(new Date());
      }
    } catch (e) {
      setFetchError("Fleet API unreachable");
    } finally { setLoading(false); }
  }, [sessionToken]);

  // Fetch metrics for a single host via /api/console/exec
  const fetchMetricsForHost = useCallback(async (hostname) => {
    try {
      const res = await fetch("/api/console/exec", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ host: hostname, tool: "get_metrics", args: {} }),
      });
      if (!res.ok) return;
      const data = await res.json();
      const result = data.results?.[hostname];
      if (result?.ok) {
        const parsed = parseMetricsResult(result.result);
        setHostMetrics((prev) => ({ ...prev, [hostname]: parsed }));
      }
    } catch {}
  }, [sessionToken]);

  // Fetch services for a single host
  const fetchServicesForHost = useCallback(async (hostname) => {
    try {
      const res = await fetch("/api/console/exec", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ host: hostname, tool: "list_services", args: {} }),
      });
      if (!res.ok) return;
      const data = await res.json();
      const result = data.results?.[hostname];
      if (result?.ok) {
        const text = Array.isArray(result.result) ? result.result.join("\n") : String(result.result);
        // Try to parse as JSON first (structured response)
        let parsed;
        try {
          const obj = JSON.parse(text);
          parsed = parseServiceList(obj.services || text);
        } catch {
          parsed = parseServiceList(text);
        }
        setHostServices((prev) => ({ ...prev, [hostname]: parsed }));
      }
    } catch {}
  }, [sessionToken]);

  // Fetch alerts from /api/alerts
  const fetchAlerts = useCallback(async () => {
    try {
      const res = await fetch("/api/alerts", { headers: { "X-Session-Token": sessionToken } });
      if (res.ok) {
        const data = await res.json();
        setAlerts(data.alerts || []);
      }
    } catch {}
  }, [sessionToken]);

  // Fetch agent status from /api/agents/status
  const fetchAgentStatus = useCallback(async () => {
    try {
      const res = await fetch("/api/agents/status", { headers: { "X-Session-Token": sessionToken } });
      if (res.ok) {
        const data = await res.json();
        setAgentStatus(data.agents || []);
        if (data.dist_version) setDistVersion(data.dist_version);
      }
    } catch {}
  }, [sessionToken]);

  // Host list polling (30s)
  useEffect(() => {
    fetchHosts();
    const interval = setInterval(fetchHosts, 30000);
    return () => clearInterval(interval);
  }, [fetchHosts]);

  // Metrics polling (30s, staggered, only when fleet/gpu sub-tab active)
  useEffect(() => {
    if (hosts.length === 0) return;
    const tab = activeSubTabRef.current;
    if (tab !== "fleet" && tab !== "gpu") return;

    // Fetch all immediately
    hosts.forEach((h) => fetchMetricsForHost(h.id));

    const interval = 30000;
    const stagger = Math.max(Math.floor(interval / hosts.length), 2000);
    const timers = hosts.map((host, i) =>
      setTimeout(() => {
        fetchMetricsForHost(host.id);
        const repeater = setInterval(() => fetchMetricsForHost(host.id), interval);
        timers[i] = repeater;
      }, i * stagger)
    );
    return () => timers.forEach((t) => clearTimeout(t) || clearInterval(t));
  }, [hosts.length, activeSubTab, fetchMetricsForHost]);

  // Services polling (60s, only when services sub-tab active)
  useEffect(() => {
    if (hosts.length === 0 || activeSubTab !== "services") return;
    hosts.forEach((h) => fetchServicesForHost(h.id));
    const interval = setInterval(() => hosts.forEach((h) => fetchServicesForHost(h.id)), 60000);
    return () => clearInterval(interval);
  }, [hosts.length, activeSubTab, fetchServicesForHost]);

  // Alerts polling (15s when active, 60s for badge)
  useEffect(() => {
    fetchAlerts();
    const ms = activeSubTab === "alerts" ? 15000 : 60000;
    const interval = setInterval(fetchAlerts, ms);
    return () => clearInterval(interval);
  }, [activeSubTab, fetchAlerts]);

  // Agent status polling (60s, when activity tab active)
  useEffect(() => {
    if (activeSubTab !== "activity") return;
    fetchAgentStatus();
    const interval = setInterval(fetchAgentStatus, 60000);
    return () => clearInterval(interval);
  }, [activeSubTab, fetchAgentStatus]);

  const unackedAlertCount = alerts.filter((a) => !a.acknowledged).length;
  const hasGPU = hosts.some((h) => h.has_gpu || hostMetrics[h.id]?.gpu);

  const subTabs = [
    { id: "fleet", label: "Fleet" },
    { id: "services", label: "Services" },
    { id: "alerts", label: "Alerts", badge: unackedAlertCount || null },
    ...(hasGPU ? [{ id: "gpu", label: "GPU" }] : []),
    { id: "activity", label: "Activity" },
  ];

  return (
    <div className="h-full flex bg-gray-900">
      <div className="flex-1 overflow-y-auto p-3 sm:p-4 md:p-6">
        <div className="flex items-center justify-between mb-4">
          <div className="flex items-center gap-3">
            {hosts.length > 0 && (
              <span className="text-xs bg-green-900 text-green-300 px-2 py-1 rounded">
                {hosts.length} {hosts.length === 1 ? "agent" : "agents"}
              </span>
            )}
            {hosts.length === 0 && !loading && !fetchError && (
              <span className="text-xs bg-gray-700 text-gray-400 px-2 py-1 rounded">No agents registered</span>
            )}
            {fetchError && (
              <span className="text-xs bg-red-900 text-red-300 px-2 py-1 rounded">{fetchError}</span>
            )}
            {lastRefresh && (
              <span className="text-xs text-gray-500">Updated {lastRefresh.toLocaleTimeString()}</span>
            )}
          </div>
          <button onClick={fetchHosts}
            className="text-xs text-gray-400 hover:text-amber-400 px-2 py-1 rounded border border-gray-700 hover:border-amber-500 transition">
            Refresh
          </button>
        </div>

        <div className="flex gap-2 mb-4">
          {subTabs.map((tab) => (
            <button key={tab.id} onClick={() => setActiveSubTab(tab.id)}
              className={`px-3 py-1 rounded text-xs font-medium transition relative ${
                activeSubTab === tab.id ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"
              }`}>
              {tab.label}
              {tab.badge && (
                <span className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
                  {tab.badge > 9 ? "9+" : tab.badge}
                </span>
              )}
            </button>
          ))}
        </div>

        {activeSubTab === "fleet" && <FleetSubTab hosts={hosts} hostMetrics={hostMetrics} sessionToken={sessionToken} />}
        {activeSubTab === "services" && <ServicesSubTab hosts={hosts} hostServices={hostServices} sessionToken={sessionToken} user={user} />}
        {activeSubTab === "alerts" && <AlertsSubTab alerts={alerts} sessionToken={sessionToken} user={user} />}
        {activeSubTab === "gpu" && <GPUSubTab hosts={hosts} hostMetrics={hostMetrics} />}
        {activeSubTab === "activity" && (
          <ActivitySubTab hosts={hosts} agentStatus={agentStatus} sessionToken={sessionToken} user={user}
            distVersion={distVersion} onChatAgent={(agent) => setChatAgent(agent)} />
        )}
      </div>
      {chatAgent && (
        <AgentChatPanel
          hostname={chatAgent.hostname}
          agentType={chatAgent.agent_type}
          provider={chatAgent.provider}
          model={chatAgent.model}
          sessionToken={sessionToken}
          onClose={() => setChatAgent(null)}
        />
      )}
    </div>
  );
}

// ============================================================================
// BUSINESS OPERATION TABS
// ============================================================================

function BusinessTab({ tabId, tabLabel, user, sessionToken, accessLevel, hasTabChat }) {
  const isAdmin = user?.roles?.includes("admin") || user?.roles?.includes("fleet_ops");
  const toast = useToast();
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [attachedFiles, setAttachedFiles] = useState([]);
  const [showFindings, setShowFindings] = useState(false);
  const [findingsCount, setFindingsCount] = useState(0);
  const [findingsPollFails, setFindingsPollFails] = useState(0);
  const findingsAutoOpenedRef = useRef(false);
  const messagesEndRef = useRef(null);
  const abortRef = useRef(null);
  const chatInputRef = useRef(null);
  useEffect(() => { setTimeout(() => chatInputRef.current?.focus(), 200); }, []);

  // Fetch tab-specific chat history on mount
  useEffect(() => {
    if (!sessionToken) return;
    fetch(`/api/chat/history?tab=${tabId}`, {
      headers: { "X-Session-Token": sessionToken },
    })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => {
        if (data.messages && data.messages.length > 0) {
          setMessages(data.messages.map((msg, idx) => ({
            id: idx + 1, role: msg.role, content: msg.content,
            timestamp: new Date(msg.timestamp || Date.now()),
            workflows: msg.workflows || undefined,
            exports: msg.exports || undefined,
          })));
        } else {
          setMessages([{
            id: 1, role: "assistant",
            content: `Hi ${user?.display_name || "there"}! I'm HAL, your ${tabLabel} assistant. I can query live data, generate reports, and help with analysis.${accessLevel === "restricted" ? " (You have read-only access.)" : ""}\n\nTry asking me something like:\n- "Show me a summary of current activity"\n- "How many items are in process?"\n- "What are the top trends this week?"`,
            timestamp: new Date(),
          }]);
        }
      })
      .catch(() => {
        setMessages([{
          id: 1, role: "assistant",
          content: `Hi! I'm HAL, assisting with ${tabLabel}. How can I help?`,
          timestamp: new Date(),
        }]);
      });
  }, [sessionToken, tabId]);

  // Poll findings count for badge
  useEffect(() => {
    if (!sessionToken || !tabId) return;
    const poll = () => {
      fetch(`/api/findings?tab_id=${tabId}&limit=1&status=active`, { headers: { "X-Session-Token": sessionToken } })
        .then((r) => r.ok ? r.json() : Promise.reject())
        .then((data) => {
          const count = data.total || 0;
          setFindingsCount(count);
          setFindingsPollFails(0);
          // Auto-open panel on first poll if there are active findings
          if (count > 0 && !findingsAutoOpenedRef.current) {
            findingsAutoOpenedRef.current = true;
            setShowFindings(true);
          }
        })
        .catch(() => { setFindingsPollFails(prev => prev + 1); });
    };
    poll();
    const interval = setInterval(poll, 30000);
    return () => clearInterval(interval);
  }, [sessionToken, tabId]);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  const handleSend = async () => {
    if ((!input.trim() && !attachedFiles.length) || isLoading) return;

    // Check for slash commands
    const parsed = parseSlashCommand(input);
    if (parsed) {
      const cmdMsg = { id: messages.length + 1, role: "user", content: input, timestamp: new Date(), isCommand: true };
      setMessages(prev => [...prev, cmdMsg]);
      setInput("");
      setIsLoading(true);
      try {
        const res = await fetch("/api/chat/command", {
          method: "POST",
          headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
          body: JSON.stringify({ command: parsed.command, args: parsed.args, tab_id: tabId }),
        });
        const data = await res.json();
        formatCommandResponse(data, setMessages, user, sessionToken);
      } catch (err) {
        setMessages(prev => [...prev, { id: Date.now(), role: "system", content: `Command failed: ${err.message}`, timestamp: new Date(), isCommand: true }]);
      } finally {
        setIsLoading(false);
        setTimeout(() => chatInputRef.current?.focus(), 50);
      }
      return;
    }

    const fileIds = attachedFiles.map((f) => f.file_id);
    const userMessage = {
      id: messages.length + 1, role: "user",
      content: input || "[Attached file(s)]", timestamp: new Date(),
      files: attachedFiles.length > 0 ? [...attachedFiles] : undefined,
    };
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setAttachedFiles([]);
    setIsLoading(true);
    const assistantId = messages.length + 2;
    const controller = new AbortController();
    abortRef.current = controller;

    setMessages((prev) => [...prev, { id: assistantId, role: "assistant", content: "", timestamp: new Date() }]);

    try {
      const res = await fetch("/api/chat/send", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ content: input, tab_id: tabId, file_ids: fileIds.length ? fileIds : undefined }),
        signal: controller.signal,
      });
      if (!res.ok) {
        const errBody = await res.json().catch(() => ({}));
        throw new Error(errBody.detail || `Error ${res.status}`);
      }
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          try {
            const data = JSON.parse(line.slice(6));
            if (data.type === "tool_start" || data.type === "tool_result") {
              if (isAdmin) {
                setMessages((prev) => prev.map((msg) =>
                  msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
                ));
              }
            } else if (data.type === "chunk" || data.type === "error") {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId ? { ...msg, content: msg.content + data.content } : msg
              ));
            } else if (data.type === "workflow_update" && data.workflow) {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId
                  ? { ...msg, workflows: [...(msg.workflows || []), data.workflow] }
                  : msg
              ));
            } else if (data.type === "export_started" && data.job_id) {
              setMessages((prev) => prev.map((msg) =>
                msg.id === assistantId
                  ? { ...msg, exports: [...(msg.exports || []), { job_id: data.job_id, format: data.format }] }
                  : msg
              ));
            }
          } catch {}
        }
      }
    } catch (err) {
      if (err.name !== "AbortError") {
        setMessages((prev) => prev.map((msg) =>
          msg.id === assistantId ? { ...msg, content: `Error: ${err.message}` } : msg
        ));
      }
    } finally {
      abortRef.current = null;
      setIsLoading(false);
      setTimeout(() => chatInputRef.current?.focus(), 50);
    }
  };

  const handleStop = () => { if (abortRef.current) abortRef.current.abort(); };

  return (
    <div className="h-full flex bg-gray-900">
      {/* Chat column */}
      <div className="flex-1 flex flex-col min-w-0">
        {/* Messages — scrollable area */}
        <div className="flex-1 overflow-y-auto p-3 sm:p-4 md:p-6 space-y-3">
          {messages.filter((msg) => {
            const visibleContent = isAdmin ? (msg.content || "").trim() : stripToolLines(msg.content);
          if (msg.role === "assistant" && !visibleContent && !(msg.workflows?.length) && !(msg.exports?.length)) return false;
            return true;
          }).map((msg) => (
            <div key={msg.id} className={`flex ${msg.role === "system" || (msg.isCommand && msg.role !== "user") ? "justify-center" : msg.role === "user" ? "justify-end" : "justify-start"}`}>
              <div className={`max-w-[90%] md:max-w-[70%] px-3 py-2 sm:px-4 sm:py-3 rounded-lg text-sm ${
                msg.role === "system" || (msg.isCommand && msg.role !== "user")
                  ? "bg-gray-800/50 text-gray-400 border border-gray-700 text-xs"
                  : msg.role === "user"
                    ? "bg-amber-600 text-white"
                    : "bg-gray-800 text-gray-200 border border-gray-700"
              }`}>
                {msg.role === "user"
                  ? <span style={{ whiteSpace: "pre-wrap" }}>{msg.content}</span>
                  : <MarkdownMessage content={isAdmin ? (msg.content || "").trimStart() : stripToolLines(msg.content)} />
                }
                {msg.files && msg.files.length > 0 && (
                  <div className="flex flex-wrap gap-1 mt-2">
                    {msg.files.map((f) => (
                      <span key={f.file_id} className="inline-flex items-center gap-1 bg-black/20 text-xs px-1.5 py-0.5 rounded">
                        <svg className="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
                        </svg>
                        {f.filename}
                      </span>
                    ))}
                  </div>
                )}
                {msg.workflows && msg.workflows.length > 0 && msg.workflows.map((wf) => (
                  <WorkflowCard key={wf.id} workflow={wf} sessionToken={sessionToken} user={user} />
                ))}
                {msg.exports && msg.exports.length > 0 && msg.exports.map((exp) => (
                  <ExportStatusCard key={exp.job_id} jobId={exp.job_id} format={exp.format} sessionToken={sessionToken} />
                ))}
                {msg.timestamp && <p className="text-xs mt-1 opacity-70">{msg.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</p>}
              </div>
            </div>
          ))}
          <TypingIndicator visible={isLoading} />
          <div ref={messagesEndRef} />
        </div>

        {/* Input bar — pinned to bottom */}
        <div className="p-4 border-t border-gray-700 bg-gray-850">
          <FileAttachmentInput sessionToken={sessionToken} files={attachedFiles} onFilesChange={setAttachedFiles} disabled={isLoading} />
          <div className="flex gap-2 mt-1 relative">
            <SlashCommandMenu input={input} onSelect={(cmd) => setInput(cmd + " ")} />
            <input
              ref={chatInputRef}
              type="text"
              value={input}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }}
              placeholder={attachedFiles.length > 0 ? "Describe what to do with the files..." : `Ask HAL about ${tabLabel}... (/ for commands)`}
              className="flex-1 bg-gray-900 border border-gray-700 rounded-lg px-4 py-2.5 text-sm text-white focus:outline-none focus:border-amber-500"
              disabled={isLoading}
            />
            <button
              onClick={() => setShowFindings(!showFindings)}
              className={`px-3 py-2.5 rounded-lg text-sm font-medium transition relative ${showFindings ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}
              title="Toggle findings panel"
            >
              {"\uD83D\uDD0D"}
              {findingsCount > 0 && !showFindings && (
                <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs w-4 h-4 rounded-full flex items-center justify-center">{findingsCount > 9 ? "9+" : findingsCount}</span>
              )}
            </button>
            {isLoading ? (
              <button onClick={handleStop} className="bg-red-600 hover:bg-red-500 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition">
                Stop
              </button>
            ) : (
              <button onClick={handleSend} disabled={!input.trim() && !attachedFiles.length} className="bg-amber-600 hover:bg-amber-500 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition disabled:opacity-50">
                Send
              </button>
            )}
          </div>
        </div>
      </div>

      {/* Findings sidebar */}
      <FindingsSidebar tabId={tabId} sessionToken={sessionToken} isOpen={showFindings} onToggle={() => setShowFindings(false)} />
    </div>
  );
}

function MFAAdminSubTab({ sessionToken }) {
  const [mfaConfig, setMfaConfig] = React.useState(null);
  const [users, setUsers] = React.useState([]);
  const [saving, setSaving] = React.useState(false);

  const VALID_ROLES = ["admin", "fleet_ops", "distribution_ops", "config_center", "services", "front_office", "sales", "manager", "executive"];

  const fetchConfig = React.useCallback(async () => {
    try {
      const r = await fetch("/api/admin/mfa-config", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) setMfaConfig(await r.json());
    } catch {}
  }, [sessionToken]);

  const fetchUsers = React.useCallback(async () => {
    try {
      const r = await fetch("/api/admin/users", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) { const d = await r.json(); setUsers(d.users || []); }
    } catch {}
  }, [sessionToken]);

  React.useEffect(() => { fetchConfig(); fetchUsers(); }, []);

  const saveConfig = async () => {
    setSaving(true);
    try {
      await fetch("/api/admin/mfa-config", {
        method: "PUT",
        headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
        body: JSON.stringify(mfaConfig),
      });
      fetchConfig();
    } catch {}
    setSaving(false);
  };

  const resetUserMFA = async (username) => {
    if (!confirm(`Reset all MFA for ${username}? They will need to re-enroll.`)) return;
    try {
      const r = await fetch(`/api/admin/users/${username}/mfa/reset`, {
        method: "POST", headers: { "X-Session-Token": sessionToken },
      });
      if (r.ok) fetchUsers();
    } catch {}
  };

  if (!mfaConfig) return <div className="text-gray-400 p-4">Loading MFA configuration...</div>;

  return (
    <div className="space-y-6">
      <h2 className="text-xl font-semibold text-white">Multi-Factor Authentication</h2>

      {/* Global Settings */}
      <div className="bg-gray-800 rounded-lg p-4 space-y-4">
        <h3 className="text-sm font-semibold text-gray-300">Global Settings</h3>
        <div className="flex flex-wrap gap-6 items-center">
          <label className="flex items-center gap-2 text-sm text-gray-300">
            <input type="checkbox" checked={mfaConfig.enabled} onChange={e => setMfaConfig({...mfaConfig, enabled: e.target.checked})}
              className="w-4 h-4 rounded bg-gray-700 border-gray-600 text-amber-600" />
            MFA Enabled
          </label>
          <div>
            <label className="block text-xs text-gray-400 mb-1">Default Enforcement</label>
            <select value={mfaConfig.enforcement} onChange={e => setMfaConfig({...mfaConfig, enforcement: e.target.value})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600">
              <option value="optional">Optional</option>
              <option value="required">Required for all</option>
            </select>
          </div>
          <div>
            <label className="block text-xs text-gray-400 mb-1">Trusted Device Days</label>
            <input type="number" value={mfaConfig.trusted_device_days} min={1} max={90}
              onChange={e => setMfaConfig({...mfaConfig, trusted_device_days: parseInt(e.target.value) || 7})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-20" />
          </div>
          <div>
            <label className="block text-xs text-gray-400 mb-1">Grace Logins</label>
            <input type="number" value={mfaConfig.grace_logins} min={0} max={20}
              onChange={e => setMfaConfig({...mfaConfig, grace_logins: parseInt(e.target.value) || 3})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-20" />
          </div>
        </div>

        {/* Allowed methods */}
        <div>
          <label className="block text-xs text-gray-400 mb-1">Allowed Methods</label>
          <div className="flex gap-4">
            <label className="flex items-center gap-2 text-sm text-gray-300">
              <input type="checkbox" checked={(mfaConfig.allowed_methods || []).includes("totp")}
                onChange={e => {
                  const m = new Set(mfaConfig.allowed_methods || []);
                  e.target.checked ? m.add("totp") : m.delete("totp");
                  setMfaConfig({...mfaConfig, allowed_methods: [...m]});
                }} className="w-4 h-4 rounded bg-gray-700 border-gray-600 text-amber-600" />
              TOTP (Authenticator App)
            </label>
            <label className="flex items-center gap-2 text-sm text-gray-300">
              <input type="checkbox" checked={(mfaConfig.allowed_methods || []).includes("webauthn")}
                onChange={e => {
                  const m = new Set(mfaConfig.allowed_methods || []);
                  e.target.checked ? m.add("webauthn") : m.delete("webauthn");
                  setMfaConfig({...mfaConfig, allowed_methods: [...m]});
                }} className="w-4 h-4 rounded bg-gray-700 border-gray-600 text-amber-600" />
              WebAuthn (Security Key / Biometric)
            </label>
          </div>
        </div>

        {/* WebAuthn RP settings */}
        <div className="flex gap-4">
          <div>
            <label className="block text-xs text-gray-400 mb-1">WebAuthn RP ID</label>
            <input type="text" value={mfaConfig.webauthn_rp_id || ""} onChange={e => setMfaConfig({...mfaConfig, webauthn_rp_id: e.target.value})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-48" />
          </div>
          <div>
            <label className="block text-xs text-gray-400 mb-1">WebAuthn RP Name</label>
            <input type="text" value={mfaConfig.webauthn_rp_name || ""} onChange={e => setMfaConfig({...mfaConfig, webauthn_rp_name: e.target.value})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-48" />
          </div>
        </div>

        {/* Per-role enforcement */}
        <div>
          <label className="block text-xs text-gray-400 mb-2">Per-Role Enforcement</label>
          <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
            {VALID_ROLES.map(role => (
              <div key={role} className="flex items-center gap-2">
                <span className="text-xs text-gray-300 w-28">{role}</span>
                <select value={(mfaConfig.role_enforcement || {})[role] || "default"}
                  onChange={e => {
                    const re = {...(mfaConfig.role_enforcement || {})};
                    if (e.target.value === "default") delete re[role]; else re[role] = e.target.value;
                    setMfaConfig({...mfaConfig, role_enforcement: re});
                  }}
                  className="bg-gray-700 text-gray-200 text-xs rounded px-1 py-0.5 border border-gray-600">
                  <option value="default">Default</option>
                  <option value="required">Required</option>
                  <option value="optional">Optional</option>
                </select>
              </div>
            ))}
          </div>
        </div>

        <button onClick={saveConfig} disabled={saving}
          className="px-4 py-1.5 bg-amber-600 hover:bg-amber-500 text-white text-sm rounded disabled:opacity-50">
          {saving ? "Saving..." : "Save MFA Settings"}
        </button>
      </div>

      {/* User MFA Status */}
      <div className="bg-gray-800 rounded-lg p-4">
        <h3 className="text-sm font-semibold text-gray-300 mb-3">User MFA Status</h3>
        <table className="w-full text-sm">
          <thead>
            <tr className="text-gray-400 border-b border-gray-700">
              <th className="text-left py-1 pr-3">User</th>
              <th className="text-left py-1 pr-3">TOTP</th>
              <th className="text-left py-1 pr-3">WebAuthn</th>
              <th className="text-left py-1 pr-3">Devices</th>
              <th className="text-left py-1 pr-3">Required</th>
              <th className="text-right py-1">Actions</th>
            </tr>
          </thead>
          <tbody>
            {users.map(u => {
              const mfa = u.mfa || {};
              const totpOk = mfa.totp_enabled && mfa.totp_verified;
              const waCount = (mfa.webauthn_credentials || []).length;
              const devCount = (mfa.trusted_devices || []).length;
              return (
                <tr key={u.username} className="text-gray-300 border-b border-gray-800">
                  <td className="py-1.5 pr-3 font-medium">{u.display_name || u.username}</td>
                  <td className="py-1.5 pr-3">{totpOk ? <span className="text-green-400">Enabled</span> : <span className="text-gray-500">Off</span>}</td>
                  <td className="py-1.5 pr-3">{waCount > 0 ? <span className="text-green-400">{waCount} key{waCount > 1 ? "s" : ""}</span> : <span className="text-gray-500">None</span>}</td>
                  <td className="py-1.5 pr-3 text-gray-400">{devCount}</td>
                  <td className="py-1.5 pr-3">{u.roles && u.roles.some(r => (mfaConfig.role_enforcement || {})[r] === "required") || mfaConfig.enforcement === "required"
                    ? <span className="text-amber-400 text-xs">Required</span> : <span className="text-gray-500 text-xs">Optional</span>}</td>
                  <td className="py-1.5 text-right">
                    {(totpOk || waCount > 0) && (
                      <button onClick={() => resetUserMFA(u.username)}
                        className="px-2 py-0.5 bg-red-900 hover:bg-red-800 text-red-200 text-xs rounded">
                        Reset MFA
                      </button>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function LogsSubTab({ sessionToken }) {
  const [logConfig, setLogConfig] = React.useState(null);
  const [logEntries, setLogEntries] = React.useState([]);
  const [logSource, setLogSource] = React.useState("dashboard");
  const [logSubFile, setLogSubFile] = React.useState("mcp-server");
  const [logLevel, setLogLevel] = React.useState("ALL");
  const [logSearch, setLogSearch] = React.useState("");
  const [logLines, setLogLines] = React.useState(200);
  const [logLoading, setLogLoading] = React.useState(false);
  const [logAutoRefresh, setLogAutoRefresh] = React.useState(false);
  const [complianceConfig, setComplianceConfig] = React.useState(null);
  const [complianceEntries, setComplianceEntries] = React.useState([]);
  const [showCompliance, setShowCompliance] = React.useState(false);
  const [expandedCompliance, setExpandedCompliance] = React.useState(null);
  const [agents, setAgents] = React.useState([]);

  const fetchLogConfig = React.useCallback(async () => {
    try {
      const r = await fetch("/api/admin/log-config", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) setLogConfig(await r.json());
    } catch {}
  }, [sessionToken]);

  const fetchAgents = React.useCallback(async () => {
    try {
      const r = await fetch("/api/hosts", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) {
        const data = await r.json();
        setAgents((data.hosts || []).filter(h => h.status === "online"));
      }
    } catch {}
  }, [sessionToken]);

  const fetchLogs = React.useCallback(async () => {
    setLogLoading(true);
    try {
      const isAgent = !["dashboard", "coordinator", "console_audit"].includes(logSource);
      const params = new URLSearchParams({
        file: isAgent ? logSubFile : logSource,
        lines: logLines.toString(),
        ...(logLevel !== "ALL" && { level: logLevel }),
        ...(logSearch && { search: logSearch }),
      });
      const url = isAgent
        ? `/api/agent/${logSource}/logs?${params}`
        : `/api/logs/coordinator?${params}`;
      const r = await fetch(url, { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) {
        const data = await r.json();
        setLogEntries(data.entries || []);
      }
    } catch {}
    setLogLoading(false);
  }, [sessionToken, logSource, logSubFile, logLevel, logSearch, logLines]);

  const fetchComplianceConfig = React.useCallback(async () => {
    try {
      const r = await fetch("/api/admin/compliance-config", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) setComplianceConfig(await r.json());
    } catch {}
  }, [sessionToken]);

  const fetchComplianceLogs = React.useCallback(async () => {
    try {
      const r = await fetch("/api/admin/compliance-logs?lines=100", { headers: { "X-Session-Token": sessionToken } });
      if (r.ok) {
        const data = await r.json();
        setComplianceEntries(data.entries || []);
      }
    } catch {}
  }, [sessionToken]);

  React.useEffect(() => { fetchLogConfig(); fetchAgents(); fetchComplianceConfig(); }, []);
  React.useEffect(() => { fetchLogs(); }, [logSource, logSubFile]);

  React.useEffect(() => {
    if (!logAutoRefresh) return;
    const iv = setInterval(fetchLogs, 5000);
    return () => clearInterval(iv);
  }, [logAutoRefresh, fetchLogs]);

  const applyLogConfig = async () => {
    if (!logConfig) return;
    try {
      await fetch("/api/admin/log-config", {
        method: "PUT",
        headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
        body: JSON.stringify(logConfig),
      });
      fetchLogConfig();
    } catch {}
  };

  const updateComplianceConfig = async (updates) => {
    const newCfg = { ...complianceConfig, ...updates };
    try {
      await fetch("/api/admin/compliance-config", {
        method: "PUT",
        headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
        body: JSON.stringify(newCfg),
      });
      setComplianceConfig(newCfg);
    } catch {}
  };

  const levelColors = { DEBUG: "text-gray-500", INFO: "text-gray-300", WARNING: "text-yellow-400", ERROR: "text-red-400", CRITICAL: "text-red-300 font-bold" };
  const levelOptions = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"];
  const isAgent = !["dashboard", "coordinator", "console_audit"].includes(logSource);

  return (
  <div className="space-y-4">
    <h2 className="text-xl font-semibold text-white">Logging</h2>

    {/* Log Level Controls */}
    <div className="bg-gray-800 rounded-lg p-4">
      <h3 className="text-sm font-semibold text-gray-300 mb-3">Log Levels</h3>
      <div className="flex flex-wrap gap-4 items-end">
        <div>
          <label className="block text-xs text-gray-400 mb-1">Coordinator</label>
          <select value={logConfig?.coordinator_level || "INFO"} onChange={e => setLogConfig({...logConfig, coordinator_level: e.target.value})}
            className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600">
            {levelOptions.map(l => <option key={l} value={l}>{l}</option>)}
          </select>
        </div>
        <div>
          <label className="block text-xs text-gray-400 mb-1">Dashboard</label>
          <select value={logConfig?.dashboard_level || "INFO"} onChange={e => setLogConfig({...logConfig, dashboard_level: e.target.value})}
            className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600">
            {levelOptions.map(l => <option key={l} value={l}>{l}</option>)}
          </select>
        </div>
        <div>
          <label className="block text-xs text-gray-400 mb-1">Default Agent</label>
          <select value={logConfig?.default_agent_level || "INFO"} onChange={e => setLogConfig({...logConfig, default_agent_level: e.target.value})}
            className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600">
            {levelOptions.map(l => <option key={l} value={l}>{l}</option>)}
          </select>
        </div>
        {agents.map(a => (
          <div key={a.hostname}>
            <label className="block text-xs text-gray-400 mb-1">{a.hostname}</label>
            <select value={(logConfig?.agent_levels || {})[a.hostname] || logConfig?.default_agent_level || "INFO"}
              onChange={e => setLogConfig({...logConfig, agent_levels: {...(logConfig?.agent_levels || {}), [a.hostname]: e.target.value}})}
              className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600">
              {levelOptions.map(l => <option key={l} value={l}>{l}</option>)}
            </select>
          </div>
        ))}
        <button onClick={applyLogConfig}
          className="px-3 py-1 bg-amber-600 hover:bg-amber-500 text-white text-sm rounded">
          Apply
        </button>
      </div>
    </div>

    {/* Log Viewer */}
    <div className="bg-gray-800 rounded-lg p-4">
      <div className="flex items-center justify-between mb-3">
        <h3 className="text-sm font-semibold text-gray-300">Log Viewer</h3>
        <div className="flex gap-2 items-center">
          <label className="text-xs text-gray-400 flex items-center gap-1">
            <input type="checkbox" checked={logAutoRefresh} onChange={e => setLogAutoRefresh(e.target.checked)}
              className="rounded" />
            Auto-refresh
          </label>
          <button onClick={fetchLogs} disabled={logLoading}
            className="px-2 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 text-xs rounded">
            {logLoading ? "Loading..." : "Refresh"}
          </button>
        </div>
      </div>

      {/* Source tabs */}
      <div className="flex gap-1 mb-3 flex-wrap">
        {["dashboard", "coordinator", "console_audit"].map(s => (
          <button key={s} onClick={() => { setLogSource(s); }}
            className={`px-2 py-1 text-xs rounded ${logSource === s ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
            {s === "console_audit" ? "Console Audit" : s.charAt(0).toUpperCase() + s.slice(1)}
          </button>
        ))}
        {agents.map(a => (
          <button key={a.hostname} onClick={() => { setLogSource(a.hostname); setLogSubFile("mcp-server"); }}
            className={`px-2 py-1 text-xs rounded ${logSource === a.hostname ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
            {a.hostname}
          </button>
        ))}
      </div>

      {/* Agent sub-file selector */}
      {isAgent && (
        <div className="flex gap-1 mb-3">
          {["mcp-server", "local-agent", "analytics-audit"].map(f => (
            <button key={f} onClick={() => setLogSubFile(f)}
              className={`px-2 py-1 text-xs rounded ${logSubFile === f ? "bg-blue-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
              {f}
            </button>
          ))}
        </div>
      )}

      {/* Filters */}
      <div className="flex gap-2 mb-3 items-center">
        <select value={logLevel} onChange={e => setLogLevel(e.target.value)}
          className="bg-gray-700 text-gray-200 text-xs rounded px-2 py-1 border border-gray-600">
          <option value="ALL">All Levels</option>
          {levelOptions.map(l => <option key={l} value={l}>{l}</option>)}
        </select>
        <input type="text" placeholder="Search..." value={logSearch} onChange={e => setLogSearch(e.target.value)}
          onKeyDown={e => e.key === "Enter" && fetchLogs()}
          className="bg-gray-700 text-gray-200 text-xs rounded px-2 py-1 border border-gray-600 flex-1" />
        <input type="number" value={logLines} onChange={e => setLogLines(Math.min(parseInt(e.target.value) || 200, 2000))}
          className="bg-gray-700 text-gray-200 text-xs rounded px-2 py-1 border border-gray-600 w-20" />
        <span className="text-xs text-gray-500">lines</span>
      </div>

      {/* Log output */}
      <div className="bg-gray-900 rounded p-3 font-mono text-xs max-h-96 overflow-y-auto space-y-0.5">
        {logEntries.length === 0 && <p className="text-gray-500 italic">No log entries{logLoading ? " (loading...)" : ""}</p>}
        {logEntries.map((e, i) => (
          <div key={i} className={`${levelColors[e.level] || "text-gray-300"} leading-tight`}>
            <span className="text-gray-600">{(e.ts || "").replace("T", " ").slice(0, 19)}</span>
            {" "}
            <span className={`font-semibold ${levelColors[e.level] || ""}`}>{(e.level || "").padEnd(8)}</span>
            {" "}
            <span className="text-gray-400">{e.logger || ""}</span>
            {" "}
            <span>{e.msg || JSON.stringify(e)}</span>
          </div>
        ))}
      </div>
    </div>

    {/* Compliance Panel */}
    <div className="bg-gray-800 rounded-lg p-4">
      <div className="flex items-center justify-between mb-3 cursor-pointer" onClick={() => setShowCompliance(!showCompliance)}>
        <h3 className="text-sm font-semibold text-gray-300">Compliance Logging</h3>
        <div className="flex items-center gap-3">
          <span className={`text-xs px-2 py-0.5 rounded ${complianceConfig?.enabled ? "bg-green-900 text-green-300" : "bg-gray-700 text-gray-400"}`}>
            {complianceConfig?.enabled ? "Enabled" : "Disabled"}
          </span>
          <span className="text-gray-500">{showCompliance ? "▲" : "▼"}</span>
        </div>
      </div>

      {showCompliance && complianceConfig && (
        <div className="space-y-4">
          <div className="flex flex-wrap gap-4 items-center">
            <label className="flex items-center gap-2 text-sm text-gray-300">
              <input type="checkbox" checked={complianceConfig.enabled}
                onChange={e => updateComplianceConfig({ enabled: e.target.checked })}
                className="rounded" />
              Enable compliance logging
            </label>
            <label className="flex items-center gap-2 text-sm text-gray-300">
              <input type="checkbox" checked={complianceConfig.log_prompts}
                onChange={e => updateComplianceConfig({ log_prompts: e.target.checked })}
                className="rounded" />
              Log prompts
            </label>
            <label className="flex items-center gap-2 text-sm text-gray-300">
              <input type="checkbox" checked={complianceConfig.log_responses}
                onChange={e => updateComplianceConfig({ log_responses: e.target.checked })}
                className="rounded" />
              Log responses
            </label>
          </div>
          <div className="flex gap-4 items-center">
            <div>
              <label className="block text-xs text-gray-400 mb-1">Response preview length</label>
              <input type="number" value={complianceConfig.response_preview_length}
                onChange={e => updateComplianceConfig({ response_preview_length: parseInt(e.target.value) || 500 })}
                className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-24" />
            </div>
            <div>
              <label className="block text-xs text-gray-400 mb-1">Exclude users (comma-sep)</label>
              <input type="text" value={(complianceConfig.exclude_users || []).join(", ")}
                onChange={e => updateComplianceConfig({ exclude_users: e.target.value.split(",").map(s => s.trim()).filter(Boolean) })}
                className="bg-gray-700 text-gray-200 text-sm rounded px-2 py-1 border border-gray-600 w-48" />
            </div>
          </div>

          {complianceConfig.enabled && (
            <div>
              <div className="flex items-center justify-between mb-2">
                <h4 className="text-sm text-gray-400">Recent Compliance Entries</h4>
                <button onClick={fetchComplianceLogs}
                  className="px-2 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 text-xs rounded">
                  Load
                </button>
              </div>
              <div className="bg-gray-900 rounded p-3 max-h-64 overflow-y-auto">
                {complianceEntries.length === 0 && <p className="text-gray-500 text-xs italic">No compliance entries</p>}
                <table className="w-full text-xs">
                  <thead>
                    <tr className="text-gray-400 border-b border-gray-700">
                      <th className="text-left py-1 pr-2">Time</th>
                      <th className="text-left py-1 pr-2">User</th>
                      <th className="text-left py-1 pr-2">Tab</th>
                      <th className="text-left py-1 pr-2">Prompt</th>
                      <th className="text-left py-1 pr-2">Model</th>
                      <th className="text-right py-1">Duration</th>
                    </tr>
                  </thead>
                  <tbody>
                    {complianceEntries.map((e, i) => (
                      <React.Fragment key={i}>
                        <tr className="text-gray-300 border-b border-gray-800 cursor-pointer hover:bg-gray-800"
                          onClick={() => setExpandedCompliance(expandedCompliance === i ? null : i)}>
                          <td className="py-1 pr-2 text-gray-500 whitespace-nowrap">
                            <span className="text-gray-600 mr-1">{expandedCompliance === i ? "▼" : "▶"}</span>
                            {(e.ts || "").replace("T", " ").slice(0, 19)}
                          </td>
                          <td className="py-1 pr-2">{e.username}</td>
                          <td className="py-1 pr-2">{e.tab_id}</td>
                          <td className="py-1 pr-2 truncate max-w-xs">{(e.prompt || "").slice(0, 80)}</td>
                          <td className="py-1 pr-2 text-gray-400">{e.model}</td>
                          <td className="py-1 text-right text-gray-400">{e.duration_ms ? `${(e.duration_ms/1000).toFixed(1)}s` : ""}</td>
                        </tr>
                        {expandedCompliance === i && (
                          <tr className="border-b border-gray-800">
                            <td colSpan={6} className="p-3 bg-gray-850">
                              <div className="space-y-2">
                                <div>
                                  <span className="text-xs font-semibold text-amber-400">Prompt:</span>
                                  <pre className="text-xs text-gray-300 mt-1 whitespace-pre-wrap bg-gray-900 rounded p-2 max-h-32 overflow-y-auto">{e.prompt}</pre>
                                </div>
                                <div>
                                  <span className="text-xs font-semibold text-blue-400">Response:</span>
                                  <pre className="text-xs text-gray-300 mt-1 whitespace-pre-wrap bg-gray-900 rounded p-2 max-h-48 overflow-y-auto">{e.response || e.response_preview || "(no response logged)"}</pre>
                                </div>
                              </div>
                            </td>
                          </tr>
                        )}
                      </React.Fragment>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  </div>
  );
}

function AdminPanel({ sessionToken, user, tabId, tabLabel, accessLevel, hasTabChat }) {
  const toast = useToast();
  const [adminSubTab, setAdminSubTab] = useState("users");
  const [llmConfig, setLlmConfig] = useState(null);
  const [llmSaving, setLlmSaving] = useState(false);
  const [llmError, setLlmError] = useState("");
  const [llmSuccess, setLlmSuccess] = useState("");
  const [llmTestResult, setLlmTestResult] = useState(null);
  const [llmTesting, setLlmTesting] = useState(false);

  // Tab Config state
  const [tabCfg, setTabCfg] = useState(null);
  const [tabCfgSaving, setTabCfgSaving] = useState(false);
  const [tabCfgError, setTabCfgError] = useState("");
  const [tabCfgSuccess, setTabCfgSuccess] = useState("");
  const [selectedTabId, setSelectedTabId] = useState("");

  // Agent Config state
  const [agentCfgs, setAgentCfgs] = useState({ configs: {} });
  const [agentCfgSaving, setAgentCfgSaving] = useState(false);
  const [agentCfgError, setAgentCfgError] = useState("");
  const [agentCfgSuccess, setAgentCfgSuccess] = useState("");
  const [selectedAgentHost, setSelectedAgentHost] = useState("");
  const [agentStatusList, setAgentStatusList] = useState([]);

  // Add / Remove agent state
  const [showAddAgent, setShowAddAgent] = useState(false);
  const AGENT_TYPE_DEFAULTS = {
    monitoring: { provider: "ollama", model: "qwen3:8b", profile: "ollama-only" },
    inference: { provider: "ollama", model: "qwen3:14b", profile: "ollama-only" },
    coding: { provider: "anthropic", model: "claude-sonnet-4-5-20250929", profile: "claude-code" },
    triage: { provider: "ollama", model: "qwen3:8b", profile: "ollama-only" },
    analytics: { provider: "anthropic", model: "claude-sonnet-4-5-20250929", profile: "ollama-only" },
    "analytics-secure": { provider: "vllm", model: "Qwen/Qwen3-32B-AWQ", profile: "ollama-only" },
  };
  const [newAgent, setNewAgent] = useState({ hostname: "", ip: "", port: "8900", profile: "ollama-only", agent_type: "monitoring", provider: "ollama", model: "" });

  // Watchdog Config state
  const [watchdogCfg, setWatchdogCfg] = useState(null);
  const [watchdogSaving, setWatchdogSaving] = useState(false);
  const [selectedCheckId, setSelectedCheckId] = useState("");
  const [testResult, setTestResult] = useState(null);
  const [testing, setTesting] = useState(false);
  const [selectedDiscoveryId, setSelectedDiscoveryId] = useState("");

  const handleAddAgent = async () => {
    if (!newAgent.hostname || !newAgent.ip) { setAgentCfgError("Hostname and IP are required"); return; }
    setAgentCfgError("");
    try {
      const res = await fetch("/api/hosts", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ hostname: newAgent.hostname, ip: newAgent.ip, port: parseInt(newAgent.port), profile: newAgent.profile, agent_type: newAgent.agent_type, provider: newAgent.provider, model: newAgent.model }),
      });
      if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to add agent"); }
      setShowAddAgent(false);
      setNewAgent({ hostname: "", ip: "", port: "8900", profile: "ollama-only", agent_type: "monitoring", provider: "ollama", model: "" });
      setAgentCfgSuccess("Agent added to fleet.");
      setTimeout(() => setAgentCfgSuccess(""), 3000);
      // Refresh agent list
      const statusData = await fetch("/api/agents/status", { headers: { "X-Session-Token": sessionToken } }).then(r => r.ok ? r.json() : { agents: [] });
      setAgentStatusList(statusData.agents || statusData || []);
    } catch (err) { setAgentCfgError(err.message); }
  };

  const handleRemoveAgent = async (hostname) => {
    if (!confirm(`Remove agent "${hostname}" from fleet? This only removes the registration — the agent's services will keep running on the host.`)) return;
    setAgentCfgError("");
    try {
      const res = await fetch(`/api/hosts/${hostname}`, {
        method: "DELETE",
        headers: { "X-Session-Token": sessionToken },
      });
      if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to remove agent"); }
      setSelectedAgentHost("");
      setAgentCfgSuccess("Agent removed from fleet.");
      setTimeout(() => setAgentCfgSuccess(""), 3000);
      // Refresh agent list
      const statusData = await fetch("/api/agents/status", { headers: { "X-Session-Token": sessionToken } }).then(r => r.ok ? r.json() : { agents: [] });
      setAgentStatusList(statusData.agents || statusData || []);
    } catch (err) { setAgentCfgError(err.message); }
  };

  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/llm-config", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed to load LLM config"))
      .then((data) => setLlmConfig(data))
      .catch(() => setLlmConfig({ ...{provider:"anthropic",model:"claude-sonnet-4-5-20250929",api_key:"",base_url:"",temperature:0.7,max_tokens:4096,system_prompt:"",smart_routing:true,api_key_set:false} }));
  }, [sessionToken]);

  // Fetch tab config
  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/tab-config", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => {
        setTabCfg(data);
        const tabIds = Object.keys(data.tabs || {});
        if (tabIds.length > 0 && !selectedTabId) setSelectedTabId(tabIds[0]);
      })
      .catch(() => setTabCfg(null));
  }, [sessionToken]);

  // Fetch agent configs + agent status
  useEffect(() => {
    if (!sessionToken) return;
    Promise.all([
      fetch("/api/admin/agent-config", { headers: { "X-Session-Token": sessionToken } })
        .then(r => r.ok ? r.json() : { configs: {} }),
      fetch("/api/agents/status", { headers: { "X-Session-Token": sessionToken } })
        .then(r => r.ok ? r.json() : { agents: [] }),
    ]).then(([cfgData, statusData]) => {
      setAgentCfgs(cfgData);
      const agents = statusData.agents || [];
      setAgentStatusList(agents);
      if (agents.length > 0 && !selectedAgentHost) setSelectedAgentHost(agents[0].hostname || agents[0].name || agents[0].id);
    });
  }, [sessionToken]);

  // Fetch watchdog config
  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/watchdog-config", { headers: { "X-Session-Token": sessionToken } })
      .then(r => r.ok ? r.json() : null)
      .then(data => { if (data) setWatchdogCfg(data); })
      .catch(() => {});
  }, [sessionToken]);

  const handleSaveAgentConfig = async () => {
    if (!selectedAgentHost) return;
    setAgentCfgSaving(true);
    setAgentCfgError("");
    setAgentCfgSuccess("");
    try {
      const cfg = (agentCfgs?.configs || {})[selectedAgentHost] || {};
      const res = await fetch(`/api/admin/agent-config/${selectedAgentHost}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify(cfg),
      });
      if (!res.ok) {
        const e = await res.json().catch(() => ({}));
        throw new Error(e.detail || "Failed to save");
      }
      setAgentCfgSuccess("Agent config saved. Agent will pick it up within 15 minutes.");
      setTimeout(() => setAgentCfgSuccess(""), 5000);
    } catch (err) { setAgentCfgError(err.message); }
    finally { setAgentCfgSaving(false); }
  };

  const updateAgentCfg = (field, value) => {
    if (!selectedAgentHost || !agentCfgs) return;
    const current = (agentCfgs.configs || {})[selectedAgentHost] || { mode: "observer", prompt: "", interval: 300, enabled: true };
    setAgentCfgs({
      ...agentCfgs,
      configs: {
        ...agentCfgs.configs,
        [selectedAgentHost]: { ...current, [field]: value },
      },
    });
  };

  const handleSaveTabConfig = async () => {
    if (!tabCfg || !selectedTabId) return;
    setTabCfgSaving(true);
    setTabCfgError("");
    setTabCfgSuccess("");
    try {
      const tabData = { ...tabCfg.tabs[selectedTabId] };
      const isNew = tabData._isNew;
      delete tabData._isNew;
      if (isNew) {
        // Create new business tab
        const res = await fetch("/api/admin/tab-config", {
          method: "POST",
          headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
          body: JSON.stringify({ tab_id: selectedTabId, ...tabData }),
        });
        if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to create"); }
        // Clear _isNew flag in local state
        setTabCfg({ ...tabCfg, tabs: { ...tabCfg.tabs, [selectedTabId]: tabData } });
        setTabCfgSuccess("Business tab created.");
      } else {
        // Update existing tab
        const res = await fetch("/api/admin/tab-config", {
          method: "PUT",
          headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
          body: JSON.stringify({ tabs: { [selectedTabId]: tabData } }),
        });
        if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to save"); }
        setTabCfgSuccess("Tab configuration saved.");
      }
      setTimeout(() => setTabCfgSuccess(""), 3000);
    } catch (err) { setTabCfgError(err.message); }
    finally { setTabCfgSaving(false); }
  };

  const updateSelectedTab = (field, value) => {
    if (!tabCfg || !selectedTabId) return;
    setTabCfg({
      ...tabCfg,
      tabs: {
        ...tabCfg.tabs,
        [selectedTabId]: { ...tabCfg.tabs[selectedTabId], [field]: value }
      }
    });
  };

  const updateRoleAccess = (role, level) => {
    if (!tabCfg || !selectedTabId) return;
    const current = tabCfg.tabs[selectedTabId].role_access || {};
    const updated = { ...current };
    if (level === "none") { delete updated[role]; } else { updated[role] = level; }
    updateSelectedTab("role_access", updated);
  };

  const handleSaveLlmConfig = async () => {
    setLlmSaving(true);
    setLlmError("");
    setLlmSuccess("");
    try {
      const res = await fetch("/api/admin/llm-config", {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify(llmConfig),
      });
      if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to save"); }
      setLlmSuccess("Configuration saved.");
      setTimeout(() => setLlmSuccess(""), 3000);
    } catch (err) { setLlmError(err.message); }
    finally { setLlmSaving(false); }
  };

  const handleTestLlm = async () => {
    setLlmTesting(true);
    setLlmTestResult(null);
    try {
      const res = await fetch("/api/admin/llm-test", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      });
      if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Test failed"); }
      const data = await res.json();
      setLlmTestResult({ ok: true, preview: data.response_preview });
    } catch (err) { setLlmTestResult({ ok: false, error: err.message }); }
    finally { setLlmTesting(false); }
  };

  // ── System Prompts state ──
  const [systemPrompts, setSystemPrompts] = useState(null);
  const [promptsSaving, setPromptsSaving] = useState(false);
  const [promptsError, setPromptsError] = useState("");
  const [promptsSuccess, setPromptsSuccess] = useState("");
  const [selectedPromptUser, setSelectedPromptUser] = useState("");
  const [previewResult, setPreviewResult] = useState(null);
  const [previewLoading, setPreviewLoading] = useState(false);

  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/system-prompts", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => setSystemPrompts(data))
      .catch(() => setSystemPrompts({ base: "", roles: {}, users: {} }));
  }, [sessionToken]);

  const handleSaveSystemPrompts = async () => {
    setPromptsSaving(true);
    setPromptsError("");
    setPromptsSuccess("");
    try {
      const res = await fetch("/api/admin/system-prompts", {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify(systemPrompts),
      });
      if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Failed to save"); }
      setPromptsSuccess("System prompts saved.");
      setTimeout(() => setPromptsSuccess(""), 3000);
    } catch (err) { setPromptsError(err.message); }
    finally { setPromptsSaving(false); }
  };

  const handlePreviewPrompt = async (username) => {
    setPreviewLoading(true);
    setPreviewResult(null);
    try {
      const res = await fetch("/api/admin/system-prompts/preview", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ username }),
      });
      if (!res.ok) throw new Error("Preview failed");
      const data = await res.json();
      setPreviewResult(data);
    } catch (err) { setPreviewResult({ error: err.message }); }
    finally { setPreviewLoading(false); }
  };

  const [users, setUsers] = useState([]);

  // Fetch users from API
  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/users", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => {
        const list = Array.isArray(data) ? data : (data.users || []);
        setUsers(list);
      })
      .catch(() => {});
  }, [sessionToken]);

  const [showNewUserForm, setShowNewUserForm] = useState(false);
  const [newUser, setNewUser] = useState({
    username: "",
    password: "",
    display_name: "",
    email: "",
    department: "",
    roles: [],
    ldap_bypass: false,
  });
  const [passwordRules, setPasswordRules] = useState(null);
  const [createUserError, setCreateUserError] = useState("");
  const [ldapLookupStatus, setLdapLookupStatus] = useState(""); // "", "looking", "found", "not_found", "error"
  const [ldapLookupError, setLdapLookupError] = useState("");

  // LDAP lookup when username changes (debounced)
  useEffect(() => {
    if (!sessionToken || !ldapStatus?.enabled || newUser.ldap_bypass || !newUser.username || newUser.username.length < 2) {
      setLdapLookupStatus("");
      setLdapLookupError("");
      return;
    }
    const timer = setTimeout(() => {
      setLdapLookupStatus("looking");
      setLdapLookupError("");
      fetch(`/api/admin/ldap/lookup/${encodeURIComponent(newUser.username)}`, { headers: { "X-Session-Token": sessionToken } })
        .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
        .then((data) => {
          if (data.found) {
            setLdapLookupStatus("found");
            setNewUser((prev) => ({
              ...prev,
              display_name: prev.display_name || data.display_name || "",
              email: prev.email || data.email || "",
              department: prev.department || data.department || "",
            }));
          } else if (data.error && !data.error.includes("not found")) {
            setLdapLookupStatus("error");
            setLdapLookupError(data.error);
          } else {
            setLdapLookupStatus("not_found");
          }
        })
        .catch(() => setLdapLookupStatus(""));
    }, 600);
    return () => clearTimeout(timer);
  }, [newUser.username, ldapStatus?.enabled, newUser.ldap_bypass, sessionToken]);

  // Fetch password rules
  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/password-rules", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => setPasswordRules(data))
      .catch(() => setPasswordRules({ min_length: 12, require_uppercase: true, require_lowercase: true, require_digit: true, require_special: true }));
  }, [sessionToken]);

  // LDAP status state
  const [ldapStatus, setLdapStatus] = useState(null);
  useEffect(() => {
    if (!sessionToken) return;
    fetch("/api/admin/ldap/status", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => setLdapStatus(data))
      .catch(() => setLdapStatus({ enabled: false, status: "unknown" }));
  }, [sessionToken]);

  const [editingUser, setEditingUser] = useState(null); // username being edited
  const [editFields, setEditFields] = useState({});

  const handleStartEdit = (user) => {
    setEditingUser(user.username);
    setEditFields({ display_name: user.display_name || "", email: user.email || "", department: user.department || "" });
  };

  const handleSaveEdit = async (username) => {
    try {
      const res = await fetch(`/api/admin/users/${username}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify(editFields),
      });
      if (res.ok) {
        setUsers(users.map((u) => u.username === username ? { ...u, ...editFields } : u));
        setEditingUser(null);
      }
    } catch (err) {}
  };

  const handleDeleteUser = async (username) => {
    if (!confirm(`Delete user "${username}"? This cannot be undone.`)) return;
    try {
      const res = await fetch(`/api/admin/users/${username}`, {
        method: "DELETE",
        headers: { "X-Session-Token": sessionToken },
      });
      if (res.ok) {
        setUsers(users.filter((u) => u.username !== username));
      } else {
        const err = await res.json().catch(() => ({}));
        toast(err.detail || "Failed to delete user", "error");
      }
    } catch (err) { toast("Failed to delete user", "error"); }
  };

  const handleToggleLdapBypass = (u) => {
    const newVal = !u.ldap_bypass;
    fetch(`/api/admin/users/${u.username}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      body: JSON.stringify({ ldap_bypass: newVal }),
    })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then(() => setUsers(users.map((x) => x.username === u.username ? { ...x, ldap_bypass: newVal } : x)))
      .catch(() => {});
  };

  const [show2FAModal, setShow2FAModal] = useState(false);
  const [selected2FAUser, setSelected2FAUser] = useState(null);
  const [twoFAMethod, setTwoFAMethod] = useState("totp");
  const [twoFAQRCode, setTwoFAQRCode] = useState(null);
  const [twoFAVerifyCode, setTwoFAVerifyCode] = useState("");
  const [twoFAModalError, setTwoFAModalError] = useState("");
  const [twoFAStep, setTwoFAStep] = useState("method"); // "method", "setup", "verify"

  const allRoles = ["admin", "fleet_ops", "distribution_ops", "config_center", "services", "front_office", "sales", "manager", "executive"];

  const handleToggleRole = (username, role) => {
    const user = users.find((u) => u.username === username);
    if (!user) return;
    const newRoles = user.roles.includes(role) ? user.roles.filter((r) => r !== role) : [...user.roles, role];
    fetch(`/api/admin/users/${username}/roles`, {
      method: "PUT",
      headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      body: JSON.stringify({ roles: newRoles }),
    })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then(() => setUsers(users.map((u) => u.username === username ? { ...u, roles: newRoles } : u)))
      .catch(() => {});
  };

  const handleToggleEnabled = (username) => {
    const user = users.find((u) => u.username === username);
    if (!user) return;
    const newEnabled = !user.enabled;
    fetch(`/api/admin/users/${username}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      body: JSON.stringify({ enabled: newEnabled }),
    })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then(() => setUsers(users.map((u) => u.username === username ? { ...u, enabled: newEnabled } : u)))
      .catch(() => {});
  };

  const needsLocalPassword = !ldapStatus?.enabled || newUser.ldap_bypass;

  const checkPasswordComplexity = (pw) => {
    if (!passwordRules || !pw) return [];
    const fails = [];
    if (pw.length < passwordRules.min_length) fails.push(`${passwordRules.min_length}+ characters`);
    if (passwordRules.require_uppercase && !/[A-Z]/.test(pw)) fails.push("uppercase letter");
    if (passwordRules.require_lowercase && !/[a-z]/.test(pw)) fails.push("lowercase letter");
    if (passwordRules.require_digit && !/[0-9]/.test(pw)) fails.push("digit");
    if (passwordRules.require_special && !/[!@#$%^&*()_+\-=\[\]{}|;:',.<>?/~`]/.test(pw)) fails.push("special character");
    return fails;
  };

  const passwordIssues = needsLocalPassword ? checkPasswordComplexity(newUser.password) : [];

  const handleAddUser = async () => {
    setCreateUserError("");
    if (!newUser.username || newUser.roles.length === 0) {
      setCreateUserError("Username and at least one role are required");
      return;
    }
    if (needsLocalPassword && !newUser.password) {
      setCreateUserError("Password is required for local/bypass users");
      return;
    }
    if (needsLocalPassword && passwordIssues.length > 0) {
      setCreateUserError("Password does not meet complexity requirements");
      return;
    }

    try {
      const body = {
        username: newUser.username,
        display_name: newUser.display_name || newUser.username,
        email: newUser.email,
        department: newUser.department,
        roles: newUser.roles,
        ldap_bypass: newUser.ldap_bypass,
      };
      if (needsLocalPassword) body.password = newUser.password;

      const res = await fetch("/api/admin/users", {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || "Failed to create user");
      }
      // Refresh user list from API
      const listRes = await fetch("/api/admin/users", { headers: { "X-Session-Token": sessionToken } });
      if (listRes.ok) {
        const data = await listRes.json();
        const list = Array.isArray(data) ? data : (data.users || []);
        setUsers(list);
      }
      setNewUser({ username: "", password: "", display_name: "", email: "", department: "", roles: [], ldap_bypass: false });
      setShowNewUserForm(false);
    } catch (err) {
      setCreateUserError(err.message);
    }
  };

  const handleEnable2FA = async (user) => {
    setSelected2FAUser(user);
    setTwoFAMethod("totp");
    setTwoFAStep("method");
    setShow2FAModal(true);
    setTwoFAModalError("");
    setTwoFAQRCode(null);
    setTwoFAVerifyCode("");
  };

  const handleSetup2FA = async () => {
    if (!selected2FAUser) return;

    setTwoFAModalError("");
    try {
      const response = await fetch(`/api/admin/users/${selected2FAUser.username}/2fa/enable`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ method: twoFAMethod }),
      });

      if (response.ok) {
        const data = await response.json();
        setTwoFAQRCode(data.qr_code);
        setTwoFAStep("verify");
      } else {
        throw new Error("Failed to setup 2FA");
      }
    } catch (err) {
      setTwoFAModalError(err.message || "Failed to setup 2FA");
    }
  };

  const handleVerify2FASetup = async () => {
    if (!selected2FAUser || !twoFAVerifyCode.trim()) {
      setTwoFAModalError("Please enter verification code");
      return;
    }

    try {
      const response = await fetch(`/api/admin/users/${selected2FAUser.username}/2fa/verify-setup`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
        body: JSON.stringify({ code: twoFAVerifyCode }),
      });

      if (response.ok) {
        setUsers(
          users.map((u) => {
            if (u.username === selected2FAUser.username) {
              return { ...u, twofa_enabled: true, twofa_method: twoFAMethod };
            }
            return u;
          })
        );
        setShow2FAModal(false);
        toast("2FA enabled successfully", "success");
      } else {
        throw new Error("Verification failed");
      }
    } catch (err) {
      setTwoFAModalError(err.message || "Verification failed");
    }
  };

  const [disable2FAConfirm, setDisable2FAConfirm] = useState(null);
  const handleDisable2FA = async (targetUser) => {
    try {
      const response = await fetch(`/api/admin/users/${targetUser.username}/2fa/disable`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Session-Token": sessionToken },
      });

      if (response.ok) {
        setUsers(
          users.map((u) => {
            if (u.username === targetUser.username) {
              return { ...u, twofa_enabled: false, twofa_method: null };
            }
            return u;
          })
        );
        toast("2FA disabled successfully", "success");
      } else {
        throw new Error("Failed to disable 2FA");
      }
    } catch (err) {
      toast(err.message || "Failed to disable 2FA", "error");
    }
  };

  const showBaseUrl = llmConfig?.provider === "vllm" || llmConfig?.provider === "ollama";
  const showApiKey = llmConfig?.provider === "anthropic" || llmConfig?.provider === "openai";
  const showSmartRouting = llmConfig?.provider === "anthropic";

  const modelSuggestions = {
    anthropic: ["claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001", "claude-opus-4-5-20251101"],
    openai: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"],
    vllm: [],
    ollama: ["qwen3:14b", "llama3.1:8b", "mistral:7b"],
  };

  return (
    <div className="h-full overflow-y-auto p-2 sm:p-4 md:p-6 bg-gray-900">
      <div className="flex flex-wrap gap-1 sm:gap-2 mb-4 sm:mb-6">
        <button onClick={() => setAdminSubTab("users")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "users" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          Users
        </button>
        <button onClick={() => setAdminSubTab("llm")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "llm" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          LLM Settings
        </button>
        <button onClick={() => setAdminSubTab("prompts")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "prompts" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          System Prompts
        </button>
        <button onClick={() => setAdminSubTab("tabs")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "tabs" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          Tab Config
        </button>
        <button onClick={() => setAdminSubTab("agents")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "agents" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          Agent Config
        </button>
        <button onClick={() => setAdminSubTab("watchdog")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "watchdog" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          Watchdog
        </button>
        <button onClick={() => setAdminSubTab("logs")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "logs" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          Logs
        </button>
        <button onClick={() => setAdminSubTab("mfa")}
          className={`px-3 py-1.5 rounded text-sm font-medium transition ${adminSubTab === "mfa" ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
          MFA
        </button>
      </div>

      {adminSubTab === "llm" && llmConfig && (
        <div className="space-y-6">
          <h2 className="text-xl font-semibold text-white">LLM Configuration</h2>
          {llmError && <div className="p-3 rounded bg-red-900 text-red-200 text-sm">{llmError}</div>}
          {llmSuccess && <div className="p-3 rounded bg-green-900 text-green-200 text-sm">{llmSuccess}</div>}

          <div className="bg-gray-800 rounded-lg p-4 border border-gray-700 space-y-4">
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div>
                <label className="block text-xs text-gray-400 mb-1">Provider</label>
                <select value={llmConfig.provider} onChange={(e) => setLlmConfig({ ...llmConfig, provider: e.target.value })}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white">
                  <option value="anthropic">Anthropic</option>
                  <option value="openai">OpenAI</option>
                  <option value="vllm">vLLM (OpenAI-compatible)</option>
                  <option value="ollama">Ollama</option>
                </select>
              </div>
              <div>
                <label className="block text-xs text-gray-400 mb-1">Model</label>
                <input value={llmConfig.model} onChange={(e) => setLlmConfig({ ...llmConfig, model: e.target.value })}
                  list="model-suggestions"
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white" />
                <datalist id="model-suggestions">
                  {(modelSuggestions[llmConfig.provider] || []).map((m) => <option key={m} value={m} />)}
                </datalist>
              </div>
              {showApiKey && (
                <div>
                  <label className="block text-xs text-gray-400 mb-1">
                    API Key {llmConfig.api_key_set && !llmConfig.api_key?.startsWith("sk-") && <span className="text-green-400">(set)</span>}
                  </label>
                  <input type="password" value={llmConfig.api_key || ""} onChange={(e) => setLlmConfig({ ...llmConfig, api_key: e.target.value })}
                    placeholder={llmConfig.api_key_set ? "Leave blank to keep current" : "Enter API key"}
                    className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white" />
                  {llmConfig.provider === "anthropic" && llmConfig.api_key_set && (
                    <p className="text-xs text-gray-500 mt-1">Key from environment or previous config</p>
                  )}
                </div>
              )}
              {showBaseUrl && (
                <div>
                  <label className="block text-xs text-gray-400 mb-1">Base URL</label>
                  <input value={llmConfig.base_url || ""} onChange={(e) => setLlmConfig({ ...llmConfig, base_url: e.target.value })}
                    placeholder={llmConfig.provider === "ollama" ? "http://localhost:11434/v1" : "http://host:port/v1"}
                    className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white" />
                </div>
              )}
              <div>
                <label className="block text-xs text-gray-400 mb-1">Temperature ({llmConfig.temperature})</label>
                <input type="range" min="0" max="2" step="0.1" value={llmConfig.temperature}
                  onChange={(e) => setLlmConfig({ ...llmConfig, temperature: parseFloat(e.target.value) })}
                  className="w-full" />
              </div>
              <div>
                <label className="block text-xs text-gray-400 mb-1">Max Tokens</label>
                <input type="number" value={llmConfig.max_tokens} onChange={(e) => setLlmConfig({ ...llmConfig, max_tokens: parseInt(e.target.value) || 4096 })}
                  min="1" max="32768"
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white" />
              </div>
            </div>
            {showSmartRouting && (
              <label className="flex items-center gap-2 text-sm text-gray-300 cursor-pointer">
                <input type="checkbox" checked={llmConfig.smart_routing || false}
                  onChange={(e) => setLlmConfig({ ...llmConfig, smart_routing: e.target.checked })}
                  className="rounded border-gray-700" />
                Smart routing (auto-select haiku/sonnet/opus based on message complexity)
              </label>
            )}
            <div>
              <label className="block text-xs text-gray-400 mb-1">System Prompt</label>
              <textarea value={llmConfig.system_prompt || ""} onChange={(e) => setLlmConfig({ ...llmConfig, system_prompt: e.target.value })}
                rows={10}
                className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y min-h-[120px]" />
            </div>
            <div className="flex gap-3 items-center">
              <button onClick={handleSaveLlmConfig} disabled={llmSaving}
                className="bg-amber-600 hover:bg-amber-500 text-white px-4 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
                {llmSaving ? "Saving..." : "Save"}
              </button>
              <button onClick={handleTestLlm} disabled={llmTesting}
                className="bg-gray-600 hover:bg-gray-500 text-white px-4 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
                {llmTesting ? "Testing..." : "Test Connection"}
              </button>
              {llmTestResult && (
                <span className={`text-sm ${llmTestResult.ok ? "text-green-400" : "text-red-400"}`}>
                  {llmTestResult.ok ? `OK: "${llmTestResult.preview?.substring(0, 60)}..."` : llmTestResult.error}
                </span>
              )}
            </div>
          </div>
        </div>
      )}

      {adminSubTab === "prompts" && systemPrompts && (
        <div className="space-y-6">
          <h2 className="text-xl font-semibold text-white">System Prompts</h2>
          <p className="text-sm text-gray-400">
            Layered prompts assembled at chat time: <span className="text-amber-400">Base</span> + <span className="text-amber-400">Role prompts</span> (for user's roles) + <span className="text-amber-400">User-specific prompt</span>.
          </p>
          {promptsError && <div className="p-3 rounded bg-red-900/50 border border-red-700 text-red-200 text-sm">{promptsError}</div>}
          {promptsSuccess && <div className="p-3 rounded bg-green-900/50 border border-green-700 text-green-200 text-sm">{promptsSuccess}</div>}

          {/* Base Prompt */}
          <div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
            <label className="block text-sm font-medium text-amber-400 mb-2">Base Prompt <span className="text-gray-500 font-normal">(always included)</span></label>
            <textarea
              value={systemPrompts.base || ""}
              onChange={(e) => setSystemPrompts({ ...systemPrompts, base: e.target.value })}
              rows={4}
              className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y focus:outline-none focus:border-amber-500"
              placeholder="You are HAL, a business intelligence assistant..."
            />
          </div>

          {/* Role Prompts */}
          <div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
            <label className="block text-sm font-medium text-amber-400 mb-3">Role Prompts <span className="text-gray-500 font-normal">(included when user has the role)</span></label>
            <div className="space-y-3">
              {allRoles.map((role) => (
                <div key={role}>
                  <label className="block text-xs text-gray-400 mb-1 font-mono">{role}</label>
                  <textarea
                    value={(systemPrompts.roles || {})[role] || ""}
                    onChange={(e) => setSystemPrompts({
                      ...systemPrompts,
                      roles: { ...systemPrompts.roles, [role]: e.target.value }
                    })}
                    rows={2}
                    className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y focus:outline-none focus:border-amber-500"
                    placeholder={`Instructions for ${role} users...`}
                  />
                </div>
              ))}
            </div>
          </div>

          {/* User-Specific Prompts */}
          <div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
            <label className="block text-sm font-medium text-amber-400 mb-3">User-Specific Prompts <span className="text-gray-500 font-normal">(optional, per user)</span></label>
            {Object.entries(systemPrompts.users || {}).map(([uname, prompt]) => (
              <div key={uname} className="mb-3">
                <div className="flex items-center justify-between mb-1">
                  <label className="text-xs text-gray-400 font-mono">@{uname}</label>
                  <button
                    onClick={() => {
                      const updated = { ...systemPrompts.users };
                      delete updated[uname];
                      setSystemPrompts({ ...systemPrompts, users: updated });
                    }}
                    className="text-xs text-red-400 hover:text-red-300 transition"
                  >Remove</button>
                </div>
                <textarea
                  value={prompt}
                  onChange={(e) => setSystemPrompts({
                    ...systemPrompts,
                    users: { ...systemPrompts.users, [uname]: e.target.value }
                  })}
                  rows={2}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y focus:outline-none focus:border-amber-500"
                />
              </div>
            ))}
            {Object.keys(systemPrompts.users || {}).length === 0 && (
              <p className="text-sm text-gray-500 mb-3">No user-specific prompts configured.</p>
            )}
            <div className="flex gap-2 mt-2">
              <select
                value={selectedPromptUser}
                onChange={(e) => setSelectedPromptUser(e.target.value)}
                className="bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
              >
                <option value="">Select user...</option>
                {users.filter((u) => !systemPrompts.users?.[u.username]).map((u) => (
                  <option key={u.username} value={u.username}>{u.display_name || u.username} (@{u.username})</option>
                ))}
              </select>
              <button
                onClick={() => {
                  if (selectedPromptUser) {
                    setSystemPrompts({
                      ...systemPrompts,
                      users: { ...systemPrompts.users, [selectedPromptUser]: "" }
                    });
                    setSelectedPromptUser("");
                  }
                }}
                disabled={!selectedPromptUser}
                className="bg-gray-700 hover:bg-gray-600 text-white px-3 py-2 rounded text-sm disabled:opacity-50 transition"
              >Add User Prompt</button>
            </div>
          </div>

          {/* Save + Preview */}
          <div className="flex flex-wrap gap-3 items-start">
            <button onClick={handleSaveSystemPrompts} disabled={promptsSaving}
              className="bg-amber-600 hover:bg-amber-500 text-white px-4 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
              {promptsSaving ? "Saving..." : "Save Prompts"}
            </button>
            <div className="flex gap-2 items-center">
              <select
                value={selectedPromptUser}
                onChange={(e) => setSelectedPromptUser(e.target.value)}
                className="bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white focus:outline-none focus:border-amber-500"
              >
                <option value="">Preview as user...</option>
                {users.map((u) => (
                  <option key={u.username} value={u.username}>{u.display_name || u.username}</option>
                ))}
              </select>
              <button
                onClick={() => selectedPromptUser && handlePreviewPrompt(selectedPromptUser)}
                disabled={!selectedPromptUser || previewLoading}
                className="bg-gray-600 hover:bg-gray-500 text-white px-3 py-1.5 rounded text-sm disabled:opacity-50 transition"
              >{previewLoading ? "Loading..." : "Preview"}</button>
            </div>
          </div>

          {/* Preview Result */}
          {previewResult && (
            <div className="bg-gray-800 rounded-lg p-4 border border-gray-700">
              <div className="flex justify-between items-center mb-2">
                <label className="text-sm font-medium text-amber-400">
                  Preview: @{previewResult.username} {previewResult.roles && `(roles: ${previewResult.roles.join(", ")})`}
                </label>
                <button onClick={() => setPreviewResult(null)} className="text-xs text-gray-400 hover:text-gray-300 transition">Close</button>
              </div>
              <pre className="text-xs text-gray-300 whitespace-pre-wrap bg-gray-900 p-3 rounded max-h-64 overflow-y-auto font-mono">
                {previewResult.assembled_prompt || previewResult.error || "(empty)"}
              </pre>
            </div>
          )}
        </div>
      )}

      {adminSubTab === "tabs" && tabCfg && (
        <div className="space-y-6">
          <h2 className="text-xl font-semibold text-white">Tab Configuration</h2>
          <p className="text-sm text-gray-400">
            <span className="text-gray-500">System</span> tabs are HAL infrastructure. <span className="text-amber-400">Business</span> tabs are operations — they get per-tab chat, prompts, and data hookups. You can add, edit, and remove business tabs.
          </p>
          {tabCfgError && <div className="p-3 rounded bg-red-900/50 border border-red-700 text-red-200 text-sm">{tabCfgError}</div>}
          {tabCfgSuccess && <div className="p-3 rounded bg-green-900/50 border border-green-700 text-green-200 text-sm">{tabCfgSuccess}</div>}

          <div className="flex gap-4" style={{ minHeight: "500px" }}>
            {/* Tab List Sidebar */}
            <div className="w-52 flex-shrink-0 bg-gray-800 rounded-lg border border-gray-700 overflow-hidden flex flex-col">
              {/* System tabs section */}
              <div className="p-2 border-b border-gray-700">
                <span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">System</span>
              </div>
              <div>
                {Object.entries(tabCfg.tabs || {}).filter(([, c]) => (c.category || "system") === "system").map(([tid, tcfg]) => (
                  <button
                    key={tid}
                    onClick={() => setSelectedTabId(tid)}
                    className={`w-full text-left px-3 py-2 text-sm border-b border-gray-700/50 transition ${selectedTabId === tid ? "bg-gray-700/60 text-white border-l-2 border-l-gray-400" : "text-gray-400 hover:bg-gray-700/30"}`}
                  >
                    <div className="flex items-center justify-between">
                      <span>{tcfg.label || tid}</span>
                      <span className="flex gap-1">
                        {tcfg.always && <span className="text-[10px] bg-gray-600/40 text-gray-400 px-1 rounded">A</span>}
                        {!tcfg.enabled && <span className="text-[10px] bg-red-600/30 text-red-400 px-1 rounded">Off</span>}
                      </span>
                    </div>
                  </button>
                ))}
              </div>
              {/* Business tabs section */}
              <div className="p-2 border-b border-gray-700 border-t border-t-gray-600">
                <span className="text-[10px] font-semibold text-amber-500 uppercase tracking-wider">Business</span>
              </div>
              <div className="overflow-y-auto flex-1">
                {Object.entries(tabCfg.tabs || {}).filter(([, c]) => c.category === "business").map(([tid, tcfg]) => (
                  <button
                    key={tid}
                    onClick={() => setSelectedTabId(tid)}
                    className={`w-full text-left px-3 py-2 text-sm border-b border-gray-700/50 transition ${selectedTabId === tid ? "bg-amber-600/20 text-amber-300 border-l-2 border-l-amber-500" : "text-gray-300 hover:bg-gray-700/50"}`}
                  >
                    <div className="flex items-center justify-between">
                      <span>{tcfg.label || tid}</span>
                      {!tcfg.enabled && <span className="text-[10px] bg-red-600/30 text-red-400 px-1 rounded">Off</span>}
                    </div>
                  </button>
                ))}
              </div>
              {/* Add Business Tab button */}
              <div className="p-2 border-t border-gray-700">
                <button
                  onClick={() => {
                    const id = prompt("Enter tab ID (lowercase, no spaces — e.g. procurement):");
                    if (!id || !id.trim()) return;
                    const tabId = id.trim().toLowerCase().replace(/\s+/g, "_");
                    if (tabCfg.tabs[tabId]) { setTabCfgError(`Tab "${tabId}" already exists`); return; }
                    setTabCfg({
                      ...tabCfg,
                      tabs: {
                        ...tabCfg.tabs,
                        [tabId]: {
                          label: tabId.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()),
                          category: "business", enabled: true, always: false,
                          system_prompt: "", data_sources: [], sensitivity: "normal", role_access: {},
                          _isNew: true,
                        }
                      }
                    });
                    setSelectedTabId(tabId);
                    setTabCfgSuccess("");
                    setTabCfgError("");
                  }}
                  className="w-full bg-amber-600/20 hover:bg-amber-600/30 text-amber-400 text-xs font-medium px-2 py-1.5 rounded transition"
                >+ Add Business Tab</button>
              </div>
            </div>

            {/* Detail Panel */}
            {selectedTabId && tabCfg.tabs[selectedTabId] && (() => {
              const t = tabCfg.tabs[selectedTabId];
              const isAlways = t.always === true;
              const isSystem = (t.category || "system") === "system";
              const isBusiness = t.category === "business";
              return (
                <div className="flex-1 bg-gray-800 rounded-lg border border-gray-700 p-5 overflow-y-auto space-y-5">
                  <div className="flex items-center justify-between">
                    <div className="flex items-center gap-3">
                      <h3 className="text-lg font-semibold text-white">{t.label || selectedTabId}</h3>
                      <span className={`text-[10px] px-2 py-0.5 rounded font-semibold uppercase tracking-wider ${isSystem ? "bg-gray-600/40 text-gray-400" : "bg-amber-600/30 text-amber-400"}`}>
                        {isSystem ? "System" : "Business"}
                      </span>
                    </div>
                    <span className="text-xs text-gray-500 font-mono">{selectedTabId}</span>
                  </div>

                  {/* Label + Enabled */}
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                      <label className="block text-xs font-medium text-gray-400 mb-1">Label</label>
                      <input
                        type="text"
                        value={t.label || ""}
                        onChange={(e) => updateSelectedTab("label", e.target.value)}
                        className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
                      />
                    </div>
                    <div className="flex items-end gap-4">
                      <label className="flex items-center gap-2 text-sm text-gray-300">
                        <input
                          type="checkbox"
                          checked={t.enabled !== false}
                          onChange={(e) => updateSelectedTab("enabled", e.target.checked)}
                          disabled={isAlways}
                          className="w-4 h-4 rounded bg-gray-900 border-gray-600 text-amber-600 focus:ring-amber-500"
                        />
                        Enabled
                      </label>
                      {isAlways && <span className="text-xs text-amber-400 bg-amber-600/20 px-2 py-1 rounded">Always visible</span>}
                    </div>
                  </div>

                  {/* Icon Picker */}
                  <div>
                    <label className="block text-xs font-medium text-gray-400 mb-2">Icon</label>
                    <div className="flex flex-wrap gap-1.5">
                      {Object.keys(TAB_ICON_PATHS).map(iconName => (
                        <button
                          key={iconName}
                          onClick={() => updateSelectedTab("icon", iconName)}
                          title={iconName}
                          className={`p-1.5 rounded border transition ${
                            t.icon === iconName
                              ? "border-amber-500 bg-amber-600/20 text-amber-400"
                              : "border-gray-700 bg-gray-900 text-gray-400 hover:border-gray-500 hover:text-gray-200"
                          }`}
                        >
                          <TabIcon name={iconName} className="w-5 h-5" />
                        </button>
                      ))}
                      <button
                        onClick={() => updateSelectedTab("icon", "")}
                        title="No icon"
                        className={`px-2 py-1.5 rounded border text-xs transition ${
                          !t.icon ? "border-amber-500 bg-amber-600/20 text-amber-400" : "border-gray-700 bg-gray-900 text-gray-400 hover:border-gray-500"
                        }`}
                      >
                        None
                      </button>
                    </div>
                  </div>

                  {/* Role Access Grid */}
                  <div>
                    <label className="block text-xs font-medium text-gray-400 mb-2">Role Access</label>
                    {isAlways ? (
                      <p className="text-xs text-gray-500 italic">This tab is always visible to all roles with full access.</p>
                    ) : (
                      <div className="bg-gray-900 rounded border border-gray-700 divide-y divide-gray-700/50">
                        {allRoles.map((role) => {
                          const access = (t.role_access || {})[role] || "none";
                          return (
                            <div key={role} className="flex items-center justify-between px-3 py-2">
                              <span className="text-sm text-gray-300 font-mono">{role}</span>
                              <select
                                value={access}
                                onChange={(e) => updateRoleAccess(role, e.target.value)}
                                className="bg-gray-800 border border-gray-600 rounded px-2 py-1 text-xs text-white focus:outline-none focus:border-amber-500"
                              >
                                <option value="none">No access</option>
                                <option value="full">Full</option>
                                <option value="restricted">Restricted</option>
                              </select>
                            </div>
                          );
                        })}
                      </div>
                    )}
                  </div>

                  {/* Business-only: Tab System Prompt + Data Sources */}
                  {isBusiness && (
                    <>
                      <div>
                        <label className="block text-xs font-medium text-amber-400 mb-1">Tab System Prompt</label>
                        <p className="text-xs text-gray-500 mb-2">Injected as context when users chat within this tab.</p>
                        <textarea
                          value={t.system_prompt || ""}
                          onChange={(e) => updateSelectedTab("system_prompt", e.target.value)}
                          rows={16}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y focus:outline-none focus:border-amber-500 min-h-[200px]"
                          placeholder="Context-specific instructions for HAL when chatting in this tab..."
                        />
                      </div>

                      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                        <div>
                          <label className="block text-xs font-medium text-gray-400 mb-1">Sensitivity <span className="text-gray-600">(future)</span></label>
                          <select
                            value={t.sensitivity || "normal"}
                            onChange={(e) => updateSelectedTab("sensitivity", e.target.value)}
                            className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
                          >
                            <option value="low">Low</option>
                            <option value="normal">Normal</option>
                            <option value="high">High</option>
                            <option value="critical">Critical</option>
                          </select>
                        </div>
                        <div>
                          <label className="block text-xs font-medium text-gray-400 mb-1">Data Sources <span className="text-gray-600">(future)</span></label>
                          <input
                            type="text"
                            value={Array.isArray(t.data_sources) ? t.data_sources.join(", ") : (t.data_sources || "")}
                            onChange={(e) => updateSelectedTab("data_sources", e.target.value.split(",").map(s => s.trim()).filter(Boolean))}
                            className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
                            placeholder="orders, inventory, crm, ..."
                          />
                        </div>
                      </div>

                      {/* Execution Mode */}
                      <div>
                        <label className="block text-xs font-medium text-gray-400 mb-1">Execution Mode</label>
                        <select
                          value={t.execution_mode || "auto_execute"}
                          onChange={(e) => updateSelectedTab("execution_mode", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
                        >
                          <option value="auto_execute">Auto Execute — low-risk actions proceed</option>
                          <option value="approval_required">Approval Required — all actions need approval</option>
                          <option value="recommend_only">Recommend Only — no actions, advice only</option>
                        </select>
                        <p className="text-xs text-gray-500 mt-1">Controls how HAL handles workflow actions in this tab.</p>
                      </div>

                      {/* Preferred Agent Type */}
                      <div>
                        <label className="block text-xs font-medium text-gray-400 mb-1">Preferred Agent Type</label>
                        <select
                          value={t.preferred_agent_type || ""}
                          onChange={(e) => updateSelectedTab("preferred_agent_type", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500"
                        >
                          <option value="">Any (coordinator default)</option>
                          <option value="inference">Inference</option>
                          <option value="coding">Coding</option>
                          <option value="triage">Triage</option>
                          <option value="monitoring">Monitoring</option>
                          <option value="analytics">Analytics</option>
                          <option value="analytics-secure">Analytics (Secure)</option>
                        </select>
                        <p className="text-xs text-gray-500 mt-1">When set, HAL prefers dispatching tasks from this tab to agents of this type.</p>
                      </div>
                    </>
                  )}

                  {isSystem && (
                    <div className="bg-gray-900/50 rounded border border-gray-700 p-3">
                      <p className="text-xs text-gray-500 italic">System tabs don't have per-tab chat, prompts, or data source hookups. Role permissions still apply.</p>
                    </div>
                  )}

                  {/* Save + Delete Buttons */}
                  <div className="flex items-center justify-between pt-2">
                    <button onClick={handleSaveTabConfig} disabled={tabCfgSaving}
                      className="bg-amber-600 hover:bg-amber-500 text-white px-4 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
                      {tabCfgSaving ? "Saving..." : "Save Tab Config"}
                    </button>
                    {isBusiness && (
                      <button
                        onClick={async () => {
                          if (!confirm(`Delete the "${t.label}" tab? This cannot be undone.`)) return;
                          setTabCfgError("");
                          try {
                            const res = await fetch(`/api/admin/tab-config/${selectedTabId}`, {
                              method: "DELETE",
                              headers: { "X-Session-Token": sessionToken },
                            });
                            if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.detail || "Delete failed"); }
                            const updated = { ...tabCfg, tabs: { ...tabCfg.tabs } };
                            delete updated.tabs[selectedTabId];
                            setTabCfg(updated);
                            const remaining = Object.keys(updated.tabs);
                            setSelectedTabId(remaining.length > 0 ? remaining[0] : "");
                            setTabCfgSuccess("Tab deleted.");
                            setTimeout(() => setTabCfgSuccess(""), 3000);
                          } catch (err) { setTabCfgError(err.message); }
                        }}
                        className="bg-red-700/50 hover:bg-red-600/60 text-red-300 px-3 py-1.5 rounded text-sm font-medium transition"
                      >Delete Tab</button>
                    )}
                  </div>
                </div>
              );
            })()}
          </div>
        </div>
      )}

      {adminSubTab === "agents" && (
        <div className="space-y-6">
          <div className="flex items-center justify-between">
            <div>
              <h2 className="text-xl font-semibold text-white">Agent Configuration</h2>
              <p className="text-sm text-gray-400 mt-1">
                Manage fleet agents, operating instructions, and registration. Config is pulled by agents every 15 minutes.
              </p>
            </div>
            <button onClick={() => setShowAddAgent(!showAddAgent)}
              className="text-xs bg-amber-600 hover:bg-amber-500 text-white px-3 py-1.5 rounded transition flex-shrink-0">
              {showAddAgent ? "Cancel" : "+ Add Agent"}
            </button>
          </div>
          {agentCfgError && <div className="p-3 rounded bg-red-900/50 border border-red-700 text-red-200 text-sm">{agentCfgError}</div>}
          {agentCfgSuccess && <div className="p-3 rounded bg-green-900/50 border border-green-700 text-green-200 text-sm">{agentCfgSuccess}</div>}

          {/* Add Agent Form (collapsible) */}
          {showAddAgent && (
            <div className="bg-gray-800 rounded-lg p-4 border border-amber-600 space-y-3">
              <h3 className="text-sm font-semibold text-white">Register Fleet Agent</h3>
              <p className="text-xs text-gray-400">Manually register a fleet host. Agents normally self-register on startup, but you can add them here for pre-configuration.</p>
              <div className="grid grid-cols-3 gap-3">
                <div>
                  <label className="block text-xs text-gray-500 mb-1">Hostname</label>
                  <input value={newAgent.hostname} onChange={(e) => setNewAgent({ ...newAgent, hostname: e.target.value })}
                    placeholder="e.g. dgx-spark" className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                </div>
                <div>
                  <label className="block text-xs text-gray-500 mb-1">IP Address</label>
                  <input value={newAgent.ip} onChange={(e) => setNewAgent({ ...newAgent, ip: e.target.value })}
                    placeholder="e.g. 10.0.1.20" className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                </div>
                <div>
                  <label className="block text-xs text-gray-500 mb-1">MCP Port</label>
                  <input value={newAgent.port} onChange={(e) => setNewAgent({ ...newAgent, port: e.target.value })}
                    placeholder="8900" className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                </div>
                <div>
                  <label className="block text-xs text-gray-500 mb-1">Agent Type</label>
                  <select value={newAgent.agent_type} onChange={(e) => {
                      const at = e.target.value;
                      const d = AGENT_TYPE_DEFAULTS[at] || AGENT_TYPE_DEFAULTS.monitoring;
                      setNewAgent({ ...newAgent, agent_type: at, provider: d.provider, model: d.model, profile: d.profile });
                    }}
                    className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white">
                    <option value="monitoring">Monitoring</option>
                    <option value="inference">Inference</option>
                    <option value="coding">Coding</option>
                    <option value="triage">Triage</option>
                    <option value="analytics">Analytics</option>
                    <option value="analytics-secure">Analytics (Secure)</option>
                  </select>
                </div>
                <div>
                  <label className="block text-xs text-gray-500 mb-1">Provider</label>
                  <input value={newAgent.provider} onChange={(e) => setNewAgent({ ...newAgent, provider: e.target.value })}
                    placeholder="ollama" className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                </div>
                <div>
                  <label className="block text-xs text-gray-500 mb-1">Model</label>
                  <input value={newAgent.model} onChange={(e) => setNewAgent({ ...newAgent, model: e.target.value })}
                    placeholder="qwen3:8b" className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                </div>
              </div>
              <div className="flex gap-2 pt-1">
                <button onClick={handleAddAgent} className="text-xs bg-green-600 hover:bg-green-500 text-white px-4 py-1.5 rounded font-medium">Add to Fleet</button>
                <button onClick={() => { setShowAddAgent(false); setAgentCfgError(""); }} className="text-xs bg-gray-600 hover:bg-gray-500 text-white px-3 py-1.5 rounded">Cancel</button>
              </div>
            </div>
          )}

          <div className="flex gap-4" style={{ minHeight: "500px" }}>
            {/* Agent List Sidebar */}
            <div className="w-56 flex-shrink-0 bg-gray-800 rounded-lg border border-gray-700 overflow-hidden flex flex-col">
              {(() => {
                const typeColors = { monitoring: "#9ca3af", inference: "#60a5fa", coding: "#a78bfa", triage: "#fbbf24", analytics: "#34d399", "analytics-secure": "#f87171" };
                const grouped = {};
                agentStatusList.forEach(a => {
                  const type = a.agent_type || "unknown";
                  if (!grouped[type]) grouped[type] = [];
                  grouped[type].push(a);
                });
                const entries = Object.entries(grouped);
                if (entries.length === 0) {
                  return <div className="p-4 text-sm text-gray-500 text-center">No agents registered</div>;
                }
                return entries.map(([type, agents]) => (
                  <div key={type}>
                    <div className="p-2 border-b border-gray-700">
                      <span className="text-[10px] font-semibold uppercase tracking-wider"
                        style={{ color: typeColors[type] || "#9ca3af" }}>
                        {type}
                      </span>
                    </div>
                    {agents.map(a => {
                      const hostname = a.hostname || a.name || a.id;
                      const displayName = a.display_name || hostname;
                      const hasCfg = !!(agentCfgs?.configs || {})[hostname];
                      const status = a.status || a.agentOnline || "unknown";
                      return (
                        <button key={hostname}
                          onClick={() => setSelectedAgentHost(hostname)}
                          className={`w-full text-left px-3 py-2 text-sm border-b border-gray-700/50 transition ${
                            selectedAgentHost === hostname
                              ? "bg-gray-700/60 text-white border-l-2 border-l-amber-500"
                              : "text-gray-300 hover:bg-gray-700/30"
                          }`}>
                          <div className="flex items-center justify-between">
                            <div className="flex flex-col">
                              <span>{displayName}</span>
                              {displayName !== hostname && <span className="text-[10px] text-gray-500 font-mono">{hostname}</span>}
                            </div>
                            <span className="flex gap-1 items-center">
                              <span className={`w-2 h-2 rounded-full ${status === "online" ? "bg-green-500" : status === "degraded" ? "bg-yellow-500" : "bg-red-500"}`} />
                              {!hasCfg && <span className="text-[9px] text-gray-500 italic">default</span>}
                            </span>
                          </div>
                        </button>
                      );
                    })}
                  </div>
                ));
              })()}
            </div>

            {/* Detail Panel */}
            {selectedAgentHost ? (() => {
              const cfg = (agentCfgs?.configs || {})[selectedAgentHost] || { mode: "observer", prompt: "", interval: 300, enabled: true };
              const agentInfo = agentStatusList.find(a => (a.hostname || a.name || a.id) === selectedAgentHost);
              return (
                <div className="flex-1 bg-gray-800 rounded-lg border border-gray-700 p-5 overflow-y-auto space-y-5">
                  <div className="flex items-center justify-between">
                    <div className="flex items-center gap-3">
                      <div>
                        <h3 className="text-lg font-semibold text-white">{cfg.nickname || selectedAgentHost}</h3>
                        {cfg.nickname && <span className="text-xs text-gray-500 font-mono">{selectedAgentHost}</span>}
                      </div>
                      <AgentTypeBadge agentType={agentInfo?.agent_type} />
                      <span className={`text-xs px-2 py-0.5 rounded ${(agentInfo?.status || agentInfo?.agentOnline) === "online" ? "bg-green-900/50 text-green-400" : "bg-red-900/50 text-red-400"}`}>
                        {agentInfo?.status || agentInfo?.agentOnline || "unknown"}
                      </span>
                    </div>
                    <span className="text-xs text-gray-500 font-mono">{agentInfo?.version || "?"}</span>
                  </div>

                  {/* Fleet Registration Info */}
                  <div className="bg-gray-900/50 rounded-lg border border-gray-700 p-3">
                    <label className="block text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wider">Fleet Registration</label>
                    <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                      <div>
                        <span className="block text-[10px] text-gray-500 uppercase">IP</span>
                        <span className="text-sm text-gray-300 font-mono">{agentInfo?.ip || agentInfo?.url || "—"}</span>
                      </div>
                      <div>
                        <span className="block text-[10px] text-gray-500 uppercase">Agent Type</span>
                        <span className="text-sm text-gray-300">{agentInfo?.agent_type || "—"}</span>
                      </div>
                      <div>
                        <span className="block text-[10px] text-gray-500 uppercase">Provider</span>
                        <span className="text-sm text-gray-300">{agentInfo?.provider || "—"}</span>
                      </div>
                      <div>
                        <span className="block text-[10px] text-gray-500 uppercase">Model</span>
                        <span className="text-sm text-gray-300 font-mono text-xs">{agentInfo?.model || "—"}</span>
                      </div>
                    </div>
                  </div>

                  {/* Nickname */}
                  <div>
                    <label className="block text-xs font-medium text-gray-400 mb-1">Nickname</label>
                    <input type="text" value={cfg.nickname || ""} onChange={e => updateAgentCfg("nickname", e.target.value)}
                      placeholder={selectedAgentHost}
                      className="w-64 bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500" />
                    <p className="text-xs text-gray-500 mt-1">Display name override. Leave empty to use hostname.</p>
                  </div>

                  {/* Mode + Enabled */}
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                      <label className="block text-xs font-medium text-gray-400 mb-1">Mode</label>
                      <select value={cfg.mode || "observer"}
                        onChange={e => updateAgentCfg("mode", e.target.value)}
                        className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500">
                        <option value="observer">Observer — continuous monitoring loop</option>
                        <option value="task">Task — reactive, waits for dispatched work</option>
                      </select>
                    </div>
                    <div className="flex items-end gap-4">
                      <label className="flex items-center gap-2 text-sm text-gray-300 cursor-pointer">
                        <input type="checkbox" checked={cfg.enabled !== false}
                          onChange={e => updateAgentCfg("enabled", e.target.checked)}
                          className="w-4 h-4 rounded bg-gray-900 border-gray-600 text-amber-600 focus:ring-amber-500" />
                        Autonomous Loop Enabled
                      </label>
                    </div>
                  </div>

                  {/* Interval (observer only) */}
                  {(cfg.mode || "observer") === "observer" && (
                    <div>
                      <label className="block text-xs font-medium text-gray-400 mb-1">Cycle Interval (seconds)</label>
                      <input type="number" value={cfg.interval || 300} min={30} step={30}
                        onChange={e => updateAgentCfg("interval", parseInt(e.target.value) || 300)}
                        className="w-32 bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-amber-500" />
                      <p className="text-xs text-gray-500 mt-1">How often the agent runs its observation cycle. Minimum 30 seconds.</p>
                    </div>
                  )}

                  {/* Prompt */}
                  <div>
                    <label className="block text-xs font-medium text-amber-400 mb-1">
                      {(cfg.mode || "observer") === "observer" ? "Observer Prompt" : "Task Standing Instructions"}
                    </label>
                    <p className="text-xs text-gray-500 mb-2">
                      {(cfg.mode || "observer") === "observer"
                        ? "What should this agent monitor? What anomalies to look for? What to escalate? The agent will use its MCP tools (tail_logs, run_command, read_file, etc.) based on these instructions."
                        : "Standing context for dispatched tasks. What domain expertise, constraints, or focus areas should this agent have?"}
                    </p>
                    <textarea value={cfg.prompt || ""} onChange={e => updateAgentCfg("prompt", e.target.value)}
                      rows={16}
                      className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y focus:outline-none focus:border-amber-500 min-h-[200px]"
                      placeholder={(cfg.mode || "observer") === "observer"
                        ? "Monitor warehouse receiving logs in /opt/warehouse/receiving/\nWatch for inventory discrepancies where qty_received != qty_ordered\nCheck /var/log/barcode-scanner/ for scan failures\nEscalate any discrepancy over $500 or repeated scan failures on the same dock"
                        : "You are a billing analysis agent. When given tasks:\n- Query the billing database for relevant data\n- Analyze patterns and anomalies\n- Present findings in structured format"
                      } />
                  </div>

                  <div className="flex items-center justify-between pt-2 border-t border-gray-700">
                    <div className="flex items-center gap-3">
                      <button onClick={handleSaveAgentConfig} disabled={agentCfgSaving}
                        className="bg-amber-600 hover:bg-amber-500 text-white px-4 py-1.5 rounded text-sm font-medium transition disabled:opacity-50">
                        {agentCfgSaving ? "Saving..." : "Save Agent Config"}
                      </button>
                      <span className="text-xs text-gray-500">Mode changes require agent restart.</span>
                    </div>
                    <button onClick={() => handleRemoveAgent(selectedAgentHost)}
                      className="text-xs text-red-400 hover:text-red-300 hover:bg-red-900/30 px-3 py-1.5 rounded transition"
                      title="Remove agent from fleet">
                      Remove Agent
                    </button>
                  </div>
                </div>
              );
            })() : (
              <div className="flex-1 bg-gray-800 rounded-lg border border-gray-700 p-5 flex items-center justify-center">
                <span className="text-gray-500 text-sm">Select an agent to configure</span>
              </div>
            )}
          </div>
        </div>
      )}

      {adminSubTab === "users" && (<div className="space-y-6">
      <div className="flex items-center justify-between mb-6">
        <div className="flex items-center gap-3">
          <h2 className="text-xl font-semibold text-white">User Management</h2>
          {ldapStatus && (
            <span className={`px-2 py-0.5 rounded text-xs font-medium ${
              ldapStatus.status === "connected" ? "bg-green-900 text-green-200" :
              ldapStatus.status === "error" ? "bg-red-900 text-red-200" :
              "bg-gray-700 text-gray-400"
            }`}>
              LDAP: {ldapStatus.status === "connected" ? "Connected" : ldapStatus.status === "error" ? "Error" : ldapStatus.enabled ? "Enabled" : "Disabled"}
            </span>
          )}
        </div>
        <button
          onClick={() => setShowNewUserForm(!showNewUserForm)}
          className="bg-amber-600 hover:bg-amber-700 text-white px-4 py-2 rounded text-sm font-medium transition"
        >
          {showNewUserForm ? "Cancel" : "New User"}
        </button>
      </div>

      {showNewUserForm && (
        <div className="bg-gray-800 rounded-lg p-4 border border-gray-700 mb-6">
          <h3 className="text-sm font-semibold text-white mb-4">Add New User</h3>
          {createUserError && (
            <div className="mb-4 p-2 bg-red-900/50 border border-red-700 rounded text-red-300 text-sm">{createUserError}</div>
          )}
          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="relative">
              <input
                type="text"
                placeholder="Username"
                value={newUser.username}
                onChange={(e) => setNewUser({ ...newUser, username: e.target.value, display_name: "", email: "", department: "" })}
                className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
              />
              {ldapLookupStatus === "looking" && <span className="absolute right-2 top-2.5 text-xs text-gray-400">Looking up...</span>}
              {ldapLookupStatus === "found" && <span className="absolute right-2 top-2.5 text-xs text-green-400">LDAP found</span>}
              {ldapLookupStatus === "not_found" && <span className="absolute right-2 top-2.5 text-xs text-yellow-400">Not in LDAP</span>}
              {ldapLookupStatus === "error" && <span className="absolute right-2 top-2.5 text-xs text-red-400" title={ldapLookupError}>LDAP error</span>}
            </div>
            <input
              type="text"
              placeholder="Display Name"
              value={newUser.display_name}
              onChange={(e) => setNewUser({ ...newUser, display_name: e.target.value })}
              className="bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
            />
            <input
              type="email"
              placeholder="Email"
              value={newUser.email}
              onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
              className="bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
            />
            <input
              type="text"
              placeholder="Department"
              value={newUser.department}
              onChange={(e) => setNewUser({ ...newUser, department: e.target.value })}
              className="bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
            />
          </div>

          {/* Auth method section */}
          <div className="mt-4 p-3 bg-gray-900 rounded border border-gray-700">
            <div className="flex items-center justify-between mb-2">
              <span className="text-xs font-medium text-gray-400">Authentication</span>
              {ldapStatus?.enabled && (
                <label className="flex items-center gap-2 cursor-pointer">
                  <input
                    type="checkbox"
                    checked={newUser.ldap_bypass}
                    onChange={(e) => setNewUser({ ...newUser, ldap_bypass: e.target.checked, password: "" })}
                    className="rounded border-gray-600 bg-gray-700 text-amber-500 focus:ring-amber-500"
                  />
                  <span className="text-xs text-gray-300">LDAP Bypass (local password)</span>
                </label>
              )}
            </div>
            <p className="text-xs text-gray-500 mb-2">
              {ldapStatus?.enabled && !newUser.ldap_bypass
                ? "User will authenticate via LDAP directory — no local password needed."
                : "User will authenticate with a local password."}
            </p>
            {needsLocalPassword && (
              <>
                <input
                  type="password"
                  placeholder="Password"
                  value={newUser.password}
                  onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
                {passwordRules && (
                  <div className="mt-2 text-xs space-y-0.5">
                    <span className="text-gray-500 font-medium">Requirements:</span>
                    {[
                      { test: (newUser.password || "").length >= passwordRules.min_length, label: `${passwordRules.min_length}+ characters` },
                      { test: /[A-Z]/.test(newUser.password || ""), label: "Uppercase letter" },
                      { test: /[a-z]/.test(newUser.password || ""), label: "Lowercase letter" },
                      { test: /[0-9]/.test(newUser.password || ""), label: "Digit" },
                      { test: /[!@#$%^&*()_+\-=\[\]{}|;:',.<>?/~`]/.test(newUser.password || ""), label: "Special character" },
                    ].map((r) => (
                      <div key={r.label} className={`flex items-center gap-1 ${r.test ? "text-green-400" : "text-gray-500"}`}>
                        <span>{r.test ? "✓" : "○"}</span>
                        <span>{r.label}</span>
                      </div>
                    ))}
                  </div>
                )}
              </>
            )}
          </div>

          {/* Roles */}
          <div className="mt-4">
            <span className="text-xs font-medium text-gray-400 block mb-2">Roles</span>
            <div className="flex flex-wrap gap-2">
              {allRoles.map((role) => (
                <button
                  key={role}
                  onClick={() => {
                    const roles = newUser.roles.includes(role)
                      ? newUser.roles.filter((r) => r !== role)
                      : [...newUser.roles, role];
                    setNewUser({ ...newUser, roles });
                  }}
                  className={`px-2 py-1 rounded text-xs font-medium transition ${
                    newUser.roles.includes(role)
                      ? "bg-amber-600 text-white"
                      : "bg-gray-700 text-gray-400 hover:bg-gray-600"
                  }`}
                >
                  {role}
                </button>
              ))}
            </div>
          </div>

          <button
            onClick={handleAddUser}
            disabled={!newUser.username || newUser.roles.length === 0 || (needsLocalPassword && (passwordIssues.length > 0 || !newUser.password))}
            className="mt-4 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white px-4 py-2 rounded text-sm font-medium transition"
          >
            Create User
          </button>
        </div>
      )}

      <div className="space-y-3">
        {users.map((user) => (
          <div key={user.username} className="bg-gray-800 rounded-lg p-4 border border-gray-700 hover:border-amber-500 transition">
            <div className="flex items-start justify-between mb-3">
              <div className="flex-1">
                {editingUser === user.username ? (
                  <input
                    type="text"
                    value={editFields.display_name}
                    onChange={(e) => setEditFields({ ...editFields, display_name: e.target.value })}
                    className="bg-gray-700 text-white rounded px-2 py-1 text-sm border border-amber-500 focus:outline-none w-64"
                    placeholder="Display Name"
                  />
                ) : (
                  <p className="font-medium text-white">{user.display_name || user.username}</p>
                )}
                <p className="text-xs text-gray-400">@{user.username}</p>
              </div>
              <div className="flex items-center gap-2">
                {editingUser === user.username ? (
                  <>
                    <button onClick={() => handleSaveEdit(user.username)} className="px-3 py-1 bg-green-600 hover:bg-green-700 text-white rounded text-xs font-medium transition">Save</button>
                    <button onClick={() => setEditingUser(null)} className="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white rounded text-xs font-medium transition">Cancel</button>
                  </>
                ) : (
                  <button onClick={() => handleStartEdit(user)} className="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded text-xs font-medium transition">Edit</button>
                )}
                <button
                  onClick={() => handleToggleEnabled(user.username)}
                  disabled={user.username === "admin"}
                  title={user.username === "admin" ? "Admin user cannot be disabled" : ""}
                  className={`px-3 py-1 rounded text-xs font-medium transition ${
                    user.username === "admin" ? "bg-green-900 text-green-200 opacity-60 cursor-not-allowed" :
                    user.enabled ? "bg-green-900 text-green-200 hover:bg-green-800" : "bg-red-900 text-red-200 hover:bg-red-800"
                  }`}
                >
                  {user.enabled ? "Enabled" : "Disabled"}
                </button>
              </div>
            </div>
            <div className="grid grid-cols-2 md:grid-cols-5 gap-3 text-xs text-gray-300 mb-3">
              <div>
                <p className="text-gray-500">Email</p>
                {editingUser === user.username ? (
                  <input
                    type="email"
                    value={editFields.email}
                    onChange={(e) => setEditFields({ ...editFields, email: e.target.value })}
                    className="bg-gray-700 text-white rounded px-2 py-0.5 text-xs border border-amber-500 focus:outline-none w-full"
                    placeholder="Email"
                  />
                ) : (
                  <p className="text-gray-100">{user.email || "—"}</p>
                )}
              </div>
              <div>
                <p className="text-gray-500">Department</p>
                {editingUser === user.username ? (
                  <input
                    type="text"
                    value={editFields.department}
                    onChange={(e) => setEditFields({ ...editFields, department: e.target.value })}
                    className="bg-gray-700 text-white rounded px-2 py-0.5 text-xs border border-amber-500 focus:outline-none w-full"
                    placeholder="Department"
                  />
                ) : (
                  <p className="text-gray-100">{user.department || "—"}</p>
                )}
              </div>
              <div>
                <p className="text-gray-500">Auth</p>
                <p className="text-gray-100">{user.auth_method === "ldap" ? "LDAP" : "Local"}{user.ldap_bypass ? " (bypass)" : ""}</p>
              </div>
              <div>
                <p className="text-gray-500">MFA Status</p>
                <p className={(user.mfa && (user.mfa.totp_enabled || (user.mfa.webauthn_credentials || []).length > 0)) ? "text-green-400" : "text-gray-400"}>
                  {user.mfa && user.mfa.totp_enabled && (user.mfa.webauthn_credentials || []).length > 0
                    ? "TOTP + WebAuthn"
                    : user.mfa && user.mfa.totp_enabled
                    ? "TOTP"
                    : user.mfa && (user.mfa.webauthn_credentials || []).length > 0
                    ? `WebAuthn (${user.mfa.webauthn_credentials.length} key${user.mfa.webauthn_credentials.length > 1 ? "s" : ""})`
                    : "Not enrolled"}
                </p>
              </div>
            </div>

            <div className="flex flex-wrap gap-2 mb-3">
              {(user.mfa && (user.mfa.totp_enabled || (user.mfa.webauthn_credentials || []).length > 0)) ? (
                <button
                  onClick={async () => {
                    if (!confirm(`Reset all MFA for ${user.username}? They will re-enroll at next login.`)) return;
                    try {
                      const r = await fetch(`/api/admin/users/${user.username}/mfa/reset`, {
                        method: "POST", headers: { "X-Session-Token": sessionToken },
                      });
                      if (r.ok) { fetchUsers(); }
                    } catch {}
                  }}
                  className="px-3 py-1 bg-red-900 hover:bg-red-800 text-red-200 rounded text-xs font-medium transition"
                >
                  Reset MFA
                </button>
              ) : (
                <span className="px-3 py-1 bg-gray-700 text-gray-400 rounded text-xs">MFA: Not enrolled</span>
              )}
              {ldapStatus && ldapStatus.enabled && (
                <button
                  onClick={() => handleToggleLdapBypass(user)}
                  className={`px-3 py-1 rounded text-xs font-medium transition ${
                    user.ldap_bypass || user.username === "admin"
                      ? "bg-yellow-900 text-yellow-200"
                      : "bg-gray-700 text-gray-400 hover:bg-gray-600"
                  }`}
                  disabled={user.username === "admin"}
                  title={user.username === "admin" ? "Admin always bypasses LDAP" : user.ldap_bypass ? "Click to require LDAP auth" : "Click to bypass LDAP auth"}
                >
                  {user.username === "admin" ? "LDAP Bypass (always)" : user.ldap_bypass ? "LDAP Bypass: ON" : "LDAP Bypass: OFF"}
                </button>
              )}
              {user.username !== "admin" && (
                <button
                  onClick={() => handleDeleteUser(user.username)}
                  className="px-3 py-1 bg-red-900 hover:bg-red-800 text-red-200 rounded text-xs font-medium transition"
                >
                  Delete User
                </button>
              )}
            </div>

            <div className="flex flex-wrap gap-2">
              {allRoles.map((role) => (
                <button
                  key={role}
                  onClick={() => handleToggleRole(user.username, role)}
                  className={`px-2 py-1 rounded text-xs font-medium transition ${
                    user.roles.includes(role)
                      ? "bg-amber-600 text-white"
                      : "bg-gray-700 text-gray-400 hover:bg-gray-600"
                  }`}
                >
                  {role}
                </button>
              ))}
            </div>
          </div>
        ))}
      </div>

      {/* Legacy 2FA modal removed — MFA is now self-service via user profile + admin MFA sub-tab */}
      </div>)}

      {adminSubTab === "watchdog" && (() => {
        /* Derive tab and category options from live tab config */
        const wdTabOptions = tabCfg && tabCfg.tabs ? Object.entries(tabCfg.tabs)
          .filter(([, t]) => t.category === "business" && t.enabled !== false)
          .map(([id, t]) => ({ id, label: t.label || id })) : [];
        const wdCategorySet = new Set();
        if (tabCfg && tabCfg.tabs) {
          Object.values(tabCfg.tabs).forEach(t => {
            (t.finding_categories || []).forEach(c => wdCategorySet.add(c));
          });
        }
        /* Add common infrastructure categories */
        ["general", "infrastructure", "database", "application", "network"].forEach(c => wdCategorySet.add(c));
        const wdCategories = [...wdCategorySet].sort();

        return (
        <div className="space-y-6">
          <div className="flex items-center justify-between">
            <h2 className="text-xl font-semibold text-white">Watchdog Checks</h2>
            <div className="flex items-center gap-3">
              {watchdogCfg && (
                <label className="flex items-center gap-2 text-sm text-gray-300">
                  <input type="checkbox" checked={watchdogCfg?.global?.enabled ?? true}
                    onChange={(e) => setWatchdogCfg({...watchdogCfg, global: {...(watchdogCfg.global || {}), enabled: e.target.checked}})}
                    className="rounded border-gray-700" />
                  Enabled
                </label>
              )}
              <button onClick={async () => {
                setWatchdogSaving(true);
                try {
                  const res = await fetch("/api/admin/watchdog-config", {
                    method: "PUT", headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
                    body: JSON.stringify(watchdogCfg),
                  });
                  if (res.ok) toast("Watchdog config saved", "success");
                  else toast("Failed to save", "error");
                } catch { toast("Failed to save", "error"); }
                setWatchdogSaving(false);
              }} disabled={watchdogSaving}
                className="px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded text-sm font-medium transition disabled:opacity-50">
                {watchdogSaving ? "Saving..." : "Save Config"}
              </button>
            </div>
          </div>

          {watchdogCfg && (
            <div className="grid grid-cols-2 md:grid-cols-4 gap-4 bg-gray-800 rounded p-4">
              <div>
                <label className="block text-xs text-gray-400 mb-1">Check Interval (sec)</label>
                <input type="number" value={watchdogCfg.global?.interval_seconds || 300}
                  onChange={(e) => setWatchdogCfg({...watchdogCfg, global: {...(watchdogCfg.global || {}), interval_seconds: parseInt(e.target.value) || 300}})}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
              </div>
              <div>
                <label className="block text-xs text-gray-400 mb-1">Stagger (seconds)</label>
                <input type="number" value={watchdogCfg.global?.stagger_seconds || 10}
                  onChange={(e) => setWatchdogCfg({...watchdogCfg, global: {...(watchdogCfg.global || {}), stagger_seconds: parseInt(e.target.value) || 10}})}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
              </div>
              <div>
                <label className="block text-xs text-gray-400 mb-1">Escalation after N cycles</label>
                <input type="number" value={watchdogCfg.global?.escalation_cycles || 6}
                  onChange={(e) => setWatchdogCfg({...watchdogCfg, global: {...(watchdogCfg.global || {}), escalation_cycles: parseInt(e.target.value) || 6}})}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
              </div>
              <div>
                <label className="block text-xs text-blue-400 mb-1">Discovery Interval (sec)</label>
                <input type="number" value={watchdogCfg.global?.discovery_interval_seconds || 1800}
                  onChange={(e) => setWatchdogCfg({...watchdogCfg, global: {...(watchdogCfg.global || {}), discovery_interval_seconds: parseInt(e.target.value) || 1800}})}
                  className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
              </div>
            </div>
          )}

          <div className="flex gap-4" style={{minHeight: "500px"}}>
            {/* Check List */}
            <div className="w-64 flex-shrink-0 bg-gray-800 rounded p-3 overflow-y-auto">
              <h3 className="text-xs font-semibold text-gray-400 uppercase mb-2">Checks</h3>
              {(watchdogCfg?.checks || []).map((check) => (
                <button key={check.id} onClick={() => setSelectedCheckId(check.id)}
                  className={`w-full text-left px-3 py-2 rounded text-sm mb-1 transition ${selectedCheckId === check.id ? "bg-amber-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
                  <div className="flex items-center justify-between">
                    <span className="truncate">{check.label || check.id}</span>
                    <span className={`w-2 h-2 rounded-full flex-shrink-0 ${check.enabled !== false ? "bg-green-400" : "bg-gray-600"}`}></span>
                  </div>
                  <div className="text-xs text-gray-400 mt-0.5">{check.tab_id} / {check.category}</div>
                </button>
              ))}
              <button onClick={() => {
                const id = `check-${Date.now()}`;
                const newCheck = { id, tab_id: "distribution", label: "New Check", sql: "", detail_sql: "", connection: "nass", condition: "row[0]['cnt'] > 0", severity: "MEDIUM", category: "general", finding_template: "", enabled: true };
                setWatchdogCfg({...watchdogCfg, checks: [...(watchdogCfg?.checks || []), newCheck]});
                setSelectedCheckId(id);
              }}
                className="w-full mt-2 px-3 py-2 bg-amber-700 hover:bg-amber-600 text-amber-100 rounded text-sm font-medium transition">
                + Add Check
              </button>
            </div>

            {/* Check Detail */}
            <div className="flex-1 bg-gray-800 rounded p-4 overflow-y-auto">
              {(() => {
                const check = (watchdogCfg?.checks || []).find(c => c.id === selectedCheckId);
                if (!check) return <div className="text-gray-600 text-center mt-20">Select a check to edit</div>;
                const updateCheck = (field, value) => {
                  setWatchdogCfg({...watchdogCfg, checks: watchdogCfg.checks.map(c => c.id === check.id ? {...c, [field]: value} : c)});
                };
                return (
                  <div className="space-y-4">
                    <div className="flex items-center justify-between">
                      <h3 className="text-lg font-semibold text-white">{check.label || check.id}</h3>
                      <div className="flex items-center gap-2">
                        <label className="flex items-center gap-2 text-sm text-gray-300">
                          <input type="checkbox" checked={check.enabled !== false} onChange={(e) => updateCheck("enabled", e.target.checked)} className="rounded border-gray-700" />
                          Enabled
                        </label>
                        <button onClick={async () => {
                          setTesting(true); setTestResult(null);
                          try {
                            const res = await fetch(`/api/admin/watchdog-config/test/${check.id}`, {
                              method: "POST", headers: { "X-Session-Token": sessionToken },
                            });
                            setTestResult(await res.json());
                          } catch (e) { setTestResult({ error: String(e) }); }
                          setTesting(false);
                        }} disabled={testing}
                          className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded text-xs font-medium transition disabled:opacity-50">
                          {testing ? "Testing..." : "Test"}
                        </button>
                        <button onClick={() => {
                          setWatchdogCfg({...watchdogCfg, checks: watchdogCfg.checks.filter(c => c.id !== check.id)});
                          setSelectedCheckId("");
                        }}
                          className="px-3 py-1 bg-red-900 hover:bg-red-800 text-red-200 rounded text-xs font-medium transition">
                          Delete
                        </button>
                      </div>
                    </div>

                    <div className="grid grid-cols-2 gap-4">
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Label</label>
                        <input type="text" value={check.label || ""} onChange={(e) => updateCheck("label", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                      </div>
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Check ID</label>
                        <input type="text" value={check.id} disabled
                          className="w-full bg-gray-950 border border-gray-700 rounded px-3 py-1.5 text-sm text-gray-500" />
                      </div>
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Tab</label>
                        <select value={check.tab_id || ""} onChange={(e) => updateCheck("tab_id", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white">
                          <option value="">— select tab —</option>
                          {wdTabOptions.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
                        </select>
                      </div>
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Category</label>
                        <select value={check.category || ""} onChange={(e) => updateCheck("category", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white">
                          <option value="">— select category —</option>
                          {wdCategories.map(c => <option key={c} value={c}>{c}</option>)}
                        </select>
                      </div>
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Severity</label>
                        <select value={check.severity || "MEDIUM"} onChange={(e) => updateCheck("severity", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white">
                          <option value="LOW">LOW</option>
                          <option value="MEDIUM">MEDIUM</option>
                          <option value="HIGH">HIGH</option>
                          <option value="CRITICAL">CRITICAL</option>
                        </select>
                      </div>
                      <div>
                        <label className="block text-xs text-gray-400 mb-1">Connection</label>
                        <input type="text" value={check.connection || "nass"} onChange={(e) => updateCheck("connection", e.target.value)}
                          className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                      </div>
                    </div>

                    <div>
                      <label className="block text-xs text-amber-400 font-medium mb-1">Check SQL (returns summary row for condition evaluation)</label>
                      <textarea value={check.sql || ""} onChange={(e) => updateCheck("sql", e.target.value)}
                        rows={4} className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y min-h-[80px] focus:outline-none focus:border-amber-500"
                        placeholder="SELECT COUNT(*) AS cnt FROM ..." />
                    </div>

                    <div>
                      <label className="block text-xs text-blue-400 font-medium mb-1">Detail SQL (optional — actual data shown in finding details when triggered)</label>
                      <textarea value={check.detail_sql || ""} onChange={(e) => updateCheck("detail_sql", e.target.value)}
                        rows={4} className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white font-mono resize-y min-h-[80px] focus:outline-none focus:border-blue-500"
                        placeholder="SELECT FIRST 20 part_no, item_desc, recv_date_time FROM all_inv_items WHERE ..." />
                    </div>

                    <div>
                      <label className="block text-xs text-amber-400 font-medium mb-1">Condition (Python expression, evaluated against row[0])</label>
                      <input type="text" value={check.condition || ""} onChange={(e) => updateCheck("condition", e.target.value)}
                        className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white font-mono focus:outline-none focus:border-amber-500"
                        placeholder="row[0]['cnt'] > 100" />
                    </div>

                    <div>
                      <label className="block text-xs text-gray-400 mb-1">Finding Template (uses Python format with row values, used as fallback if no LLM prompt)</label>
                      <input type="text" value={check.finding_template || ""} onChange={(e) => updateCheck("finding_template", e.target.value)}
                        className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white font-mono"
                        placeholder="Aging inventory: {cnt} items > 30 days" />
                    </div>

                    <div>
                      <label className="block text-xs text-blue-400 font-medium mb-1">LLM Prompt (optional — sends query results to LLM for intelligent finding description)</label>
                      <textarea value={check.llm_prompt || ""} onChange={(e) => updateCheck("llm_prompt", e.target.value)}
                        rows={3} className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white resize-y min-h-[60px] focus:outline-none focus:border-blue-500"
                        placeholder="e.g., Summarize what was received today, highlight any unusual items or quantities compared to the norm." />
                    </div>

                    {testResult && (
                      <div className={`rounded p-3 text-sm ${testResult.error ? "bg-red-900/30 border border-red-700" : testResult.triggered ? "bg-amber-900/30 border border-amber-700" : "bg-green-900/30 border border-green-700"}`}>
                        <div className="font-semibold mb-1">
                          {testResult.error ? "Error" : testResult.triggered ? "TRIGGERED" : "OK (not triggered)"}
                        </div>
                        {testResult.error && <p className="text-red-300">{testResult.error}</p>}
                        {testResult.finding_text && <p className="text-gray-300">{testResult.finding_text}</p>}
                        {testResult.rows && testResult.rows.length > 0 && (
                          <pre className="mt-2 text-xs text-gray-400 bg-gray-900/50 p-2 rounded overflow-x-auto">{JSON.stringify(testResult.rows, null, 2)}</pre>
                        )}
                        {testResult.agent && <p className="text-xs text-gray-500 mt-1">Agent: {testResult.agent}</p>}
                      </div>
                    )}
                  </div>
                );
              })()}
            </div>
          </div>

          {/* Discovery Prompts Section */}
          <div className="mt-8">
            <div className="flex items-center justify-between mb-4">
              <div>
                <h2 className="text-xl font-semibold text-white">LLM Discovery Prompts</h2>
                <p className="text-xs text-gray-500 mt-1">Open-ended questions sent to the analytics LLM on a longer interval. It explores the data autonomously and flags anomalies.</p>
              </div>
            </div>

            <div className="flex gap-4" style={{minHeight: "350px"}}>
              {/* Discovery Prompt List */}
              <div className="w-64 flex-shrink-0 bg-gray-800 rounded p-3 overflow-y-auto">
                <h3 className="text-xs font-semibold text-blue-400 uppercase mb-2">Prompts</h3>
                {(watchdogCfg?.discovery_prompts || []).map((dp) => (
                  <button key={dp.id} onClick={() => setSelectedDiscoveryId(dp.id)}
                    className={`w-full text-left px-3 py-2 rounded text-sm mb-1 transition ${selectedDiscoveryId === dp.id ? "bg-blue-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
                    <div className="flex items-center justify-between">
                      <span className="truncate">{dp.label || dp.id}</span>
                      <span className={`w-2 h-2 rounded-full flex-shrink-0 ${dp.enabled !== false ? "bg-blue-400" : "bg-gray-600"}`}></span>
                    </div>
                    <div className="text-xs text-gray-400 mt-0.5">{dp.tab_id || "—"}</div>
                  </button>
                ))}
                <button onClick={() => {
                  const id = `disc-${Date.now()}`;
                  const newPrompt = { id, label: "New Discovery", tab_id: "distribution", prompt: "", enabled: true };
                  setWatchdogCfg({...watchdogCfg, discovery_prompts: [...(watchdogCfg?.discovery_prompts || []), newPrompt]});
                  setSelectedDiscoveryId(id);
                }}
                  className="w-full mt-2 px-3 py-2 bg-blue-700 hover:bg-blue-600 text-blue-100 rounded text-sm font-medium transition">
                  + Add Prompt
                </button>
              </div>

              {/* Discovery Prompt Detail */}
              <div className="flex-1 bg-gray-800 rounded p-4 overflow-y-auto">
                {(() => {
                  const dp = (watchdogCfg?.discovery_prompts || []).find(d => d.id === selectedDiscoveryId);
                  if (!dp) return <div className="text-gray-600 text-center mt-20">Select a prompt to edit</div>;
                  const idx = watchdogCfg.discovery_prompts.findIndex(d => d.id === dp.id);
                  const updateDisc = (field, value) => {
                    const updated = [...watchdogCfg.discovery_prompts];
                    updated[idx] = {...updated[idx], [field]: value};
                    setWatchdogCfg({...watchdogCfg, discovery_prompts: updated});
                  };
                  return (
                    <div className="space-y-4">
                      <div className="flex items-center justify-between">
                        <h3 className="text-lg font-semibold text-white">{dp.label || dp.id}</h3>
                        <div className="flex items-center gap-2">
                          <label className="flex items-center gap-2 text-sm text-gray-300">
                            <input type="checkbox" checked={dp.enabled !== false} onChange={(e) => updateDisc("enabled", e.target.checked)} className="rounded border-gray-700" />
                            Enabled
                          </label>
                          <button onClick={() => {
                            setWatchdogCfg({...watchdogCfg, discovery_prompts: watchdogCfg.discovery_prompts.filter(d => d.id !== dp.id)});
                            setSelectedDiscoveryId("");
                          }}
                            className="px-3 py-1 bg-red-900 hover:bg-red-800 text-red-200 rounded text-xs font-medium transition">
                            Delete
                          </button>
                        </div>
                      </div>

                      <div className="grid grid-cols-2 gap-4">
                        <div>
                          <label className="block text-xs text-gray-400 mb-1">Label</label>
                          <input type="text" value={dp.label || ""} onChange={(e) => updateDisc("label", e.target.value)}
                            className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white" />
                        </div>
                        <div>
                          <label className="block text-xs text-gray-400 mb-1">Tab</label>
                          <select value={dp.tab_id || ""} onChange={(e) => updateDisc("tab_id", e.target.value)}
                            className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-1.5 text-sm text-white">
                            <option value="">— select tab —</option>
                            {wdTabOptions.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
                          </select>
                        </div>
                      </div>

                      <div>
                        <label className="block text-xs text-blue-400 font-medium mb-1">Discovery Prompt</label>
                        <textarea value={dp.prompt || ""} onChange={(e) => updateDisc("prompt", e.target.value)}
                          rows={8} className="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white resize-y min-h-[150px] focus:outline-none focus:border-blue-500"
                          placeholder="e.g., Look at today's receiving activity in all_inv_items. Compare the volume and part numbers received today to the average over the last 30 days. Flag anything that stands out — unusually high volumes, new part numbers not seen before, or significant drops." />
                      </div>

                      <div className="text-xs text-gray-500 bg-gray-900/50 rounded p-3">
                        <p className="font-medium text-gray-400 mb-1">How it works:</p>
                        <p>This prompt is sent to the analytics LLM every {watchdogCfg?.global?.discovery_interval_seconds || 1800} seconds. The LLM has access to query_sql and flag_anomaly tools. It will write its own SQL, analyze results, and create findings automatically.</p>
                      </div>
                    </div>
                  );
                })()}
              </div>
            </div>
          </div>
        </div>
        ); })()}

      {adminSubTab === "logs" && <LogsSubTab sessionToken={sessionToken} />}

      {adminSubTab === "mfa" && <MFAAdminSubTab sessionToken={sessionToken} />}

    </div>
  );
}

// ============================================================================
// AUTHENTICATION & MAIN APP
// ============================================================================

function ProfileDropdown({ user, sessionToken, onLogout }) {
  const toast = useToast();
  const [showMenu, setShowMenu] = useState(false);
  const [showProfileModal, setShowProfileModal] = useState(false);
  const [showPasswordModal, setShowPasswordModal] = useState(false);
  const [showMFAModal, setShowMFAModal] = useState(false);
  const [mfaStatus, setMfaStatus] = useState(null);
  const [mfaQR, setMfaQR] = useState(null);
  const [mfaSetupCode, setMfaSetupCode] = useState("");
  const [profileData, setProfileData] = useState({
    display_name: user?.display_name || "",
    email: user?.email || "",
    department: user?.department || "",
  });
  const [passwordData, setPasswordData] = useState({
    current: "",
    new: "",
    confirm: "",
  });
  const [modalError, setModalError] = useState("");
  const [isSaving, setIsSaving] = useState(false);

  const handleProfileSave = async () => {
    if (!profileData.display_name.trim()) {
      setModalError("Display name is required");
      return;
    }

    setIsSaving(true);
    setModalError("");

    try {
      const response = await fetch("/api/profile", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "X-Session-Token": sessionToken,
        },
        body: JSON.stringify(profileData),
      });

      if (response.ok) {
        setShowProfileModal(false);
        toast("Profile updated successfully", "success");
      } else {
        throw new Error("Failed to update profile");
      }
    } catch (err) {
      setModalError(err.message || "Failed to save profile");
    } finally {
      setIsSaving(false);
    }
  };

  const handlePasswordChange = async () => {
    if (!passwordData.current || !passwordData.new || !passwordData.confirm) {
      setModalError("All fields are required");
      return;
    }

    if (passwordData.new !== passwordData.confirm) {
      setModalError("New passwords do not match");
      return;
    }

    if (passwordData.new.length < 6) {
      setModalError("New password must be at least 6 characters");
      return;
    }

    setIsSaving(true);
    setModalError("");

    try {
      const response = await fetch("/api/profile/change-password", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Session-Token": sessionToken,
        },
        body: JSON.stringify({
          current_password: passwordData.current,
          new_password: passwordData.new,
        }),
      });

      if (response.ok) {
        setShowPasswordModal(false);
        setPasswordData({ current: "", new: "", confirm: "" });
        toast("Password changed successfully", "success");
      } else {
        throw new Error("Failed to change password");
      }
    } catch (err) {
      setModalError(err.message || "Failed to change password");
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="relative">
      <button
        onClick={() => setShowMenu(!showMenu)}
        className="flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-700 transition text-gray-300"
        aria-expanded={showMenu}
        aria-haspopup="true"
        aria-label="User menu"
      >
        <span className="text-sm">{user?.display_name || user?.username}</span>
        <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
          <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
        </svg>
      </button>

      {showMenu && (
        <div className="absolute right-0 mt-2 w-48 bg-gray-800 border border-gray-700 rounded-lg shadow-lg z-50" role="menu" aria-label="User options">
          <button
            onClick={() => {
              setShowProfileModal(true);
              setShowMenu(false);
            }}
            className="block w-full text-left px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 hover:text-white transition"
          >
            Profile
          </button>
          {user?.auth_method === "ldap" ? (
            <div className="px-4 py-2 text-xs text-gray-500 italic">
              Password managed by Active Directory
            </div>
          ) : (
            <button
              onClick={() => {
                setShowPasswordModal(true);
                setShowMenu(false);
              }}
              className="block w-full text-left px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 hover:text-white transition"
            >
              Change Password
            </button>
          )}
          <button
            onClick={async () => {
              setShowMenu(false);
              try {
                const r = await fetch("/api/mfa/status", { headers: { "X-Session-Token": sessionToken } });
                if (r.ok) setMfaStatus(await r.json());
              } catch {}
              setShowMFAModal(true);
            }}
            className="block w-full text-left px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 hover:text-white transition"
          >
            Security (MFA)
          </button>
          <hr className="border-gray-700" />
          <button
            onClick={() => {
              onLogout();
              setShowMenu(false);
            }}
            className="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-gray-700 transition"
          >
            Logout
          </button>
        </div>
      )}

      {showProfileModal && (
        <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
          <div className="bg-gray-800 border border-gray-700 rounded-lg p-4 sm:p-6 w-full max-w-md max-h-[90vh] overflow-y-auto mx-2 sm:mx-0">
            <h3 className="text-lg font-semibold text-white mb-4">Edit Profile</h3>

            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">Display Name</label>
                <input
                  type="text"
                  value={profileData.display_name}
                  onChange={(e) => setProfileData({ ...profileData, display_name: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">Email</label>
                <input
                  type="email"
                  value={profileData.email}
                  onChange={(e) => setProfileData({ ...profileData, email: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">Department</label>
                <input
                  type="text"
                  value={profileData.department}
                  onChange={(e) => setProfileData({ ...profileData, department: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              {user?.roles && (
                <div>
                  <label className="block text-sm font-medium text-gray-300 mb-1">Roles</label>
                  <div className="flex flex-wrap gap-2">
                    {user.roles.map((role) => (
                      <span key={role} className="px-2 py-1 bg-amber-900 text-amber-200 text-xs rounded">
                        {role}
                      </span>
                    ))}
                  </div>
                </div>
              )}

              {user?.twofa_enabled && (
                <div>
                  <label className="block text-sm font-medium text-gray-300 mb-1">2FA Status</label>
                  <div className="text-sm text-green-400">Enabled ({user.twofa_method || "unknown"})</div>
                </div>
              )}

              {modalError && <div className="bg-red-900 text-red-200 rounded px-3 py-2 text-sm">{modalError}</div>}
            </div>

            <div className="flex gap-2 mt-6">
              <button
                onClick={() => {
                  setShowProfileModal(false);
                  setModalError("");
                  setProfileData({
                    display_name: user?.display_name || "",
                    email: user?.email || "",
                    department: user?.department || "",
                  });
                }}
                className="flex-1 px-4 py-2 text-sm text-gray-300 bg-gray-700 hover:bg-gray-600 rounded transition"
              >
                Cancel
              </button>
              <button
                onClick={handleProfileSave}
                disabled={isSaving}
                className="flex-1 px-4 py-2 text-sm text-white bg-amber-600 hover:bg-amber-700 rounded transition disabled:opacity-50"
              >
                {isSaving ? "Saving..." : "Save"}
              </button>
            </div>
          </div>
        </div>
      )}

      {showPasswordModal && (
        <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
          <div className="bg-gray-800 border border-gray-700 rounded-lg p-4 sm:p-6 w-full max-w-md max-h-[90vh] overflow-y-auto mx-2 sm:mx-0">
            <h3 className="text-lg font-semibold text-white mb-4">Change Password</h3>

            <div className="space-y-4">
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">Current Password</label>
                <input
                  type="password"
                  value={passwordData.current}
                  onChange={(e) => setPasswordData({ ...passwordData, current: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">New Password</label>
                <input
                  type="password"
                  value={passwordData.new}
                  onChange={(e) => setPasswordData({ ...passwordData, new: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              <div>
                <label className="block text-sm font-medium text-gray-300 mb-1">Confirm New Password</label>
                <input
                  type="password"
                  value={passwordData.confirm}
                  onChange={(e) => setPasswordData({ ...passwordData, confirm: e.target.value })}
                  className="w-full bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
                />
              </div>

              {modalError && <div className="bg-red-900 text-red-200 rounded px-3 py-2 text-sm">{modalError}</div>}
            </div>

            <div className="flex gap-2 mt-6">
              <button
                onClick={() => {
                  setShowPasswordModal(false);
                  setModalError("");
                  setPasswordData({ current: "", new: "", confirm: "" });
                }}
                className="flex-1 px-4 py-2 text-sm text-gray-300 bg-gray-700 hover:bg-gray-600 rounded transition"
              >
                Cancel
              </button>
              <button
                onClick={handlePasswordChange}
                disabled={isSaving}
                className="flex-1 px-4 py-2 text-sm text-white bg-amber-600 hover:bg-amber-700 rounded transition disabled:opacity-50"
              >
                {isSaving ? "Saving..." : "Save"}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* MFA Security Modal */}
      {showMFAModal && (
        <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
          <div className="bg-gray-800 border border-gray-700 rounded-lg p-4 sm:p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto mx-2 sm:mx-0">
            <h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
              <TabIcon name="shield" className="w-5 h-5 text-amber-400" />
              Security Settings
            </h3>

            {mfaStatus && (
              <div className="space-y-4">
                {/* TOTP Section */}
                <div className="bg-gray-900 rounded p-4">
                  <div className="flex items-center justify-between mb-2">
                    <h4 className="text-sm font-medium text-gray-300">Authenticator App (TOTP)</h4>
                    <span className={`text-xs px-2 py-0.5 rounded ${mfaStatus.totp_enabled ? "bg-green-900 text-green-300" : "bg-gray-700 text-gray-400"}`}>
                      {mfaStatus.totp_enabled ? "Enabled" : "Disabled"}
                    </span>
                  </div>
                  {!mfaStatus.totp_enabled && !mfaQR && (
                    <button onClick={async () => {
                      try {
                        const r = await fetch("/api/mfa/totp/setup", { method: "POST", headers: { "X-Session-Token": sessionToken } });
                        if (r.ok) { const d = await r.json(); setMfaQR(d.qr_code); setMfaSetupCode(""); }
                      } catch {}
                    }} className="px-3 py-1 bg-teal-600 hover:bg-teal-500 text-white text-sm rounded">
                      Enable TOTP
                    </button>
                  )}
                  {mfaQR && (
                    <div className="space-y-3">
                      <p className="text-xs text-gray-400">Scan this QR code with your authenticator app:</p>
                      <div className="flex justify-center"><img src={`data:image/png;base64,${mfaQR}`} alt="TOTP QR" className="w-48 h-48 rounded" /></div>
                      <div className="flex gap-2">
                        <input type="text" value={mfaSetupCode} onChange={e => setMfaSetupCode(e.target.value.replace(/[^\d]/g, "").slice(0, 6))}
                          placeholder="Enter 6-digit code" maxLength="6"
                          className="flex-1 bg-gray-700 text-white rounded px-3 py-2 text-sm border border-gray-600 text-center tracking-widest font-mono" />
                        <button onClick={async () => {
                          try {
                            const r = await fetch("/api/mfa/totp/verify-setup", {
                              method: "POST",
                              headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
                              body: JSON.stringify({ code: mfaSetupCode }),
                            });
                            if (r.ok) { setMfaQR(null); setMfaSetupCode(""); toast("TOTP enabled!", "success");
                              const sr = await fetch("/api/mfa/status", { headers: { "X-Session-Token": sessionToken } });
                              if (sr.ok) setMfaStatus(await sr.json());
                            } else { toast("Invalid code", "error"); }
                          } catch { toast("Failed to verify", "error"); }
                        }} className="px-3 py-2 bg-teal-600 hover:bg-teal-500 text-white text-sm rounded">
                          Verify
                        </button>
                      </div>
                    </div>
                  )}
                  {mfaStatus.totp_enabled && (
                    <button onClick={async () => {
                      try {
                        const r = await fetch("/api/mfa/totp/disable", { method: "POST", headers: { "X-Session-Token": sessionToken } });
                        if (r.ok) { toast("TOTP disabled", "success");
                          const sr = await fetch("/api/mfa/status", { headers: { "X-Session-Token": sessionToken } });
                          if (sr.ok) setMfaStatus(await sr.json());
                        } else { const d = await r.json().catch(() => ({})); toast(d.detail || "Failed", "error"); }
                      } catch {}
                    }} className="px-3 py-1 bg-red-900 hover:bg-red-800 text-red-200 text-xs rounded mt-2">
                      Disable TOTP
                    </button>
                  )}
                </div>

                {/* WebAuthn Section */}
                <div className="bg-gray-900 rounded p-4">
                  <div className="flex items-center justify-between mb-2">
                    <h4 className="text-sm font-medium text-gray-300">Security Keys (WebAuthn)</h4>
                    <span className="text-xs text-gray-400">{mfaStatus.webauthn_credentials} registered</span>
                  </div>
                  {mfaStatus.webauthn_names && mfaStatus.webauthn_names.length > 0 && (
                    <div className="space-y-1 mb-2">
                      {mfaStatus.webauthn_names.map((name, i) => (
                        <div key={i} className="flex items-center gap-2 text-sm text-gray-300">
                          <TabIcon name="key" className="w-4 h-4 text-gray-400" />
                          {name}
                        </div>
                      ))}
                    </div>
                  )}
                  <button onClick={async () => {
                    try {
                      const optR = await fetch("/api/mfa/webauthn/register-options", { method: "POST", headers: { "X-Session-Token": sessionToken } });
                      if (!optR.ok) { toast("WebAuthn not available", "error"); return; }
                      const options = await optR.json();
                      const b64ToAb = (b64) => { const s = atob(b64.replace(/-/g,"+").replace(/_/g,"/")); const a = new Uint8Array(s.length); for(let i=0;i<s.length;i++) a[i]=s.charCodeAt(i); return a.buffer; };
                      const abToB64 = (ab) => btoa(String.fromCharCode(...new Uint8Array(ab))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");
                      options.challenge = b64ToAb(options.challenge);
                      options.user.id = b64ToAb(options.user.id);
                      if (options.excludeCredentials) options.excludeCredentials = options.excludeCredentials.map(c => ({...c, id: b64ToAb(c.id)}));
                      const cred = await navigator.credentials.create({ publicKey: options });
                      const keyName = prompt("Name this security key:", "My Security Key") || "Security Key";
                      const complR = await fetch("/api/mfa/webauthn/register-complete", {
                        method: "POST",
                        headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
                        body: JSON.stringify({
                          key_name: keyName,
                          id: abToB64(cred.rawId),
                          rawId: abToB64(cred.rawId),
                          type: cred.type,
                          response: {
                            attestationObject: abToB64(cred.response.attestationObject),
                            clientDataJSON: abToB64(cred.response.clientDataJSON),
                          },
                        }),
                      });
                      if (complR.ok) { toast(`Key "${keyName}" registered!`, "success");
                        const sr = await fetch("/api/mfa/status", { headers: { "X-Session-Token": sessionToken } });
                        if (sr.ok) setMfaStatus(await sr.json());
                      } else { const d = await complR.json().catch(() => ({})); toast(d.detail || "Registration failed", "error"); }
                    } catch (err) {
                      if (err.name !== "NotAllowedError") toast(`WebAuthn error: ${err.message}`, "error");
                    }
                  }} className="px-3 py-1 bg-teal-600 hover:bg-teal-500 text-white text-sm rounded">
                    Register New Key
                  </button>
                </div>

                {/* Trusted Devices */}
                <div className="bg-gray-900 rounded p-4">
                  <h4 className="text-sm font-medium text-gray-300 mb-2">Trusted Devices ({mfaStatus.trusted_devices} active)</h4>
                  <p className="text-xs text-gray-500">Devices remembered for 7 days. Explicit logout removes trust.</p>
                </div>
              </div>
            )}

            <div className="flex justify-end mt-4">
              <button onClick={() => { setShowMFAModal(false); setMfaQR(null); setMfaSetupCode(""); }}
                className="px-4 py-2 text-sm text-gray-300 bg-gray-700 hover:bg-gray-600 rounded transition">
                Close
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function LoginScreen({ onLogin }) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [twoFACode, setTwoFACode] = useState("");
  const [error, setError] = useState("");
  const [twoFARequired, setTwoFARequired] = useState(false);
  const [challengeToken, setChallengeToken] = useState(null);
  const [availableMethods, setAvailableMethods] = useState([]);
  const [selectedMethod, setSelectedMethod] = useState("totp");
  const [rememberDevice, setRememberDevice] = useState(true);
  const [mfaSetupRequired, setMfaSetupRequired] = useState(false);
  const [setupStep, setSetupStep] = useState(null); // null, "qr", "verify"
  const [qrCode, setQrCode] = useState("");
  const [totpSecret, setTotpSecret] = useState("");
  const [webauthnLoading, setWebauthnLoading] = useState(false);

  // Persistent device ID for trusted device tracking
  const getDeviceId = () => {
    let id = localStorage.getItem("hal_device_id");
    if (!id) {
      try { id = crypto.randomUUID(); } catch { id = Math.random().toString(36).slice(2) + Date.now().toString(36); }
      localStorage.setItem("hal_device_id", id);
    }
    return id;
  };

  // Signal password manager to save credentials after successful login
  const notifyPasswordManager = (user, pass) => {
    // Method 1: PasswordCredential API (Chrome, Edge)
    if (window.PasswordCredential) {
      try {
        const cred = new PasswordCredential({ id: user, password: pass, name: user });
        navigator.credentials.store(cred).catch(() => {});
      } catch {}
    }
    // Method 2: Hidden form submit (Firefox, Safari, Bitwarden, 1Password)
    try {
      const iframe = document.createElement("iframe");
      iframe.style.display = "none";
      iframe.name = "hal-pw-save";
      document.body.appendChild(iframe);
      const form = document.createElement("form");
      form.method = "POST";
      form.action = "/api/auth/login";
      form.target = "hal-pw-save";
      const uInput = document.createElement("input");
      uInput.type = "text"; uInput.name = "username"; uInput.autocomplete = "username"; uInput.value = user;
      const pInput = document.createElement("input");
      pInput.type = "password"; pInput.name = "password"; pInput.autocomplete = "current-password"; pInput.value = pass;
      form.appendChild(uInput);
      form.appendChild(pInput);
      document.body.appendChild(form);
      form.submit();
      setTimeout(() => { form.remove(); iframe.remove(); }, 2000);
    } catch {}
  };

  const handleLogin = async () => {
    setError("");
    if (!username.trim() || !password.trim()) { setError("Please enter both username and password"); return; }
    try {
      const response = await fetch("/api/auth/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password, device_id: getDeviceId() }),
      });
      if (response.ok) {
        const data = await response.json();
        if (data.requires_2fa) {
          setChallengeToken(data.challenge_token);
          setAvailableMethods(data.methods || [data.method || "totp"]);
          setSelectedMethod((data.methods || [])[0] || data.method || "totp");
          setTwoFARequired(true);
          notifyPasswordManager(username, password);
        } else if (data.requires_mfa_setup) {
          setChallengeToken(data.challenge_token);
          setMfaSetupRequired(true);
          notifyPasswordManager(username, password);
        } else {
          notifyPasswordManager(username, password);
          if (data.mfa_setup_required) {
            onLogin(data.session_token, data.user, { mfaSetupRequired: true, graceRemaining: data.grace_logins_remaining });
          } else {
            onLogin(data.session_token, data.user);
          }
        }
      } else {
        const errData = await response.json().catch(() => ({}));
        setError(errData.detail || "Invalid credentials");
      }
    } catch (err) {
      setError("Unable to reach server. Please try again.");
    }
  };

  const handleVerifyTOTP = async () => {
    if (!twoFACode.trim()) { setError("Please enter your authentication code"); return; }
    try {
      const response = await fetch("/api/auth/2fa-verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          challenge_token: challengeToken, code: twoFACode, method: "totp",
          remember_device: rememberDevice, device_id: getDeviceId(),
        }),
      });
      if (response.ok) {
        const data = await response.json();
        onLogin(data.session_token, data.user);
      } else {
        setError("Invalid authentication code. Please try again.");
      }
    } catch (err) {
      setError("Verification failed. Please try again.");
    }
  };

  const handleWebAuthn = async () => {
    setWebauthnLoading(true);
    setError("");
    try {
      // Get auth options
      const optResp = await fetch("/api/mfa/webauthn/auth-options", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ challenge_token: challengeToken }),
      });
      if (!optResp.ok) throw new Error("Failed to get WebAuthn options");
      const options = await optResp.json();
      // Convert base64url to ArrayBuffer
      const b64ToAb = (b64) => { const s = atob(b64.replace(/-/g,"+").replace(/_/g,"/")); const a = new Uint8Array(s.length); for(let i=0;i<s.length;i++) a[i]=s.charCodeAt(i); return a.buffer; };
      const abToHex = (ab) => Array.from(new Uint8Array(ab)).map(b=>b.toString(16).padStart(2,"0")).join("");
      const abToB64 = (ab) => btoa(String.fromCharCode(...new Uint8Array(ab))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");

      options.challenge = b64ToAb(options.challenge);
      if (options.allowCredentials) {
        options.allowCredentials = options.allowCredentials.map(c => ({...c, id: b64ToAb(c.id)}));
      }
      const assertion = await navigator.credentials.get({ publicKey: options });
      // Send assertion
      const complResp = await fetch("/api/mfa/webauthn/auth-complete", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          challenge_token: challengeToken,
          remember_device: rememberDevice,
          device_id: getDeviceId(),
          credential: {
            id: abToB64(assertion.rawId),
            rawId: abToB64(assertion.rawId),
            type: assertion.type,
            response: {
              authenticatorData: abToB64(assertion.response.authenticatorData),
              clientDataJSON: abToB64(assertion.response.clientDataJSON),
              signature: abToB64(assertion.response.signature),
            },
          },
        }),
      });
      if (complResp.ok) {
        const data = await complResp.json();
        onLogin(data.session_token, data.user);
      } else {
        const errData = await complResp.json().catch(() => ({}));
        setError(errData.detail || "WebAuthn authentication failed");
      }
    } catch (err) {
      if (err.name === "NotAllowedError") {
        setError("Authentication cancelled or timed out");
      } else {
        setError(`WebAuthn error: ${err.message}`);
      }
    }
    setWebauthnLoading(false);
  };

  const handleBack2FA = () => {
    setTwoFARequired(false); setTwoFACode(""); setChallengeToken(null);
    setAvailableMethods([]); setSelectedMethod("totp"); setError("");
    setMfaSetupRequired(false); setSetupStep(null); setQrCode(""); setTotpSecret("");
  };

  // ── MFA Setup Flow (forced enrollment) ──
  if (mfaSetupRequired) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
        <div className="w-full max-w-md">
          <div className="bg-gray-800 rounded-lg p-5 sm:p-8 border border-gray-700">
            <div className="flex items-center justify-center mb-4">
              <div className="w-10 h-10 bg-amber-600 rounded-full flex items-center justify-center">
                <TabIcon name="shield" className="w-5 h-5 text-white" />
              </div>
              <div className="ml-3">
                <h1 className="text-xl font-bold text-white">MFA Required</h1>
                <p className="text-xs text-amber-400">Set up multi-factor authentication</p>
              </div>
            </div>
            <p className="text-sm text-gray-300 text-center mb-6">Your account requires MFA. Please set up an authenticator app to continue.</p>
            {/* Simplified: TOTP setup only for forced enrollment */}
            {!setupStep && (
              <button onClick={async () => {
                try {
                  // Need a temp session — use the challenge token to identify
                  const r = await fetch("/api/mfa/totp/setup", { method: "POST", headers: { "Content-Type": "application/json" } });
                  // This won't work without a session — enrollment during forced setup needs the challenge token
                  // For now, show instructions
                  setSetupStep("instructions");
                } catch {}
              }} className="w-full bg-amber-600 hover:bg-amber-500 text-white font-semibold py-2 rounded transition">
                Set Up Authenticator App
              </button>
            )}
            {setupStep === "instructions" && (
              <div className="text-sm text-gray-300 space-y-3">
                <p>Please log in first (you have grace logins remaining), then go to your profile to set up MFA.</p>
                <button onClick={handleBack2FA} className="w-full bg-gray-700 hover:bg-gray-600 text-white py-2 rounded transition">Back to Login</button>
              </div>
            )}
          </div>
        </div>
      </div>
    );
  }

  // ── 2FA Verification Screen ──
  if (twoFARequired) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
        <div className="w-full max-w-md">
          <div className="bg-gray-800 rounded-lg p-5 sm:p-8 border border-gray-700">
            <div className="flex items-center justify-center mb-6">
              <div className="w-10 h-10 bg-teal-600 rounded-full flex items-center justify-center">
                <TabIcon name="shield" className="w-5 h-5 text-white" />
              </div>
              <div className="ml-3">
                <h1 className="text-xl font-bold text-white">Verify Identity</h1>
                <p className="text-xs text-teal-400">Two-Factor Authentication</p>
              </div>
            </div>

            {/* Method selector — only show if multiple methods available */}
            {availableMethods.length > 1 && (
              <div className="flex gap-2 mb-4">
                {availableMethods.includes("totp") && (
                  <button onClick={() => setSelectedMethod("totp")}
                    className={`flex-1 py-2 px-3 rounded text-sm font-medium transition ${selectedMethod === "totp" ? "bg-teal-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
                    Authenticator App
                  </button>
                )}
                {availableMethods.includes("webauthn") && (
                  <button onClick={() => setSelectedMethod("webauthn")}
                    className={`flex-1 py-2 px-3 rounded text-sm font-medium transition ${selectedMethod === "webauthn" ? "bg-teal-600 text-white" : "bg-gray-700 text-gray-300 hover:bg-gray-600"}`}>
                    Security Key
                  </button>
                )}
              </div>
            )}

            <div className="space-y-4">
              {selectedMethod === "totp" && (
                <>
                  <div>
                    <label className="block text-sm font-medium text-gray-300 mb-1">Authentication Code</label>
                    <input type="text" value={twoFACode}
                      onChange={(e) => setTwoFACode(e.target.value.replace(/[^\d]/g, "").slice(0, 6))}
                      onKeyPress={(e) => e.key === "Enter" && handleVerifyTOTP()}
                      placeholder="000000" autoFocus maxLength="6"
                      className="w-full bg-gray-700 text-white rounded px-4 py-3 text-sm border border-gray-600 focus:border-teal-500 focus:outline-none text-center text-xl tracking-[0.5em] font-mono"
                    />
                  </div>
                  <button onClick={handleVerifyTOTP}
                    className="w-full bg-teal-600 hover:bg-teal-700 text-white font-semibold py-2 rounded transition">
                    Verify
                  </button>
                </>
              )}

              {selectedMethod === "webauthn" && (
                <>
                  <div className="text-center py-4">
                    <TabIcon name="key" className="w-12 h-12 mx-auto text-teal-400 mb-3" />
                    <p className="text-sm text-gray-300 mb-4">Touch your security key or use biometric authentication</p>
                    <button onClick={handleWebAuthn} disabled={webauthnLoading}
                      className="w-full bg-teal-600 hover:bg-teal-700 text-white font-semibold py-3 rounded transition disabled:opacity-50">
                      {webauthnLoading ? "Waiting for key..." : "Use Security Key"}
                    </button>
                  </div>
                </>
              )}

              {/* Remember device checkbox */}
              <label className="flex items-center gap-2 text-sm text-gray-400">
                <input type="checkbox" checked={rememberDevice} onChange={(e) => setRememberDevice(e.target.checked)}
                  className="w-4 h-4 rounded bg-gray-700 border-gray-600 text-teal-600" />
                Remember this device for 7 days
              </label>

              {error && <div className="bg-red-900 text-red-200 rounded px-3 py-2 text-sm">{error}</div>}

              <button onClick={handleBack2FA}
                className="w-full text-teal-400 hover:text-teal-300 text-sm font-medium transition">
                Back to Login
              </button>
            </div>

            <p className="text-xs text-gray-400 text-center mt-6">Contact your administrator if you have issues.</p>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center p-4">
      <div className="w-full max-w-md">
        <div className="bg-gray-800 rounded-lg p-5 sm:p-8 border border-gray-700">
          <div className="flex flex-col items-center mb-6">
            <ICPLogo className="h-12 mb-3" />
            <p className="text-xs text-gray-400 tracking-widest uppercase">Island Computer Products</p>
          </div>

          <h2 className="text-lg font-semibold text-white text-center mb-6">Business Intelligence Platform</h2>

          <form className="space-y-4" onSubmit={(e) => { e.preventDefault(); handleLogin(); }} autoComplete="on" method="post" action="/api/auth/login">
            <div>
              <label htmlFor="hal-username" className="block text-sm font-medium text-gray-300 mb-1">Username</label>
              <input
                id="hal-username"
                name="username"
                type="text"
                autoComplete="username"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                placeholder="Enter your username"
                autoFocus
                className="w-full bg-gray-700 text-white rounded px-4 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
              />
            </div>

            <div>
              <label htmlFor="hal-password" className="block text-sm font-medium text-gray-300 mb-1">Password</label>
              <input
                id="hal-password"
                name="password"
                type="password"
                autoComplete="current-password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder="Enter your password"
                className="w-full bg-gray-700 text-white rounded px-4 py-2 text-sm border border-gray-600 focus:border-amber-500 focus:outline-none"
              />
            </div>

            {error && <div className="bg-red-900 text-red-200 rounded px-3 py-2 text-sm">{error}</div>}

            <button
              type="submit"
              className="w-full bg-amber-600 hover:bg-amber-700 text-white font-semibold py-2 rounded transition"
            >
              Sign In
            </button>
          </form>

          <p className="text-xs text-gray-400 text-center mt-6">Contact your administrator for access.</p>

          <div className="flex items-center justify-end mt-4 gap-1.5 opacity-50">
            <TabIcon name="brain-chip" className="w-5 h-5 text-amber-500" />
            <div>
              <p className="text-[10px] text-gray-400 leading-tight">Powered by <span className="text-gray-300 font-medium">HAL</span></p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function SessionExpiryWarning({ sessionToken }) {
  const [remaining, setRemaining] = useState(null);
  const [dismissed, setDismissed] = useState(false);

  useEffect(() => {
    if (!sessionToken) return;
    const check = () => {
      fetch("/api/auth/me", { headers: { "X-Session-Token": sessionToken } })
        .then(r => r.ok ? r.json() : null)
        .then(data => {
          if (data?.session_remaining !== undefined) setRemaining(data.session_remaining);
        })
        .catch(() => {});
    };
    check();
    const interval = setInterval(check, 60000); // Check every minute
    return () => clearInterval(interval);
  }, [sessionToken]);

  // Show warning when less than 10 minutes remain
  if (dismissed || remaining === null || remaining > 600) return null;
  const mins = Math.ceil(remaining / 60);
  return (
    <div className="bg-amber-900/80 border-b border-amber-600 px-4 py-2 text-center text-sm text-amber-100" role="alert">
      Your session expires in {mins} minute{mins !== 1 ? "s" : ""}. Save your work.
      <button onClick={() => setDismissed(true)} className="ml-3 text-amber-300 hover:text-white underline text-xs">Dismiss</button>
    </div>
  );
}

function HALDashboard() {
  const [sessionToken, setSessionToken] = useState(null);
  const [user, setUser] = useState(null);
  const [sessionChecked, setSessionChecked] = useState(false);
  const [activeTab, _setActiveTab] = useState(() => {
    try { return localStorage.getItem("hal_active_tab") || "insights"; } catch { return "insights"; }
  });
  const setActiveTab = (tab) => {
    _setActiveTab(tab);
    try { localStorage.setItem("hal_active_tab", tab); } catch {}
  };

  // Hardcoded fallback tabs (used if /api/tabs is unreachable)
  const FALLBACK_TABS = [
    { id: "chat", label: "HAL", icon: "brain-chip", always: true, access_level: "full", has_tab_chat: false },
    { id: "insights", label: "Insights", icon: "lightning", always: true, access_level: "full", has_tab_chat: false },
    { id: "dashboard", label: "Agents", icon: "server", always: false, access_level: "full", has_tab_chat: false, roles: ["fleet_ops", "admin"] },
    { id: "console", label: "Console", icon: "terminal", always: false, access_level: "full", has_tab_chat: true, roles: ["fleet_ops", "admin"] },
    { id: "distribution", label: "Distribution", icon: "truck", always: false, access_level: "full", has_tab_chat: true, roles: ["distribution_ops"] },
    { id: "config", label: "Config Center", icon: "settings", always: false, access_level: "full", has_tab_chat: true, roles: ["config_center"] },
    { id: "services", label: "SmartSwap", icon: "wrench", always: false, access_level: "full", has_tab_chat: true, roles: ["services"] },
    { id: "front_office", label: "Front Office", icon: "users", always: false, access_level: "full", has_tab_chat: true, roles: ["front_office"] },
    { id: "sales", label: "Sales", icon: "briefcase", always: false, access_level: "full", has_tab_chat: true, roles: ["sales"] },
    { id: "reports", label: "Reports", icon: "chart-bar", always: false, access_level: "restricted", has_tab_chat: true, roles: ["manager", "executive"] },
    { id: "admin", label: "Admin", icon: "shield", always: false, access_level: "full", has_tab_chat: false, roles: ["admin"] },
  ];

  const [visibleTabs, setVisibleTabs] = useState([]);

  // Fetch visible tabs from API (dynamic, role-aware)
  useEffect(() => {
    if (!sessionToken || !user) return;
    fetch("/api/tabs", { headers: { "X-Session-Token": sessionToken } })
      .then((r) => r.ok ? r.json() : Promise.reject("Failed"))
      .then((data) => setVisibleTabs(data.tabs || []))
      .catch(() => {
        // Fallback: filter by hardcoded roles
        const filtered = FALLBACK_TABS.filter((tab) => {
          if (tab.always) return true;
          if (!tab.roles) return false;
          return hasRole(user, ...tab.roles);
        });
        setVisibleTabs(filtered);
      });
  }, [sessionToken, user]);

  // Poll for pending approvals
  const [pendingApprovalCount, setPendingApprovalCount] = useState(0);
  useEffect(() => {
    if (!sessionToken) return;
    const fetchPending = () => {
      fetch("/api/approvals/pending", { headers: { "X-Session-Token": sessionToken } })
        .then((r) => r.ok ? r.json() : Promise.reject())
        .then((data) => setPendingApprovalCount((data.approvals || []).length))
        .catch(() => {});
    };
    fetchPending();
    const interval = setInterval(fetchPending, 30000);
    return () => clearInterval(interval);
  }, [sessionToken]);

  // Restore session from cookie on mount
  useEffect(() => {
    const saved = getCookie("hal_session");
    if (saved && saved.token && saved.user) {
      // Validate session is still good with the server
      fetch("/api/auth/me", {
        headers: { "X-Session-Token": saved.token },
      })
        .then((r) => {
          if (r.ok) return r.json();
          throw new Error("Session expired");
        })
        .then((data) => {
          setSessionToken(saved.token);
          const u = data.user || saved.user || {};
          if (data.auth_method) u.auth_method = data.auth_method;
          setUser(u);
        })
        .catch(() => {
          deleteCookie("hal_session");
        })
        .finally(() => setSessionChecked(true));
    } else {
      setSessionChecked(true);
    }
  }, []);

  const handleLogin = (token, userData) => {
    setSessionToken(token);
    setUser(userData);
    setCookie("hal_session", { token, user: userData }, 24);
    // Fetch auth_method from /api/auth/me to know if user is LDAP
    fetch("/api/auth/me", { headers: { "X-Session-Token": token } })
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (data && data.auth_method) {
          const u = { ...userData, auth_method: data.auth_method };
          setUser(u);
          setCookie("hal_session", { token, user: u }, 24);
        }
      }).catch(() => {});
  };

  const handleLogout = () => {
    if (sessionToken) {
      const deviceId = localStorage.getItem("hal_device_id") || "";
      fetch("/api/auth/logout", {
        method: "POST",
        headers: { "X-Session-Token": sessionToken, "Content-Type": "application/json" },
        body: JSON.stringify({ device_id: deviceId }),
      }).catch(() => {});
    }
    setSessionToken(null);
    setUser(null);
    setActiveTab("chat");
    deleteCookie("hal_session");
  };

  if (!sessionChecked) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center">
        <div className="text-center">
          <img src="/static/icp-logo-blue.png" alt="ICP" className="h-12 mx-auto mb-4" style={{filter: "brightness(0) invert(1)"}} />
          <div className="flex items-center gap-2 justify-center">
            <div className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{animationDelay: "0ms"}}></div>
            <div className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{animationDelay: "150ms"}}></div>
            <div className="w-2 h-2 bg-amber-500 rounded-full animate-bounce" style={{animationDelay: "300ms"}}></div>
          </div>
        </div>
      </div>
    );
  }

  if (!sessionToken || !user) {
    return <LoginScreen onLogin={handleLogin} />;
  }

  return (
    <div className="flex h-screen bg-gray-900">
      <div className="w-full flex flex-col">
        <SessionExpiryWarning sessionToken={sessionToken} />
        <div className="bg-gray-800 border-b border-gray-700 px-3 py-2 sm:px-4 md:px-6 md:py-3">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-2 sm:gap-3">
              <ICPLogo className="h-5 sm:h-6" />
              <h1 className="hidden sm:block text-sm font-semibold text-gray-300">Business Intelligence Platform</h1>
            </div>

            <div className="flex items-center gap-2 sm:gap-4">
              {pendingApprovalCount > 0 && (
                <button
                  onClick={() => setActiveTab("chat")}
                  className="relative text-gray-300 hover:text-white transition"
                  title={`${pendingApprovalCount} pending approval(s)`}
                  aria-label={`${pendingApprovalCount} pending approvals`}
                >
                  <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
                  </svg>
                  <span className="absolute -top-1 -right-1 bg-red-500 text-white text-[10px] rounded-full w-4 h-4 flex items-center justify-center font-bold">
                    {pendingApprovalCount > 9 ? "9+" : pendingApprovalCount}
                  </span>
                </button>
              )}
              <div className="flex items-center gap-1 opacity-40">
                <TabIcon name="brain-chip" className="w-4 h-4 text-amber-500" />
                <span className="text-[9px] text-gray-400">HAL</span>
              </div>
              <ProfileDropdown user={user} sessionToken={sessionToken} onLogout={handleLogout} />
            </div>
          </div>
        </div>

        <div className="bg-gray-800 border-b border-gray-700 px-2 py-1.5 sm:px-4 md:px-6 md:py-2 overflow-x-auto">
          <div className="flex gap-1">
            {visibleTabs.map((tab) => (
              <button
                key={tab.id}
                onClick={() => setActiveTab(tab.id)}
                className={`px-2 py-1 sm:px-3 sm:py-1.5 md:px-4 md:py-2 rounded text-xs sm:text-sm font-medium transition whitespace-nowrap ${
                  activeTab === tab.id
                    ? "bg-amber-600 text-white"
                    : "text-gray-300 hover:bg-gray-700 hover:text-white"
                }`}
              >
                <span className="inline-flex items-center gap-1 sm:gap-1.5">
                  {tab.icon && <TabIcon name={tab.icon} className="w-4 h-4" />}
                  <span className="hidden sm:inline">{tab.label}</span>
                  {tab.access_level === "restricted" && (
                    <span className="text-xs opacity-60 hidden sm:inline" title="Read-only access">R</span>
                  )}
                </span>
              </button>
            ))}
          </div>
        </div>

        <div className="flex-1 overflow-hidden">
          {(() => {
            const activeTabCfg = visibleTabs.find((t) => t.id === activeTab) || {};
            const tabProps = {
              user, sessionToken,
              tabId: activeTab,
              accessLevel: activeTabCfg.access_level || "full",
              hasTabChat: activeTabCfg.has_tab_chat || false,
              tabLabel: activeTabCfg.label || activeTab,
            };
            return (
              <>
                {activeTab === "insights" && <InsightsTab {...tabProps} />}
                {activeTab === "chat" && <MainChat {...tabProps} />}
                {activeTab === "dashboard" && <DashboardTab {...tabProps} />}
                {activeTab === "console" && <ConsoleTab fleet={MOCK_FLEET} agents={MOCK_AGENTS} {...tabProps} />}
                {["distribution", "config", "services", "front_office", "sales", "reports"].includes(activeTab) && <BusinessTab {...tabProps} />}
                {activeTab === "admin" && <AdminPanel {...tabProps} />}
              </>
            );
          })()}
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<ToastProvider><HALDashboard /></ToastProvider>);
