import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import {
  replaceProjectionWorkspace,
  upsertProjectionGhost,
  upsertProjectionNote,
} from '../lib/researchProjection';
import type {
  PipelineJob,
  ResearchGhostUpdateEvent,
  ResearchJobUpdateEvent,
  ResearchNoteUpdateEvent,
  ResearchWorkspace,
  ResearchWorkspaceUpdateEvent,
  WorkspaceProjection,
} from '../types/research';
import { researchApi } from './researchApi';
import { researchQueryKeys } from './researchQueries';

const mergeJobs = (current: PipelineJob[], incoming: PipelineJob[]) => {
  const nextById = new Map(current.map((job) => [job.id, job]));
  incoming.forEach((job) => nextById.set(job.id, job));
  return [...nextById.values()].sort((left, right) =>
    right.updatedAt.localeCompare(left.updatedAt),
  );
};

const upsertWorkspace = (
  current: ResearchWorkspace[],
  workspace: ResearchWorkspace,
) => {
  const existingIndex = current.findIndex((candidate) => candidate.id === workspace.id);
  if (existingIndex === -1) {
    return [workspace, ...current];
  }
  return current.map((candidate) =>
    candidate.id === workspace.id ? workspace : candidate,
  );
};

export const syncResearchWorkspaceEvent = (
  queryClient: ReturnType<typeof useQueryClient>,
  payload: ResearchWorkspaceUpdateEvent,
) => {
  queryClient.setQueryData<ResearchWorkspace[]>(
    researchQueryKeys.workspaces,
    (current = []) => upsertWorkspace(current, payload.workspace),
  );
  queryClient.setQueryData<WorkspaceProjection | undefined>(
    researchQueryKeys.snapshot(payload.workspaceId),
    (current) => (current ? replaceProjectionWorkspace(current, payload.workspace) : current),
  );
};

export const syncResearchNoteEvent = (
  queryClient: ReturnType<typeof useQueryClient>,
  payload: ResearchNoteUpdateEvent,
) => {
  queryClient.setQueryData<WorkspaceProjection | undefined>(
    researchQueryKeys.snapshot(payload.workspaceId),
    (current) => (current ? upsertProjectionNote(current, payload.note) : current),
  );
  void queryClient.invalidateQueries({
    queryKey: researchQueryKeys.searchIdsPrefix(payload.workspaceId),
  });
  void queryClient.invalidateQueries({
    queryKey: researchQueryKeys.auditTrail(payload.note.id),
  });
};

export const syncResearchGhostEvent = (
  queryClient: ReturnType<typeof useQueryClient>,
  payload: ResearchGhostUpdateEvent,
) => {
  queryClient.setQueryData<WorkspaceProjection | undefined>(
    researchQueryKeys.snapshot(payload.workspaceId),
    (current) => (current ? upsertProjectionGhost(current, payload.ghost) : current),
  );
};

export const syncResearchJobEvent = (
  queryClient: ReturnType<typeof useQueryClient>,
  payload: ResearchJobUpdateEvent,
) => {
  queryClient.setQueryData<PipelineJob[]>(
    researchQueryKeys.jobs(payload.workspaceId),
    (current = []) => mergeJobs(current, [payload.job]),
  );

  if (
    payload.job.status === 'completed' &&
    (payload.job.jobKind === 'infer_links' ||
      payload.job.jobKind === 'evaluate_ghosts')
  ) {
    void queryClient.invalidateQueries({
      queryKey: researchQueryKeys.snapshot(payload.workspaceId),
    });
    void queryClient.invalidateQueries({
      queryKey: researchQueryKeys.jobs(payload.workspaceId),
    });
    void queryClient.invalidateQueries({
      queryKey: ['research', 'auditTrail'],
    });
  }
};

export const ResearchEventSync = () => {
  const queryClient = useQueryClient();

  useEffect(() => {
    let disposed = false;
    const unlisteners: Array<() => void> = [];

    void Promise.all([
      researchApi.listenForWorkspaceUpdates((payload) => {
        if (!disposed) {
          syncResearchWorkspaceEvent(queryClient, payload);
        }
      }),
      researchApi.listenForNoteUpdates((payload) => {
        if (!disposed) {
          syncResearchNoteEvent(queryClient, payload);
        }
      }),
      researchApi.listenForGhostUpdates((payload) => {
        if (!disposed) {
          syncResearchGhostEvent(queryClient, payload);
        }
      }),
      researchApi.listenForJobUpdates((payload) => {
        if (!disposed) {
          syncResearchJobEvent(queryClient, payload);
        }
      }),
    ]).then((listeners) => {
      if (disposed) {
        listeners.forEach((listener) => listener());
        return;
      }
      unlisteners.push(...listeners);
    });

    return () => {
      disposed = true;
      unlisteners.forEach((listener) => listener());
    };
  }, [queryClient]);

  return null;
};
