import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "sonner";

import { api } from "../api";
import { eventTitle } from "../constants";
import { EmptyState } from "../components/EmptyState";
import type { JobEvent } from "../types";

const EVENT_PAGE_SIZE = 50;

function mergeEvents(current: JobEvent[], incoming: JobEvent[]) {
  const seen = new Set(current.map((event) => event.id));
  return [...current, ...incoming.filter((event) => !seen.has(event.id))].sort((a, b) => b.id - a.id);
}

function eventTone(type: string) {
  if (type.includes("fail")) return "danger";
  if (type.includes("success")) return "success";
  if (type.includes("retry") || type.includes("timeout")) return "warning";
  if (type.includes("progress") || type.includes("lease")) return "info";
  if (type.includes("claim")) return "accent";
  return "neutral";
}

function formatLogTime(value: string) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return "--:--:--";
  return date.toLocaleTimeString(undefined, { hour12: false });
}

export function EventsPage() {
  const [events, setEvents] = useState<JobEvent[]>([]);
  const [nextPageUrl, setNextPageUrl] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const [loadingMore, setLoadingMore] = useState(false);
  const [lastRefreshAt, setLastRefreshAt] = useState<string | null>(null);
  const sentinelRef = useRef<HTMLDivElement | null>(null);
  const feedRef = useRef<HTMLDivElement | null>(null);

  const refreshFirstPage = useCallback(async (replace = false) => {
    if (replace) setLoading(true);
    try {
      const page = await api.listEvents({ limit: EVENT_PAGE_SIZE });
      setNextPageUrl((current) => (replace ? page.next : current));
      setEvents((current) => (replace ? page.results : mergeEvents(current, page.results)));
      setLastRefreshAt(new Date().toISOString());
    } finally {
      if (replace) setLoading(false);
    }
  }, []);

  useEffect(() => {
    void refreshFirstPage(true).catch((caught) => toast.error(caught instanceof Error ? caught.message : String(caught)));
  }, [refreshFirstPage]);

  useEffect(() => {
    const id = window.setInterval(() => {
      void refreshFirstPage(false).catch(() => undefined);
    }, 1000);
    return () => window.clearInterval(id);
  }, [refreshFirstPage]);

  const loadMore = useCallback(async () => {
    if (!nextPageUrl || loadingMore) return;
    setLoadingMore(true);
    try {
      const page = await api.listEventsPage(nextPageUrl);
      setNextPageUrl(page.next);
      setEvents((current) => mergeEvents(current, page.results));
    } catch (caught) {
      toast.error(caught instanceof Error ? caught.message : String(caught));
    } finally {
      setLoadingMore(false);
    }
  }, [loadingMore, nextPageUrl]);

  const handleFeedScroll = useCallback(() => {
    const node = feedRef.current;
    if (!node || !nextPageUrl || loadingMore) return;
    const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
    if (distanceFromBottom <= 140) void loadMore();
  }, [loadMore, loadingMore, nextPageUrl]);

  const newestFirst = useMemo(() => [...events].sort((a, b) => b.id - a.id), [events]);

  return (
    <div className="page">
      <header className="page-header">
        <div>
          <span className="eyebrow">Audit log</span>
          <h2>Events</h2>
        </div>
        <div className="event-feed-meta">
          <span className="connection open">Live cursor feed</span>
          <span className="muted-text">{newestFirst.length} loaded</span>
        </div>
      </header>

      <section className="terminal-panel">
        <div className="terminal-header">
          <div className="terminal-window-controls" aria-hidden="true">
            <i />
            <i />
            <i />
          </div>
          <code>job-events.log</code>
          <span>refreshed {lastRefreshAt ? new Date(lastRefreshAt).toLocaleTimeString() : "-"}</span>
        </div>
        <div className="terminal-feed" role="log" aria-live="polite" onScroll={handleFeedScroll} ref={feedRef}>
          {newestFirst.map((event) => (
            <article className={`terminal-line ${eventTone(event.type)}`} key={event.id}>
              <time className="terminal-time" dateTime={event.created_at}>
                {formatLogTime(event.created_at)}
              </time>
              <span className="terminal-prompt">$</span>
              <strong className="terminal-event">{eventTitle(event.type)}</strong>
              <Link className="terminal-job" to={`/jobs/${event.job}`}>
                job:{event.job.slice(0, 8)}
              </Link>
              <span className="terminal-token">attempt:{event.attempt}</span>
              <span className="terminal-worker">worker:{event.worker_id ?? "system"}</span>
              {event.message && <span className="terminal-message">{event.message}</span>}
            </article>
          ))}
          {loading && !newestFirst.length && <EmptyState title="Loading events" />}
          {!loading && !newestFirst.length && <EmptyState title="No events yet" />}
          <div className="infinite-sentinel" ref={sentinelRef}>
            {loadingMore && <span>Loading older events...</span>}
            {!loadingMore && nextPageUrl && (
              <button className="secondary-button" type="button" onClick={() => void loadMore()}>
                Load older events
              </button>
            )}
            {!loadingMore && !nextPageUrl && newestFirst.length > 0 && <span>End of event history</span>}
          </div>
        </div>
      </section>
    </div>
  );
}
