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

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

function applyEvent(
  prev: ScenePlayerTokensSessionState,
  event: ScenePlayerTokensSessionEvent,
): ScenePlayerTokensSessionState {
  if (event.kind === 'clear') {
    return { revision: prev.revision + 1, selectedPlayerIds: [], visible: false, byPlayerId: {} };
  }
  if (event.kind === 'clearPlacements') {
    return { ...prev, revision: prev.revision + 1, visible: false, byPlayerId: {} };
  }
  if (event.kind === 'setSelection') {
    const seen = new Set<string>();
    const selectedPlayerIds: string[] = [];
    for (const id of event.playerIds) {
      const s = String(id ?? '').trim();
      if (!s || seen.has(s)) continue;
      seen.add(s);
      selectedPlayerIds.push(s);
    }
    return { revision: prev.revision + 1, selectedPlayerIds, visible: false, byPlayerId: {} };
  }
  if (event.kind === 'setVisible') {
    if (event.visible && prev.selectedPlayerIds.length === 0) return prev;
    return { ...prev, revision: prev.revision + 1, visible: Boolean(event.visible) };
  }
  if (event.kind === 'show') {
    if (prev.selectedPlayerIds.length === 0) return prev;
    // placements приходят с main; локально только включаем visible после seed через broadcast
    return { ...prev, revision: prev.revision + 1, visible: true };
  }
  if (event.kind === 'seedBottom') {
    return prev;
  }
  if (event.kind === 'move') {
    const prevPlacement = prev.byPlayerId[event.playerId];
    return {
      ...prev,
      revision: prev.revision + 1,
      byPlayerId: {
        ...prev.byPlayerId,
        [event.playerId]: {
          nx: event.nx,
          ny: event.ny,
          sizeN: prevPlacement?.sizeN ?? 0.1,
        },
      },
    };
  }
  if (event.kind === 'setSize') {
    const prevPlacement = prev.byPlayerId[event.playerId];
    if (!prevPlacement) return prev;
    return {
      ...prev,
      revision: prev.revision + 1,
      byPlayerId: {
        ...prev.byPlayerId,
        [event.playerId]: { ...prevPlacement, sizeN: event.sizeN },
      },
    };
  }
  return prev;
}

export function useScenePlayerTokensSession(): [
  ScenePlayerTokensSessionState,
  { dispatch: (event: ScenePlayerTokensSessionEvent) => void },
] {
  const api = getDndApi();
  const [state, setState] = useState<ScenePlayerTokensSessionState>({
    revision: 0,
    selectedPlayerIds: [],
    visible: false,
    byPlayerId: {},
  });
  const localRevRef = useRef(0);

  useEffect(() => {
    void api.invoke(ipcChannels.scenePlayerTokensSession.getState, {}).then((res) => {
      if (res.state.revision < localRevRef.current) return;
      localRevRef.current = res.state.revision;
      setState(res.state);
    });
    return api.on(ipcChannels.scenePlayerTokensSession.stateChanged, ({ state: next }) => {
      if (next.revision < localRevRef.current) return;
      localRevRef.current = next.revision;
      setState(next);
    });
  }, [api]);

  const dispatch = useCallback(
    (event: ScenePlayerTokensSessionEvent) => {
      if (event.kind !== 'seedBottom' && event.kind !== 'show') {
        setState((prev) => {
          const next = applyEvent(prev, event);
          localRevRef.current = Math.max(localRevRef.current, next.revision);
          return next;
        });
      }
      void api.invoke(ipcChannels.scenePlayerTokensSession.dispatch, { event });
    },
    [api],
  );

  return [state, { dispatch }];
}
