import { useCoreServices } from '@learnitive/context';
import type { ProjectStorageMutationEvent } from '@learnitive/service-core';
import type { RootEmitter } from '@learnitive/types';
import { WsPath } from '@learnitive/ws-path';
import React from 'react';
import { interactiveDocSaveStatus } from '../interactive-content/save-status';
import {
  PROJECT_STORAGE_MUTATION_EVENT,
  ProjectMirrorService,
} from './project-mirror-service';

export function ProjectMirrorCoordinator({ rootEmitter }: { rootEmitter: RootEmitter }) {
  const core = useCoreServices();
  const serviceRef = React.useRef<ProjectMirrorService>();
  const timersRef = React.useRef(new Map<string, ReturnType<typeof setTimeout>>());
  const lastFullSyncRef = React.useRef(0);
  const hasUnsavedEditorWork = React.useCallback(() => (
    core.editorEngine.hasPendingOrFailedSave()
    || core.latexSource.hasPendingOrFailedSave()
    || interactiveDocSaveStatus.hasPendingOrFailedSave()
  ), [core.editorEngine, core.latexSource]);

  React.useEffect(() => {
    const service = new ProjectMirrorService({
      workspaceOps: core.workspaceOps,
      projectStorage: core.projectStorage,
      workspaceState: core.workspaceState,
    });
    serviceRef.current = service;
    void service.getSnapshot().then((snapshot) => {
      if (snapshot.connected && snapshot.status !== 'permission-required' && !hasUnsavedEditorWork()) {
        void service.syncAll().catch(() => undefined);
      }
    });
    return () => {
      serviceRef.current = undefined;
      for (const timer of timersRef.current.values()) clearTimeout(timer);
      timersRef.current.clear();
    };
  }, [core.projectStorage, core.workspaceOps, core.workspaceState, hasUnsavedEditorWork]);

  React.useEffect(() => {
    const controller = new AbortController();
    const schedule = (wsName: string) => {
      if (!wsName) return;
      const previous = timersRef.current.get(wsName);
      if (previous) clearTimeout(previous);
      const timer = setTimeout(() => {
        timersRef.current.delete(wsName);
        void serviceRef.current?.syncProject(wsName).catch(() => undefined);
      }, 900);
      timersRef.current.set(wsName, timer);
    };

    rootEmitter.on('event::file:update', (payload) => {
      const parsed = WsPath.safeParse(payload.wsPath).data;
      if (!parsed) return;
      const service = serviceRef.current;
      if (service && payload.type === 'file-delete') void service.recordBrowserDeletion(parsed.wsName, parsed.path, false);
      if (service && payload.type === 'file-rename' && payload.oldWsPath) {
        const previous = WsPath.safeParse(payload.oldWsPath).data;
        if (previous) void service.recordBrowserDeletion(previous.wsName, previous.path, false);
      }
      schedule(parsed.wsName);
    }, controller.signal);

    rootEmitter.on('event::file:force-update', (payload) => {
      if (payload.wsName) schedule(payload.wsName);
      else void serviceRef.current?.syncAll().catch(() => undefined);
    }, controller.signal);

    const onMutation = (event: Event) => {
      const detail = (event as CustomEvent<ProjectStorageMutationEvent>).detail;
      if (!detail?.wsName) return;
      const service = serviceRef.current;
      if (service && detail.type === 'delete-file') void service.recordBrowserDeletion(detail.wsName, detail.path, false);
      if (service && detail.type === 'delete-directory') void service.recordBrowserDeletion(detail.wsName, detail.path, true);
      if (service && detail.type === 'move-file') void service.recordBrowserDeletion(detail.wsName, detail.from, false);
      schedule(detail.wsName);
    };

    const onFocus = () => {
      if (hasUnsavedEditorWork()) return;
      const now = Date.now();
      if (now - lastFullSyncRef.current < 15_000) return;
      lastFullSyncRef.current = now;
      void serviceRef.current?.getSnapshot().then((snapshot) => {
        if (snapshot.connected && snapshot.status !== 'permission-required') return serviceRef.current?.syncAll();
        return undefined;
      }).catch(() => undefined);
    };

    window.addEventListener(PROJECT_STORAGE_MUTATION_EVENT, onMutation);
    window.addEventListener('focus', onFocus);
    return () => {
      controller.abort();
      window.removeEventListener(PROJECT_STORAGE_MUTATION_EVENT, onMutation);
      window.removeEventListener('focus', onFocus);
      for (const timer of timersRef.current.values()) clearTimeout(timer);
      timersRef.current.clear();
    };
  }, [hasUnsavedEditorWork, rootEmitter]);

  return null;
}
