import type { Rgb } from "../image/Raster.js";
import type { CoverageBand } from "./coverage.js";
import type { MeshData } from "../mesh/MeshBuilder.js";
import type { Skeleton } from "../rig/skeleton.js";
import type { BodyStyle } from "./bodyStyle.js";
import type { HeadStyle } from "./headStyle.js";

/**
 * Modular character creator — PSX-budget outfit assembly.
 *
 * Two base bodies (male / female) share the canonical skeleton. Clothing,
 * hair and accessories are separate low-poly parts drawn in code, then merged,
 * skinned and exported as one GLB. Swap parts to mint every cast member
 * without re-running photo analysis.
 */

export type BodyType = "male" | "female";

export type { BodyStyle, HeadStyle };

/** Equipment slots. Body is always present; the rest are optional overlays. */
export type PartSlot = "hair" | "upper" | "lower" | "feet" | "accessory";

export type PartId = string;

export interface CreatorPalette {
  skin: Rgb;
  hair: Rgb;
  /** Fallback dyes when a part doesn't specify its own colour. */
  primary: Rgb;
  secondary: Rgb;
  accent: Rgb;
  metal: Rgb;
  leather: Rgb;
}

/** Context handed to every part builder. */
export interface PartContext {
  skeleton: Skeleton;
  body: BodyType;
  palette: CreatorPalette;
  /** Mass / muscle / fat sliders (ludus-style). */
  bodyStyle: BodyStyle;
  /** Face length / jaw / brow sliders. Hair and headgear fit off these. */
  headStyle: HeadStyle;
  /** Optional per-part colour override. */
  color?: Rgb;
}

export type PartBuilder = (ctx: PartContext) => MeshData;

export interface PartDefinition {
  id: PartId;
  slot: PartSlot;
  name: string;
  /** If set, only valid on this body type. */
  body?: BodyType;
  build: PartBuilder;
  /**
   * Skin this piece encloses, as bands of the body's loft parameters. The
   * assembler deletes that skin so the garment owns the form outright.
   *
   * Only claim what a *closed* shell covers. Claiming skin under an open or
   * banded piece shows the inside of the character through the gaps.
   */
  coverage?: CoverageBand[];
}

/** A complete character recipe. */
export interface CharacterRecipe {
  id: string;
  name: string;
  body: BodyType;
  height: number;
  palette: CreatorPalette;
  /**
   * Physique sliders (ludus mass / muscle / fat).
   * Omitted = average build.
   */
  bodyStyle?: BodyStyle;
  /**
   * Face sliders (length / jaw / brow) on top of the sex face base.
   * Omitted = the neutral face for this body type.
   */
  headStyle?: HeadStyle;
  /**
   * Free-text art direction for the texture pass — wardrobe, material, wear.
   * e.g. "charcoal three-piece business suit, white shirt, burgundy tie,
   * polished black oxfords".
   *
   * Steers the Grok albedo only; it cannot change the silhouette, which is
   * fixed by the equipped parts. Ignored when building vertex-colour GLBs.
   *
   * Part of the texture prompt, so editing it invalidates the cached albedo on
   * its own — no need to force a regenerate after changing it.
   */
  styleNotes?: string;
  /** Part ids keyed by slot. Missing slots = bare for that region. */
  parts: Partial<Record<PartSlot, PartId>>;
  /** Optional colour overrides keyed by part id. */
  partColors?: Record<PartId, Rgb>;
}

export interface AssembledCharacter {
  recipe: CharacterRecipe;
  skeleton: Skeleton;
  mesh: MeshData;
  glb: Uint8Array;
  stats: {
    vertices: number;
    triangles: number;
    bones: number;
    clips: number;
    /** Body triangles deleted because a garment enclosed them. */
    skinHidden: number;
    /** Body triangles kept but pushed inside a collar / cuff opening. */
    skinRecessed: number;
  };
}
