// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";

import type { Pf2eCreature } from "@initiative/domain";
import { creatureId } from "@initiative/domain";
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { Pf2eStatBlock } from "../pf2e-stat-block.js";

afterEach(cleanup);

const PERCEPTION_SENSES_REGEX = /\+2.*Darkvision/;
const SKILLS_REGEX = /Acrobatics \+5.*Stealth \+5/;
const SAVE_CONDITIONAL_REGEX = /\+12.*\+1 status to all saves vs\. magic/;
const SAVE_CONDITIONAL_ABSENT_REGEX = /status to all saves/;
const HP_DETAILS_REGEX = /115.*regeneration 20/;
const REGEN_REGEX = /regeneration/;
const ATTACK_NAME_REGEX = /Dogslicer/;
const ATTACK_DAMAGE_REGEX = /1d6 slashing/;
const SPELLCASTING_ENTRY_REGEX = /Divine Innate Spells\./;
const ABILITY_MID_NAME_REGEX = /Goblin Scuttle/;
const ABILITY_MID_DESC_REGEX = /The goblin Steps\./;
const CANTRIPS_REGEX = /Cantrips:/;
const AC_REGEX = /16/;

const GOBLIN_WARRIOR: Pf2eCreature = {
	system: "pf2e",
	id: creatureId("pathfinder-monster-core:goblin-warrior"),
	name: "Goblin Warrior",
	source: "pathfinder-monster-core",
	sourceDisplayName: "Monster Core",
	level: -1,
	traits: ["small", "goblin", "humanoid"],
	perception: 2,
	senses: "Darkvision",
	languages: "Common, Goblin",
	skills: "Acrobatics +5, Athletics +2, Nature +1, Stealth +5",
	abilityMods: { str: 0, dex: 3, con: 1, int: 0, wis: -1, cha: 1 },
	ac: 16,
	saveFort: 5,
	saveRef: 7,
	saveWill: 3,
	hp: 6,
	speed: "25 feet",
	attacks: [
		{
			name: "Dogslicer",
			activity: { number: 1, unit: "action" },
			segments: [
				{
					type: "text",
					value: "+7 (agile, backstabber, finesse), 1d6 slashing",
				},
			],
		},
	],
	abilitiesMid: [
		{
			name: "Goblin Scuttle",
			activity: { number: 1, unit: "reaction" },
			segments: [{ type: "text", value: "The goblin Steps." }],
		},
	],
};

const NAUNET: Pf2eCreature = {
	system: "pf2e",
	id: creatureId("pathfinder-monster-core-2:naunet"),
	name: "Naunet",
	source: "pathfinder-monster-core-2",
	sourceDisplayName: "Monster Core 2",
	level: 7,
	traits: ["large", "monitor", "protean"],
	perception: 14,
	senses: "Darkvision",
	languages: "Chthonian, Empyrean, Protean",
	skills:
		"Acrobatics +14, Athletics +16, Intimidation +16, Stealth +14, Survival +12",
	abilityMods: { str: 5, dex: 3, con: 5, int: 0, wis: 3, cha: 3 },
	ac: 24,
	saveFort: 18,
	saveRef: 14,
	saveWill: 12,
	saveConditional: "+1 status to all saves vs. magic",
	hp: 120,
	resistances: "Precision 5, Protean anatomy 10",
	speed: "25 feet, Fly 30 feet, Swim 25 feet (unfettered movement)",
	spellcasting: [
		{
			name: "Divine Innate Spells",
			headerText: "DC 25, attack +17",
			daily: [
				{ uses: 4, each: true, spells: ["Unfettered Movement (Constant)"] },
			],
			atWill: ["Detect Magic"],
		},
	],
};

const TROLL: Pf2eCreature = {
	system: "pf2e",
	id: creatureId("pathfinder-monster-core:forest-troll"),
	name: "Forest Troll",
	source: "pathfinder-monster-core",
	sourceDisplayName: "Monster Core",
	level: 5,
	traits: ["large", "giant", "troll"],
	perception: 11,
	senses: "Darkvision",
	languages: "Jotun",
	skills: "Athletics +12, Intimidation +12",
	abilityMods: { str: 5, dex: 2, con: 6, int: -2, wis: 0, cha: -2 },
	ac: 20,
	saveFort: 17,
	saveRef: 11,
	saveWill: 7,
	hp: 115,
	hpDetails: "regeneration 20 (deactivated by acid or fire)",
	weaknesses: "Fire 10",
	speed: "30 feet",
};

function renderStatBlock(creature: Pf2eCreature) {
	return render(<Pf2eStatBlock creature={creature} />);
}

