import {
  startTransition,
  useDeferredValue,
  useEffect,
  useEffectEvent,
  useReducer,
  useRef,
} from 'react';
import {
  replaceProjectionWorkspace,
  upsertProjectionGhost,
  upsertProjectionNote,
} from '../lib/researchProjection';
import { researchBridge } from '../lib/researchBridge';
import type {
  GhostNote,
  PipelineJob,
  ResearchNote,
  ResearchWorkspace,
  WorkspaceProjection,
  WorkspaceViewKind,
} from '../types/research';

interface ProjectionState {
  projection: WorkspaceProjection | null;
  isLoading: boolean;
  isRefreshing: boolean;
  error?: string;
  jobs: Record<string, PipelineJob>;
}

type ProjectionAction =
  | { type: 'reset' }
  | { type: 'load_started'; refresh: boolean }
  | { type: 'load_succeeded'; projection: WorkspaceProjection }
  | { type: 'load_failed'; error: string }
  | { type: 'workspace_updated'; workspace: ResearchWorkspace }
  | { type: 'note_updated'; note: ResearchNote }
  | { type: 'ghost_updated'; ghost: GhostNote }
  | { type: 'job_updated'; job: PipelineJob };

const createProjectionState = (): ProjectionState => ({
  projection: null,
  isLoading: true,
  isRefreshing: false,
  error: undefined,
  jobs: {},
});

const projectionReducer = (
  state: ProjectionState,
  action: ProjectionAction,
): ProjectionState => {
  switch (action.type) {
    case 'reset':
      return createProjectionState();
    case 'load_started':
      return {
        ...state,
        isLoading: !action.refresh,
        isRefreshing: action.refresh,
        error: undefined,
      };
    case 'load_succeeded':
      return {
        ...state,
        projection: action.projection,
        isLoading: false,
        isRefreshing: false,
        error: undefined,
      };
    case 'load_failed':
      return {
        ...state,
        isLoading: false,
        isRefreshing: false,
        error: action.error,
      };
    case 'job_updated':
      return {
        ...state,
        jobs: {
          ...state.jobs,
          [action.job.id]: action.job,
        },
      };
    case 'workspace_updated':
      return state.projection
        ? {
            ...state,
            projection: replaceProjectionWorkspace(state.projection, action.workspace),
          }
        : state;
    case 'note_updated':
      return state.projection
        ? {
            ...state,
            projection: upsertProjectionNote(state.projection, action.note),
          }
        : state;
    case 'ghost_updated':
      return state.projection
        ? {
            ...state,
            projection: upsertProjectionGhost(state.projection, action.ghost),
          }
        : state;
    default:
      return state;
  }
};

export const useResearchProjection = (
  workspaceId: string | null,
  view: WorkspaceViewKind,
) => {
  const [state, dispatch] = useReducer(projectionReducer, undefined, createProjectionState);
  const deferredView = useDeferredValue(view);
  const refreshTimeoutRef = useRef<number | null>(null);
  const requestIdRef = useRef(0);
  const previousWorkspaceIdRef = useRef<string | null>(workspaceId);

  const loadProjection = useEffectEvent(async (refresh: boolean) => {
    const nextWorkspaceId = workspaceId;
    if (!nextWorkspaceId) {
      return;
    }

    const requestId = ++requestIdRef.current;
    dispatch({ type: 'load_started', refresh });
    try {
      const projection = await researchBridge.getWorkspaceProjection({
        workspaceId: nextWorkspaceId,
        view: deferredView,
      });
      if (requestId !== requestIdRef.current) {
        return;
      }
      dispatch({ type: 'load_succeeded', projection });
    } catch (loadError) {
      if (requestId !== requestIdRef.current) {
        return;
      }
      dispatch({
        type: 'load_failed',
        error: loadError instanceof Error ? loadError.message : String(loadError),
      });
    }
  });

  useEffect(() => {
    if (!workspaceId) {
      previousWorkspaceIdRef.current = null;
      requestIdRef.current += 1;
      dispatch({ type: 'reset' });
      return;
    }

    if (previousWorkspaceIdRef.current !== workspaceId) {
      previousWorkspaceIdRef.current = workspaceId;
      requestIdRef.current += 1;
      dispatch({ type: 'reset' });
    }

    void loadProjection(false);
  }, [deferredView, loadProjection, workspaceId]);

  useEffect(() => {
    return () => {
      if (refreshTimeoutRef.current != null) {
        window.clearTimeout(refreshTimeoutRef.current);
      }
    };
  }, []);

  const scheduleRefresh = useEffectEvent(() => {
    if (refreshTimeoutRef.current != null) {
      window.clearTimeout(refreshTimeoutRef.current);
    }

    refreshTimeoutRef.current = window.setTimeout(() => {
      void loadProjection(true);
      refreshTimeoutRef.current = null;
    }, 150);
  });

  useEffect(() => {
    if (!workspaceId) {
      return;
    }

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

    const handleWorkspaceUpdate = (payload: { workspace: ResearchWorkspace }) => {
      if (payload.workspace.id === workspaceId && !disposed) {
        startTransition(() => {
          dispatch({ type: 'workspace_updated', workspace: payload.workspace });
        });
      }
    };

    const handleNoteUpdate = (payload: { note: ResearchNote; workspaceId: string }) => {
      if (payload.workspaceId === workspaceId && !disposed) {
        startTransition(() => {
          dispatch({ type: 'note_updated', note: payload.note });
        });
      }
    };

    const handleGhostUpdate = (payload: { ghost: GhostNote; workspaceId: string }) => {
      if (payload.workspaceId === workspaceId && !disposed) {
        startTransition(() => {
          dispatch({ type: 'ghost_updated', ghost: payload.ghost });
        });
      }
    };

    const handleJobUpdate = (payload: { job: PipelineJob; workspaceId: string }) => {
      if (payload.workspaceId === workspaceId && !disposed) {
        startTransition(() => {
          dispatch({ type: 'job_updated', job: payload.job });
        });

        if (
          payload.job.status === 'completed' &&
          (payload.job.jobKind === 'infer_links' || payload.job.jobKind === 'evaluate_ghosts')
        ) {
          scheduleRefresh();
        }
      }
    };

    Promise.all([
      researchBridge.listenForWorkspaceUpdates(handleWorkspaceUpdate),
      researchBridge.listenForNoteUpdates(handleNoteUpdate),
      researchBridge.listenForGhostUpdates(handleGhostUpdate),
      researchBridge.listenForJobUpdates(handleJobUpdate),
    ]).then((listeners) => {
      if (!disposed) {
        unlisteners.push(...listeners);
      }
    });

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

  return {
    ...state,
    refreshProjection: () => loadProjection(true),
  };
};
