import { writable, derived, get } from "svelte/store";
import { invoke } from "@tauri-apps/api/core";
import type { EditorState, EditorTab, FileEntry } from "$lib/types/editor";

const defaultState: EditorState = {
  tabs: [],
  activeTabId: null,
  isFileBrowserOpen: true,
  currentDirectory: "",
  fileTree: [],
  isLoadingTree: false,
};

function getLanguageFromPath(filePath: string): string {
  const ext = filePath.split(".").pop()?.toLowerCase() || "";
  const languageMap: Record<string, string> = {
    js: "javascript",
    jsx: "javascript",
    ts: "typescript",
    tsx: "typescript",
    py: "python",
    rs: "rust",
    go: "go",
    java: "java",
    c: "c",
    cpp: "cpp",
    h: "c",
    hpp: "cpp",
    cs: "csharp",
    rb: "ruby",
    php: "php",
    swift: "swift",
    kt: "kotlin",
    scala: "scala",
    html: "html",
    htm: "html",
    css: "css",
    scss: "scss",
    sass: "sass",
    less: "less",
    json: "json",
    xml: "xml",
    yaml: "yaml",
    yml: "yaml",
    toml: "toml",
    md: "markdown",
    markdown: "markdown",
    sql: "sql",
    sh: "shell",
    bash: "shell",
    zsh: "shell",
    ps1: "powershell",
    dockerfile: "dockerfile",
    svelte: "svelte",
    vue: "vue",
    graphql: "graphql",
    gql: "graphql",
    lua: "lua",
    r: "r",
    dart: "dart",
    elm: "elm",
    ex: "elixir",
    exs: "elixir",
    erl: "erlang",
    hs: "haskell",
    clj: "clojure",
    lisp: "lisp",
    ml: "ocaml",
    fs: "fsharp",
    zig: "zig",
    nim: "nim",
    v: "v",
    wasm: "wasm",
    wat: "wasm",
  };
  return languageMap[ext] || "plaintext";
}

