import storageAvailable from 'storage-available';

import { workspaceCollaborationService } from '@/app/(root)/create/WorkspaceCollaborationInitializer';
import { invalidateWorkspaceQueries } from '@/components/clipBrowser/clipBrowserQueryClient';
import { toast } from '@/components/toast/Toast';
import { components } from '@/lib/gen';
import { Clip } from '@/state/clipStore';
import {
  DEFAULT_PAGE_SIZE,
  DEFAULT_PROJECT_ID,
  DEFAULT_PROJECT_NAME,
} from '@/utils/constants';
import { loadFromLocalStorage, setInLocalStorage } from '@/utils/storage';
import { getProjectName } from '@/utils/utils';

import { RootStore, Substore } from './rootStore';
import { makeAutoObservableSubstore } from './utils';

export type Project = components['schemas']['ProjectSchema'];
export type ProjectClip = components['schemas']['ProjectClipSchema'];
export type ProjectMetadataSchema =
  components['schemas']['ProjectMetadataSchema'] & { is_pending?: boolean }; // is_pending denotes optimistically created projects with temp IDs
export type ProjectInviteSchema = components['schemas']['ProjectInviteSchema'];
export type SimpleProjectSchema = components['schemas']['SimpleProjectSchema'];

const defaultFilters = {
  liked: false,
  hide_disliked: true,
  hide_gen_stems: true,
  hide_studio_clips: true,

  public: false,
  full_song: false,
  is_suno_short: false,
  is_cover: false,
  is_upsample: false,
  is_extend: false,
  is_persona: false,
  is_uploaded_audio: false,
  is_infill: false,
  is_gen_stem: false,
  page: 1,
  query: '',
};

export class ProjectStore implements Substore {
  currentProject?: Project = undefined;
  currentProjectId: string = DEFAULT_PROJECT_ID;
  currentProjectName: string | null = DEFAULT_PROJECT_NAME;
  allProjects: ProjectMetadataSchema[] = [];
  projectsById: { [key: string]: ProjectMetadataSchema | SimpleProjectSchema } =
    {};
  activeClip?: Clip = undefined;
  archivedProjects: ProjectMetadataSchema[] = [];
  invites: ProjectInviteSchema[] = [];

  userSelectedProjectId: string | null = null;

  currentProjectPage = 1;
  totalProjects = 0;
  totalArchivedProjects = 0;

  loadingArchivedProjects = false;
  loadingAllProjects = false;
  loadingInvitedProjects = false;

  currentPage = 1;
  clipIds: string[] = [];
  pinnedClipIds: string[] = [];
  numTotalClips = 0;
  loadingProjectClips = false;
  isLoaded = false;
  showTrash = false;
  isLocalStorageAvailable: boolean = false;

  filters = {
    ...defaultFilters,
  };

  projectQuery = '';

  readonly root: RootStore;
  get apiClient() {
    return this.root.apiClient;
  }
  // DO NOT ADD INIT LOGIC IN constructor. this will run on every page! considering adding any it in the component init logic instead
  constructor(root: RootStore) {
    this.root = root;
    makeAutoObservableSubstore(this);
  }

  setUserSelectedProjectId = async (
    projectId: string | null,
    projectName: string | null
  ) => {
    this.currentProjectId = projectId || DEFAULT_PROJECT_ID;
    this.userSelectedProjectId = projectId;

    // Fetch project if not in projectsById
    if (projectId && !this.projectsById[projectId]) {
      await this.fetchSingleProject(projectId);
    } else if (
      projectId &&
      this.projectsById[projectId] &&
      !(
        'clip_count' in this.projectsById[projectId] &&
        'last_updated_clip' in this.projectsById[projectId]
      )
    ) {
      // if SimpleProjectSchema is stored in projects mapping, async fetch project data
      this.fetchSingleProject(projectId);
    }

    if (projectId !== this.currentProject?.id) {
      this.currentProject = undefined;
      this.currentProjectName =
        projectName || (projectId ? this.projectsById[projectId]?.name : null);
      this.filters.page = 1;
      this.currentPage = 1;
    }

    const userId = await this.waitForUserId();
    // Save to localStorage and userId are available
    if (storageAvailable('localStorage') && userId) {
      if (projectId) {
        setInLocalStorage(`lastSelectedProjectId-${userId}`, projectId);
        setInLocalStorage(
          `lastSelectedProjectName-${userId}`,
          projectName || ''
        );
        this.setHasSetProjectId();
      } else {
        localStorage.removeItem(`lastSelectedProjectId-${userId}`);
        localStorage.removeItem(`lastSelectedProjectName-${userId}`);
      }
    }
  };

