import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { downloadFile } from "@app/services/downloadService";
import MenuBookRoundedIcon from "@mui/icons-material/MenuBookRounded";
import { alert } from "@app/components/toast";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import EditTableOfContentsWorkbenchView, {
  EditTableOfContentsWorkbenchViewData,
} from "@app/components/tools/editTableOfContents/EditTableOfContentsWorkbenchView";
import EditTableOfContentsSettings from "@app/components/tools/editTableOfContents/EditTableOfContentsSettings";
import { useEditTableOfContentsParameters } from "@app/hooks/tools/editTableOfContents/useEditTableOfContentsParameters";
import { useEditTableOfContentsOperation } from "@app/hooks/tools/editTableOfContents/useEditTableOfContentsOperation";
import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useBaseTool } from "@app/hooks/tools/shared/useBaseTool";
import apiClient from "@app/services/apiClient";
import {
  BookmarkPayload,
  BookmarkNode,
  hydrateBookmarkPayload,
  serializeBookmarkNodes,
} from "@app/utils/editTableOfContents";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
import {
  useNavigationActions,
  useNavigationState,
} from "@app/contexts/NavigationContext";
import { useFileSelection } from "@app/contexts/FileContext";
import { isStirlingFile } from "@app/types/fileContext";

const extractBookmarks = async (file: File): Promise<BookmarkPayload[]> => {
  const formData = new FormData();
  formData.append("file", file);

  const response = await apiClient.post(
    "/api/v1/general/extract-bookmarks",
    formData,
  );

  return response.data as BookmarkPayload[];
};

const useStableCallback = <T extends (...args: never[]) => unknown>(
  callback: T,
): T => {
  const callbackRef = useRef(callback);

  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  return useMemo(
    () => ((...args: Parameters<T>) => callbackRef.current(...args)) as T,
    [],
  );
};

