import { describe, expect, it } from "vitest";
import { presetById } from "../creator/presets.js";
import type { CharacterRecipe } from "../creator/types.js";
import { texturePrompt } from "./textureCharacter.js";

/**
 * The prompt is the whole steering surface for the albedo pass, and it is
 * cheap to get subtly wrong in ways only a $0.05 generation would reveal.
 */

const base: CharacterRecipe = {
  id: "suit",
  name: "Broker",
  body: "male",
  height: 1.8,
  palette: {
    skin: { r: 200, g: 160, b: 130 },
    hair: { r: 40, g: 36, b: 32 },
    primary: { r: 60, g: 62, b: 68 },
    secondary: { r: 30, g: 32, b: 36 },
    accent: { r: 120, g: 30, b: 40 },
    metal: { r: 150, g: 152, b: 158 },
    leather: { r: 40, g: 30, b: 26 },
  },
  parts: {
    hair: "hair.short",
    upper: "upper.shirt",
    lower: "lower.pants",
    feet: "feet.shoes",
    accessory: "accessory.none",
  },
};

describe("texturePrompt", () => {
  it("names the garments actually equipped, not a hard-coded outfit", () => {
    const prompt = texturePrompt(base);

    expect(prompt).toContain("wearing short hair, shirt, pants and shoes");
    // The old prompt named the survivor's kit for every character.
    expect(prompt).not.toContain("cargo pants");
    expect(prompt).not.toContain("backpack");
    // Slots that are ".none" are not garments.
    expect(prompt).not.toContain("none");
  });

  it("uses readable part names rather than raw ids", () => {
    const prompt = texturePrompt({
      ...base,
      parts: { upper: "upper.labcoat", lower: "lower.cargo" },
    });

    expect(prompt).toContain("lab coat");
    expect(prompt).toContain("cargo pants");
    expect(prompt).not.toContain("upper.labcoat");
  });

  it("carries the palette so the texture matches the model's colours", () => {
    const prompt = texturePrompt(base);
    expect(prompt).toContain("skin #c8a082");
    expect(prompt).toContain("accent #781e28");
  });

  it("quotes per-part colour overrides next to the garment", () => {
    const prompt = texturePrompt({
      ...base,
      partColors: { "upper.shirt": { r: 255, g: 255, b: 255 } },
    });
    expect(prompt).toContain("shirt (#ffffff)");
  });

  it("adds wardrobe direction only when set, and marks it authoritative", () => {
    expect(texturePrompt(base)).not.toContain("WARDROBE DIRECTION");

    const steered = texturePrompt({
      ...base,
      styleNotes: "charcoal three-piece business suit, burgundy tie",
    });
    expect(steered).toContain("WARDROBE DIRECTION");
    expect(steered).toContain("charcoal three-piece business suit, burgundy tie.");
    expect(steered).toContain("authoritative");
    // Direction must land after the palette, or it reads as the thing being
    // overridden rather than the thing overriding.
    expect(steered.indexOf("WARDROBE DIRECTION")).toBeGreaterThan(steered.indexOf("Base palette"));
  });

  it("treats blank notes as absent", () => {
    expect(texturePrompt({ ...base, styleNotes: "   " })).not.toContain("WARDROBE DIRECTION");
  });

  it("changes with the notes, which is what busts the albedo cache", () => {
    const a = texturePrompt({ ...base, styleNotes: "navy suit" });
    const b = texturePrompt({ ...base, styleNotes: "charcoal suit" });
    expect(a).not.toBe(b);
  });

  it("survives a character wearing nothing", () => {
    const prompt = texturePrompt({ ...base, parts: {} });
    expect(prompt).toContain("bare skin");
  });

  it("describes the shipped business-suit preset as a suit", () => {
    const prompt = texturePrompt(presetById("civilian-m")!);
    expect(prompt).toContain("business suit");
    expect(prompt).toContain("WARDROBE DIRECTION");
  });
});
