import type { Router, RouteLocationNormalizedLoaded } from "vue-router";
import type { ApiError, ErrorDiagnostic } from "@/types";
import type { useMdtodoReportPreview } from "@/composables/mdtodo/useMdtodoReportPreview";
import type { useMdtodoRouteSelection } from "@/composables/mdtodo/useMdtodoRouteSelection";
import type { useMdtodoSource } from "@/composables/mdtodo/useMdtodoSource";
import type { useMdtodoTaskData } from "@/composables/mdtodo/useMdtodoTaskData";
import type { useMdtodoViewState } from "@/composables/mdtodo/useMdtodoViewState";
import type { MdtodoRefreshMode, MdtodoWorkspaceState } from "@/composables/mdtodo/useMdtodoWorkspaceState";

export function useMdtodoWorkspaceNavigation(dependencies: {
  router: Router;
  route: RouteLocationNormalizedLoaded;
  selection: ReturnType<typeof useMdtodoRouteSelection>;
  source: ReturnType<typeof useMdtodoSource>;
  taskData: ReturnType<typeof useMdtodoTaskData>;
  report: ReturnType<typeof useMdtodoReportPreview>;
  viewState: ReturnType<typeof useMdtodoViewState>;
  state: MdtodoWorkspaceState;
}) {
  const { router, route, selection, source, taskData, report, viewState, state } = dependencies;
  const {
    loading, error, apiError, diagnostic, selectedSourceId, selectedFileRef,
    selectedTaskRef, selectedTask, selectedTaskLinks, collapsedTaskRefs,
    refreshMode, taskSearch, taskStatus,
  } = state;
  let refreshSequence = 0;
  function currentQuery() {
    return {
      search: taskSearch.value.trim() || undefined,
      status: taskStatus.value === "all" ? undefined : taskStatus.value,
    };
  }

  function setError(value: unknown): void {
    const result =
      value && typeof value === "object" && "error" in value
        ? (value as {
            error?: string | null;
            apiError?: ApiError | null;
            diagnostic?: ErrorDiagnostic | null;
            status?: number;
          })
        : null;
    error.value =
      result?.error ||
      (value instanceof Error
        ? value.message
        : String(value || "MDTODO 加载失败"));
    apiError.value = result?.apiError ?? null;
    diagnostic.value = result?.diagnostic ?? null;
  }

  async function syncRoute(
    replace = false,
    reportLinkId = report.activeReportLink.value?.linkId ?? null,
  ): Promise<void> {
    await selection.syncSelectionRoute({
      sourceId: selectedSourceId.value,
      fileRef: selectedFileRef.value,
      taskId: selectedTask.value?.taskId ?? null,
      reportLinkId,
      replace,
    });
  }

  async function loadWindow(
    sourceId: string,
    options: {
      fileRef?: string | null;
      taskId?: string | null;
      reportLinkId?: string | null;
      updateRoute?: boolean;
      mode?: MdtodoRefreshMode;
    } = {},
  ): Promise<boolean> {
    const sequence = ++refreshSequence;
    refreshMode.value = options.mode ?? "blocking";
    taskData.taskLoading.value = true;
    try {
      await taskData.loadFiles(sourceId);
      if (sequence !== refreshSequence) return false;
      const requestedFileExists =
        options.fileRef &&
        taskData.files.value.some((item) => item.fileRef === options.fileRef);
      const currentFileExists = taskData.files.value.some(
        (item) => item.fileRef === selectedFileRef.value,
      );
      selectedFileRef.value = requestedFileExists
        ? (options.fileRef ?? null)
        : currentFileExists
          ? selectedFileRef.value
          : (taskData.files.value[0]?.fileRef ?? null);
      const requestedTask =
        options.taskId !== undefined ? options.taskId : selectedTaskRef.value;
      await taskData.loadTaskWindow(
        sourceId,
        selectedFileRef.value,
        requestedTask,
        currentQuery(),
      );
      if (sequence !== refreshSequence) return false;
      const preferred = taskData.resolvePreferredTask(requestedTask);
      const unresolved = Boolean(requestedTask && !preferred);
      if (!unresolved)
        selectedTaskRef.value =
          preferred?.taskRef ?? taskData.tasks.value[0]?.taskRef ?? null;
      if (refreshMode.value === "blocking") collapsedTaskRefs.value = new Set();
      if (options.reportLinkId && selectedTaskRef.value) {
        await Promise.all([
          taskData.loadLinks(selectedTaskRef.value),
          taskData.loadTaskDetail(selectedTaskRef.value),
        ]);
        await report.openReportPreviewById(
          selectedTaskRef.value,
          selectedTaskLinks.value,
          options.reportLinkId,
        );
      } else if (options.updateRoute !== false && !unresolved)
        await syncRoute(true);
      error.value = null;
      apiError.value = null;
      diagnostic.value = null;
      return true;
    } catch (value) {
      if (sequence === refreshSequence) setError(value);
      return false;
    } finally {
      if (sequence === refreshSequence) taskData.taskLoading.value = false;
    }
  }

  async function loadPage(): Promise<void> {
    loading.value = true;
    error.value = null;
    try {
      const result = await source.loadNavigationAndSources();
      const routeSourceId = selection.readSelection().sourceId;
      selectedSourceId.value =
        routeSourceId &&
        result.sources.some((item) => item.sourceId === routeSourceId)
          ? routeSourceId
          : (result.sources[0]?.sourceId ?? null);
      if (selectedSourceId.value) {
        await loadWindow(selectedSourceId.value, {
          ...selection.routeSelectionOptions(true),
          mode: "blocking",
        });
      }
    } catch (value) {
      setError(value);
    } finally {
      loading.value = false;
    }
  }

  function snapshotTaskWorkspace() {
    return {
      files: [...taskData.files.value],
      tasks: [...taskData.tasks.value],
      taskPage: taskData.taskPage.value,
      taskDetail: taskData.taskDetail.value,
      authority: taskData.taskDetailAuthority.value,
      links: [...taskData.links.value],
    };
  }

  function restoreTaskWorkspace(
    snapshot: ReturnType<typeof snapshotTaskWorkspace>,
  ): void {
    taskData.files.value = snapshot.files;
    taskData.tasks.value = snapshot.tasks;
    taskData.taskPage.value = snapshot.taskPage;
    taskData.taskDetail.value = snapshot.taskDetail;
    taskData.taskDetailAuthority.value = snapshot.authority;
    taskData.links.value = snapshot.links;
  }

  async function selectSource(sourceId: string): Promise<void> {
    const previous = {
      sourceId: selectedSourceId.value,
      fileRef: selectedFileRef.value,
      taskRef: selectedTaskRef.value,
    };
    const workspace = snapshotTaskWorkspace();
    selectedSourceId.value = sourceId || null;
    if (selectedSourceId.value) {
      const loaded = await loadWindow(selectedSourceId.value, {
        updateRoute: false,
        mode: "blocking",
      });
      if (loaded) {
        report.closeReportPreview();
        await syncRoute(false);
      } else {
        selectedSourceId.value = previous.sourceId;
        selectedFileRef.value = previous.fileRef;
        selectedTaskRef.value = previous.taskRef;
        restoreTaskWorkspace(workspace);
      }
    }
  }

  async function selectFile(fileRef: string): Promise<void> {
    const previous = {
      fileRef: selectedFileRef.value,
      taskRef: selectedTaskRef.value,
    };
    const workspace = snapshotTaskWorkspace();
    if (selectedSourceId.value) {
      const loaded = await loadWindow(selectedSourceId.value, {
        fileRef: fileRef || null,
        taskId: null,
        updateRoute: false,
        mode: "blocking",
      });
      if (loaded) {
        report.closeReportPreview();
        await syncRoute(false);
      } else {
        selectedFileRef.value = previous.fileRef;
        selectedTaskRef.value = previous.taskRef;
        restoreTaskWorkspace(workspace);
      }
    }
  }

  async function selectTask(taskRef: string): Promise<void> {
    selectedTaskRef.value = taskRef;
    report.closeReportPreview();
    await syncRoute();
    await viewState.update({ tab: "document" }, true);
  }

  async function applyRoute(): Promise<void> {
    const next = selection.readSelection();
    const sourceId =
      next.sourceId &&
      source.sources.value.some((item) => item.sourceId === next.sourceId)
        ? next.sourceId
        : selectedSourceId.value;
    if (!sourceId) return;
    const sourceChanged = sourceId !== selectedSourceId.value;
    const fileChanged = Boolean(
      next.fileRef && next.fileRef !== selectedFileRef.value,
    );
    selectedSourceId.value = sourceId;
    if (sourceChanged || fileChanged || !taskData.tasks.value.length) {
      await loadWindow(sourceId, {
        ...selection.routeSelectionOptions(false),
        mode: "blocking",
      });
      return;
    }
    const task = next.taskId
      ? taskData.resolvePreferredTask(next.taskId)
      : selectedTask.value;
    if (next.taskId && !task) {
      await loadWindow(sourceId, {
        ...selection.routeSelectionOptions(false),
        mode: "persistent",
      });
      return;
    }
    selectedTaskRef.value = task?.taskRef ?? selectedTaskRef.value;
    if (next.linkId && selectedTaskRef.value) {
      await Promise.all([
        taskData.loadLinks(selectedTaskRef.value),
        taskData.loadTaskDetail(selectedTaskRef.value),
      ]);
      await report.openReportPreviewById(
        selectedTaskRef.value,
        selectedTaskLinks.value,
        next.linkId,
      );
    } else report.closeReportPreview();
  }

  async function reloadWindow(): Promise<void> {
    if (!selectedSourceId.value) return;
    await loadWindow(selectedSourceId.value, {
      fileRef: selectedFileRef.value,
      taskId: selectedTaskRef.value,
      updateRoute: false,
      mode: taskData.tasks.value.length ? "persistent" : "blocking",
    });
    if (selectedTaskRef.value)
      await Promise.all([
        taskData.loadTaskDetail(selectedTaskRef.value),
        taskData.loadLinks(selectedTaskRef.value),
      ]);
  }

  async function reindex(): Promise<void> {
    if (
      selectedSourceId.value &&
      (await source.reindexSource(selectedSourceId.value))
    )
      await reloadWindow();
  }
  async function loadMore(): Promise<void> {
    taskData.taskLoading.value = true;
    try {
      await taskData.loadMoreTaskWindow();
    } catch (value) {
      setError(value);
    } finally {
      taskData.taskLoading.value = false;
    }
  }

  return {
    currentQuery,
    setError,
    syncRoute,
    loadWindow,
    loadPage,
    snapshotTaskWorkspace,
    restoreTaskWorkspace,
    selectSource,
    selectFile,
    selectTask,
    applyRoute,
    reloadWindow,
    reindex,
    loadMore,
  };
}

export type MdtodoWorkspaceNavigation = ReturnType<typeof useMdtodoWorkspaceNavigation>;
