import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Checkbox } from '@/components/ui/checkbox';
import { useI18n } from '@/lib/i18n';
import { TailLogFile, GetLogFilePath } from '../../wailsjs/go/main/App';

// LogViewer shows the tail of opslog.log and keeps it current while open.
//
// This exists because diagnosing a problem used to mean asking the operator to
// find a file in their data directory, open it in a text editor, and re-open it
// after every attempt. Half the round-trips in a bug report were spent on that.
//
// Three behaviours matter more than they look:
//   • Auto-scroll is a checkbox the operator owns, AND it releases itself the
//     moment they scroll up — otherwise the line they are reading is yanked away
//     a second later, which defeats the one thing this window is for. Scrolling
//     back to the bottom re-arms it, so following is never a dead end.
//   • The filter keeps only matching LINES rather than highlighting inside a
//     256 KB blob: with "cluster" or "acom" typed in, the window becomes exactly
//     the subsystem trace you would have grepped for.
//   • It polls only while open. A closed dialog costs nothing.
// 512 KB ≈ 3200 lines. The window is a sliding tail: once new lines push past it
// the oldest fall out of the fetched text entirely, so a small buffer dropped
// lines the operator was still reading during a busy trace (CAT/cluster/antenna
// polling). This is double the old 256 KB — 1 MB was tried but the per-second
// fetch + split + re-render of ~6500 lines made the window lag. 512 KB keeps a
// useful backlog while staying smooth. The backend caps this at 4 MB.
const TAIL_BYTES = 512 * 1024;
const POLL_MS = 1000;

export function LogViewer({ open, onOpenChange }: { open: boolean; onOpenChange: (v: boolean) => void }) {
  const { t } = useI18n();
  const [text, setText] = useState('');
  const [path, setPath] = useState('');
  const [query, setQuery] = useState('');
  const [autoScroll, setAutoScroll] = useState(true);
  // Shown next to the checkbox so a QUIET log still proves it is being polled —
  // without it there is no way to tell 'nothing new' from 'window is dead'.
  const [lastPull, setLastPull] = useState('');
  const boxRef = useRef<HTMLPreElement>(null);
  const autoRef = useRef(true);
  useEffect(() => { autoRef.current = autoScroll; }, [autoScroll]);

  // stick pins the view to the bottom on the NEXT frame. Setting scrollTop
  // straight away is not enough while the dialog is still animating in: the box
  // has no height yet, scrollHeight is wrong, and the scroll silently does
  // nothing — leaving the operator staring at the top of a 256 KB buffer, i.e.
  // at lines from hours ago, which looks exactly like a frozen window.
  const stick = useCallback(() => {
    if (!autoRef.current) return;
    requestAnimationFrame(() => {
      const el = boxRef.current;
      if (el) el.scrollTop = el.scrollHeight;
    });
  }, []);

  const pull = useCallback(() => {
    TailLogFile(TAIL_BYTES).then((s: any) => {
      setText(String(s ?? ''));
      setLastPull(new Date().toLocaleTimeString());
    }).catch(() => {});
  }, []);

  useEffect(() => {
    if (!open) return;
    setAutoScroll(true);
    autoRef.current = true;
    GetLogFilePath().then((p: any) => setPath(String(p ?? ''))).catch(() => {});
    pull();
    // Re-assert the bottom after the open animation has settled. A quiet log
    // never changes, so the content-driven scroll below would never fire again
    // and the first attempt would be the only one.
    const t1 = window.setTimeout(stick, 120);
    const t2 = window.setTimeout(stick, 450);
    const id = window.setInterval(pull, POLL_MS);
    return () => { window.clearInterval(id); window.clearTimeout(t1); window.clearTimeout(t2); };
  }, [open, pull, stick]);

  const all = useMemo(() => text.split('\n'), [text]);
  const shown = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return all;
    return all.filter((l) => l.toLowerCase().includes(q));
  }, [all, query]);

  // Stick to the bottom after each refresh, but only while auto-scroll is on.
  useEffect(() => { if (open) stick(); }, [shown, open, stick]);

  // Scrolling away from the bottom releases auto-scroll; coming back re-arms it.
  function onScroll() {
    const el = boxRef.current;
    if (!el) return;
    setAutoScroll(el.scrollHeight - el.scrollTop - el.clientHeight < 24);
  }

  const body = shown.join('\n');
  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-5xl">
        <DialogHeader>
          <DialogTitle>{t('logview.title')}</DialogTitle>
          <DialogDescription className="font-mono text-[11px] break-all">{path}</DialogDescription>
        </DialogHeader>

        <div className="flex items-center gap-2">
          <Input className="h-8 flex-1 font-mono text-xs" value={query} placeholder={t('logview.searchPh')}
            onChange={(e) => setQuery(e.target.value)} />
          {query.trim() !== '' && (
            <span className="text-xs text-muted-foreground tabular-nums whitespace-nowrap">
              {t('logview.matches', { n: shown.length, total: all.length })}
            </span>
          )}
        </div>

        <pre ref={boxRef} onScroll={onScroll}
          className="h-[60vh] overflow-auto rounded-md border border-border bg-muted/30 p-2 text-[11px] leading-snug font-mono whitespace-pre-wrap break-all">
          {body.trim() === '' ? t('logview.empty') : body}
        </pre>

        <div className="flex items-center gap-2">
          <label className="flex items-center gap-2 text-xs cursor-pointer">
            <Checkbox checked={autoScroll} onCheckedChange={(c) => {
              const on = !!c;
              setAutoScroll(on);
              if (on) { const el = boxRef.current; if (el) el.scrollTop = el.scrollHeight; }
            }} />
            {t('logview.autoScroll')}
          </label>
          {lastPull && <span className="text-xs text-muted-foreground tabular-nums">{t('logview.updated', { time: lastPull })}</span>}
          <div className="flex-1" />
          <Button variant="outline" size="sm" onClick={() => navigator.clipboard?.writeText(body)}>
            {t('logview.copy')}
          </Button>
          <Button variant="outline" size="sm" onClick={() => { setQuery(''); setAutoScroll(true); pull(); }}>
            {t('logview.toEnd')}
          </Button>
        </div>
      </DialogContent>
    </Dialog>
  );
}
