import {
  clampSceneTokenSizeN,
  type SceneTokensSessionEvent,
  type SceneTokensSessionPlacement,
  type SceneTokensSessionState,
} from '../../shared/types';
import { rotateMapNormPointByCwSteps } from '../../shared/types/scenePreviewRotation';

function emptyState(): SceneTokensSessionState {
  return {
    revision: 1,
    byPlacementId: {},
  };
}

function withMove(
  prev: SceneTokensSessionPlacement | undefined,
  nx: number,
  ny: number,
): SceneTokensSessionPlacement {
  return {
    nx,
    ny,
    ...(prev?.sizeN !== undefined ? { sizeN: prev.sizeN } : {}),
  };
}

export class SceneTokensSessionStore {
  private state: SceneTokensSessionState = emptyState();

  getState(): SceneTokensSessionState {
    return this.state;
  }

  reset(): SceneTokensSessionState {
    if (Object.keys(this.state.byPlacementId).length === 0) return this.state;
    this.state = {
      revision: this.state.revision + 1,
      byPlacementId: {},
    };
    return this.state;
  }

  /** Rotate session position overrides with the scene preview (CSS rotate steps). */
  rotateMapCwSteps(steps: number): SceneTokensSessionState {
    const n = ((steps % 4) + 4) % 4;
    if (n === 0) return this.state;
    const ids = Object.keys(this.state.byPlacementId);
    if (ids.length === 0) return this.state;
    const byPlacementId: SceneTokensSessionState['byPlacementId'] = {};
    for (const id of ids) {
      const prev = this.state.byPlacementId[id];
      if (!prev) continue;
      if (typeof prev.nx === 'number' && typeof prev.ny === 'number') {
        const next = rotateMapNormPointByCwSteps(prev.nx, prev.ny, n);
        byPlacementId[id] = { ...prev, nx: next.nx, ny: next.ny };
      } else {
        byPlacementId[id] = prev;
      }
    }
    this.state = {
      revision: this.state.revision + 1,
      byPlacementId,
    };
    return this.state;
  }

  dispatch(event: SceneTokensSessionEvent): SceneTokensSessionState {
    switch (event.kind) {
      case 'clear':
        return this.reset();
      case 'move': {
        const placementId = String(event.placementId ?? '');
        if (!placementId) return this.state;
        const nx = Math.max(0, Math.min(1, event.nx));
        const ny = Math.max(0, Math.min(1, event.ny));
        if (!Number.isFinite(nx) || !Number.isFinite(ny)) return this.state;
        const prev = this.state.byPlacementId[placementId];
        if (prev && prev.nx === nx && prev.ny === ny) return this.state;
        this.state = {
          revision: this.state.revision + 1,
          byPlacementId: {
            ...this.state.byPlacementId,
            [placementId]: withMove(prev, nx, ny),
          },
        };
        return this.state;
      }
      case 'setSize': {
        const placementId = String(event.placementId ?? '');
        if (!placementId) return this.state;
        const sizeN = clampSceneTokenSizeN(event.sizeN);
        const prev = this.state.byPlacementId[placementId];
        if (prev?.sizeN === sizeN) return this.state;
        this.state = {
          revision: this.state.revision + 1,
          byPlacementId: {
            ...this.state.byPlacementId,
            [placementId]: {
              ...(typeof prev?.nx === 'number' ? { nx: prev.nx } : {}),
              ...(typeof prev?.ny === 'number' ? { ny: prev.ny } : {}),
              sizeN,
            },
          },
        };
        return this.state;
      }
      default: {
        const _exhaustive: never = event;
        void _exhaustive;
        return this.state;
      }
    }
  }
}
