'use client';

import type { ITheme, Terminal } from '@xterm/xterm';
import { useEffect, useRef, useState } from 'react';
import { useTheme } from 'next-themes';

import { Button } from '@spoon/ui';

import '@xterm/xterm/css/xterm.css';

const TERMINAL_FONT =
  "var(--font-victor-mono), 'Symbols Nerd Font Mono', 'Geist Mono', ui-monospace, monospace";

type Status = 'connecting' | 'connected' | 'closed' | 'error' | 'unconfigured';

const darkTheme: ITheme = {
  background: '#080e14',
  foreground: '#eef3f5',
  cursor: '#1fb895',
  cursorAccent: '#080e14',
  selectionBackground: '#1fb89544',
  black: '#10171e',
  red: '#f3625d',
  green: '#8fd6b4',
  yellow: '#e3b341',
  blue: '#6aa6ff',
  magenta: '#b692e8',
  cyan: '#5fd0e0',
  white: '#cdd6dc',
  brightBlack: '#93a1a9',
  brightRed: '#f3625d',
  brightGreen: '#8fd6b4',
  brightYellow: '#e3b341',
  brightBlue: '#6aa6ff',
  brightMagenta: '#b692e8',
  brightCyan: '#5fd0e0',
  brightWhite: '#eef3f5',
};

const lightTheme: ITheme = {
  background: '#f7fbfa',
  foreground: '#0d1218',
  cursor: '#007560',
  cursorAccent: '#f7fbfa',
  selectionBackground: '#00756033',
  black: '#0d1218',
  red: '#d73337',
  green: '#2f8f6e',
  yellow: '#9a6b00',
  blue: '#2f6bd8',
  magenta: '#7c4dd1',
  cyan: '#0f7d92',
  white: '#26323c',
  brightBlack: '#555f68',
  brightRed: '#d73337',
  brightGreen: '#2f8f6e',
  brightYellow: '#9a6b00',
  brightBlue: '#2f6bd8',
  brightMagenta: '#7c4dd1',
  brightCyan: '#0f7d92',
  brightWhite: '#0d1218',
};

export const WorkspaceTerminal = ({
  jobId,
  active,
}: {
  jobId: string;
  active: boolean;
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const termRef = useRef<Terminal | null>(null);
  const { resolvedTheme } = useTheme();
  const themeIsLight = resolvedTheme === 'light';
  const [status, setStatus] = useState<Status>('connecting');
  const [errorText, setErrorText] = useState<string>();
  const [reconnectKey, setReconnectKey] = useState(0);

  // Update the live terminal's theme without tearing down the session.
  useEffect(() => {
    if (termRef.current) {
      termRef.current.options.theme = themeIsLight ? lightTheme : darkTheme;
    }
  }, [themeIsLight]);

  useEffect(() => {
    if (!active) return;
    const container = containerRef.current;
    if (!container) return;

    const abortController = new AbortController();
    const signal = abortController.signal;
    // Read through a function so TS doesn't narrow `aborted` to a constant after
    // the first guard (it changes asynchronously, on cleanup).
    const isAborted = () => signal.aborted;
    let ws: WebSocket | undefined;
    let resizeObserver: ResizeObserver | undefined;
    const encoder = new TextEncoder();

    const start = async () => {
      const [{ Terminal }, { FitAddon }, { WebLinksAddon }] = await Promise.all(
        [
          import('@xterm/xterm'),
          import('@xterm/addon-fit'),
          import('@xterm/addon-web-links'),
        ],
      );
      if (isAborted()) return;

      setStatus('connecting');
      setErrorText(undefined);
      let response: Response;
      try {
        response = await fetch(`/api/agent-jobs/${jobId}/terminal-token`, {
          signal,
        });
      } catch {
        if (!isAborted()) setStatus('error');
        return;
      }
      if (isAborted()) return;
      if (response.status === 503) {
        setStatus('unconfigured');
        return;
      }
      if (!response.ok) {
        setStatus('error');
        setErrorText(await response.text().catch(() => undefined));
        return;
      }
      const { url } = (await response.json()) as { url: string };

      const term = new Terminal({
        fontFamily: TERMINAL_FONT,
        fontSize: 13,
        lineHeight: 1.2,
        cursorBlink: true,
        theme: themeIsLight ? lightTheme : darkTheme,
        allowProposedApi: true,
        scrollback: 5000,
      });
      const fit = new FitAddon();
      term.loadAddon(fit);
      term.loadAddon(new WebLinksAddon());
      term.open(container);
      fit.fit();
      termRef.current = term;

      // Pull in the Nerd Font icon glyphs (loaded lazily by unicode-range) and
      // repaint once ready so powerline/oh-my-posh/eza icons render.
      void document.fonts
        .load("16px 'Symbols Nerd Font Mono'", '\ue0b0')
        .then(() => {
          if (!isAborted()) term.refresh(0, term.rows - 1);
        })
        .catch(() => undefined);

      const sendResize = () => {
        if (ws?.readyState !== WebSocket.OPEN) return;
        ws.send(
          JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }),
        );
      };

      ws = new WebSocket(url);
      ws.binaryType = 'arraybuffer';
      ws.onopen = () => {
        if (isAborted()) return;
        setStatus('connected');
        sendResize();
        term.focus();
      };
      ws.onmessage = (event: MessageEvent<ArrayBuffer | string>) => {
        if (typeof event.data === 'string') term.write(event.data);
        else term.write(new Uint8Array(event.data));
      };
      ws.onclose = () => {
        if (!isAborted()) setStatus('closed');
      };
      ws.onerror = () => {
        if (!isAborted()) setStatus('error');
      };

      term.onData((data) => {
        if (ws?.readyState === WebSocket.OPEN) ws.send(encoder.encode(data));
      });
      term.onResize(() => sendResize());
      resizeObserver = new ResizeObserver(() => {
        try {
          fit.fit();
        } catch {
          // ignore transient layout errors
        }
      });
      resizeObserver.observe(container);
    };

    void start();

    return () => {
      abortController.abort();
      resizeObserver?.disconnect();
      ws?.close();
      termRef.current?.dispose();
      termRef.current = null;
    };
    // resolvedTheme intentionally excluded: handled by the theme effect above.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [active, jobId, reconnectKey]);

  return (
    <div className='relative flex h-full min-h-0 flex-col'>
      <div className='border-border flex h-10 flex-none items-center justify-between gap-3 border-b px-3'>
        <p className='text-muted-foreground text-xs'>
          {status === 'connected'
            ? 'Connected · workspace shell'
            : status === 'connecting'
              ? 'Connecting…'
              : status === 'closed'
                ? 'Session ended'
                : status === 'unconfigured'
                  ? 'Terminal not configured'
                  : 'Connection error'}
        </p>
        {status === 'closed' || status === 'error' ? (
          <Button
            type='button'
            variant='outline'
            size='sm'
            onClick={() => setReconnectKey((key) => key + 1)}
          >
            Reconnect
          </Button>
        ) : null}
      </div>
      {status === 'unconfigured' ? (
        <div className='text-muted-foreground flex flex-1 items-center justify-center p-6 text-center text-sm'>
          The terminal is not enabled on this deployment.
          <br />
          Set NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL to enable it.
        </div>
      ) : (
        <div className='min-h-0 flex-1 overflow-hidden bg-[#080e14] p-2'>
          <div ref={containerRef} className='h-full w-full' />
          {errorText ? (
            <p className='text-destructive mt-2 px-1 text-xs break-all'>
              {errorText}
            </p>
          ) : null}
        </div>
      )}
    </div>
  );
};
