// Logs — a live tail/log-viewer over the agent's rotating log files. Wired to the
// dashboard log plane via HERMES.logs({ file, lines, level, search }) which returns
// { file, lines:[ "raw line\n", ... ] }. File tabs switch between agent/errors/
// gateway/gui/desktop; a lines cap + level filter + search narrow the slice; an
// auto-refresh toggle re-tails every 5s. Offline (no backend) shows a graceful
// empty state with the live badge flipped to "preview".
const { useState, useEffect, useCallback, useRef } = React;

// The rotating log files the backend exposes (?file=...). default = "agent".
const LOG_FILES = [
  ["agent", "Agent"],
  ["errors", "Errors"],
  ["gateway", "Gateway"],
  ["gui", "GUI"],
  ["desktop", "Desktop"],
];

// Line-count caps for the slice selector. default = 200.
const LINE_CAPS = [100, 200, 500, 1000];

// Level filter options — "" = All. These are passed straight through to the backend.
const LEVELS = [
  ["", "All levels"],
  ["DEBUG", "Debug"],
  ["INFO", "Info"],
  ["WARNING", "Warning"],
  ["ERROR", "Error"],
];

// Classify a raw log line by the first level token we can spot, mapping to a theme
// color. ERROR/CRITICAL -> danger; WARN/WARNING -> warn; INFO -> text; DEBUG -> faint.
// Unmatched lines stay muted so structured lines pop against continuation/trace text.
const LEVEL_RE = /\b(CRITICAL|FATAL|ERROR|WARNING|WARN|INFO|DEBUG|TRACE)\b/;
function lineColor(line) {
  const m = LEVEL_RE.exec(line);
  if (!m) return "var(--muted)";
  switch (m[1]) {
    case "CRITICAL":
    case "FATAL":
    case "ERROR": return "var(--danger)";
    case "WARNING":
    case "WARN": return "var(--warn)";
    case "INFO": return "var(--text)";
    case "DEBUG":
    case "TRACE": return "var(--faint)";
    default: return "var(--muted)";
  }
}

