import { useEffect, useRef } from 'react';
import Editor, { type OnMount } from '@monaco-editor/react';
import type { editor as MonacoEditor } from 'monaco-editor';
import { toast } from 'sonner';
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
import { useFilesAPI } from '../../hooks/useFilesAPI';
import { FileTree } from './FileTree';
import { EditorTabs } from './EditorTabs';
import { useEditorState } from './useEditorState';
import { getLanguage } from './language-map';

type CodeEditorViewProps = {
  className?: string;
  theme?: string;
  root?: string;
  initialPath?: string;
  /** Put the open file in `?file=`. Opt-in — only the /code-editor screen is guaranteed one editor. */
  urlState?: boolean;
};

export const CodeEditorView = ({
  className,
  theme = 'vs-dark',
  root = 'home',
  initialPath,
  urlState,
}: CodeEditorViewProps) => {
  const { files, activePath, setActivePath, openFile, closeFile, setContent, markSaved, getActiveFile, searchForFile } =
    useEditorState(urlState);
  const { readFile, writeFile } = useFilesAPI(root);
  const editorRef = useRef<MonacoEditor.IStandaloneCodeEditor | null>(null);
  // useFilesAPI rebuilds its verbs every render, so the loader below reads them through a ref instead of
  // depending on them — the same trap that silently broke the Jellyfin playback reports.
  const readFileRef = useRef(readFile);
  readFileRef.current = readFile;
  const unreadable = useRef(new Set<string>());

  const activeFile = getActiveFile();

  const handleOpenFile = async (path: string, name: string) => {
    const existing = files.find((f) => f.path === path);
    if (existing) {
      setActivePath(path);
      return;
    }
    try {
      const res = await readFile(path);
      openFile(path, name, res.content);
    } catch {
      toast.error('Failed to read file');
    }
  };

  /**
   * A URL that names a file has to actually open it — otherwise the param is a label, not an address.
   * Covers a pasted link, a reload, and Back onto a tab that has since been closed. A path that fails to
   * read is remembered so a broken link produces one error rather than one per render, and the address is
   * left alone: an id that does not resolve gets an empty pane, it does not get rewritten.
   */
  useEffect(() => {
    if (!activePath || unreadable.current.has(activePath)) return;
    if (files.some((f) => f.path === activePath)) return;

    let alive = true;
    const path = activePath;
    readFileRef
      .current(path)
      .then((res) => alive && openFile(path, path.split('/').pop() ?? path, res.content))
      .catch(() => {
        unreadable.current.add(path);
        if (alive) toast.error('Failed to read file');
      });
    return () => {
      alive = false;
    };
  }, [activePath, files]);

  const handleSave = async () => {
    if (!activeFile || !activeFile.isDirty) return;
    try {
      await writeFile(activeFile.path, activeFile.content);
      markSaved(activeFile.path, activeFile.content);
      toast.success('File saved');
    } catch {
      toast.error('Failed to save file');
    }
  };

  const handleEditorMount: OnMount = (editor, monaco) => {
    editorRef.current = editor;
    editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
      handleSave();
    });
  };

  useEffect(() => {
    const handler = (ev: KeyboardEvent) => {
      if ((ev.ctrlKey || ev.metaKey) && ev.key === 's') {
        ev.preventDefault();
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []);

  const bg = theme === 'vs-dark' ? '#1e1e1e' : theme === 'vs' ? '#ffffff' : '#1e1e1e';
  const borderColor = theme === 'vs-dark' ? '#333' : '#e0e0e0';

  return (
    <div className={className} style={{ background: bg }}>
      <ResizablePanelGroup direction="horizontal" className="h-full rounded-lg" style={{ borderColor }}>
        <ResizablePanel defaultSize={20} minSize={10} maxSize={40}>
          <div className="h-full flex flex-col" style={{ background: bg }}>
            <div
              className="px-3 py-2 text-xs font-semibold uppercase tracking-wider"
              style={{ color: '#888', borderBottom: `1px solid ${borderColor}` }}
            >
              Explorer
            </div>
            <FileTree
              root={root}
              basePath={initialPath ?? '/'}
              onOpenFile={handleOpenFile}
              searchForFile={searchForFile}
            />
          </div>
        </ResizablePanel>
        <ResizableHandle />
        <ResizablePanel defaultSize={80}>
          <div className="h-full flex flex-col" style={{ background: bg }}>
            <EditorTabs
              files={files}
              activePath={activePath}
              onSelect={setActivePath}
              onClose={closeFile}
              theme={theme}
              searchForFile={searchForFile}
            />
            {activeFile ? (
              <Editor
                key={activeFile.path}
                theme={theme}
                language={getLanguage(activeFile.name)}
                value={activeFile.content}
                onChange={(value) => setContent(activeFile.path, value ?? '')}
                onMount={handleEditorMount}
                options={{
                  automaticLayout: true,
                  minimap: { enabled: true },
                  fontSize: 14,
                  tabSize: 2,
                  wordWrap: 'on',
                  scrollBeyondLastLine: false,
                  renderWhitespace: 'selection',
                  smoothScrolling: true,
                  cursorBlinking: 'smooth',
                  cursorSmoothCaretAnimation: 'on',
                  padding: { top: 8 },
                }}
              />
            ) : (
              <div className="flex-1 flex items-center justify-center text-sm" style={{ color: '#888' }}>
                Open a file from the explorer to start editing
              </div>
            )}
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  );
};