describe("Pf2eStatBlock", () => {
	describe("header", () => {
		it("renders creature name and level", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(
				screen.getByRole("heading", { name: "Goblin Warrior" }),
			).toBeInTheDocument();
			expect(screen.getByText("Level -1")).toBeInTheDocument();
		});

		it("renders traits as tags", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Small")).toBeInTheDocument();
			expect(screen.getByText("Goblin")).toBeInTheDocument();
			expect(screen.getByText("Humanoid")).toBeInTheDocument();
		});

		it("renders source display name", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Monster Core")).toBeInTheDocument();
		});
	});

	describe("perception and senses", () => {
		it("renders perception modifier and senses", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Perception")).toBeInTheDocument();
			expect(screen.getByText(PERCEPTION_SENSES_REGEX)).toBeInTheDocument();
		});

		it("renders languages", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Languages")).toBeInTheDocument();
			expect(screen.getByText("Common, Goblin")).toBeInTheDocument();
		});

		it("renders skills", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Skills")).toBeInTheDocument();
			expect(screen.getByText(SKILLS_REGEX)).toBeInTheDocument();
		});
	});

	describe("ability modifiers", () => {
		it("renders all six ability labels", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			for (const label of ["Str", "Dex", "Con", "Int", "Wis", "Cha"]) {
				expect(screen.getByText(label)).toBeInTheDocument();
			}
		});

		it("renders positive and negative modifiers", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("+3")).toBeInTheDocument();
			expect(screen.getByText("-1")).toBeInTheDocument();
		});
	});

	describe("defenses", () => {
		it("renders AC and saves", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("AC")).toBeInTheDocument();
			expect(screen.getByText(AC_REGEX)).toBeInTheDocument();
			expect(screen.getByText("Fort")).toBeInTheDocument();
			expect(screen.getByText("Ref")).toBeInTheDocument();
			expect(screen.getByText("Will")).toBeInTheDocument();
		});

		it("renders HP", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("HP")).toBeInTheDocument();
			expect(screen.getByText("6")).toBeInTheDocument();
		});

		it("renders saveConditional inline with saves", () => {
			renderStatBlock(NAUNET);
			expect(screen.getByText(SAVE_CONDITIONAL_REGEX)).toBeInTheDocument();
		});

		it("omits saveConditional when absent", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(
				screen.queryByText(SAVE_CONDITIONAL_ABSENT_REGEX),
			).not.toBeInTheDocument();
		});

		it("renders hpDetails in parentheses after HP", () => {
			renderStatBlock(TROLL);
			expect(screen.getByText(HP_DETAILS_REGEX)).toBeInTheDocument();
		});

		it("omits hpDetails when absent", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.queryByText(REGEN_REGEX)).not.toBeInTheDocument();
		});

		it("renders resistances and weaknesses", () => {
			renderStatBlock(NAUNET);
			expect(screen.getByText("Resistances")).toBeInTheDocument();
			expect(
				screen.getByText("Precision 5, Protean anatomy 10"),
			).toBeInTheDocument();
		});
	});

	describe("abilities", () => {
		it("renders mid (defensive) abilities", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText(ABILITY_MID_NAME_REGEX)).toBeInTheDocument();
			expect(screen.getByText(ABILITY_MID_DESC_REGEX)).toBeInTheDocument();
		});
	});

	describe("speed and attacks", () => {
		it("renders speed", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText("Speed")).toBeInTheDocument();
			expect(screen.getByText("25 feet")).toBeInTheDocument();
		});

		it("renders attacks", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.getByText(ATTACK_NAME_REGEX)).toBeInTheDocument();
			expect(screen.getByText(ATTACK_DAMAGE_REGEX)).toBeInTheDocument();
		});
	});

	describe("spellcasting", () => {
		it("renders spellcasting entry with header", () => {
			renderStatBlock(NAUNET);
			expect(screen.getByText(SPELLCASTING_ENTRY_REGEX)).toBeInTheDocument();
			expect(screen.getByText("DC 25, attack +17")).toBeInTheDocument();
		});

		it("renders ranked spells", () => {
			renderStatBlock(NAUNET);
			expect(screen.getByText("Rank 4:")).toBeInTheDocument();
			expect(
				screen.getByText("Unfettered Movement (Constant)"),
			).toBeInTheDocument();
		});

		it("renders cantrips", () => {
			renderStatBlock(NAUNET);
			expect(screen.getByText("Cantrips:")).toBeInTheDocument();
			expect(screen.getByText("Detect Magic")).toBeInTheDocument();
		});

		it("omits spellcasting when absent", () => {
			renderStatBlock(GOBLIN_WARRIOR);
			expect(screen.queryByText(CANTRIPS_REGEX)).not.toBeInTheDocument();
		});
	});
});
