import { useEffect, useRef, useState } from 'react';
import Editor, { OnMount } from '@monaco-editor/react';
import type { SubdirId } from './FileTree';

interface MonacoFileEditorProps {
  subdir: SubdirId;
  filename: string;
  content: string;
  mtime: string;
  size: number;
  onSave: (content: string) => Promise<void>;
  onDirtyChange: (dirty: boolean) => void;
}

function detectLanguage(filename: string): string {
  const dotIdx = filename.lastIndexOf('.');
  const ext = dotIdx >= 0 ? filename.slice(dotIdx).toLowerCase() : '';
  switch (ext) {
    case '.js': return 'javascript';
    case '.ts': return 'typescript';
    case '.md': return 'markdown';
    case '.json': return 'json';
    case '.yaml':
    case '.yml': return 'yaml';
    case '.sh': return 'shell';
    case '.py': return 'python';
    default: return 'plaintext';
  }
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function formatMtime(iso: string): string {
  try {
    return new Date(iso).toLocaleString('ja-JP', {
      year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit',
    });
  } catch {
    return iso;
  }
}

export function MonacoFileEditor({ subdir, filename, content, mtime, size, onSave, onDirtyChange }: MonacoFileEditorProps) {
  const [localContent, setLocalContent] = useState(content);
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const editorRef = useRef<Parameters<OnMount>[0] | null>(null);

  // Fix #2 (part): Reset editor state on file navigation only — not on background refetch.
  // content is intentionally excluded from deps so a refetchOnWindowFocus doesn't clobber edits.
  useEffect(() => {
    setLocalContent(content);
    setDirty(false);
    setSaveError(null);
  }, [subdir, filename]); // eslint-disable-line react-hooks/exhaustive-deps -- intentional: do not depend on content

  // Fix #5: Notify parent when dirty state changes so the nav guard can run there.
  useEffect(() => {
    onDirtyChange(dirty);
  }, [dirty, onDirtyChange]);

  const isReadOnly = subdir === 'trash' || subdir === 'memory';
  const language = detectLanguage(filename);

  const handleSave = async () => {
    if (isReadOnly || !dirty || saving) return;
    setSaving(true);
    setSaveError(null);
    try {
      await onSave(localContent);
      setDirty(false);
    } catch (err) {
      setSaveError(err instanceof Error ? err.message : 'Save failed');
    } finally {
      setSaving(false);
    }
  };

  // Fix #1: Keep a ref that always points to the latest handleSave so the
  // keyboard shortcut (bound once at mount) never closes over a stale version.
  const handleSaveRef = useRef<() => void | Promise<void>>(() => {});
  handleSaveRef.current = handleSave;

  const handleMount: OnMount = (editor, monaco) => {
    editorRef.current = editor;
    // Cmd/Ctrl+S binding — dispatches through the ref so it always sees the
    // latest dirty state, not the value captured at mount time.
    editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
      void handleSaveRef.current();
    });
  };

  return (
    <div className="flex flex-col h-full overflow-hidden">
      {/* File header */}
      <div className="flex-shrink-0 flex items-center gap-2 px-4 py-2.5 border-b border-hairline bg-canvas">
        <span className="text-xs font-mono font-semibold text-slate-800 truncate">
          {filename}
        </span>
        {dirty && (
          <span className="text-[10px] font-medium text-amber-600 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 rounded px-1.5 py-0.5">
            unsaved
          </span>
        )}
        {isReadOnly && (
          <span className="text-[10px] font-medium text-slate-500 bg-surface-2 border border-hairline rounded px-1.5 py-0.5">
            read-only
          </span>
        )}
        <div className="flex-1" />
        <span className="text-[10px] text-slate-400 font-mono hidden sm:inline">
          {language}
        </span>
      </div>

      {/* Monaco editor */}
      <div className="flex-1 min-h-0 overflow-hidden">
        <Editor
          height="100%"
          language={language}
          value={localContent}
          options={{
            readOnly: isReadOnly,
            fontSize: 13,
            lineHeight: 20,
            minimap: { enabled: false },
            scrollBeyondLastLine: false,
            wordWrap: 'on',
            automaticLayout: true,
            tabSize: 2,
          }}
          onMount={handleMount}
          onChange={val => {
            if (!isReadOnly) {
              setLocalContent(val ?? '');
              setDirty((val ?? '') !== content);
            }
          }}
        />
      </div>

      {/* Footer: save button + metadata */}
      <div className="flex-shrink-0 flex items-center gap-3 px-4 py-2.5 border-t border-hairline bg-canvas">
        {!isReadOnly && (
          <button
            type="button"
            onClick={handleSave}
            disabled={!dirty || saving}
            className="px-3 py-1.5 rounded-md text-xs font-semibold bg-accent text-accent-fg hover:bg-accent-deep disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
          >
            {saving ? 'Saving…' : 'Save'}
          </button>
        )}
        {saveError && (
          <span className="text-2xs text-red-600">{saveError}</span>
        )}
        <div className="flex-1" />
        <span className="text-[10px] text-slate-400 font-mono tabular-nums">
          {formatSize(size)} · {formatMtime(mtime)}
        </span>
      </div>
    </div>
  );
}
