import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { message, Modal } from 'antd';
import { api } from '@/lib/api';
import type { FileNode, ServicesConfig } from '@/types/api';

// Tree cache constants
const TREE_CACHE_KEY = 'docs-tree-cache';
const TREE_CACHE_TIMESTAMP_KEY = 'docs-tree-cache-timestamp';
const TREE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes

const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico']);

export function isImageFile(filePath: string): boolean {
  const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
  return IMAGE_EXTENSIONS.has(ext);
}

export function filePathToMkDocsUrl(filePath: string): string {
  let url = filePath.replace(/\.md$/, '');
  if (url.endsWith('/index') || url === 'index') {
    url = url.replace(/\/?index$/, '');
  }
  return '/mkdocs-proxy/' + url + (url ? '/' : '');
}

function getCachedTree(): FileNode[] | null {
  try {
    const cached = localStorage.getItem(TREE_CACHE_KEY);
    const timestamp = localStorage.getItem(TREE_CACHE_TIMESTAMP_KEY);
    if (!cached || !timestamp) return null;
    const age = Date.now() - parseInt(timestamp, 10);
    if (age > TREE_CACHE_TTL) {
      localStorage.removeItem(TREE_CACHE_KEY);
      localStorage.removeItem(TREE_CACHE_TIMESTAMP_KEY);
      return null;
    }
    return JSON.parse(cached) as FileNode[];
  } catch {
    return null;
  }
}

function setCachedTree(tree: FileNode[]): void {
  try {
    localStorage.setItem(TREE_CACHE_KEY, JSON.stringify(tree));
    localStorage.setItem(TREE_CACHE_TIMESTAMP_KEY, Date.now().toString());
  } catch { /* ignore */ }
}

function invalidateTreeCache(): void {
  try {
    localStorage.removeItem(TREE_CACHE_KEY);
    localStorage.removeItem(TREE_CACHE_TIMESTAMP_KEY);
  } catch { /* ignore */ }
}

/** Collect all directory keys for expand-all */
export function collectAllDirKeys(nodes: FileNode[]): string[] {
  const keys: string[] = [];
  for (const node of nodes) {
    if (node.isDirectory) {
      keys.push(node.path);
      if (node.children) keys.push(...collectAllDirKeys(node.children));
    }
  }
  return keys;
}

/** Filter tree to only show nodes matching the query (+ their parent dirs) */
export function filterTree(nodes: FileNode[], query: string): FileNode[] {
  const q = query.toLowerCase();
  const filtered: FileNode[] = [];
  for (const node of nodes) {
    if (node.isDirectory) {
      const childMatches = node.children ? filterTree(node.children, query) : [];
      if (childMatches.length > 0 || node.name.toLowerCase().includes(q)) {
        filtered.push({ ...node, children: childMatches.length > 0 ? childMatches : node.children });
      }
    } else {
      if (node.name.toLowerCase().includes(q)) {
        filtered.push(node);
      }
    }
  }
  return filtered;
}

export interface UseDocsEditorReturn {
  // State
  fileTree: FileNode[];
  config: ServicesConfig | null;
  loading: boolean;
  fetchError: boolean;
  selectedFile: string | null;
  fileContent: string;
  dirty: boolean;
  saving: boolean;
  fileLoading: boolean;
  filterQuery: string;
  expandedKeys: React.Key[];
  modalType: 'newFile' | 'newFolder' | 'rename' | null;
  modalInput: string;
  contextPath: string;

  // Setters
  setFilterQuery: (q: string) => void;
  setExpandedKeys: React.Dispatch<React.SetStateAction<React.Key[]>>;
  setModalType: (t: 'newFile' | 'newFolder' | 'rename' | null) => void;
  setModalInput: (s: string) => void;
  setContextPath: (s: string) => void;
  setSelectedFile: (s: string | null) => void;

  // Derived
  filteredTree: FileNode[];

  // Actions
  fetchData: () => Promise<void>;
  loadFile: (filePath: string) => Promise<void>;
  saveFile: () => Promise<void>;
  onContentChange: (value: string) => void;
  handleDelete: (filePath: string) => void;
  handleModalOk: () => Promise<void>;
  handleMoveFile: (sourcePath: string, targetDir: string) => Promise<void>;
  handleNewFileRoot: () => void;
  handleNewFolderRoot: () => void;
  refreshTree: () => void;
  handleUploadFiles: (files: FileList | File[]) => Promise<void>;
  isDirectoryPath: (path: string) => boolean;
  onTreeSelect: (keys: React.Key[]) => Promise<void>;

  // Refs
  previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
  fileInputRef: React.RefObject<HTMLInputElement | null>;

