import { useEffect, useMemo, useRef, useState } from 'react';

import { ipcChannels } from '../../../shared/ipc/contracts';
import type { SceneTokensSessionEvent, SceneTokensSessionState } from '../../../shared/types';
import { getDndApi } from '../dndApi';

function applyEvent(
  prev: SceneTokensSessionState | null,
  event: SceneTokensSessionEvent,
): SceneTokensSessionState {
  const base = prev ?? { revision: 0, byPlacementId: {} };
  if (event.kind === 'clear') {
    return { revision: base.revision + 1, byPlacementId: {} };
  }
  const placementId = String(event.placementId ?? '');
  if (!placementId) return base;
  const cur = base.byPlacementId[placementId];
  if (event.kind === 'setSize') {
    return {
      revision: base.revision + 1,
      byPlacementId: {
        ...base.byPlacementId,
        [placementId]: {
          ...(typeof cur?.nx === 'number' ? { nx: cur.nx } : {}),
          ...(typeof cur?.ny === 'number' ? { ny: cur.ny } : {}),
          sizeN: event.sizeN,
        },
      },
    };
  }
  return {
    revision: base.revision + 1,
    byPlacementId: {
      ...base.byPlacementId,
      [placementId]: {
        nx: event.nx,
        ny: event.ny,
        ...(cur?.sizeN !== undefined ? { sizeN: cur.sizeN } : {}),
      },
    },
  };
}

export function useSceneTokensSession(): [
  SceneTokensSessionState | null,
  { dispatch: (event: SceneTokensSessionEvent) => Promise<void> },
] {
  const api = getDndApi();
  const [state, setState] = useState<SceneTokensSessionState | null>(null);
  const localRevisionRef = useRef(0);

  useEffect(() => {
    void api.invoke(ipcChannels.sceneTokensSession.getState, {}).then(({ state: s }) => {
      localRevisionRef.current = Math.max(localRevisionRef.current, s.revision);
      setState(s);
    });
    return api.on(ipcChannels.sceneTokensSession.stateChanged, ({ state: s }) => {
      // Не затираем более свежий optimistic-стейт устаревшим broadcast
      // (например, emit при смене сцены, пока последний move ещё в полёте).
      if (localRevisionRef.current > s.revision) return;
      localRevisionRef.current = s.revision;
      setState(s);
    });
  }, [api]);

  const apiWrap = useMemo(
    () => ({
      dispatch: async (event: SceneTokensSessionEvent) => {
        setState((prev) => {
          const next = applyEvent(prev, event);
          localRevisionRef.current = Math.max(localRevisionRef.current, next.revision);
          return next;
        });
        const res = await api.invoke(ipcChannels.sceneTokensSession.dispatch, { event });
        void res;
      },
    }),
    [api],
  );

  return [state, apiWrap];
}
