/** App-local библиотека неигровых токенов (userData, не в project zip). */

import type { SceneTokenId, TokenId } from './ids';
import { asSceneTokenId, asTokenId } from './ids';
import { normalizeTokenPath, type TokenPath } from './tokenPath';

export type { SceneTokenId, TokenId };
export { asSceneTokenId, asTokenId };

export type AppToken = {
  id: TokenId;
  name: string;
  /** Относительный путь файла в каталоге `userData/tokens/`. */
  imageRelPath: string;
  /** sha256 содержимого файла для дедупа при импорте. */
  sha256: string;
};

/** Расстановка токена на карте сцены (в проекте). */
export type SceneToken = {
  id: SceneTokenId;
  tokenId: TokenId;
  nx: number;
  ny: number;
  sizeN: number;
  /** Непрерывный угол поворота в градусах. */
  rotationDeg: number;
  /** Optional movement path (editor → session playback). */
  path?: TokenPath | null;
};

export const DEFAULT_SCENE_TOKEN_SIZE_N = 0.08;
export const SCENE_TOKEN_SIZE_MIN = 0.02;
export const SCENE_TOKEN_SIZE_MAX = 0.45;

export type SceneTokensSessionPlacement = {
  nx?: number;
  ny?: number;
  /** Session-only override of map diameter. */
  sizeN?: number;
};

export type SceneTokensSessionState = {
  revision: number;
  /** Смещение позиции/размера на текущую сессию. */
  byPlacementId: Record<string, SceneTokensSessionPlacement>;
};

export type SceneTokensSessionEvent =
  | { kind: 'move'; placementId: string; nx: number; ny: number }
  | { kind: 'setSize'; placementId: string; sizeN: number }
  | { kind: 'clear' };

export function clampSceneTokenSizeN(sizeN: number): number {
  if (!Number.isFinite(sizeN)) return DEFAULT_SCENE_TOKEN_SIZE_N;
  return Math.max(SCENE_TOKEN_SIZE_MIN, Math.min(SCENE_TOKEN_SIZE_MAX, sizeN));
}

export function normalizeSceneToken(raw: unknown): SceneToken | null {
  if (!raw || typeof raw !== 'object') return null;
  const obj = raw as Partial<SceneToken>;
  if (typeof obj.id !== 'string' || !obj.id) return null;
  if (typeof obj.tokenId !== 'string' || !obj.tokenId) return null;
  const nx = typeof obj.nx === 'number' && Number.isFinite(obj.nx) ? obj.nx : null;
  const ny = typeof obj.ny === 'number' && Number.isFinite(obj.ny) ? obj.ny : null;
  if (nx === null || ny === null) return null;
  const sizeN = clampSceneTokenSizeN(typeof obj.sizeN === 'number' ? obj.sizeN : DEFAULT_SCENE_TOKEN_SIZE_N);
  const rotationDeg =
    typeof obj.rotationDeg === 'number' && Number.isFinite(obj.rotationDeg) ? obj.rotationDeg : 0;
  const path = normalizeTokenPath((obj as { path?: unknown }).path);
  return {
    id: asSceneTokenId(obj.id),
    tokenId: asTokenId(obj.tokenId),
    nx: Math.max(0, Math.min(1, nx)),
    ny: Math.max(0, Math.min(1, ny)),
    sizeN,
    rotationDeg,
    ...(path ? { path } : {}),
  };
}

export type AppTokensExportBundle = {
  tokens: AppToken[];
};
