import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';

import './UnpackLogsWindow.css';

interface UnpackLogEntry {
    archive: string;
    destination: string;
    status_code: number | null;
    stdout: string;
    stderr: string;
    started_at_ms: number;
    finished_at_ms: number;
    success: boolean;
}

interface FilteredEntry {
    entry: UnpackLogEntry;
    originalIndex: number;
    stdoutLines: string[];
    stderrLines: string[];
    matchCount: number;
}

export const isUnpackLogsView = (): boolean =>
    new URLSearchParams(window.location.search).get('view') === 'unpack-logs';

const formatLogTime = (timestampMs: number): string => {
    if (timestampMs <= 0) {
        return 'unknown time';
    }
    return new Date(timestampMs).toLocaleString();
};

const logSortTime = (entry: UnpackLogEntry): number =>
    entry.finished_at_ms > 0 ? entry.finished_at_ms : entry.started_at_ms;

const basename = (path: string): string => {
    const segments = path.split(/[\\/]/);
    return segments[segments.length - 1] || path;
};

const nonEmptyLines = (text: string): string[] =>
    text.split(/\r?\n/).filter(line => line.trim().length > 0);

export const UnpackLogsWindow = () => {
    const [logs, setLogs] = useState<UnpackLogEntry[]>([]);
    const [errorsOnly, setErrorsOnly] = useState(false);
    const [regexInput, setRegexInput] = useState('');
    const [selectedOriginalIndex, setSelectedOriginalIndex] = useState<number | null>(null);

    const refreshLogs = useCallback(async () => {
        const unpackLogs = await invoke<UnpackLogEntry[]>('get_unpack_logs');
        setLogs(unpackLogs);
    }, []);

    useEffect(() => {
        let unlisten: (() => void) | undefined;

        const setup = async () => {
            await refreshLogs();
            unlisten = await listen('unpack-logs-updated', () => {
                void refreshLogs();
            });
        };

        void setup();

        return () => {
            unlisten?.();
        };
    }, [refreshLogs]);

    const { regex, regexError } = useMemo(() => {
        if (!regexInput) {
            return { regex: null as RegExp | null, regexError: null as string | null };
        }
        try {
            return { regex: new RegExp(regexInput, 'i'), regexError: null };
        } catch (e) {
            return { regex: null, regexError: e instanceof Error ? e.message : String(e) };
        }
    }, [regexInput]);

    const filteredLogs = useMemo<FilteredEntry[]>(() => {
        const out: FilteredEntry[] = [];
        logs.forEach((entry, originalIndex) => {
            if (errorsOnly && entry.success) return;

            const stdoutClean = nonEmptyLines(entry.stdout);
            const stderrClean = nonEmptyLines(entry.stderr);

            const stdoutLines = regex ? stdoutClean.filter(line => regex.test(line)) : stdoutClean;
            const stderrLines = regex ? stderrClean.filter(line => regex.test(line)) : stderrClean;
            const matchCount = stdoutLines.length + stderrLines.length;

            // With an active regex, hide entries that contribute no matching lines.
            if (regex && matchCount === 0) return;

            out.push({ entry, originalIndex, stdoutLines, stderrLines, matchCount });
        });
        out.sort((a, b) => {
            const timestampDelta = logSortTime(b.entry) - logSortTime(a.entry);
            return timestampDelta !== 0 ? timestampDelta : b.originalIndex - a.originalIndex;
        });
        return out;
    }, [logs, errorsOnly, regex]);

    const selectedListIndex = useMemo(() => {
        if (filteredLogs.length === 0) return -1;
        if (selectedOriginalIndex === null) return 0;
        const idx = filteredLogs.findIndex(item => item.originalIndex === selectedOriginalIndex);
        return idx >= 0 ? idx : 0;
    }, [filteredLogs, selectedOriginalIndex]);

    const current = selectedListIndex >= 0 ? filteredLogs[selectedListIndex] : null;

    const goTo = useCallback((listIndex: number) => {
        if (listIndex < 0 || listIndex >= filteredLogs.length) return;
        setSelectedOriginalIndex(filteredLogs[listIndex].originalIndex);
    }, [filteredLogs]);

    useEffect(() => {
        const handler = (e: KeyboardEvent) => {
            const target = e.target as HTMLElement | null;
            if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) return;
            if (e.key === 'ArrowUp' || e.key === 'k') {
                e.preventDefault();
                goTo(selectedListIndex - 1);
            } else if (e.key === 'ArrowDown' || e.key === 'j') {
                e.preventDefault();
                goTo(selectedListIndex + 1);
            }
        };
        window.addEventListener('keydown', handler);
        return () => window.removeEventListener('keydown', handler);
    }, [goTo, selectedListIndex]);

    const activeItemRef = useRef<HTMLButtonElement | null>(null);
    useEffect(() => {
        activeItemRef.current?.scrollIntoView({ block: 'nearest' });
    }, [selectedListIndex]);

    return (
        <main className="unpack-log-window">
            <div className="unpack-log-header">
                <h1>Unpack Logs</h1>
                <div className="unpack-log-controls">
                    <label className="unpack-log-toggle">
                        <input
                            type="checkbox"
                            checked={errorsOnly}
                            onChange={(e) => setErrorsOnly(e.target.checked)}
                        />
                        Errors only
                    </label>
                    <input
                        className={`unpack-log-regex ${regexError ? 'invalid' : ''}`}
                        type="text"
                        placeholder="Filter lines by regex (case-insensitive)..."
                        value={regexInput}
                        onChange={(e) => setRegexInput(e.target.value)}
                        title={regexError ?? ''}
                        spellCheck={false}
                    />
                    <button className="settings-button" onClick={() => void refreshLogs()}>Refresh</button>
                </div>
            </div>
            {regexError && (
                <div className="unpack-log-regex-error">regex error: {regexError}</div>
            )}
            <div className="unpack-log-body">
                <aside className="unpack-log-list">
                    <div className="unpack-log-list-stats">
                        showing {filteredLogs.length} of {logs.length}
                    </div>
                    {filteredLogs.length === 0 ? (
                        <div className="unpack-log-empty">
                            {logs.length === 0 ? 'No unpack logs recorded yet.' : 'No logs match the current filters.'}
                        </div>
                    ) : filteredLogs.map((item, listIndex) => {
                        const isActive = listIndex === selectedListIndex;
                        return (
                            <button
                                key={`${item.originalIndex}-${item.entry.finished_at_ms}`}
                                ref={isActive ? activeItemRef : undefined}
                                className={`unpack-log-list-item ${isActive ? 'active' : ''} ${item.entry.success ? 'success' : 'error'}`}
                                onClick={() => goTo(listIndex)}
                            >
                                <span className="unpack-log-list-badge">
                                    {item.entry.success ? 'OK' : 'FAIL'}
                                </span>
                                <span className="unpack-log-list-name" title={item.entry.archive}>
                                    {basename(item.entry.archive)}
                                </span>
                                <span className="unpack-log-list-time">
                                    {item.entry.finished_at_ms > 0
                                        ? new Date(item.entry.finished_at_ms).toLocaleTimeString()
                                        : '--:--:--'}
                                </span>
                                {regex && (
                                    <span className="unpack-log-list-matches">{item.matchCount}</span>
                                )}
                            </button>
                        );
                    })}
                </aside>
                <section className="unpack-log-detail">
                    {current ? (
                        <>
                            <div className="unpack-log-detail-nav">
                                <button
                                    className="settings-button"
                                    onClick={() => goTo(selectedListIndex - 1)}
                                    disabled={selectedListIndex <= 0}
                                >
                                    ← Prev
                                </button>
                                <span className="unpack-log-detail-pos">
                                    {selectedListIndex + 1} / {filteredLogs.length}
                                    {regex && ` · ${current.matchCount} matching lines`}
                                </span>
                                <button
                                    className="settings-button"
                                    onClick={() => goTo(selectedListIndex + 1)}
                                    disabled={selectedListIndex >= filteredLogs.length - 1}
                                >
                                    Next →
                                </button>
                            </div>
                            <article className="unpack-log-entry">
                                <div className={`unpack-log-meta ${current.entry.success ? 'success' : 'error'}`}>
                                    [{formatLogTime(current.entry.finished_at_ms)}] {current.entry.success ? 'OK' : 'FAILED'}
                                    {' '}status={current.entry.status_code ?? 'none'}
                                </div>
                                <div className="unpack-log-path">archive: {current.entry.archive}</div>
                                <div className="unpack-log-path">dest: {current.entry.destination}</div>
                                {current.stdoutLines.length > 0 ? (
                                    <pre className="unpack-log-stream stdout">{current.stdoutLines.join('\n')}</pre>
                                ) : (
                                    <div className="unpack-log-empty-stream">
                                        {regex ? '(stdout: no matching lines)' : '(stdout empty)'}
                                    </div>
                                )}
                                {current.stderrLines.length > 0 && (
                                    <pre className="unpack-log-stream stderr">{current.stderrLines.join('\n')}</pre>
                                )}
                            </article>
                        </>
                    ) : (
                        <div className="unpack-log-empty">Nothing to show.</div>
                    )}
                </section>
            </div>
        </main>
    );
};