function generateTabId(): string {
  return `tab-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}

function createEditorStore() {
  const state = writable<EditorState>(defaultState);
  const isEditorVisible = writable<boolean>(false);
  const saveError = writable<string | null>(null);

  async function loadDirectory(dirPath: string): Promise<FileEntry[]> {
    try {
      const entries = await invoke<FileEntry[]>("list_directory", { path: dirPath });
      return entries.sort((a, b) => {
        if (a.isDirectory && !b.isDirectory) return -1;
        if (!a.isDirectory && b.isDirectory) return 1;
        return a.name.localeCompare(b.name);
      });
    } catch (error) {
      console.error("Failed to load directory:", error);
      return [];
    }
  }

  async function initializeFileTree(rootPath: string) {
    state.update((s) => ({ ...s, isLoadingTree: true, currentDirectory: rootPath }));
    try {
      const entries = await loadDirectory(rootPath);
      state.update((s) => ({ ...s, fileTree: entries, isLoadingTree: false }));
    } catch (error) {
      console.error("Failed to initialize file tree:", error);
      state.update((s) => ({ ...s, isLoadingTree: false }));
    }
  }

  async function toggleDirectory(entry: FileEntry) {
    if (!entry.isDirectory) return;

    state.update((s) => {
      const updateTree = (entries: FileEntry[]): FileEntry[] => {
        return entries.map((e) => {
          if (e.path === entry.path) {
            return { ...e, isExpanded: !e.isExpanded, isLoading: !e.isExpanded && !e.children };
          }
          if (e.children) {
            return { ...e, children: updateTree(e.children) };
          }
          return e;
        });
      };
      return { ...s, fileTree: updateTree(s.fileTree) };
    });

    if (!entry.isExpanded && !entry.children) {
      const children = await loadDirectory(entry.path);
      state.update((s) => {
        const updateTree = (entries: FileEntry[]): FileEntry[] => {
          return entries.map((e) => {
            if (e.path === entry.path) {
              return { ...e, children, isLoading: false };
            }
            if (e.children) {
              return { ...e, children: updateTree(e.children) };
            }
            return e;
          });
        };
        return { ...s, fileTree: updateTree(s.fileTree) };
      });
    }
  }

  async function openFile(filePath: string) {
    const currentState = get(state);

    const existingTab = currentState.tabs.find((t) => t.filePath === filePath);
    if (existingTab) {
      state.update((s) => ({ ...s, activeTabId: existingTab.id }));
      return;
    }

    try {
      const content = await invoke<string>("read_file_content", { path: filePath });
      const fileName = filePath.split(/[/\\]/).pop() || "untitled";
      const language = getLanguageFromPath(filePath);
      const newTab: EditorTab = {
        id: generateTabId(),
        filePath,
        fileName,
        content,
        originalContent: content,
        isDirty: false,
        language,
      };

      state.update((s) => ({
        ...s,
        tabs: [...s.tabs, newTab],
        activeTabId: newTab.id,
      }));
    } catch (error) {
      console.error("Failed to open file:", error);
      saveError.set(`Failed to open file: ${error}`);
    }
  }

  async function saveFile(tabId?: string) {
    const currentState = get(state);
    const tab = tabId
      ? currentState.tabs.find((t) => t.id === tabId)
      : currentState.tabs.find((t) => t.id === currentState.activeTabId);

    if (!tab) return;

    saveError.set(null);
    try {
      await invoke("write_file_content", { path: tab.filePath, content: tab.content });
      state.update((s) => ({
        ...s,
        tabs: s.tabs.map((t) =>
          t.id === tab.id ? { ...t, originalContent: t.content, isDirty: false } : t
        ),
      }));
    } catch (error) {
      console.error("Failed to save file:", error);
      saveError.set(`Failed to save file: ${error}`);
      throw error;
    }
  }

  function updateTabContent(tabId: string, content: string) {
    state.update((s) => ({
      ...s,
      tabs: s.tabs.map((t) =>
        t.id === tabId ? { ...t, content, isDirty: content !== t.originalContent } : t
      ),
    }));
  }

  function closeTab(tabId: string) {
    state.update((s) => {
      const tabIndex = s.tabs.findIndex((t) => t.id === tabId);
      const newTabs = s.tabs.filter((t) => t.id !== tabId);

      let newActiveId = s.activeTabId;
      if (s.activeTabId === tabId) {
        if (newTabs.length === 0) {
          newActiveId = null;
        } else if (tabIndex >= newTabs.length) {
          newActiveId = newTabs[newTabs.length - 1].id;
        } else {
          newActiveId = newTabs[tabIndex].id;
        }
      }

      return { ...s, tabs: newTabs, activeTabId: newActiveId };
    });
  }

  function setActiveTab(tabId: string) {
    state.update((s) => ({ ...s, activeTabId: tabId }));
  }

  function toggleFileBrowser() {
    state.update((s) => ({ ...s, isFileBrowserOpen: !s.isFileBrowserOpen }));
  }

  function toggleEditor() {
    isEditorVisible.update((v) => !v);
  }

  async function createFile(parentPath: string, fileName: string): Promise<boolean> {
    const filePath = `${parentPath}/${fileName}`;
    try {
      await invoke("create_file", { path: filePath });
      // Refresh the parent directory
      await refreshDirectory(parentPath);
      return true;
    } catch (error) {
      console.error("Failed to create file:", error);
      saveError.set(`Failed to create file: ${error}`);
      return false;
    }
  }

  async function createDirectory(parentPath: string, dirName: string): Promise<boolean> {
    const dirPath = `${parentPath}/${dirName}`;
    try {
      await invoke("create_directory", { path: dirPath });
      // Refresh the parent directory
      await refreshDirectory(parentPath);
      return true;
    } catch (error) {
      console.error("Failed to create directory:", error);
      saveError.set(`Failed to create directory: ${error}`);
      return false;
    }
  }

  async function deleteFile(filePath: string): Promise<boolean> {
    try {
      await invoke("delete_file", { path: filePath });
      // Close the tab if it's open
      const currentState = get(state);
      const openTab = currentState.tabs.find((t) => t.filePath === filePath);
      if (openTab) {
        closeTab(openTab.id);
      }
      // Refresh the parent directory
      const parentPath = filePath.substring(0, filePath.lastIndexOf("/"));
      await refreshDirectory(parentPath);
      return true;
    } catch (error) {
      console.error("Failed to delete file:", error);
      saveError.set(`Failed to delete file: ${error}`);
      return false;
    }
  }

  async function deleteDirectory(dirPath: string): Promise<boolean> {
    try {
      await invoke("delete_directory", { path: dirPath });
      // Close any tabs that are in this directory
      const currentState = get(state);
      const tabsToClose = currentState.tabs.filter((t) => t.filePath.startsWith(dirPath + "/"));
      tabsToClose.forEach((tab) => closeTab(tab.id));
      // Refresh the parent directory
      const parentPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
      await refreshDirectory(parentPath);
      return true;
    } catch (error) {
      console.error("Failed to delete directory:", error);
      saveError.set(`Failed to delete directory: ${error}`);
      return false;
    }
  }

  async function refreshDirectory(dirPath: string) {
    const currentState = get(state);

    // If refreshing the root directory, reload the entire tree
    if (dirPath === currentState.currentDirectory) {
      const entries = await loadDirectory(dirPath);
      state.update((s) => ({ ...s, fileTree: entries }));
      return;
    }

    // Otherwise, update the specific directory in the tree
    const children = await loadDirectory(dirPath);
    state.update((s) => {
      const updateTree = (entries: FileEntry[]): FileEntry[] => {
        return entries.map((e) => {
          if (e.path === dirPath) {
            return { ...e, children, isExpanded: true };
          }
          if (e.children) {
            return { ...e, children: updateTree(e.children) };
          }
          return e;
        });
      };
      return { ...s, fileTree: updateTree(s.fileTree) };
    });
  }

  async function renamePath(oldPath: string, newName: string): Promise<boolean> {
    const parentPath = oldPath.substring(0, oldPath.lastIndexOf("/"));
    const newPath = `${parentPath}/${newName}`;

    try {
      await invoke("rename_path", { oldPath, newPath });

      // Update any open tabs that reference this path
      state.update((s) => ({
        ...s,
        tabs: s.tabs.map((t) => {
          if (t.filePath === oldPath) {
            // Exact match - this file was renamed
            return {
              ...t,
              filePath: newPath,
              fileName: newName,
            };
          }
          if (t.filePath.startsWith(oldPath + "/")) {
            // File is inside a renamed directory
            const relativePath = t.filePath.substring(oldPath.length);
            return {
              ...t,
              filePath: newPath + relativePath,
            };
          }
          return t;
        }),
      }));

      // Refresh the parent directory
      await refreshDirectory(parentPath);
      return true;
    } catch (error) {
      console.error("Failed to rename:", error);
      saveError.set(`Failed to rename: ${error}`);
      return false;
    }
  }

  function showEditor() {
    isEditorVisible.set(true);
  }

  function hideEditor() {
    isEditorVisible.set(false);
  }

  return {
    state: { subscribe: state.subscribe },
    isEditorVisible: { subscribe: isEditorVisible.subscribe },
    saveError: { subscribe: saveError.subscribe },

    initializeFileTree,
    toggleDirectory,
    openFile,
    saveFile,
    updateTabContent,
    closeTab,
    setActiveTab,
    toggleFileBrowser,
    toggleEditor,
    showEditor,
    hideEditor,
    createFile,
    createDirectory,
    deleteFile,
    deleteDirectory,
    refreshDirectory,
    renamePath,

    tabs: derived(state, ($state) => $state.tabs),
    activeTab: derived(state, ($state) => $state.tabs.find((t) => t.id === $state.activeTabId)),
    activeTabId: derived(state, ($state) => $state.activeTabId),
    fileTree: derived(state, ($state) => $state.fileTree),
    isFileBrowserOpen: derived(state, ($state) => $state.isFileBrowserOpen),
    isLoadingTree: derived(state, ($state) => $state.isLoadingTree),
    currentDirectory: derived(state, ($state) => $state.currentDirectory),
    hasDirtyTabs: derived(state, ($state) => $state.tabs.some((t) => t.isDirty)),
  };
}

export const editorStore = createEditorStore();