  setCurrentProjectToClipProject = async (clip: Clip) => {
    if (clip.project?.id && !this.projectsById[clip.project?.id]) {
      this.projectsById[clip.project?.id] = clip.project;
    }
    if (clip.project) {
      await this.setUserSelectedProjectId(clip.project.id, clip.project.name);
    }
  };

  // This function is used to clear the clips when switching projects to prevent
  // the older clips from being displayed in the new project while the new clips are loading
  clearClips = () => {
    this.clipIds = [];
    this.numTotalClips = 0;
    this.pinnedClipIds = [];
  };

  clearFilters = () => {
    this.filters = {
      ...defaultFilters,
    };
  };

  waitForUserId = async (): Promise<string | null> => {
    const maxRetries = 20;
    const retryDelay = 100; // milliseconds
    for (let i = 0; i < maxRetries; i++) {
      if (this.root.session.user?.id) {
        return this.root.session.user.id;
      }
      await new Promise((resolve) => setTimeout(resolve, retryDelay));
    }
    return null;
  };

  updateFilters = (filters: any, forceReload: boolean = true) => {
    if (typeof filters.page === 'undefined') {
      this.filters.page = 1;
    }
    this.filters = {
      ...this.filters,
      ...filters,
    };
    if (forceReload) {
      this.loadClips();
    }
  };

  updateFiltersSync = async (filters: any) => {
    if (typeof filters.page === 'undefined') {
      this.filters.page = 1;
    }
    this.filters = {
      ...this.filters,
      ...filters,
    };
    await this.loadClips();
  };

  incrementPage = (offset: number) => {
    this.currentPage = Math.max(1, this.currentPage + offset);
    this.updateFilters({
      page: this.currentPage,
    });
  };

  setPageNumber = (newPageNumber: number) => {
    this.currentPage = newPageNumber;
    this.updateFilters({ page: this.currentPage });
  };

  incrementProjectPage = (offset: number) => {
    this.currentProjectPage = Math.max(1, this.currentProjectPage + offset);
    this.loadAllProjects();
  };

  setProjectPageNumber = (newPageNumber: number) => {
    this.currentProjectPage = newPageNumber;
    if (this.showTrash) {
      this.loadArchivedProjects();
    } else {
      this.loadAllProjects();
    }
  };

