import { describe, expect, it } from "vitest";

import {
  currentOverride,
  isPinned,
  kindLabel,
  reasonLabel,
  withExcluded,
  withPinToggled,
  withRecentN,
  withRestored,
  type InjectionOverrideRequest,
  type InjectionResponse,
} from "./injection";

const REF = { kind: "character", name: "林动" } as const;

const emptyOverride = (): InjectionOverrideRequest => ({
  pinned: [],
  excluded: [],
  recent_n: 5,
});

describe("reasonLabel", () => {
  it("maps each known selection reason to a Chinese badge", () => {
    expect(reasonLabel("explicit_beat")).toBe("本章点名");
    expect(reasonLabel("main_character")).toBe("主角常驻");
    expect(reasonLabel("recent_digest")).toBe("近章出现");
    expect(reasonLabel("foreshadow_window")).toBe("伏笔窗口");
    expect(reasonLabel("author_pin")).toBe("作者置顶");
  });

  it("falls back to the raw code for an unknown reason", () => {
    expect(reasonLabel("future_reason")).toBe("future_reason");
  });
});

describe("kindLabel", () => {
  it("maps entity kinds to Chinese", () => {
    expect(kindLabel("character")).toBe("角色");
    expect(kindLabel("world_entity")).toBe("设定");
  });

  it("falls back to the raw kind when unknown", () => {
    expect(kindLabel("mystery")).toBe("mystery");
  });
});

describe("currentOverride", () => {
  it("extracts pinned/excluded/recent_n from a response", () => {
    const data = {
      project_id: "p",
      chapter_no: 1,
      selected: [],
      recent_n: 3,
      pinned: [REF],
      excluded: [{ kind: "world_entity", name: "玄铁" }],
    } as unknown as InjectionResponse;
    expect(currentOverride(data)).toEqual({
      pinned: [REF],
      excluded: [{ kind: "world_entity", name: "玄铁" }],
      recent_n: 3,
    });
  });
});

describe("withPinToggled", () => {
  it("pins an unpinned ref and removes it from excluded", () => {
    const next = withPinToggled(withExcluded(emptyOverride(), REF), REF);
    expect(isPinned(next, REF)).toBe(true);
    expect(next.excluded).toEqual([]);
  });

  it("unpins an already-pinned ref", () => {
    const pinned = withPinToggled(emptyOverride(), REF);
    const next = withPinToggled(pinned, REF);
    expect(isPinned(next, REF)).toBe(false);
  });
});

describe("withExcluded / withRestored", () => {
  it("excludes a ref, removing it from pinned, then restores it", () => {
    const pinnedThenExcluded = withExcluded(withPinToggled(emptyOverride(), REF), REF);
    expect(isPinned(pinnedThenExcluded, REF)).toBe(false);
    expect(pinnedThenExcluded.excluded).toEqual([REF]);
    const restored = withRestored(pinnedThenExcluded, REF);
    expect(restored.excluded).toEqual([]);
  });

  it("is idempotent when excluding twice", () => {
    const once = withExcluded(emptyOverride(), REF);
    const twice = withExcluded(once, REF);
    expect(twice.excluded).toEqual([REF]);
  });
});

describe("withRecentN", () => {
  it("clamps below the minimum and above the maximum", () => {
    expect(withRecentN(emptyOverride(), 0).recent_n).toBe(1);
    expect(withRecentN(emptyOverride(), 999).recent_n).toBe(20);
    expect(withRecentN(emptyOverride(), 7).recent_n).toBe(7);
  });
});
