import { rotateTokenPathByCwSteps, type TokenPath } from './tokenPath';

/** Rotate map-normalized markers with scene previewRotationDeg (CSS rotate, y-down). */

export type PreviewRotationDeg = 0 | 90 | 180 | 270;

export function asPreviewRotationDeg(raw: unknown): PreviewRotationDeg {
  return raw === 90 || raw === 180 || raw === 270 ? raw : 0;
}

/** How many 90° clockwise steps take `from` to `to` (0..3). */
export function previewRotationStepsCw(from: PreviewRotationDeg, to: PreviewRotationDeg): number {
  return ((((to - from) / 90) % 4) + 4) % 4;
}

/**
 * One 90° clockwise step in image/AABB normalized space (origin top-left, y down).
 * Matches CSS `transform: rotate(90deg)` of the media around its center.
 */
export function rotateMapNormPointCw90(nx: number, ny: number): { nx: number; ny: number } {
  return {
    nx: clamp01(1 - ny),
    ny: clamp01(nx),
  };
}

export function rotateMapNormPointByCwSteps(
  nx: number,
  ny: number,
  steps: number,
): { nx: number; ny: number } {
  let x = nx;
  let y = ny;
  const n = ((steps % 4) + 4) % 4;
  for (let i = 0; i < n; i += 1) {
    const next = rotateMapNormPointCw90(x, y);
    x = next.nx;
    y = next.ny;
  }
  return { nx: x, ny: y };
}

function clamp01(v: number): number {
  if (!Number.isFinite(v)) return 0;
  return Math.max(0, Math.min(1, v));
}

export function rotateMapMarkersByCwSteps<T extends { nx: number; ny: number; path?: TokenPath | null }>(
  items: readonly T[],
  steps: number,
): T[] {
  const n = ((steps % 4) + 4) % 4;
  if (n === 0) {
    return items.map((item) => ({
      ...item,
      ...(item.path ? { path: rotateTokenPathByCwSteps(item.path, 0) } : null),
    }));
  }
  return items.map((item) => {
    const p = rotateMapNormPointByCwSteps(item.nx, item.ny, n);
    const next: T = { ...item, nx: p.nx, ny: p.ny };
    if (item.path) {
      (next as { path?: TokenPath | null }).path = rotateTokenPathByCwSteps(item.path, n);
    }
    return next;
  });
}

/** Non-player tokens: move with the map and keep facing relative to the art. */
export function rotateSceneTokensByCwSteps<
  T extends { nx: number; ny: number; rotationDeg: number; path?: TokenPath | null },
>(items: readonly T[], steps: number): T[] {
  const n = ((steps % 4) + 4) % 4;
  if (n === 0) {
    return items.map((item) => ({
      ...item,
      ...(item.path ? { path: rotateTokenPathByCwSteps(item.path, 0) } : null),
    }));
  }
  const delta = n * 90;
  return items.map((item) => {
    const p = rotateMapNormPointByCwSteps(item.nx, item.ny, n);
    const next: T = { ...item, nx: p.nx, ny: p.ny, rotationDeg: item.rotationDeg + delta };
    if (item.path) {
      (next as { path?: TokenPath | null }).path = rotateTokenPathByCwSteps(item.path, n);
    }
    return next;
  });
}

/**
 * Apply marker remaps when preview rotation changes.
 * Skips collections explicitly provided in the patch (caller already sent final values).
 */
export function applyPreviewRotationToSceneMarkers<
  TScene extends {
    previewRotationDeg: PreviewRotationDeg;
    tokens: readonly { nx: number; ny: number; rotationDeg: number }[];
    npcTokens: readonly { nx: number; ny: number }[];
    traps: readonly { nx: number; ny: number }[];
  },
>(
  scene: TScene,
  nextRotationDeg: PreviewRotationDeg,
  opts?: {
    tokensProvided?: boolean;
    npcTokensProvided?: boolean;
    trapsProvided?: boolean;
  },
): Pick<TScene, 'tokens' | 'npcTokens' | 'traps'> {
  const steps = previewRotationStepsCw(scene.previewRotationDeg, nextRotationDeg);
  return {
    tokens: opts?.tokensProvided
      ? (scene.tokens as TScene['tokens'])
      : (rotateSceneTokensByCwSteps(scene.tokens, steps) as TScene['tokens']),
    npcTokens: opts?.npcTokensProvided
      ? (scene.npcTokens as TScene['npcTokens'])
      : (rotateMapMarkersByCwSteps(scene.npcTokens, steps) as TScene['npcTokens']),
    traps: opts?.trapsProvided
      ? (scene.traps as TScene['traps'])
      : (rotateMapMarkersByCwSteps(scene.traps, steps) as TScene['traps']),
  };
}