  createProject = async (
    name: string,
    description: string,
    suppressToast?: boolean
  ) => {
    name = name?.slice(0, 100) || '';
    const { data } = await this.apiClient.POST('/api/project', {
      body: {
        name: name,
        description: description,
      },
    });

    if (data) {
      const newProject = {
        id: data.id,
        name: data.name,
        owner: data.owner,
        description: data.description,
        clip_count: data.clip_count,
        last_updated_clip: null,
        shared: data.shared,
      };

      // Update projectsById
      this.projectsById[data.id] = newProject;

      // Add to allProjects array
      this.allProjects = [newProject, ...this.allProjects];

      // Increment total projects count
      this.totalProjects += 1;

      if (!suppressToast) {
        toast({
          title: 'New workspace created',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      }

      return data;
    }
    return null;
  };

  addClip = (clip: Clip, addToFront: boolean = true) => {
    this.root.clips.clipById[clip.id] = clip;
    if (!this.clipIds.includes(clip.id)) {
      if (addToFront) {
        this.clipIds.unshift(clip.id);
      } else {
        this.clipIds.push(clip.id);
      }
    }
  };

  updateProjectName = async (projectId: string, newName: string) => {
    newName = newName?.slice(0, 100) || '';
    const { response } = await this.apiClient.POST(
      '/api/project/{project_id}/metadata',
      {
        params: { path: { project_id: projectId } },
        body: { name: newName, description: newName },
      }
    );

    if (projectId === this.currentProjectId) {
      this.currentProjectName = newName;
      if (this.currentProject) {
        this.currentProject.name = newName;
      }
    }
    this.projectsById[projectId] = {
      ...(this.projectsById[projectId] || {}),
      name: newName,
    };

    if (response.ok) {
      toast({
        title: 'Workspace name updated',
        status: 'info',
        duration: 2000,
        isClosable: true,
      });
    }
  };

  addClipsToProject = async (
    clips: Clip[],
    projectId: string,
    silent: boolean = true
  ) => {
    if (projectId === DEFAULT_PROJECT_ID) {
      return;
    }

    const { response } = await this.apiClient.POST(
      '/api/project/{project_id}/clips',
      {
        params: {
          path: {
            project_id: projectId,
          },
        },
        body: {
          update_type: 'add',
          metadata: {
            clip_ids: clips.map((clip) => clip.id),
          },
        },
      }
    );

    if (response.ok) {
      // Only remove clips from current view if adding to a different project
      if (projectId !== this.currentProjectId) {
        const clipIdsToRemove = new Set(clips.map((clip) => clip.id));
        this.clipIds = this.clipIds.filter((id) => !clipIdsToRemove.has(id));
        this.numTotalClips = Math.max(0, this.numTotalClips - clips.length);

        invalidateWorkspaceQueries(this.currentProjectId);
        invalidateWorkspaceQueries(projectId);

        // If there are less than half the default page size, fetch all clips
        if (
          this.clipIds.length <= DEFAULT_PAGE_SIZE / 2 &&
          this.numTotalClips > DEFAULT_PAGE_SIZE
        ) {
          this.loadClips();
        }
      }

      if (!silent) {
        toast({
          title:
            clips.length === 1
              ? 'Added clip to workspace'
              : `Added ${clips.length} clips to workspace`,
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      }
      const published =
        workspaceCollaborationService.makeWorkspaceChange('add_clips');
      if (
        !published &&
        projectId !== DEFAULT_PROJECT_ID &&
        (this.projectsById[projectId] as ProjectMetadataSchema)?.shared &&
        this.root.session.flags?.['collab-workspaces']
      ) {
        this.apiClient.POST('/api/project/{project_id}/ably-update', {
          params: {
            path: {
              project_id: projectId,
            },
          },
          body: {
            update_type: 'add_clips',
          },
        });
      }
    }
  };

  removeClipsFromProject = async (clips: Clip[], projectId: string) => {
    const { response } = await this.apiClient.POST(
      '/api/project/{project_id}/clips',
      {
        params: {
          path: {
            project_id: projectId,
          },
        },
        body: {
          update_type: 'remove',
          metadata: {
            clip_ids: clips.map((clip) => clip.id),
          },
        },
      }
    );

    if (response.ok) {
      const clipIdsToRemove = new Set(clips.map((clip) => clip.id));
      this.clipIds = this.clipIds.filter((id) => !clipIdsToRemove.has(id));
      this.numTotalClips = Math.max(0, this.numTotalClips - clips.length);
      invalidateWorkspaceQueries(projectId);

      // If there are less than half the default page size, fetch all clips
      if (
        this.clipIds.length <= DEFAULT_PAGE_SIZE / 2 &&
        this.numTotalClips > DEFAULT_PAGE_SIZE
      ) {
        this.loadClips();
      }

      toast({
        title: 'Removed clip from workspace',
        status: 'info',
        duration: 2000,
        isClosable: true,
      });
      workspaceCollaborationService.makeWorkspaceChange('remove_clips');
    }
  };

  pinClipToProject = async (
    clips: Clip[],
    projectId: string,
    pinned: boolean = true
  ) => {
    const { response } = await this.apiClient.POST(
      '/api/project/{project_id}/clips',
      {
        params: {
          path: {
            project_id: projectId,
          },
        },
        body: {
          update_type: 'pinned',
          metadata: {
            clip_ids: clips.map((clip) => clip.id),
            pinned: pinned,
          },
        },
      }
    );

    if (response.ok) {
      if (pinned) {
        this.pinnedClipIds = [
          ...this.pinnedClipIds,
          ...clips
            .map((clip) => clip.id)
            .filter((id) => !this.pinnedClipIds?.includes(id)),
        ];
      } else {
        this.pinnedClipIds = this.pinnedClipIds.filter(
          (id) => !clips.map((clip) => clip.id).includes(id)
        );
      }

      toast({
        title: pinned
          ? 'Pinned clip to workspace'
          : 'Unpinned clip from workspace',
        status: 'info',
        duration: 2000,
        isClosable: true,
      });
      workspaceCollaborationService.makeWorkspaceChange(
        pinned ? 'pin_clips' : 'unpin_clips'
      );
    }
  };

  archiveProject = async (projectId: string, unarchive: boolean = false) => {
    // Optimistically update local state
    if (unarchive) {
      // Move from archived to all projects
      const projectToUnarchive = this.archivedProjects.find(
        (p) => p.id === projectId
      );
      if (projectToUnarchive) {
        this.archivedProjects = this.archivedProjects.filter(
          (p) => p.id !== projectId
        );
        this.allProjects = [...this.allProjects, projectToUnarchive];
        this.totalArchivedProjects = Math.max(
          0,
          this.totalArchivedProjects - 1
        );
        this.totalProjects = this.totalProjects + 1;
      }
    } else {
      // Move from all to archived projects
      const projectToArchive = this.allProjects.find((p) => p.id === projectId);
      if (projectToArchive) {
        this.allProjects = this.allProjects.filter((p) => p.id !== projectId);
        this.archivedProjects = [...this.archivedProjects, projectToArchive];
        this.totalProjects = Math.max(0, this.totalProjects - 1);
        this.totalArchivedProjects = this.totalArchivedProjects + 1;
      }
    }

    // Make API call
    const { response } = await this.apiClient.POST('/api/project/trash', {
      body: {
        project_id: projectId,
        undo_trash: unarchive,
      },
    });

    if (response.ok) {
      toast({
        title: unarchive ? 'Workspace restored' : 'Workspace trashed',
        status: 'info',
        duration: 2000,
        isClosable: true,
      });
    } else {
      // If API call fails, revert the optimistic update
      toast({
        title: unarchive
          ? 'Workspace failed to restore'
          : 'Workspace removal failed',
        status: 'error',
        duration: 2000,
        isClosable: true,
      });
      if (unarchive) {
        const projectToRevert = this.allProjects.find(
          (p) => p.id === projectId
        );
        if (projectToRevert) {
          this.allProjects = this.allProjects.filter((p) => p.id !== projectId);
          this.archivedProjects = [...this.archivedProjects, projectToRevert];
          this.totalProjects = Math.max(0, this.totalProjects - 1);
          this.totalArchivedProjects = this.totalArchivedProjects + 1;
        }
      } else {
        const projectToRevert = this.archivedProjects.find(
          (p) => p.id === projectId
        );
        if (projectToRevert) {
          this.archivedProjects = this.archivedProjects.filter(
            (p) => p.id !== projectId
          );
          this.allProjects = [...this.allProjects, projectToRevert];
          this.totalArchivedProjects = Math.max(
            0,
            this.totalArchivedProjects - 1
          );
          this.totalProjects = this.totalProjects + 1;
        }
      }
    }
  };

  loadArchivedProjects = async () => {
    this.loadingArchivedProjects = true;
    const { data } = await this.apiClient.GET('/api/project/me', {
      params: {
        query: {
          show_trashed: true,
          page: this.currentProjectPage,
          query: this.projectQuery !== '' ? this.projectQuery : undefined,
        },
      },
    });
    this.archivedProjects = data?.projects || [];
    this.totalArchivedProjects = data?.num_total_results || 0;
    this.currentProjectPage = data?.current_page || 1;
    this.archivedProjects.forEach((project) => {
      this.projectsById[project.id] = project;
    });
    this.loadingArchivedProjects = false;
  };

  loadInviteProjects = async () => {
    this.loadingInvitedProjects = true;
    const { data } = await this.apiClient.GET('/api/project/invites', {
      // params: {
      //   query: {
      //     page: this.currentProjectPage,
      //   },
      // },
    });

    this.invites = data || [];
    this.loadingInvitedProjects = false;
  };

  fetchProjectsList = async (
    page: number = 1,
    sort: string = 'created_at',
    showTrashed = false
  ) => {
    const { data } = await this.apiClient.GET('/api/project/me', {
      params: {
        query: {
          page: page,
          sort: sort,
          show_trashed: showTrashed,
        },
      },
    });

    // Update projectsById mapping
    (data?.projects || []).forEach((project) => {
      this.projectsById[project.id] = project;
    });

    return {
      projects: data?.projects || [],
      numTotalResults: data?.num_total_results || 0,
      currentPage: data?.current_page || 1,
    };
  };

  loadAllProjects = async () => {
    this.loadingAllProjects = true;
    const { data } = await this.apiClient.GET('/api/project/me', {
      params: {
        query: {
          page: this.currentProjectPage,
          query: this.projectQuery !== '' ? this.projectQuery : undefined,
        },
      },
    });
    this.allProjects = data?.projects || [];
    this.totalProjects = data?.num_total_results || 0;
    this.currentProjectPage = data?.current_page || 1;
    this.allProjects.forEach((project) => {
      this.projectsById[project.id] = project;
    });
    this.loadingAllProjects = false;
  };

  loadClips = async () => {
    this.loadingProjectClips = true;
    const filtersKey = JSON.stringify(this.filters);

    try {
      const { data } = await this.apiClient.GET('/api/project/{project_id}', {
        params: {
          path: {
            project_id: this.currentProjectId,
          },
          query: {
            ...(this.filters.liked ? { is_liked: true } : {}),
            ...(this.filters.hide_disliked ? { hide_disliked: true } : {}),
            ...(this.filters.hide_gen_stems ? { hide_gen_stems: true } : {}),
            ...(this.filters.public ? { is_public: true } : {}),
            ...(this.filters.is_suno_short ? { is_suno_short: true } : {}),
            ...(this.filters.full_song ? { is_full_song: true } : {}),
            ...(this.filters.is_cover ? { is_cover: true } : {}),
            ...(this.filters.is_infill ? { is_infill: true } : {}),
            ...(this.filters.is_gen_stem ? { is_gen_stem: true } : {}),
            ...(this.filters.is_upsample ? { is_upsample: true } : {}),
            ...(this.filters.is_extend ? { is_extend: true } : {}),
            ...(this.filters.is_persona ? { is_persona: true } : {}),
            ...(this.filters.is_uploaded_audio
              ? { is_uploaded_audio: true }
              : {}),
            ...(this.filters.hide_studio_clips
              ? { hide_studio_clips: this.filters.hide_studio_clips }
              : {}),
            page: this.filters.page,
            query: this.filters.query !== '' ? this.filters.query : undefined,
          },
        },
      });

      // Fail the call if project is not found or is trashed
      if (!data) {
        this.loadingProjectClips = false;
        return { failed: true };
      } else if (data.is_trashed) {
        this.loadingProjectClips = false;
        return { failed: true };
      }

      const clipsData = data.project_clips;
      const pinnedClipsData = data.pinned_clips || [];
      const clips = clipsData.map((clip: ProjectClip) => clip.clip);
      const pinnedClips = pinnedClipsData.map((clip: ProjectClip) => clip.clip);

      this.root.clips.updateClips(clips);
      this.root.clips.updateClips(pinnedClips);

      // Queue any non-terminal clips for polling
      clips.forEach((clip) => {
        this.queueProjectClipToPoll(clip);
      });

      pinnedClips.forEach((clip) => {
        this.queueProjectClipToPoll(clip);
        this.root.clips.addClip(clip);
      });

      this.numTotalClips = data.clip_count;
      this.isLoaded = true;
      this.currentProject = data;
      this.currentPage = data.current_page;
      this.currentProjectName = data.name;

      // Update projectsById with the full project data
      this.projectsById[this.currentProjectId] = {
        id: data.id,
        name: data.name,
        owner: data.owner,
        shared: data.shared,
        clip_count: data.clip_count,
        last_updated_clip: data.project_clips?.[0]?.clip?.created_at || null,
        description: data.description || '',
      };

      if (filtersKey !== JSON.stringify(this.filters)) {
        this.loadingProjectClips = false;
        return;
      }
      this.clipIds = clips.map((clip) => clip.id);
      this.pinnedClipIds = pinnedClips.map((clip) => clip.id);
      this.root.clips.clipIds = clips.map((clip) => clip.id);
    } catch (error) {
      console.error('Error in loadClips:', error);
    } finally {
      this.loadingProjectClips = false;
    }
  };

  get currentClips() {
    if (!this.isLoaded) return [];
    return this.clipIds
      .map((id) => this.root.clips.clipById[id])
      .filter((clip) => clip?.status !== 'error');
  }

  get pinnedClips() {
    if (!this.isLoaded) return [];
    return this.pinnedClipIds
      ?.map((id) => this.root.clips.clipById[id])
      .filter((clip) => clip?.status !== 'error')
      .sort((a, b) => {
        // Sort by created_at in descending order (newest first)
        return (
          new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
        );
      });
  }

  isPinned = (clipId: string) => {
    return this.pinnedClipIds?.includes(clipId);
  };

  getAuraImageForProject = (projectId: string): string => {
    const hash = projectId.split('-')[0];
    let auraNumber = (parseInt(hash, 16) % 16) + 1;

    if (auraNumber >= 9) {
      auraNumber += 1;
    }
    if (auraNumber >= 15) {
      auraNumber += 1;
    }

    return `https://cdn-o.suno.com/auras/Aura-${auraNumber.toString().padStart(2, '0')}.jpg`;
  };

  setHasSetProjectId = () => {
    if (this.root.isLocalStorageAvailable) {
      setInLocalStorage('hasSetProjectId', 'true');
    }
  };

  getHasSetProjectId = (): boolean => {
    if (!this.root.isLocalStorageAvailable) {
      return false;
    }
    return loadFromLocalStorage('hasSetProjectId') === 'true';
  };

  loadLastSelectedProjectId = async (
    lazyLoad: boolean = false,
    basePath: string = '/create'
  ) => {
    // Only load from localStorage if available
    const userId = await this.waitForUserId();
    if (storageAvailable('localStorage') && userId) {
      const lastSelectedProjectId = loadFromLocalStorage(
        `lastSelectedProjectId-${userId}`
      );
      if (lastSelectedProjectId) {
        this.setUserSelectedProjectId(lastSelectedProjectId, null);
        if (!lazyLoad && typeof window !== 'undefined') {
          const urlParams = new URLSearchParams(window.location.search);
          urlParams.set('wid', lastSelectedProjectId);
          window.history.pushState(
            null,
            '',
            `${basePath}?${urlParams.toString()}`
          );
        }
        return lastSelectedProjectId;
      }
    }
  };

  lazyLoadLastSelectedProjectId = async () => {
    // Check if window is defined (client-side)
    if (typeof window !== 'undefined') {
      const urlParams = new URLSearchParams(window.location.search);
      const widFromUrl = urlParams.get('wid');

      if (widFromUrl) {
        await this.setUserSelectedProjectId(widFromUrl, null);
      } else {
        await this.loadLastSelectedProjectId(true);
      }
    }
  };

  isUsingDefaultFilters = (): boolean => {
    return (
      !this.filters.liked &&
      !this.filters.public &&
      this.filters.hide_disliked &&
      !this.filters.hide_gen_stems &&
      !this.filters.full_song &&
      !this.filters.is_suno_short &&
      !this.filters.is_cover &&
      !this.filters.is_upsample &&
      !this.filters.is_extend &&
      !this.filters.is_persona &&
      !this.filters.is_uploaded_audio &&
      this.filters.page === 1 &&
      this.filters.query === ''
    );
  };

  queueProjectClipToPoll = (clip: Clip) => {
    if (clip.status === 'submitted') {
      this.root.clips.runningRequests.add(clip.id);
      this.root.clips.expectCreditDeductionForClip(clip);
    } else if (
      clip.status &&
      clip.status !== 'complete' &&
      clip.status !== 'error'
    ) {
      this.root.clips.runningRequests.add(clip.id);
    }
  };

  checkAndUpdateProjectState = (projectId: string) => {
    // Don't update state if on edit page
    if (typeof window === 'undefined') {
      return false;
    }
    if (window.location.pathname.startsWith('/edit')) {
      return false;
    }

    const currentUrlParams = new URLSearchParams(window.location.search);
    const urlProjectId = currentUrlParams.get('wid');

    // Check if project needs to be updated
    if (
      projectId !== this.userSelectedProjectId ||
      projectId !== urlProjectId
    ) {
      // Update project state
      this.setUserSelectedProjectId(projectId, null);

      // Update URL
      window.history.pushState(null, '', `/create?wid=${projectId}`);

      return true; // State was updated
    }

    return false; // No update needed
  };

  moveClipsToProject = async (
    clips: Clip[],
    targetProjectId: string,
    sourceProjectId: string,
    isInCreate?: boolean
  ) => {
    if (targetProjectId === DEFAULT_PROJECT_ID && isInCreate) {
      // If moving to default project, just remove from current project
      return this.removeClipsFromProject(clips, sourceProjectId);
    }

    const { response } = await this.apiClient.POST(
      '/api/project/{project_id}/clips',
      {
        params: {
          path: {
            project_id: sourceProjectId,
          },
        },
        body: {
          update_type: 'move',
          metadata: {
            clip_ids: clips.map((clip) => clip.id),
            target_project_id: targetProjectId,
          },
        },
      }
    );

    if (response.ok) {
      invalidateWorkspaceQueries(sourceProjectId);
      invalidateWorkspaceQueries(targetProjectId);

      // Update local state
      const clipIdsToMove = new Set(clips.map((clip) => clip.id));
      this.clipIds = this.clipIds.filter((id) => !clipIdsToMove.has(id));
      this.numTotalClips = Math.max(0, this.numTotalClips - clips.length);

      // Reload clips if needed
      if (
        this.clipIds.length <= DEFAULT_PAGE_SIZE / 2 &&
        this.numTotalClips > DEFAULT_PAGE_SIZE
      ) {
        this.loadClips();
      }

      const targetProject = this.projectsById[targetProjectId];
      const workspaceName = targetProject
        ? getProjectName(targetProject as any as Project)
        : 'Workspace';
      const toastTitle = `Moved clips to "${workspaceName}"`;

      toast({
        title: toastTitle,
        status: 'info',
        duration: 2000,
        isClosable: true,
      });
      //don't need makeWorkspaceChange here because we are moving clips to a different project
      if (
        targetProjectId !== DEFAULT_PROJECT_ID &&
        (targetProject as ProjectMetadataSchema)?.shared &&
        this.root.session.flags?.['collab-workspaces']
      ) {
        this.apiClient.POST('/api/project/{project_id}/ably-update', {
          params: {
            path: {
              project_id: targetProjectId,
            },
          },
          body: {
            update_type: 'move_clips',
          },
        });
      }
    }
  };

  silentlySyncURLWithProjectId = (projectId: string) => {
    if (projectId && typeof window !== 'undefined') {
      const url = new URL(window.location.href);
      url.searchParams.set('wid', projectId);
      window.history.replaceState(null, '', url.toString());
    }
  };

  fetchSingleProject = async (projectId: string) => {
    try {
      const { data } = await this.apiClient.GET('/api/project/{project_id}', {
        params: {
          path: {
            project_id: projectId,
          },
        },
      });

      if (data) {
        this.projectsById[projectId] = {
          id: data.id,
          name: data.name,
          owner: data.owner,
          clip_count: data.project_clips?.length || 0,
          last_updated_clip: data.project_clips?.[0]?.clip?.created_at || null,
          description: data.description || '',
          shared: data.shared,
        };
        return data;
      }
      return null;
    } catch (error) {
      console.error('Error fetching single project:', error);
      return null;
    }
  };

  getCreateURL(): string {
    if (this.currentProjectId) {
      return `/create?wid=${this.currentProjectId}`;
    }
    return '/create';
  }

  get currentProjectOwner():
    | components['schemas']['CollaboratorSchema']
    | undefined {
    // First try to get from currentProject if it exists
    if (this.currentProject?.owner) {
      return this.currentProject.owner;
    }
    if (this.currentProjectId && this.projectsById[this.currentProjectId]) {
      const project = this.projectsById[this.currentProjectId];
      if ('owner' in project) {
        return project.owner;
      }
    }

    return undefined;
  }

  getCurrentProjectCollaborators = async () => {
    if (!this.currentProjectId) {
      return null;
    }

    try {
      const { data } = await this.apiClient.GET(
        '/api/project/{project_id}/collaborators',
        {
          params: {
            path: {
              project_id: this.currentProjectId,
            },
          },
        }
      );
      if (data) {
        return data;
      }
      return null;
    } catch (error) {
      console.error('Error fetching collaborators:', error);
      return null;
    }
  };

  sendInviteToProject = async (projectId: string, userID: string) => {
    try {
      const { response, error } = await this.apiClient.POST(
        '/api/project/{project_id}/invite',
        {
          params: {
            path: {
              project_id: projectId,
            },
          },
          body: {
            invited_user_id: userID,
          },
        }
      );

      return response.ok
        ? { error: null }
        : {
            error: (error as any)?.detail || 'Unknown error occurred',
          };
    } catch (error) {
      console.error('Error sending invite:', error);
      return {
        error:
          error instanceof Error ? error.message : 'Unknown error occurred',
      };
    }
  };
  removeCollaboratorFromProject = async (projectId: string, userID: string) => {
    try {
      const { response, error } = await this.apiClient.DELETE(
        '/api/project/{project_id}/collaborators',
        {
          params: {
            path: {
              project_id: projectId,
            },
          },
          body: {
            invited_user_id: userID,
          },
        }
      );

      if (response.ok) {
        workspaceCollaborationService.kickUserFromWorkspace(userID);
      }

      return response.ok
        ? { error: null }
        : {
            error: (error as any)?.detail || 'Unknown error occurred',
          };
    } catch (error) {
      console.error('Error removing collaborator:', error);
      return {
        error:
          error instanceof Error ? error.message : 'Unknown error occurred',
      };
    }
  };
  removeSelfFromProject = async (projectId: string) => {
    try {
      const { response, error } = await this.apiClient.DELETE(
        '/api/project/{project_id}/collaborators/me',
        {
          params: {
            path: {
              project_id: projectId,
            },
          },
        }
      );

      return response.ok
        ? { error: null }
        : {
            error: (error as any)?.detail || 'Unknown error occurred',
          };
    } catch (error) {
      console.error('Error removing self from project:', error);
      return {
        error:
          error instanceof Error ? error.message : 'Unknown error occurred',
      };
    }
  };
  respondToInvite = async (
    inviteId: string,
    inviteResponse: 'accept' | 'reject'
  ) => {
    try {
      const { response, error } = await this.apiClient.PUT(
        '/api/project/invites/{invite_id}',
        {
          params: {
            path: {
              invite_id: inviteId,
            },
          },
          body: {
            status: inviteResponse,
          },
        }
      );

      return response.ok
        ? { error: null }
        : {
            error: (error as any)?.detail || 'Unknown error occurred',
          };
    } catch (error) {
      console.error('Error responding to invite:', error);
      return {
        error:
          error instanceof Error ? error.message : 'Unknown error occurred',
      };
    }
  };
}
