import { useEffect, useRef, useState, useCallback } from "react";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import "@xterm/xterm/css/xterm.css";

interface CastHeader {
    version: number;
    width: number;
    height: number;
    timestamp?: number;
    title?: string;
    env?: Record<string, string>;
}

interface CastEvent {
    time: number;
    type: string;
    data: string;
}

interface TerminalPlayerProps {
    /** Path to the .cast file (asciinema v2 format) */
    castUrl: string;
    /** Optional callback when playback completes */
    onComplete?: () => void;
    /** Optional callback for current playback time */
    onTimeUpdate?: (time: number) => void;
    /** External control: seek to this time (in seconds) */
    seekTo?: number;
    /** External control: play/pause state */
    playing?: boolean;
    /** Playback speed multiplier (default: 1) */
    speed?: number;
    /** Show built-in controls */
    showControls?: boolean;
}

export default function TerminalPlayer({
    castUrl,
    onComplete,
    onTimeUpdate,
    seekTo,
    playing: externalPlaying,
    speed = 1,
    showControls = true,
}: TerminalPlayerProps) {
    const containerRef = useRef<HTMLDivElement>(null);
    const terminalRef = useRef<Terminal | null>(null);
    const fitAddonRef = useRef<FitAddon | null>(null);

    const [header, setHeader] = useState<CastHeader | null>(null);
    const [events, setEvents] = useState<CastEvent[]>([]);
    const [currentTime, setCurrentTime] = useState(0);
    const [duration, setDuration] = useState(0);
    const [isPlaying, setIsPlaying] = useState(false);
    const [isLoaded, setIsLoaded] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const playbackRef = useRef<{
        startTime: number;
        startEventIndex: number;
        rafId: number | null;
    }>({ startTime: 0, startEventIndex: 0, rafId: null });

    // Parse cast file
    useEffect(() => {
        async function loadCast() {
            try {
                const response = await fetch(castUrl);
                if (!response.ok) {
                    throw new Error(`Failed to load cast file: ${response.statusText}`);
                }
                const text = await response.text();
                const lines = text.trim().split("\n");

                if (lines.length === 0) {
                    throw new Error("Empty cast file");
                }

                // First line is header
                const parsedHeader = JSON.parse(lines[0]) as CastHeader;
                setHeader(parsedHeader);

                // Remaining lines are events
                const parsedEvents: CastEvent[] = [];
                for (let i = 1; i < lines.length; i++) {
                    const line = lines[i].trim();
                    if (!line) continue;
                    const parsed = JSON.parse(line) as [number, string, string];
                    parsedEvents.push({
                        time: parsed[0],
                        type: parsed[1],
                        data: parsed[2],
                    });
                }

                setEvents(parsedEvents);
                if (parsedEvents.length > 0) {
                    setDuration(parsedEvents[parsedEvents.length - 1].time);
                }
                setIsLoaded(true);
            } catch (e) {
                setError(e instanceof Error ? e.message : "Failed to load cast file");
            }
        }

        loadCast();
    }, [castUrl]);

    // Initialize terminal
    useEffect(() => {
        if (!containerRef.current || !header) return;

        const terminal = new Terminal({
            cols: header.width,
            rows: header.height,
            cursorBlink: false,
            disableStdin: true,
            fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace",
            fontSize: 14,
            lineHeight: 1.2,
            theme: {
                background: "#1a1a1a",
                foreground: "#d4d4d4",
                cursor: "#d4d4d4",
                black: "#1a1a1a",
                red: "#f87171",
                green: "#4ade80",
                yellow: "#facc15",
                blue: "#60a5fa",
                magenta: "#c084fc",
                cyan: "#22d3ee",
                white: "#d4d4d4",
                brightBlack: "#525252",
                brightRed: "#fca5a5",
                brightGreen: "#86efac",
                brightYellow: "#fde047",
                brightBlue: "#93c5fd",
                brightMagenta: "#d8b4fe",
                brightCyan: "#67e8f9",
                brightWhite: "#f5f5f5",
            },
        });

        const fitAddon = new FitAddon();
        terminal.loadAddon(fitAddon);
        terminal.open(containerRef.current);
        fitAddon.fit();

        terminalRef.current = terminal;
        fitAddonRef.current = fitAddon;

        // Handle resize
        const resizeObserver = new ResizeObserver(() => {
            fitAddon.fit();
        });
        resizeObserver.observe(containerRef.current);

        return () => {
            resizeObserver.disconnect();
            terminal.dispose();
            terminalRef.current = null;
            fitAddonRef.current = null;
        };
    }, [header]);

    // Apply events up to a specific time
    const applyEventsUpTo = useCallback(
        (targetTime: number, fromIndex: number = 0) => {
            if (!terminalRef.current) return fromIndex;

            let lastIndex = fromIndex;
            for (let i = fromIndex; i < events.length; i++) {
                if (events[i].time > targetTime) break;
                if (events[i].type === "o") {
                    terminalRef.current.write(events[i].data);
                }
                lastIndex = i + 1;
            }
            return lastIndex;
        },
        [events]
    );

    // Reset terminal and apply events from start
    const seekToTime = useCallback(
        (targetTime: number) => {
            if (!terminalRef.current) return;

            // Reset terminal
            terminalRef.current.reset();

            // Apply all events up to target time
            const nextIndex = applyEventsUpTo(targetTime, 0);
            playbackRef.current.startEventIndex = nextIndex;
            setCurrentTime(targetTime);
            onTimeUpdate?.(targetTime);
        },
        [applyEventsUpTo, onTimeUpdate]
    );

    // Handle external seek control
    useEffect(() => {
        if (seekTo !== undefined && isLoaded) {
            seekToTime(seekTo);
        }
    }, [seekTo, isLoaded, seekToTime]);

    // Handle external play/pause control
    useEffect(() => {
        if (externalPlaying !== undefined) {
            setIsPlaying(externalPlaying);
        }
    }, [externalPlaying]);

    // Playback loop
    useEffect(() => {
        if (!isPlaying || !isLoaded) {
            if (playbackRef.current.rafId) {
                cancelAnimationFrame(playbackRef.current.rafId);
                playbackRef.current.rafId = null;
            }
            return;
        }

        playbackRef.current.startTime = performance.now() - (currentTime * 1000) / speed;

        const tick = () => {
            const elapsed = ((performance.now() - playbackRef.current.startTime) * speed) / 1000;
            const newTime = Math.min(elapsed, duration);

            // Apply any events between last time and now
            const nextIndex = applyEventsUpTo(newTime, playbackRef.current.startEventIndex);
            playbackRef.current.startEventIndex = nextIndex;

            setCurrentTime(newTime);
            onTimeUpdate?.(newTime);

            if (newTime >= duration) {
                setIsPlaying(false);
                onComplete?.();
                return;
            }

            playbackRef.current.rafId = requestAnimationFrame(tick);
        };

        playbackRef.current.rafId = requestAnimationFrame(tick);

        return () => {
            if (playbackRef.current.rafId) {
                cancelAnimationFrame(playbackRef.current.rafId);
                playbackRef.current.rafId = null;
            }
        };
    }, [
        isPlaying,
        isLoaded,
        speed,
        duration,
        currentTime,
        applyEventsUpTo,
        onTimeUpdate,
        onComplete,
    ]);

    // Play/pause toggle
    const togglePlayback = () => {
        if (!isPlaying && currentTime >= duration) {
            // Restart from beginning
            seekToTime(0);
        }
        setIsPlaying(!isPlaying);
    };

    // Format time for display
    const formatTime = (seconds: number) => {
        const mins = Math.floor(seconds / 60);
        const secs = Math.floor(seconds % 60);
        return `${mins}:${secs.toString().padStart(2, "0")}`;
    };

    if (error) {
        return (
            <div
                style={{
                    padding: "2rem",
                    color: "#ef4444",
                    backgroundColor: "#1a1a1a",
                    borderRadius: "8px",
                }}
            >
                Error: {error}
            </div>
        );
    }

    return (
        <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
            <div
                ref={containerRef}
                style={{
                    flex: 1,
                    backgroundColor: "#1a1a1a",
                    borderRadius: showControls ? "8px 8px 0 0" : "8px",
                    overflow: "hidden",
                    minHeight: "200px",
                }}
            />
            {showControls && isLoaded && (
                <div
                    style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "1rem",
                        padding: "0.75rem 1rem",
                        backgroundColor: "#262626",
                        borderRadius: "0 0 8px 8px",
                    }}
                >
                    <button
                        onClick={togglePlayback}
                        style={{
                            width: "36px",
                            height: "36px",
                            borderRadius: "50%",
                            border: "none",
                            backgroundColor: "#3b82f6",
                            color: "white",
                            cursor: "pointer",
                            display: "flex",
                            alignItems: "center",
                            justifyContent: "center",
                            fontSize: "14px",
                        }}
                    >
                        {isPlaying ? "||" : "\u25B6"}
                    </button>
                    <span style={{ color: "#888", fontSize: "0.875rem", fontFamily: "monospace" }}>
                        {formatTime(currentTime)} / {formatTime(duration)}
                    </span>
                    <input
                        type="range"
                        min={0}
                        max={duration}
                        step={0.1}
                        value={currentTime}
                        onChange={(e) => seekToTime(parseFloat(e.target.value))}
                        style={{ flex: 1 }}
                    />
                </div>
            )}
        </div>
    );
}
