import {
  forwardRef,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from "react";
import type { FitAddon } from "@xterm/addon-fit";
import type { Terminal } from "xterm";
import "xterm/css/xterm.css";
import { useT } from "@/i18n";
import { useThreadTerminal } from "@/features/terminal/model/use-thread-terminal";
import { useTerminalSettings } from "@/features/terminal/model/terminal-settings-context";

type TerminalHostProps = {
  threadId: string | null;
  active: boolean;
  bootstrapError?: string | null;
  idleMessage?: string;
};

export type TerminalHostHandle = {
  restart: () => Promise<void>;
  close: () => Promise<void>;
};

const COMPOSED_INPUT_FALLBACK_MS = 16;

const hasPrintableTerminalText = (data: string) =>
  /[^\u0000-\u001f\u007f]/u.test(data) && !data.includes("\u001b");

const hasNonAsciiText = (data: string) => /[^\u0000-\u007f]/u.test(data);

const findTerminalHelperTextarea = (container: HTMLDivElement | null) =>
  container?.querySelector<HTMLTextAreaElement>("textarea.xterm-helper-textarea, textarea") ??
  null;

export const TerminalHost = forwardRef<TerminalHostHandle, TerminalHostProps>(function TerminalHost(
  { threadId, active, bootstrapError, idleMessage },
  ref,
) {
  const t = useT();
  const terminalSettings = useTerminalSettings();
  const containerRef = useRef<HTMLDivElement | null>(null);
  const terminalRef = useRef<Terminal | null>(null);
  const fitAddonRef = useRef<FitAddon | null>(null);
  const geometryRef = useRef({ cols: 120, rows: 36 });
  const syncGeometryRef = useRef<(() => void) | null>(null);
  const writeInputRef = useRef<(data: string) => Promise<void>>(async () => {});
  const isReplayingRef = useRef(false);
  const isComposingRef = useRef(false);
  const pendingComposedTextRef = useRef<string | null>(null);
  const pendingComposedFlushTimerRef = useRef<number | null>(null);
  const [geometry, setGeometry] = useState({ cols: 120, rows: 36 });
  const [isTerminalReady, setTerminalReady] = useState(false);
  const [isGeometrySettled, setGeometrySettled] = useState(false);

  useEffect(() => {
    if (!containerRef.current || terminalRef.current) {
      return;
    }

    let cancelled = false;
    const container = containerRef.current;

    void (async () => {
      try {
        const [{ Terminal: XTerminal }, { FitAddon: XFitAddon }] = await Promise.all([
          import("xterm"),
          import("@xterm/addon-fit"),
        ]);

        if (cancelled || !container.isConnected) {
          return;
        }

        const fitAddon = new XFitAddon();
        const terminal = new XTerminal({
          cursorBlink: terminalSettings.cursorBlink,
          cursorStyle: terminalSettings.cursorStyle,
          convertEol: false,
          allowTransparency: true,
          fontFamily: terminalSettings.fontFamily,
          fontSize: terminalSettings.fontSize,
          lineHeight: terminalSettings.lineHeight,
          scrollback: terminalSettings.scrollback,
          theme: {
            background: "#0b1220",
            foreground: "#d8e1f3",
            cursor: "#7dd3fc",
            selectionBackground: "rgba(125, 211, 252, 0.24)",
          },
        });

        terminal.loadAddon(fitAddon);
        terminal.open(container);

        terminalRef.current = terminal;
        fitAddonRef.current = fitAddon;
        syncGeometryRef.current = () => {
          fitAddon.fit();
          const next = { cols: terminal.cols, rows: terminal.rows };
          geometryRef.current = next;
          setGeometry((current) =>
            current.cols === next.cols && current.rows === next.rows ? current : next,
          );
        };

        const frameId = window.requestAnimationFrame(() => {
          syncGeometryRef.current?.();
          setTerminalReady(true);
        });

        if (cancelled) {
          window.cancelAnimationFrame(frameId);
          terminal.dispose();
        }
      } catch (err) {
        if (!cancelled) {
          console.warn("Failed to load xterm", err);
        }
      }
    })();

    return () => {
      cancelled = true;
      syncGeometryRef.current = null;
      if (terminalRef.current) {
        terminalRef.current.dispose();
        terminalRef.current = null;
      }
      fitAddonRef.current = null;
    };
  }, []);

  // ── copyOnSelect: copy selected text to clipboard automatically ──
  useEffect(() => {
    const terminal = terminalRef.current;
    if (!terminal || !terminalSettings.copyOnSelect) {
      return;
    }

    const disposable = terminal.onSelectionChange(() => {
      const selection = terminal.getSelection();
      if (selection) {
        void navigator.clipboard.writeText(selection).catch(() => {
          // Silently ignore clipboard write failures
        });
      }
    });

    return () => {
      disposable.dispose();
    };
  }, [isTerminalReady, terminalSettings.copyOnSelect]);

  useEffect(() => {
    if (!active || !isTerminalReady) {
      setGeometrySettled(false);
      return;
    }

    setGeometrySettled(false);
    const timeoutId = window.setTimeout(() => {
      setGeometrySettled(true);
    }, 120);

    return () => {
      window.clearTimeout(timeoutId);
    };
  }, [active, geometry.cols, geometry.rows, isTerminalReady]);

  const terminalApi = useThreadTerminal({
    threadId,
    active: active && isTerminalReady && isGeometrySettled,
    cols: geometry.cols,
    rows: geometry.rows,
    onReplay: (replay) => {
      const terminal = terminalRef.current;
      if (!terminal) {
        return;
      }

      isReplayingRef.current = true;
      terminal.reset();
      if (replay) {
        terminal.write(replay, () => {
          isReplayingRef.current = false;
          syncGeometryRef.current?.();
        });
        return;
      }

      isReplayingRef.current = false;
    },
    onStdout: (data) => {
      terminalRef.current?.write(data);
    },
    onStderr: (data) => {
      terminalRef.current?.write(data);
    },
    onExit: (exitCode) => {
      terminalRef.current?.writeln(
        `\r\n[terminal exited${exitCode === null ? "" : ` with code ${exitCode}`}]`,
      );
    },
  });
  writeInputRef.current = (data: string) => terminalApi.writeInput(data);

  useEffect(() => {
    return () => {
      if (pendingComposedFlushTimerRef.current !== null) {
        window.clearTimeout(pendingComposedFlushTimerRef.current);
      }
    };
  }, []);

  useEffect(() => {
    const helperTextarea = findTerminalHelperTextarea(containerRef.current);
    if (!terminalRef.current || !helperTextarea) {
      return;
    }

    const clearPendingComposedFlush = () => {
      if (pendingComposedFlushTimerRef.current !== null) {
        window.clearTimeout(pendingComposedFlushTimerRef.current);
        pendingComposedFlushTimerRef.current = null;
      }
    };

    const handleCompositionStart = () => {
      isComposingRef.current = true;
      pendingComposedTextRef.current = null;
      clearPendingComposedFlush();
    };

    const handleCompositionEnd = (event: CompositionEvent) => {
      isComposingRef.current = false;
      const committedText = event.data ?? helperTextarea.value ?? "";
      if (!committedText || !hasNonAsciiText(committedText)) {
        pendingComposedTextRef.current = null;
        clearPendingComposedFlush();
        return;
      }

      pendingComposedTextRef.current = committedText;
      clearPendingComposedFlush();
      pendingComposedFlushTimerRef.current = window.setTimeout(() => {
        const pendingText = pendingComposedTextRef.current;
        pendingComposedTextRef.current = null;
        pendingComposedFlushTimerRef.current = null;
        if (!pendingText) {
          return;
        }
        void writeInputRef.current(pendingText).catch((error) => {
          console.warn("[TerminalHost] failed to flush composed terminal input", error);
        });
      }, COMPOSED_INPUT_FALLBACK_MS);
    };

    const handleBlur = () => {
      isComposingRef.current = false;
      pendingComposedTextRef.current = null;
      clearPendingComposedFlush();
    };

    helperTextarea.addEventListener("compositionstart", handleCompositionStart);
    helperTextarea.addEventListener("compositionend", handleCompositionEnd);
    helperTextarea.addEventListener("blur", handleBlur);

    return () => {
      clearPendingComposedFlush();
      helperTextarea.removeEventListener("compositionstart", handleCompositionStart);
      helperTextarea.removeEventListener("compositionend", handleCompositionEnd);
      helperTextarea.removeEventListener("blur", handleBlur);
    };
  }, [isTerminalReady]);

  useEffect(() => {
    const terminal = terminalRef.current;
    if (!terminal) {
      return;
    }

    const disposable = terminal.onData((data) => {
      if (isReplayingRef.current) {
        return;
      }

      const pendingComposedText = pendingComposedTextRef.current;
      if (pendingComposedText) {
        if (pendingComposedText.startsWith(data)) {
          if (pendingComposedFlushTimerRef.current !== null) {
            window.clearTimeout(pendingComposedFlushTimerRef.current);
            pendingComposedFlushTimerRef.current = null;
          }

          const remaining = pendingComposedText.slice(data.length);
          pendingComposedTextRef.current = remaining.length > 0 ? remaining : null;
          void writeInputRef.current(data).catch(() => {});
          return;
        }

        if (hasPrintableTerminalText(data)) {
          pendingComposedTextRef.current = null;
          if (pendingComposedFlushTimerRef.current !== null) {
            window.clearTimeout(pendingComposedFlushTimerRef.current);
            pendingComposedFlushTimerRef.current = null;
          }
        }
      }

      void writeInputRef.current(data).catch(() => {});
    });

    return () => {
      disposable.dispose();
    };
  }, [isTerminalReady]);

  useEffect(() => {
    if (threadId) {
      return;
    }

    terminalRef.current?.reset();
  }, [threadId]);

  useEffect(() => {
    if (!active || !containerRef.current || !terminalRef.current || !fitAddonRef.current) {
      return;
    }

    const resizeObserver = new ResizeObserver(() => {
      syncGeometryRef.current?.();
    });

    resizeObserver.observe(containerRef.current);
    syncGeometryRef.current?.();

    return () => resizeObserver.disconnect();
  }, [active, isTerminalReady]);

  const placeholder = useMemo(() => {
    if (bootstrapError) {
      return bootstrapError;
    }

    if (!threadId) {
      return idleMessage ?? t("terminal.noAttachableThread");
    }

    if (terminalApi.isConnecting) {
      return t("terminal.connecting");
    }

    return terminalApi.error ?? null;
  }, [bootstrapError, idleMessage, terminalApi.error, terminalApi.isConnecting, threadId]);
  const isTerminalCanvasVisible = placeholder === null;

  useImperativeHandle(
    ref,
    () => ({
      restart: async () => {
        await terminalApi.restart(geometryRef.current.cols, geometryRef.current.rows);
      },
      close: async () => {
        await terminalApi.close();
        terminalRef.current?.reset();
      },
    }),
    [terminalApi],
  );

  return (
    <div className="relative h-full min-h-0">
      <div
        ref={containerRef}
        aria-hidden={!isTerminalCanvasVisible}
        className={`h-full min-h-0 transition-opacity duration-150 [&_.xterm]:h-full [&_.xterm-viewport]:overscroll-contain ${
          isTerminalCanvasVisible ? "opacity-100" : "pointer-events-none opacity-0"
        }`}
      />
      {placeholder ? (
        <div className="pointer-events-none absolute inset-0 flex items-center justify-center px-6 text-center text-xs text-app-muted">
          <div className="max-w-md">
            {placeholder}
          </div>
        </div>
      ) : null}
    </div>
  );
});
