import { describe, expect, it } from "vitest";
import {
  BLOCKBREAKER_BRICK_COLORS,
  BLOCKBREAKER_DANGER_Z,
  BLOCKBREAKER_MINIMUM_ROW_INTERVAL,
  BLOCKBREAKER_PUCK_SPEED,
  BLOCKBREAKER_RESERVE_SECONDS,
  advanceBlockbreaker,
  blockbreakerClusterScore,
  blockbreakerColumnX,
  blockbreakerConnectedCluster,
  blockbreakerRowInterval,
  blockbreakerRowZ,
  blockbreakerTimeMultiplier,
  createBlockbreakerState,
  type BlockbreakerBrick,
} from "./blockbreaker";
import { HOCKEY_HEALER_GOAL_Z } from "./hockeyHealing";

describe("Blockbreaker", () => {
  it("creates deterministic five-brick rows from four colors", () => {
    const first = createBlockbreakerState(true, 12345);
    const second = createBlockbreakerState(true, 12345);
    expect(first.bricks).toHaveLength(5);
    expect(first.bricks.map((brick) => brick.color)).toEqual(second.bricks.map((brick) => brick.color));
    expect(first.bricks.every((brick) => BLOCKBREAKER_BRICK_COLORS.includes(brick.color))).toBe(true);
    expect(Math.hypot(...first.puckVelocity)).toBeCloseTo(BLOCKBREAKER_PUCK_SPEED);
  });

  it("accelerates rows by ten percent per minute with a three-second floor", () => {
    expect(blockbreakerRowInterval(0)).toBe(10);
    expect(blockbreakerRowInterval(60)).toBe(9);
    expect(blockbreakerRowInterval(600)).toBeCloseTo(3.486784401, 8);
    expect(blockbreakerRowInterval(2_000)).toBe(BLOCKBREAKER_MINIMUM_ROW_INTERVAL);
  });

  it("uses the per-brick ladder and current uncapped time multiplier", () => {
    expect(blockbreakerTimeMultiplier(0)).toBe(1);
    expect(blockbreakerTimeMultiplier(30)).toBe(1.1);
    expect(blockbreakerTimeMultiplier(60)).toBe(1.2);
    expect(blockbreakerTimeMultiplier(600)).toBe(3);
    expect(blockbreakerClusterScore(1, 0)).toBe(10);
    expect(blockbreakerClusterScore(3, 0)).toBe(60);
    expect(blockbreakerClusterScore(3, 30)).toBe(66);
    expect(blockbreakerClusterScore(3, 60)).toBe(72);
  });

  it("propagates through orthogonal matches but not diagonals", () => {
    const bricks: BlockbreakerBrick[] = [
      { id: "source", row: 1, column: 1, color: "cyan" },
      { id: "right", row: 1, column: 2, color: "cyan" },
      { id: "up", row: 0, column: 2, color: "cyan" },
      { id: "diagonal", row: 0, column: 0, color: "cyan" },
      { id: "other", row: 2, column: 1, color: "amber" },
    ];
    expect(blockbreakerConnectedCluster(bricks, bricks[0]).map((brick) => brick.id).sort()).toEqual(["right", "source", "up"]);
  });

  it("breaks the swept-hit cluster and applies score at collision time", () => {
    const state = createBlockbreakerState(true, 7);
    state.nextRowAt = 999;
    state.bricks = [
      { id: "front", row: 0, column: 2, color: "magenta" },
      { id: "behind", row: 1, column: 2, color: "magenta" },
    ];
    state.puckPosition = [blockbreakerColumnX(2), blockbreakerRowZ(0) + 2.2];
    state.puckVelocity = [0, -6.25];
    const next = advanceBlockbreaker(state, { delta: 0.4, time: 60, playerPosition: [8, 8] });
    expect(next.bricks).toEqual([]);
    expect(next.bricksBroken).toBe(2);
    expect(next.score).toBe(36);
    expect(next.puckVelocity[1]).toBeGreaterThan(0);
  });

  it("removes a breached row and keeps the run live", () => {
    let state = createBlockbreakerState(true, 8);
    state.puckVelocity = [0, 0];
    while (state.breaches === 0) {
      state = advanceBlockbreaker(state, { delta: 0.1, time: state.nextRowAt, playerPosition: [0, 8.5] });
    }
    expect(state.status).toBe("live");
    expect(state.breaches).toBe(1);
    expect(state.bricks.every((brick) => blockbreakerRowZ(brick.row) < BLOCKBREAKER_DANGER_Z)).toBe(true);
    expect(state.lostAt).toBeNull();
  });

  it("adds complete five-column rows on schedule", () => {
    const state = createBlockbreakerState(true, 81);
    state.puckVelocity = [0, 0];
    state.puckPosition = [0, 8];
    const next = advanceBlockbreaker(state, { delta: 0.1, time: 10, playerPosition: [0, 8.5] });
    expect(next.bricks).toHaveLength(10);
    expect(next.bricks.filter((brick) => brick.row === 0).map((brick) => brick.column).sort()).toEqual([0, 1, 2, 3, 4]);
    expect(next.bricks.filter((brick) => brick.row === 1).map((brick) => brick.column).sort()).toEqual([0, 1, 2, 3, 4]);
  });

  it("rebounds from side and far walls", () => {
    const side = createBlockbreakerState(true, 82);
    side.nextRowAt = 999;
    side.bricks = [];
    side.puckPosition = [9.45, 0];
    side.puckVelocity = [6.25, 0];
    const sideBounce = advanceBlockbreaker(side, { delta: 0.2, time: 1, playerPosition: [0, 8.5] });
    expect(sideBounce.puckVelocity[0]).toBeLessThan(0);

    const far = createBlockbreakerState(true, 83);
    far.nextRowAt = 999;
    far.bricks = [];
    far.puckPosition = [0, -14];
    far.puckVelocity = [0, -6.25];
    const farBounce = advanceBlockbreaker(far, { delta: 0.2, time: 1, playerPosition: [8, 8.5] });
    expect(farBounce.puckVelocity[1]).toBeGreaterThan(0);
  });

  it("uses healer position and aim for player rebounds", () => {
    const state = createBlockbreakerState(true, 84);
    state.nextRowAt = 999;
    state.bricks = [];
    state.aimDirection = [1, -1];
    state.puckPosition = [0, 7.2];
    state.puckVelocity = [0, 6.25];
    const returned = advanceBlockbreaker(state, { delta: 0.2, time: 1, playerPosition: [0, 8] });
    expect(returned.puckVelocity[0]).toBeGreaterThan(0);
    expect(returned.puckVelocity[1]).toBeLessThan(0);
  });

  it("safe re-serves one second after a missed puck without ending the run", () => {
    const state = createBlockbreakerState(true, 9);
    state.nextRowAt = 999;
    state.bricks = [];
    state.puckPosition = [0, HOCKEY_HEALER_GOAL_Z - 0.1];
    state.puckVelocity = [0, 6.25];
    const missed = advanceBlockbreaker(state, { delta: 0.1, time: 5, playerPosition: [8, 8] });
    expect(missed.status).toBe("reserving");
    expect(missed.lostAt).toBeNull();
    expect(missed.reServeAt).toBe(5 + BLOCKBREAKER_RESERVE_SECONDS);
    const served = advanceBlockbreaker(missed, { delta: 0.1, time: missed.reServeAt!, playerPosition: [8, 8] });
    expect(served.status).toBe("live");
    expect(served.puckVelocity[1]).toBeGreaterThan(0);
  });
});
