import fixStudioProjectState from '@suno/studiokit/projectState/fixStudioProjectState';
import { serializeProjectState } from '@suno/studiokit/projectState/serialization';
import { useQueryClient } from '@tanstack/react-query';
import { useSearchParams } from 'next/navigation';
import { useCallback, useState } from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import { toast } from '@/components/toast/Toast';
import { createContext } from '@/hooks/useContextSelector';

import prepareStateForEditMode from '../edit2025/prepareStateForEditMode';
import DEFAULT_STATE, { getDefaultStateWithOneTrack } from './defaultState';
import getEditModeStateForClip from './getEditModeStateForClip';
import * as studioProjectApi from './studioProjectApi';
import { StudioProjectApiError } from './studioProjectApi';
import { StudioProjectState } from './types';
import { getBufferKeys } from './uploadedClipCache';

export const useStudioProjectManagement = (
  loadedProject: {
    title: string;
    state: StudioProjectState;
    id: string;
  } | null,
  setLoadedProject: (
    loadedProject: {
      title: string;
      state: StudioProjectState;
      id: string;
    } | null,
    options?: {
      sourceVersionId?: string;
      skipLocalStorage?: boolean;
    }
  ) => void
) => {
  const { clips: clipsStore } = useStores();
  const queryClient = useQueryClient();
  const searchParams = useSearchParams();
  const [isSaving, setIsSaving] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
  const [latestVersionId, setLatestVersionId] = useState<string | null>(null);

  // Derive lock state reactively from URL - locked when viewing a specific version via URL
  const isLocked = !!searchParams?.get('project_version_id');

  const fetchProject = useCallback(
    async (studioProjectId: string, mode: 'edit' | 'studio') => {
      try {
        setIsLoading(true);
        const projectData = await studioProjectApi.fetchProjectById(
          clipsStore.apiClient,
          studioProjectId
        );

        // Treat project state as unknown before validation/migration
        const rawProjectState: unknown = projectData.state || DEFAULT_STATE;

        // Basic validation before passing to fixStudioProjectState
        if (
          typeof rawProjectState === 'object' &&
          rawProjectState !== null &&
          'timing' in rawProjectState &&
          'tracks' in rawProjectState &&
          'selection' in rawProjectState
        ) {
          let fixedState = fixStudioProjectState(rawProjectState, {
            keepUploads: getBufferKeys(),
          }) as StudioProjectState;

          // Apply edit mode transformations if needed
          if (mode === 'edit') {
            fixedState = prepareStateForEditMode(fixedState);
          }

          return {
            title:
              projectData.title ||
              (mode === 'edit' ? 'Untitled' : 'Untitled Project'),
            state: fixedState,
            id: studioProjectId,
          };
        } else {
          throw new Error('Invalid project state format');
        }
      } catch (err) {
        toast({
          title: 'Failed to load project',
          description: 'Please try again later.',
          status: 'error',
        });
      } finally {
        setIsLoading(false);
      }
    },
    [clipsStore.apiClient]
  );

  const loadProject = useCallback(
    async (studioProjectId: string, mode: 'edit' | 'studio') => {
      const project = await fetchProject(studioProjectId, mode);
      if (project) {
        setCurrentVersionId(null); // Clear version ID for regular project loads

        // Clear version ID from URL when loading owned project
        if (typeof window !== 'undefined') {
          const url = new URL(window.location.href);
          if (url.searchParams.has('project_version_id')) {
            url.searchParams.delete('project_version_id');
            window.history.replaceState({}, '', url.toString());
          }
        }

        setLoadedProject(project);
      }
    },
    [fetchProject, setLoadedProject]
  );

  const loadProjectByVersionId = useCallback(
    async (
      versionId: string,
      mode: 'edit' | 'studio',
      onSharedProject?: (versionId: string, projectTitle: string) => void
    ) => {
      try {
        setIsLoading(true);

        // First get the project ID and revision info from the version ID
        const revisionData = await studioProjectApi.fetchRevisionById(
          clipsStore.apiClient,
          versionId
        );

        if (!revisionData.project_id) {
          throw new Error('Project ID not found for version');
        }

        const projectId = revisionData.project_id;
        const isOwner = revisionData.is_owner;

        // Set current version ID (this will automatically lock via derived state)
        setCurrentVersionId(versionId);

        // Get the specific version state
        const versionData = await studioProjectApi.fetchProjectVersion(
          clipsStore.apiClient,
          projectId,
          versionId
        );

        // Use the version-specific state, not the current project state
        const versionState = fixStudioProjectState(
          versionData.state as Record<string, unknown>,
          {
            keepUploads: getBufferKeys(),
          }
        ) as StudioProjectState;

        // Apply edit mode transformations if needed
        let finalState = versionState;
        if (mode === 'edit') {
          finalState = prepareStateForEditMode(versionState);
        }

        // Use title from the revision response
        const projectTitle = revisionData.title || 'Untitled Project';

        // If user doesn't own the project, trigger the shared project callback
        if (!isOwner && onSharedProject) {
          onSharedProject(versionId, projectTitle);
        }

        setLoadedProject(
          {
            title: projectTitle,
            state: finalState,
            id: projectId,
          },
          {
            sourceVersionId: versionId, // Store version ID for both owned and non-owned projects
            skipLocalStorage: !isOwner && !!onSharedProject, // Skip localStorage for non-owned projects that trigger clone modal
          }
        );
      } catch (err) {
        // Provide more specific error messaging based on error type
        let errorMessage = 'Please try again later.';
        if (err instanceof StudioProjectApiError && err.status) {
          if (err.status === 404) {
            errorMessage =
              'This project version was not found or may have been deleted.';
          } else if (err.status === 403) {
            errorMessage =
              'You do not have permission to access this project version.';
          }
        }

        toast({
          title: 'Failed to load project version',
          description: errorMessage,
          status: 'error',
        });

        // Re-throw the error so the caller can handle it (e.g., redirect)
        throw err;
      } finally {
        setIsLoading(false);
      }
    },
    [setLoadedProject, clipsStore.apiClient]
  );

  const saveProject = useCallback(
    async (
      id: string,
      state: StudioProjectState,
      title: string
    ): Promise<string | undefined> => {
      if (isLoading) return;
      try {
        setIsSaving(true);

        // Serialize state before passing to API
        const serializedState = serializeProjectState(state);

        // Call API to save project
        const versionId = await studioProjectApi.saveProject(
          clipsStore.apiClient,
          id,
          serializedState,
          title
        );

        // Store latest version ID for immediate share modal access
        setLatestVersionId(versionId);

        // Invalidate versions cache to ensure share modal gets latest version
        queryClient.invalidateQueries({
          queryKey: ['studio-versions-latest', id],
        });

        return versionId;
      } catch (error) {
        toast({
          title: 'Failed to save project',
          description: 'Please try again later.',
          status: 'error',
        });
        return undefined;
      } finally {
        setIsSaving(false);
      }
    },
    [clipsStore.apiClient, isLoading, queryClient]
  );

  const createNewProject = useCallback(
    async (title: string = 'Untitled Project') => {
      return await studioProjectApi.createProject(clipsStore.apiClient, title);
    },
    [clipsStore.apiClient]
  );

  const createOrLoadEditProject = useCallback(
    async (editModeClipId: string) => {
      if (loadedProject) {
        return;
      }

      const projectId = await studioProjectApi.createOrLoadProjectForClip(
        clipsStore.apiClient,
        editModeClipId
      );

      const project = await fetchProject(projectId, 'edit');
      if (project) {
        if (project.state.tracks.length > 0) {
          setLoadedProject(project);
        } else {
          setLoadedProject({
            ...project,
            state: await getEditModeStateForClip(
              clipsStore,
              editModeClipId,
              true
            ),
          });
        }
      }
    },
    [fetchProject, loadedProject, setLoadedProject, clipsStore]
  );

  const createAndLoadNewStudioProject = useCallback(
    async (state: StudioProjectState | null = null) => {
      if (isLoading) return;
      const { title, id } = await createNewProject(
        state === null ? undefined : (state.title ?? state.tracks[0]?.name)
      );

      // Clear version ID from URL when creating new project
      if (typeof window !== 'undefined') {
        const url = new URL(window.location.href);
        if (url.searchParams.has('project_version_id')) {
          url.searchParams.delete('project_version_id');
          window.history.replaceState({}, '', url.toString());
        }
      }

      const project = {
        title,
        state: state ?? getDefaultStateWithOneTrack(),
        id,
      };

      setLoadedProject(project);

      return project;
    },
    [createNewProject, isLoading, setLoadedProject]
  );

  const createAndSaveNewStudioProject = useCallback(
    async (
      state: StudioProjectState,
      title: string = state.title ?? state.tracks[0]?.name ?? 'Untitled Project'
    ) => {
      const { id } = await createNewProject(state === null ? undefined : title);
      await saveProject(id, state, title);
      return id;
    },
    [createNewProject, saveProject]
  );

  const saveCurrentProject = useCallback(
    async (
      state: StudioProjectState,
      title: string = loadedProject?.title || 'Untitled Project'
    ) => {
      if (isLoading) return;

      if (!loadedProject) {
        return;
      }
      const originalLoadedProject = loadedProject;
      try {
        setLoadedProject({
          ...originalLoadedProject,
          state,
          title,
        });
        await saveProject(loadedProject.id, state, title);
      } catch (error) {
        setLoadedProject(originalLoadedProject);
        throw error;
      }
    },
    [loadedProject, saveProject, isLoading, setLoadedProject]
  );

  const cloneProject = useCallback(
    async (versionId: string, title?: string) => {
      try {
        setIsLoading(true);

        // Call API to clone project
        const newProjectId = await studioProjectApi.cloneProject(
          clipsStore.apiClient,
          versionId,
          title
        );

        // Return the new project ID for redirect
        return newProjectId;
      } catch (error) {
        toast({
          title: 'Failed to clone project',
          description: 'Please try again later.',
          status: 'error',
        });
        throw error;
      } finally {
        setIsLoading(false);
      }
    },
    [clipsStore.apiClient]
  );

  return {
    loadProject,
    loadProjectByVersionId,
    cloneProject,
    saveProject: saveCurrentProject,
    createAndLoadNewStudioProject,
    createOrLoadEditProject,
    createAndSaveNewStudioProject,
    isSaving,
    isLoading,
    loadedProject,
    isLocked,
    currentVersionId,
    latestVersionId,
  };
};

export const StudioProjectManagementContext = createContext<
  ReturnType<typeof useStudioProjectManagement>
>(undefined as never);