  // Message context
  contextHolder: React.ReactElement;
}

export function useDocsEditor(): UseDocsEditorReturn {
  const location = useLocation();
  const [messageApi, contextHolder] = message.useMessage();

  const [fileTree, setFileTree] = useState<FileNode[]>(() => getCachedTree() || []);
  const [config, setConfig] = useState<ServicesConfig | null>(null);
  const [loading, setLoading] = useState(true);
  const [fetchError, setFetchError] = useState(false);
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
  const [fileContent, setFileContent] = useState<string>('');
  const [originalContent, setOriginalContent] = useState<string>('');
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [fileLoading, setFileLoading] = useState(false);
  const [fileContentCache, setFileContentCache] = useState<Map<string, string>>(new Map());
  const [filterQuery, setFilterQuery] = useState('');
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);

  // Modal state
  const [modalType, setModalType] = useState<'newFile' | 'newFolder' | 'rename' | null>(null);
  const [modalInput, setModalInput] = useState('');
  const [contextPath, setContextPath] = useState<string>('');

  const previewIframeRef = useRef<HTMLIFrameElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  // Fetch tree
  const fetchTree = useCallback(async (showLoading = true, force = false) => {
    try {
      if (showLoading) setLoading(true);
      setFetchError(false);
      const url = force ? '/docs/files?force=true' : '/docs/files';
      const res = await api.get<FileNode[]>(url);
      setFileTree(res.data);
      setCachedTree(res.data);
    } catch {
      setFetchError(true);
    } finally {
      if (showLoading) setLoading(false);
    }
  }, []);

  const fetchConfig = useCallback(async () => {
    try {
      const res = await api.get<ServicesConfig>('/services/config');
      setConfig(res.data);
    } catch { /* ignore */ }
  }, []);

  const fetchData = useCallback(async () => {
    const cached = getCachedTree();
    if (cached) {
      setFileTree(cached);
      setLoading(false);
      fetchTree(false);
    } else {
      setLoading(true);
      setFetchError(false);
      await fetchTree(true);
    }
    fetchConfig();
  }, [fetchTree, fetchConfig]);

  useEffect(() => { fetchData(); }, [fetchData]);

  // Load file content
  const loadFile = useCallback(async (filePath: string) => {
    const cached = fileContentCache.get(filePath);
    if (cached !== undefined) {
      setFileContent(cached);
      setOriginalContent(cached);
      setSelectedFile(filePath);
      setDirty(false);
      if (previewIframeRef.current && filePath.endsWith('.md')) {
        previewIframeRef.current.src = filePathToMkDocsUrl(filePath);
      }
      return;
    }

    setFileLoading(true);
    try {
      const res = await api.get<{ path: string; content: string }>(`/docs/files/${filePath}`);
      const content = res.data.content;
      setFileContentCache(prev => new Map(prev).set(filePath, content));
      setFileContent(content);
      setOriginalContent(content);
      setSelectedFile(filePath);
      setDirty(false);
      if (previewIframeRef.current && filePath.endsWith('.md')) {
        previewIframeRef.current.src = filePathToMkDocsUrl(filePath);
      }
    } catch {
      messageApi.error('Failed to load file');
    } finally {
      setFileLoading(false);
    }
  }, [fileContentCache, messageApi]);

  // Handle navigation state — auto-select a file
  useEffect(() => {
    const selectFile = (location.state as { selectFile?: string } | null)?.selectFile;
    if (!selectFile || loading) return;
    const parts = selectFile.split('/');
    if (parts.length > 1) {
      const parentKeys: string[] = [];
      for (let i = 1; i < parts.length; i++) {
        parentKeys.push(parts.slice(0, i).join('/'));
      }
      setExpandedKeys(prev => {
        const set = new Set(prev.map(String));
        for (const k of parentKeys) set.add(k);
        return Array.from(set);
      });
    }
    loadFile(selectFile);
    window.history.replaceState({}, '');
  }, [location.state, loading, loadFile]);

  // Save file
  const saveFile = useCallback(async () => {
    if (!selectedFile || !dirty) return;
    setSaving(true);
    try {
      await api.put(`/docs/files/${selectedFile}`, { content: fileContent });
      setOriginalContent(fileContent);
      setDirty(false);
      setFileContentCache(prev => new Map(prev).set(selectedFile, fileContent));
      messageApi.success('Saved');
      setTimeout(() => {
        if (previewIframeRef.current) {
          previewIframeRef.current.contentWindow?.location.reload();
        }
      }, 500);
    } catch {
      messageApi.error('Failed to save');
    } finally {
      setSaving(false);
    }
  }, [selectedFile, dirty, fileContent, messageApi]);

  // Ctrl+S keyboard shortcut
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault();
        saveFile();
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [saveFile]);

  const onContentChange = useCallback((value: string) => {
    setFileContent(value);
    setDirty(value !== originalContent);
  }, [originalContent]);

  const refreshTree = useCallback(() => {
    invalidateTreeCache();
    fetchTree(false, true);
  }, [fetchTree]);

  const isDirectoryPath = useCallback((path: string): boolean => {
    function find(nodes: FileNode[]): boolean {
      for (const node of nodes) {
        if (node.path === path) return node.isDirectory;
        if (node.isDirectory && node.children && find(node.children)) return true;
      }
      return false;
    }
    return find(fileTree);
  }, [fileTree]);

  const handleDelete = useCallback((filePath: string) => {
    Modal.confirm({
      title: 'Delete',
      content: `Are you sure you want to delete "${filePath}"?`,
      okText: 'Delete',
      okButtonProps: { danger: true },
      onOk: async () => {
        try {
          await api.delete(`/docs/files/${filePath}`);
          messageApi.success('Deleted');
          invalidateTreeCache();
          setFileContentCache(prev => {
            const next = new Map(prev);
            next.delete(filePath);
            return next;
          });
          if (selectedFile === filePath) {
            setSelectedFile(null);
            setFileContent('');
            setOriginalContent('');
            setDirty(false);
          }
          fetchTree();
        } catch {
          messageApi.error('Failed to delete');
        }
      },
    });
  }, [selectedFile, messageApi, fetchTree]);

  const handleModalOk = useCallback(async () => {
    if (!modalInput.trim()) return;
    try {
      if (modalType === 'newFile') {
        const name = modalInput.endsWith('.md') ? modalInput : `${modalInput}.md`;
        const path = contextPath ? `${contextPath}/${name}` : name;
        await api.post(`/docs/files/${path}`, { content: `# ${modalInput.replace(/\.md$/, '')}\n` });
        messageApi.success('File created');
        invalidateTreeCache();
        await Promise.all([fetchTree(), loadFile(path)]);
      } else if (modalType === 'newFolder') {
        const path = contextPath ? `${contextPath}/${modalInput}` : modalInput;
        await api.post(`/docs/files/${path}`, { isDirectory: true });
        messageApi.success('Folder created');
        invalidateTreeCache();
        fetchTree();
      } else if (modalType === 'rename') {
        const parentDir = contextPath.includes('/') ? contextPath.substring(0, contextPath.lastIndexOf('/')) : '';
        const newPath = parentDir ? `${parentDir}/${modalInput}` : modalInput;
        await api.post('/docs/files/rename', { from: contextPath, to: newPath });
        messageApi.success('Renamed');
        invalidateTreeCache();
        setFileContentCache(prev => {
          const next = new Map(prev);
          const cached = next.get(contextPath);
          next.delete(contextPath);
          if (cached) next.set(newPath, cached);
          return next;
        });
        if (selectedFile === contextPath) setSelectedFile(newPath);
        fetchTree();
      }
    } catch {
      messageApi.error('Operation failed');
    }
    setModalType(null);
  }, [modalType, modalInput, contextPath, messageApi, fetchTree, loadFile, selectedFile]);

  const handleMoveFile = useCallback(async (sourcePath: string, targetDir: string) => {
    const fileName = sourcePath.split('/').pop() || '';
    const newPath = targetDir ? `${targetDir}/${fileName}` : fileName;
    if (newPath === sourcePath) return;
    if (sourcePath === targetDir || targetDir.startsWith(sourcePath + '/')) {
      messageApi.warning('Cannot move a folder into itself');
      return;
    }
    try {
      await api.post('/docs/files/rename', { from: sourcePath, to: newPath });
      messageApi.success(`Moved to ${targetDir || '/'}`);
      invalidateTreeCache();
      setFileContentCache(prev => {
        const next = new Map(prev);
        // Move exact match
        const cached = next.get(sourcePath);
        next.delete(sourcePath);
        if (cached) next.set(newPath, cached);
        // Move children (for directory moves)
        const prefix = sourcePath + '/';
        for (const [key, val] of Array.from(next.entries())) {
          if (key.startsWith(prefix)) {
            next.delete(key);
            next.set(newPath + key.substring(sourcePath.length), val);
          }
        }
        return next;
      });
      if (selectedFile === sourcePath) {
        setSelectedFile(newPath);
      } else if (selectedFile && selectedFile.startsWith(sourcePath + '/')) {
        setSelectedFile(newPath + selectedFile.substring(sourcePath.length));
      }
      fetchTree();
    } catch {
      messageApi.error('Failed to move');
    }
  }, [selectedFile, messageApi, fetchTree]);

  const handleNewFileRoot = useCallback(() => { setContextPath(''); setModalInput(''); setModalType('newFile'); }, []);
  const handleNewFolderRoot = useCallback(() => { setContextPath(''); setModalInput(''); setModalType('newFolder'); }, []);

  const selectImageFile = useCallback((filePath: string) => {
    setSelectedFile(filePath);
    setFileContent('');
    setOriginalContent('');
    setDirty(false);
    if (previewIframeRef.current) {
      previewIframeRef.current.src = '/mkdocs-proxy/';
    }
  }, []);

  const onTreeSelect = useCallback(async (keys: React.Key[]) => {
    if (keys.length === 0) return;
    const path = keys[0] as string;

    if (isImageFile(path)) {
      if (dirty) {
        Modal.confirm({
          title: 'Unsaved Changes',
          content: `Save changes to ${selectedFile} before switching?`,
          okText: 'Save',
          cancelText: 'Discard',
          onOk: async () => { await saveFile(); selectImageFile(path); },
          onCancel: () => { setDirty(false); selectImageFile(path); },
        });
        return;
      }
      selectImageFile(path);
      return;
    }

    if (dirty) {
      Modal.confirm({
        title: 'Unsaved Changes',
        content: `Save changes to ${selectedFile} before switching?`,
        okText: 'Save',
        cancelText: 'Discard',
        onOk: async () => { await saveFile(); await loadFile(path); },
        onCancel: () => { setDirty(false); loadFile(path); },
      });
      return;
    }
    await loadFile(path);
  }, [dirty, selectedFile, saveFile, loadFile, selectImageFile]);

  const handleUploadFiles = useCallback(async (files: FileList | File[]) => {
    const fileArray = Array.from(files);
    if (fileArray.length === 0) return;

    let targetDir = '';
    if (selectedFile) {
      if (isDirectoryPath(selectedFile)) {
        targetDir = selectedFile;
      } else if (selectedFile.includes('/')) {
        targetDir = selectedFile.substring(0, selectedFile.lastIndexOf('/'));
      }
    }

    const hideLoading = messageApi.loading(`Uploading ${fileArray.length} file${fileArray.length > 1 ? 's' : ''}...`, 0);
    let successCount = 0;
    let lastMdPath: string | null = null;

    for (const file of fileArray) {
      try {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('path', targetDir);
        const res = await api.post<{ success: boolean; path: string }>('/docs/upload', formData);
        successCount++;
        if (file.name.endsWith('.md')) {
          lastMdPath = res.data.path;
        }
      } catch (err: unknown) {
        const msg = (err as { response?: { data?: { error?: { message?: string } } } })?.response?.data?.error?.message || 'Upload failed';
        messageApi.error(`Failed to upload ${file.name}: ${msg}`);
      }
    }

    hideLoading();

    if (successCount > 0) {
      messageApi.success(`Uploaded ${successCount} file${successCount > 1 ? 's' : ''}`);
      invalidateTreeCache();
      await fetchTree(false, true);
      if (lastMdPath) {
        await loadFile(lastMdPath);
      }
    }
  }, [selectedFile, isDirectoryPath, messageApi, fetchTree, loadFile]);

  // Filtered tree
  const filteredTree = useMemo(() => {
    if (!filterQuery.trim()) return fileTree;
    return filterTree(fileTree, filterQuery.trim());
  }, [fileTree, filterQuery]);

  // Sync expanded keys when filter changes
  const expandedKeysForFilter = useMemo(() => {
    if (filterQuery.trim()) return collectAllDirKeys(filteredTree);
    return [];
  }, [filterQuery, filteredTree]);

  useEffect(() => {
    setExpandedKeys(expandedKeysForFilter);
  }, [expandedKeysForFilter]);

  return {
    fileTree,
    config,
    loading,
    fetchError,
    selectedFile,
    fileContent,
    dirty,
    saving,
    fileLoading,
    filterQuery,
    expandedKeys,
    modalType,
    modalInput,
    contextPath,
    setFilterQuery,
    setExpandedKeys,
    setModalType,
    setModalInput,
    setContextPath,
    setSelectedFile,
    filteredTree,
    fetchData,
    loadFile,
    saveFile,
    onContentChange,
    handleDelete,
    handleModalOk,
    handleMoveFile,
    handleNewFileRoot,
    handleNewFolderRoot,
    refreshTree,
    handleUploadFiles,
    isDirectoryPath,
    onTreeSelect,
    previewIframeRef,
    fileInputRef,
    contextHolder,
  };
}