function PageLogs({ toast }) {
  const [file, setFile] = useState("agent");     // active log file tab
  const [lineCap, setLineCap] = useState(200);   // max lines requested
  const [level, setLevel] = useState("");        // level filter ("" = all)
  const [search, setSearch] = useState("");      // free-text filter passed to backend
  const [lines, setLines] = useState([]);        // returned raw log lines
  const [live, setLive] = useState(null);        // null=loading, true=backend, false=offline
  const [loading, setLoading] = useState(false); // in-flight indicator for refresh
  const [auto, setAuto] = useState(false);       // 5s auto-refresh toggle

  const viewRef = useRef(null);   // scrollable viewer (auto-scroll to bottom)
  const autoRef = useRef(false);  // mirrors `auto` so the interval reads the latest

  // Pull the current slice. `silent` skips the loading flicker for auto-refresh ticks.
  const load = useCallback(async (silent = false) => {
    if (!silent) setLoading(true);
    try {
      const r = await HERMES.logs({ file, lines: lineCap, level, search });
      setLines(Array.isArray(r && r.lines) ? r.lines : []);
      setLive(true);
    } catch (e) {
      setLines([]);
      setLive(false);
      if (!silent && !auto) toast(e.message || "Could not load logs");
    }
    if (!silent) setLoading(false);
  }, [file, lineCap, level, search, auto, toast]);

  // Manual refresh button — always toasts so the user gets feedback either way.
  const refresh = useCallback(async () => {
    setLoading(true);
    try {
      const r = await HERMES.logs({ file, lines: lineCap, level, search });
      setLines(Array.isArray(r && r.lines) ? r.lines : []);
      setLive(true);
      toast(`${(LOG_FILES.find((f) => f[0] === file) || [, file])[1]} log refreshed`);
    } catch (e) {
      setLines([]);
      setLive(false);
      toast(e.message || "Could not load logs");
    }
    setLoading(false);
  }, [file, lineCap, level, search, toast]);

  // Re-fetch whenever the file / cap / level / search controls change.
  useEffect(() => { load(); /* eslint-disable-next-line */ }, [file, lineCap, level, search]);

  // Auto-refresh: tick every 5s while `auto` is on. Cleared on unmount and when off.
  useEffect(() => {
    autoRef.current = auto;
    if (!auto) return;
    const id = setInterval(() => { if (autoRef.current) load(true); }, 5000);
    return () => clearInterval(id);
  }, [auto, load]);

  // Auto-scroll to the newest line whenever fresh data lands.
  useEffect(() => {
    const el = viewRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [lines]);

  const fileLabel = (LOG_FILES.find((f) => f[0] === file) || [, file])[1];

  return (
    <div className="page">
      <PageHead eyebrow="System" title="Logs" sub="Tail the agent, gateway, and GUI log files — filter by level or search, with optional live auto-refresh.">
        {live === true && <span className="badge ok"><span className="d" />live</span>}
        {live === false && <span className="badge warn"><span className="d" />preview</span>}
        {live === null && <span className="badge"><span className="d" />loading…</span>}
        <button className="btn btn-ghost" onClick={refresh}><Icon name="refresh" size={16} />Refresh</button>
      </PageHead>

      {/* ---- File tabs ---- */}
      <div className="tabs" style={{ marginBottom: "var(--gap)" }}>
        {LOG_FILES.map(([id, label]) => (
          <button key={id} className={cls("tab", file === id && "on")} onClick={() => setFile(id)}>{label}</button>
        ))}
      </div>

      {/* ---- Controls ---- */}
      <div className="card" style={{ marginBottom: "var(--gap)" }}>
        <div className="flex" style={{ gap: 12, alignItems: "flex-end", flexWrap: "wrap" }}>
          <div className="field" style={{ flex: "1 1 260px", minWidth: 200 }}>
            <label>Search</label>
            <div className="search" style={{ width: "100%" }}>
              <Icon name="search" size={15} />
              <input placeholder="Filter lines by text…" value={search} onChange={(e) => setSearch(e.target.value)} />
              {search && <button className="icon-btn x" onClick={() => setSearch("")} title="Clear search"><Icon name="x" size={14} /></button>}
            </div>
          </div>
          <div className="field" style={{ flex: "0 0 130px" }}>
            <label>Level</label>
            <select className="select" value={level} onChange={(e) => setLevel(e.target.value)}>
              {LEVELS.map(([v, label]) => <option key={v || "all"} value={v}>{label}</option>)}
            </select>
          </div>
          <div className="field" style={{ flex: "0 0 120px" }}>
            <label>Lines</label>
            <select className="select" value={lineCap} onChange={(e) => setLineCap(Number(e.target.value))}>
              {LINE_CAPS.map((n) => <option key={n} value={n}>{n} lines</option>)}
            </select>
          </div>
          <div className="field" style={{ flex: "0 0 auto" }}>
            <label>Auto-refresh (5s)</label>
            <div className="flex" style={{ gap: 8, alignItems: "center", height: 38 }}>
              <Toggle on={auto} onChange={(v) => { setAuto(v); toast(v ? "Auto-refresh on (every 5s)" : "Auto-refresh off"); }} />
              <span className="faint mono" style={{ fontSize: 12 }}>{auto ? "live tail" : "off"}</span>
            </div>
          </div>
        </div>
      </div>

      {/* ---- Viewer ---- */}
      <div className="card">
        <div className="card-head">
          <div>
            <h2>{fileLabel} log</h2>
            <p>
              <span className="mono">{file}</span>
              {level ? <> · level <span className="mono">{level}</span></> : null}
              {search ? <> · matching “{search}”</> : null}
            </p>
          </div>
          <div className="flex" style={{ gap: 8, alignItems: "center" }}>
            {auto && <span className="badge ok"><span className="d" />tailing</span>}
            <span className="faint mono" style={{ fontSize: 12 }}>{lines.length} line{lines.length === 1 ? "" : "s"}</span>
          </div>
        </div>

        {loading && !lines.length ? (
          <div className="empty"><span className="dotpulse" /> Loading {fileLabel.toLowerCase()} log…</div>
        ) : !lines.length ? (
          <div className="empty">
            {live === false
              ? "No log backend connected — start Hermes to tail logs here."
              : `No ${fileLabel.toLowerCase()} log lines${level || search ? " match the current filters" : ""}.`}
          </div>
        ) : (
          <div
            ref={viewRef}
            style={{
              background: "var(--surface-2)",
              border: "1px solid var(--line)",
              borderRadius: 10,
              padding: 12,
              fontFamily: "var(--font-mono)",
              fontSize: 12,
              lineHeight: 1.6,
              maxHeight: 520,
              overflow: "auto",
              whiteSpace: "pre-wrap",
              wordBreak: "break-all",
            }}
          >
            {lines.map((raw, i) => {
              const text = String(raw).replace(/\n$/, "");
              return (
                <div key={i} style={{ color: lineColor(text) }}>{text || " "}</div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}
window.PageLogs = PageLogs;
