// Leveled — a tiny game-agnostic level editor.
// Adapter contract + the generic level-document model (schema v2).

export type Cell = [number, number];

/** One placed thing on the grid. The doc is the storage format. */
export interface LevelEntity {
  type: string;
  x: number;
  y: number;
  props?: Record<string, unknown>;
}

/** Generic level document (schema v2) — what gets saved/loaded/autosaved. */
export interface LevelDoc {
  version: 2;
  gameType: string;
  /** grid size in cells [w, h] */
  size: [number, number];
  entities: LevelEntity[];
  /** adapter-specific extras (e.g. which room this doc edits) */
  meta?: Record<string, unknown>;
}

/** Palette item + placement rules for one entity type. */
export interface EntityDef {
  type: string;
  label: string;
  hint: string;
  /** CSS background for the palette swatch (color or gradient) */
  icon: string;
  /** round swatch (bots / spawn points) instead of square (tiles) */
  round?: boolean;
  /** many per level vs single — placing a single moves it */
  multiple: boolean;
  /** hold-LMB drag paints this type */
  paint?: boolean;
  /** erase priority when stacked on one cell — higher goes first */
  layer: number;
  /** right-click cannot remove it (e.g. spawn: move-only) */
  permanent?: boolean;
  /** entity types evicted from the cell when this is placed */
  displaces?: string[];
  /** entity types that veto placement on their cell */
  blockedBy?: string[];
  /** snap the clicked cell before placing (e.g. pin exits to an edge) */
  snapCell?: (x: number, y: number, size: [number, number]) => Cell;
  /** initial props attached at placement */
  defaultProps?: (x: number, y: number) => Record<string, unknown> | undefined;
  /** custom erase hit test (e.g. exits hit anywhere in their column) */
  hitTest?: (e: LevelEntity, x: number, y: number) => boolean;
}

/** Doc parsing/validation spec — the adapter extends this. */
export interface DocSpec {
  gameType: string;
  minSize?: [number, number];
  maxSize: [number, number];
  entities: EntityDef[];
  /** read path for pre-v2 / legacy saves; return null if unrecognizable */
  migrate?(raw: unknown): LevelDoc | null;
}

/** What the host game gives the editor to look at / aim with. */
export interface EditorView {
  /** element the editor binds pointer gestures to (usually the game canvas) */
  element: HTMLElement;
  /** client pixel coords -> grid cell, or null when off-grid */
  pointerToCell(clientX: number, clientY: number): Cell | null;
  /** pan the edit camera by a client-pixel delta */
  pan(dxPx: number, dyPx: number): void;
  /** zoom the edit camera (wheel deltaY, + = out) */
  zoom(deltaY: number): void;
  /** frame the whole level */
  fit(): void;
}

export interface LevelVariant {
  id: string;
  label: string;
}

export interface LeveledAdapter extends DocSpec {
  schemaVersion: number;
  /** big title at the top of the palette */
  title: string;
  /** download filename for "Save Level" */
  fileName(doc: LevelDoc): string;
  /** localStorage key for autosave */
  storageKey(doc: LevelDoc): string;
  view: EditorView;
  defaultLevel(): LevelDoc;
  /** the doc changed (place/erase/load/undo) — resync game visuals */
  onChange(doc: LevelDoc): void;
  /** hand the doc to the game runtime and start playing */
  enterPlayMode(doc: LevelDoc): void;
  /** a file was loaded — adopt it (e.g. switch the active room slot) */
  adoptLoaded?(doc: LevelDoc): LevelDoc;
  /** multi-level games (room selector dropdown); omit for single-level games */
  variants?: {
    list(): LevelVariant[];
    current(): string;
    /** switch the working slot and return the doc to edit */
    select(id: string): LevelDoc;
  };
}
