import {
  emptyTokenPathSessionState,
  tokenPathKey,
  type TokenPathPlaybackEntry,
  type TokenPathSessionEvent,
  type TokenPathSessionState,
  type TokenPathTargetRef,
} from '../../shared/types/tokenPathSession';

function bump(
  state: TokenPathSessionState,
  patch: Partial<Omit<TokenPathSessionState, 'revision' | 'serverNowMs'>>,
): TokenPathSessionState {
  return {
    ...state,
    ...patch,
    revision: state.revision + 1,
    serverNowMs: Date.now(),
  };
}

export class TokenPathSessionStore {
  private state: TokenPathSessionState = emptyTokenPathSessionState();

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

  /** Refresh clock without logical change (optional heartbeat). */
  touchClock(): TokenPathSessionState {
    this.state = { ...this.state, serverNowMs: Date.now() };
    return this.state;
  }

  reset(): TokenPathSessionState {
    if (
      Object.keys(this.state.playback).length === 0 &&
      Object.keys(this.state.presentationVisible).length === 0
    ) {
      return this.state;
    }
    this.state = emptyTokenPathSessionState(this.state.revision + 1);
    return this.state;
  }

  dispatch(event: TokenPathSessionEvent): TokenPathSessionState {
    switch (event.kind) {
      case 'clear':
        return this.reset();
      case 'showPresentation': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        if (this.state.presentationVisible[key]) return this.state;
        this.state = bump(this.state, {
          presentationVisible: { ...this.state.presentationVisible, [key]: true },
        });
        return this.state;
      }
      case 'hidePresentation': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        if (!this.state.presentationVisible[key]) return this.state;
        const { [key]: _removed, ...rest } = this.state.presentationVisible;
        this.state = bump(this.state, { presentationVisible: rest });
        return this.state;
      }
      case 'stop': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        const prev = this.state.playback[key];
        if (!prev || prev.phase === 'stopped') return this.state;
        const atDist =
          typeof event.atDist === 'number' && Number.isFinite(event.atDist)
            ? Math.max(0, event.atDist)
            : prev.baseDist;
        this.state = bump(this.state, {
          playback: {
            ...this.state.playback,
            [key]: {
              ...prev,
              phase: 'stopped',
              baseDist: atDist,
              rejoinDist: null,
              segmentStartedAtMs: Date.now(),
            },
          },
        });
        return this.state;
      }
      case 'resume': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        const prev = this.state.playback[key];
        if (!prev) return this.state;
        if (prev.phase !== 'stopped' && prev.phase !== 'done') return this.state;
        const entry: TokenPathPlaybackEntry = {
          ...prev,
          phase: 'moving',
          segmentStartedAtMs: Date.now(),
          // Jump to nearest-ahead on path, then continue (v1: no off-path lerp).
          baseDist: Math.max(0, event.fromDist),
          rejoinDist: null,
          direction: 1,
        };
        this.state = bump(this.state, {
          playback: { ...this.state.playback, [key]: entry },
        });
        return this.state;
      }
      case 'resetToStart': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        const prev = this.state.playback[key];
        const entry: TokenPathPlaybackEntry = {
          kind: event.target.kind,
          placementId: event.target.placementId,
          phase: 'moving',
          segmentStartedAtMs: Date.now(),
          baseDist: 0,
          direction: 1,
          rejoinDist: null,
          durationSec: prev?.durationSec ?? 8,
          pathLength: prev?.pathLength ?? 1,
        };
        this.state = bump(this.state, {
          playback: { ...this.state.playback, [key]: entry },
        });
        return this.state;
      }
      case 'seedPlayback': {
        const e = event.entry;
        const key = tokenPathKey(e.kind, e.placementId);
        const entry: TokenPathPlaybackEntry = {
          kind: e.kind,
          placementId: e.placementId,
          phase: e.phase,
          segmentStartedAtMs: e.segmentStartedAtMs ?? Date.now(),
          baseDist: e.baseDist,
          direction: e.direction,
          rejoinDist: e.rejoinDist,
          durationSec: e.durationSec,
          pathLength: e.pathLength,
        };
        this.state = bump(this.state, {
          playback: { ...this.state.playback, [key]: entry },
        });
        return this.state;
      }
      case 'markDone': {
        const key = tokenPathKey(event.target.kind, event.target.placementId);
        const prev = this.state.playback[key];
        if (!prev || prev.phase === 'done') return this.state;
        this.state = bump(this.state, {
          playback: {
            ...this.state.playback,
            [key]: {
              ...prev,
              phase: 'done',
              baseDist: prev.pathLength,
              rejoinDist: null,
              segmentStartedAtMs: Date.now(),
            },
          },
        });
        return this.state;
      }
      default: {
        const _exhaustive: never = event;
        void _exhaustive;
        return this.state;
      }
    }
  }
}

export type { TokenPathTargetRef };