const EditTableOfContents = (props: BaseToolProps) => {
  const { t } = useTranslation();
  const base = useBaseTool(
    "edit-table-of-contents",
    useEditTableOfContentsParameters,
    useEditTableOfContentsOperation,
    props,
    { minFiles: 1 },
  );
  const {
    registerCustomWorkbenchView,
    unregisterCustomWorkbenchView,
    setCustomWorkbenchViewData,
    clearCustomWorkbenchViewData,
  } = useToolWorkflow();
  const { openFilesModal } = useFilesModalContext();
  const { clearSelections } = useFileSelection();
  const navigationState = useNavigationState();
  const { actions: navigationActions } = useNavigationActions();

  const WORKBENCH_VIEW_ID = "editTableOfContentsWorkbench";
  const WORKBENCH_ID = "custom:editTableOfContents" as const;
  const viewIcon = useMemo(() => <MenuBookRoundedIcon fontSize="small" />, []);

  const [loadError, setLoadError] = useState<string | null>(null);
  const [isLoadingBookmarks, setIsLoadingBookmarks] = useState(false);
  const [lastLoadedFileId, setLastLoadedFileId] = useState<string | null>(null);
  const hasAutoOpenedWorkbenchRef = useRef(false);

  const selectedFile = base.selectedFiles[0];

  const { setBookmarks } = base.params;

  useEffect(() => {
    registerCustomWorkbenchView({
      id: WORKBENCH_VIEW_ID,
      workbenchId: WORKBENCH_ID,
      label: "Outline workspace",
      icon: viewIcon,
      component: EditTableOfContentsWorkbenchView,
    });

    return () => {
      clearCustomWorkbenchViewData(WORKBENCH_VIEW_ID);
      unregisterCustomWorkbenchView(WORKBENCH_VIEW_ID);
    };
    // Register once; avoid re-registering which clears data mid-flight
  }, []);

  const loadBookmarksForFile = useCallback(
    async (file: File, { showToast }: { showToast?: boolean } = {}) => {
      setIsLoadingBookmarks(true);
      setLoadError(null);

      try {
        const payload = await extractBookmarks(file);
        const bookmarks = hydrateBookmarkPayload(payload);
        setBookmarks(bookmarks);
        setLastLoadedFileId(isStirlingFile(file) ? file.fileId : file.name);

        if (showToast) {
          alert({
            title: t(
              "editTableOfContents.messages.loadedTitle",
              "Bookmarks extracted",
            ),
            body: t(
              "editTableOfContents.messages.loadedBody",
              "Existing bookmarks from the PDF were loaded into the editor.",
            ),
            alertType: "success",
          });
        }

        if (bookmarks.length === 0) {
          setLoadError(
            t(
              "editTableOfContents.messages.noBookmarks",
              "No bookmarks were found in the selected PDF.",
            ),
          );
        }
      } catch (error) {
        console.error("Failed to load bookmarks", error);
        setLoadError(
          t(
            "editTableOfContents.messages.loadFailed",
            "Unable to extract bookmarks from the selected PDF.",
          ),
        );
      } finally {
        setIsLoadingBookmarks(false);
      }
    },
    [setBookmarks, t],
  );

  useEffect(() => {
    // Don't auto-load bookmarks if we have results - user is viewing the output
    if (base.hasResults) {
      return;
    }

    if (!selectedFile) {
      setBookmarks([]);
      setLastLoadedFileId(null);
      setLoadError(null);
      return;
    }

    const fileId = selectedFile.fileId;
    if (fileId === lastLoadedFileId) {
      return;
    }

    loadBookmarksForFile(selectedFile).catch(() => {
      // errors handled in hook
    });
  }, [
    selectedFile,
    lastLoadedFileId,
    loadBookmarksForFile,
    setBookmarks,
    base.hasResults,
  ]);

  const importJsonCallback = async (file: File) => {
    try {
      const text = await file.text();
      const json = JSON.parse(text) as BookmarkPayload[];
      setBookmarks(hydrateBookmarkPayload(json));
      alert({
        title: t("editTableOfContents.messages.imported", "Bookmarks imported"),
        body: t(
          "editTableOfContents.messages.importedBody",
          "Your JSON outline replaced the current editor contents.",
        ),
        alertType: "success",
      });
    } catch (error) {
      console.error("Failed to import JSON bookmarks", error);
      alert({
        title: t(
          "editTableOfContents.messages.invalidJson",
          "Invalid JSON structure",
        ),
        body: t(
          "editTableOfContents.messages.invalidJsonBody",
          "Please provide a valid bookmark JSON file and try again.",
        ),
        alertType: "error",
      });
    }
  };
  const handleImportJson = useStableCallback(importJsonCallback);

  const importClipboardCallback = async () => {
    if (!navigator.clipboard?.readText) {
      alert({
        title: t(
          "editTableOfContents.actions.clipboardUnavailable",
          "Clipboard access unavailable",
        ),
        alertType: "warning",
      });
      return;
    }

    try {
      const clipboard = await navigator.clipboard.readText();
      const json = JSON.parse(clipboard) as BookmarkPayload[];
      setBookmarks(hydrateBookmarkPayload(json));
      alert({
        title: t("editTableOfContents.messages.imported", "Bookmarks imported"),
        body: t(
          "editTableOfContents.messages.importedClipboard",
          "Clipboard data replaced the current bookmark list.",
        ),
        alertType: "success",
      });
    } catch (error) {
      console.error("Failed to import bookmarks from clipboard", error);
      alert({
        title: t(
          "editTableOfContents.messages.invalidJson",
          "Invalid JSON structure",
        ),
        body: t(
          "editTableOfContents.messages.invalidJsonBody",
          "Please provide a valid bookmark JSON file and try again.",
        ),
        alertType: "error",
      });
    }
  };
  const handleImportClipboard = useStableCallback(importClipboardCallback);

  const exportJsonCallback = () => {
    const data = JSON.stringify(
      serializeBookmarkNodes(base.params.parameters.bookmarks),
      null,
      2,
    );
    const blob = new Blob([data], { type: "application/json" });
    void downloadFile({ data: blob, filename: "bookmarks.json" });
    alert({
      title: t("editTableOfContents.messages.exported", "JSON download ready"),
      alertType: "success",
    });
  };
  const handleExportJson = useStableCallback(exportJsonCallback);

  const exportClipboardCallback = async () => {
    if (!navigator.clipboard?.writeText) {
      alert({
        title: t(
          "editTableOfContents.actions.clipboardUnavailable",
          "Clipboard access unavailable",
        ),
        alertType: "warning",
      });
      return;
    }

    const data = JSON.stringify(
      serializeBookmarkNodes(base.params.parameters.bookmarks),
      null,
      2,
    );
    try {
      await navigator.clipboard.writeText(data);
      alert({
        title: t("editTableOfContents.messages.copied", "Copied to clipboard"),
        body: t(
          "editTableOfContents.messages.copiedBody",
          "Bookmark JSON copied successfully.",
        ),
        alertType: "success",
      });
    } catch (error) {
      console.error("Failed to copy bookmarks", error);
      alert({
        title: t("editTableOfContents.messages.copyFailed", "Copy failed"),
        alertType: "error",
      });
    }
  };
  const handleExportClipboard = useStableCallback(exportClipboardCallback);

  const clipboardReadAvailable =
    typeof navigator !== "undefined" && Boolean(navigator.clipboard?.readText);
  const clipboardWriteAvailable =
    typeof navigator !== "undefined" && Boolean(navigator.clipboard?.writeText);

  const loadFromSelectedCallback = () => {
    if (selectedFile) {
      loadBookmarksForFile(selectedFile, { showToast: true });
    }
  };
  const handleLoadFromSelected = useStableCallback(loadFromSelectedCallback);

  const replaceExistingCallback = (value: boolean) => {
    base.params.updateParameter("replaceExisting", value);
  };
  const handleReplaceExistingChange = useStableCallback(
    replaceExistingCallback,
  );

  const bookmarksChangeCallback = (bookmarks: BookmarkNode[]) => {
    setBookmarks(bookmarks);
  };
  const handleBookmarksChange = useStableCallback(bookmarksChangeCallback);

  const executeCallback = () => {
    void base.handleExecute();
  };
  const handleExecute = useStableCallback(executeCallback);

  const undoCallback = () => {
    base.handleUndo();
  };
  const handleUndo = useStableCallback(undoCallback);

  const clearErrorCallback = () => {
    base.operation.clearError();
  };
  const handleClearError = useStableCallback(clearErrorCallback);

  const fileClickCallback = (file: File) => {
    base.handleThumbnailClick(file);
  };
  const handleFileClick = useStableCallback(fileClickCallback);

  const selectFilesCallback = () => {
    // Clear existing selection first so the new file replaces instead of adds
    clearSelections();
    openFilesModal();
  };
  const handleSelectFiles = useStableCallback(selectFilesCallback);

  // Always keep workbench data updated
  useEffect(() => {
    const data: EditTableOfContentsWorkbenchViewData = {
      bookmarks: base.params.parameters.bookmarks,
      selectedFileName: selectedFile?.name,
      disabled: base.endpointLoading || base.operation.isLoading,
      files: base.operation.files ?? [],
      thumbnails: base.operation.thumbnails ?? [],
      downloadUrl: base.operation.downloadUrl ?? null,
      downloadFilename: base.operation.downloadFilename ?? null,
      errorMessage: base.operation.errorMessage ?? null,
      isGeneratingThumbnails: base.operation.isGeneratingThumbnails,
      isExecuteDisabled:
        !selectedFile ||
        !base.hasFiles ||
        base.endpointEnabled === false ||
        base.operation.isLoading ||
        base.endpointLoading,
      isExecuting: base.operation.isLoading,
      onClearError: handleClearError,
      onBookmarksChange: handleBookmarksChange,
      onExecute: handleExecute,
      onUndo: handleUndo,
      onFileClick: handleFileClick,
    };

    setCustomWorkbenchViewData(WORKBENCH_VIEW_ID, data);
  }, [
    WORKBENCH_VIEW_ID,
    base.endpointEnabled,
    base.endpointLoading,
    base.hasFiles,
    base.operation.downloadFilename,
    base.operation.downloadUrl,
    base.operation.errorMessage,
    base.operation.files,
    base.operation.isGeneratingThumbnails,
    base.operation.isLoading,
    base.operation.thumbnails,
    base.params.parameters.bookmarks,
    handleBookmarksChange,
    handleClearError,
    handleExecute,
    handleFileClick,
    handleUndo,
    selectedFile,
    setCustomWorkbenchViewData,
  ]);

  // Auto-navigate to workbench when tool is selected
  useEffect(() => {
    if (navigationState.selectedTool !== "editTableOfContents") {
      hasAutoOpenedWorkbenchRef.current = false;
      return;
    }

    if (hasAutoOpenedWorkbenchRef.current) {
      return;
    }

    hasAutoOpenedWorkbenchRef.current = true;
    // Use timeout to ensure data effect has run first
    setTimeout(() => {
      navigationActions.setWorkbench(WORKBENCH_ID);
    }, 0);
  }, [navigationActions, navigationState.selectedTool, WORKBENCH_ID]);

  return createToolFlow({
    files: {
      selectedFiles: base.selectedFiles,
      isCollapsed: true,
      minFiles: 1,
      isVisible: false,
    },
    steps: [
      {
        title: t("editTableOfContents.settings.title", "Bookmarks & outline"),
        isCollapsed: false,
        content: (
          <EditTableOfContentsSettings
            bookmarks={base.params.parameters.bookmarks}
            replaceExisting={base.params.parameters.replaceExisting}
            onReplaceExistingChange={handleReplaceExistingChange}
            onSelectFiles={handleSelectFiles}
            onLoadFromPdf={handleLoadFromSelected}
            onImportJson={handleImportJson}
            onImportClipboard={handleImportClipboard}
            onExportJson={handleExportJson}
            onExportClipboard={handleExportClipboard}
            isLoading={isLoadingBookmarks}
            loadError={loadError}
            canReadClipboard={clipboardReadAvailable}
            canWriteClipboard={clipboardWriteAvailable}
            disabled={base.endpointLoading}
            selectedFileName={selectedFile?.name}
          />
        ),
      },
    ],
    review: {
      isVisible: base.hasResults,
      operation: base.operation,
      title: t(
        "editTableOfContents.results.title",
        "Updated PDF with bookmarks",
      ),
      onFileClick: base.handleThumbnailClick,
      onUndo: handleUndo,
    },
  });
};

(EditTableOfContents as ToolComponent).tool = () =>
  useEditTableOfContentsOperation;

export default EditTableOfContents as ToolComponent;
