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

import type { ForeshadowView } from "@/lib/api/types";
import {
  buildRegisterRequest,
  buildTransitionRequest,
  countByStatus,
  extractReason,
  groupByStatus,
  isWindowApproaching,
  LANE_LABELS,
  nextFreeForeshadowCode,
  suggestForeshadowCode,
  validationReasonMessage,
} from "./board";

describe("suggestForeshadowCode", () => {
  it("prefers the suggestion's own code when present", () => {
    expect(suggestForeshadowCode("F-007", 3)).toBe("F-007");
    expect(suggestForeshadowCode("  F-008  ", 3)).toBe("F-008");
  });

  it("falls back to FS-<2-digit index> when code is empty/null", () => {
    expect(suggestForeshadowCode(null, 0)).toBe("FS-01");
    expect(suggestForeshadowCode("", 4)).toBe("FS-05");
    expect(suggestForeshadowCode("   ", 11)).toBe("FS-12");
  });
});

describe("nextFreeForeshadowCode", () => {
  it("keeps the preferred code when it is free", () => {
    expect(nextFreeForeshadowCode("FS-04", new Set(["FS-01", "FS-02"]))).toBe(
      "FS-04",
    );
  });

  it("skips taken codes and returns the first free FS-NN", () => {
    const taken = new Set(["FS-01", "FS-02", "FS-03", "FS-05", "FS-OLD"]);
    // preferred FS-01 已占 → 取首个空闲 FS-04
    expect(nextFreeForeshadowCode("FS-01", taken)).toBe("FS-04");
  });

  it("returns FS-01 when nothing is taken", () => {
    expect(nextFreeForeshadowCode("FS-01", new Set())).toBe("FS-01");
  });
});

const view = (over: Partial<ForeshadowView>): ForeshadowView => ({
  code: "F-001",
  title: "线索",
  status: "OPEN",
  ...over,
});

describe("groupByStatus", () => {
  it("splits items into four lanes preserving order", () => {
    const lanes = groupByStatus([
      view({ code: "F-001", status: "OPEN" }),
      view({ code: "F-002", status: "OVERDUE" }),
      view({ code: "F-003", status: "OPEN" }),
      view({ code: "F-004", status: "CLOSED" }),
      view({ code: "F-005", status: "PARTIAL" }),
    ]);
    expect(lanes.OPEN.map((v) => v.code)).toEqual(["F-001", "F-003"]);
    expect(lanes.OVERDUE.map((v) => v.code)).toEqual(["F-002"]);
    expect(lanes.CLOSED.map((v) => v.code)).toEqual(["F-004"]);
    expect(lanes.PARTIAL.map((v) => v.code)).toEqual(["F-005"]);
  });

  it("falls back unknown status to OPEN and handles undefined", () => {
    const lanes = groupByStatus([view({ status: "WEIRD" })]);
    expect(lanes.OPEN).toHaveLength(1);
    expect(groupByStatus(undefined).OPEN).toEqual([]);
  });
});

describe("countByStatus", () => {
  it("counts items in the same lane model used by the board", () => {
    expect(
      countByStatus([
        view({ status: "OPEN" }),
        view({ status: "OPEN" }),
        view({ status: "PARTIAL" }),
        view({ status: "CLOSED" }),
        view({ status: "OVERDUE" }),
        view({ status: "WEIRD" }),
      ]),
    ).toEqual({ OPEN: 3, PARTIAL: 1, CLOSED: 1, OVERDUE: 1 });
  });
});

describe("LANE_LABELS", () => {
  it("keeps author-facing labels distinct from status codes", () => {
    expect(LANE_LABELS).toEqual({
      OPEN: "待推进",
      PARTIAL: "推进中",
      CLOSED: "已回收",
      OVERDUE: "已逾期",
    });
  });
});

describe("isWindowApproaching", () => {
  it("is true when current chapter is inside [from,to] for OPEN/PARTIAL", () => {
    const f = view({
      status: "PARTIAL",
      expected_close_from: 40,
      expected_close_to: 60,
    });
    expect(isWindowApproaching(f, 50)).toBe(true);
    expect(isWindowApproaching(f, 39)).toBe(false);
    expect(isWindowApproaching(f, 61)).toBe(false);
  });

  it("never flags CLOSED/OVERDUE or items without a window", () => {
    expect(
      isWindowApproaching(
        view({ status: "CLOSED", expected_close_to: 60 }),
        50,
      ),
    ).toBe(false);
    expect(isWindowApproaching(view({ status: "OPEN" }), 50)).toBe(false);
  });
});

describe("buildRegisterRequest", () => {
  it("trims and omits empty optionals", () => {
    expect(
      buildRegisterRequest({
        projectId: "p1",
        code: "  F-007 ",
        title: " 旧誓约 ",
        content: "  ",
        expectedCloseTo: 110,
      }),
    ).toEqual({ code: "F-007", title: "旧誓约", expected_close_to: 110 });
  });

  it("keeps numeric and non-empty string optionals", () => {
    expect(
      buildRegisterRequest({
        projectId: "p1",
        code: "F-1",
        title: "t",
        plantedAt: 8,
        content: "线索",
        expectedCloseFrom: 40,
        importance: "主线",
      }),
    ).toEqual({
      code: "F-1",
      title: "t",
      planted_at: 8,
      content: "线索",
      expected_close_from: 40,
      importance: "主线",
    });
  });
});

describe("buildTransitionRequest", () => {
  it("includes only provided fields", () => {
    expect(buildTransitionRequest({ projectId: "p1", toStatus: "PARTIAL" })).toEqual({
      to_status: "PARTIAL",
    });
    expect(
      buildTransitionRequest({
        projectId: "p1",
        progressEntry: { chapter: 23, note: "发光" },
      }),
    ).toEqual({ progress_entry: { chapter: 23, note: "发光" } });
    expect(buildTransitionRequest({ projectId: "p1" })).toEqual({});
    expect(
      buildTransitionRequest({ projectId: "p1", progressEntry: {} }),
    ).toEqual({});
  });
});

describe("validationReasonMessage / extractReason", () => {
  it("maps known reasons to friendly text", () => {
    expect(validationReasonMessage("duplicate")).toContain("已存在");
    expect(validationReasonMessage("invalid_transition")).toContain("终态");
    expect(validationReasonMessage("empty_update")).toContain("进展");
    expect(validationReasonMessage("invalid_status")).toContain("筛选");
    expect(validationReasonMessage(undefined)).toContain("校验");
  });

  it("extracts details.reason from envelope", () => {
    expect(
      extractReason({ error: { details: { reason: "duplicate" } } }),
    ).toBe("duplicate");
    expect(extractReason({ error: { details: null } })).toBeUndefined();
    expect(extractReason("oops")).toBeUndefined();
  });
});
