/**
 * 選択窓の絞り込み（指示書BE §4-1・§8）。AND・大文字小文字・0件・分類の初出順。
 */
import { describe, expect, it } from "vitest";
import {
  errorListCategories,
  filterErrorLists,
  groupErrorLists,
  normalizeErrorListKeyword,
  shouldCloseOnBackdrop,
  trapFocusIndex,
} from "@/lib/errors/error-content-picker";
import type { ErrorListItem } from "@/lib/types";

function item(partial: Partial<ErrorListItem> & Pick<ErrorListItem, "id">): ErrorListItem {
  return {
    error_category_main_en: "ROBOT",
    error_category_main_ja: "清掃ロボットエラー",
    error_category_sub_en: "Safety",
    error_category_sub_ja: "安全",
    error_detail_en: "Emergency stop",
    error_detail_ja: "非常停止",
    note_required: false,
    ...partial,
  };
}

const a = item({ id: 2, error_detail_ja: "非常停止", error_detail_en: "Emergency STOP" });
const b = item({
  id: 10,
  error_category_main_ja: "制御盤エラー",
  error_category_main_en: "PANEL",
  error_category_sub_ja: "通信",
  error_category_sub_en: "Link",
  error_detail_ja: "通信断",
  error_detail_en: "Link lost",
});
const c = item({ id: 1, error_detail_ja: "ブラシ", error_detail_en: "Brush" });
const catalog = [a, b, c];

describe("filterErrorLists", () => {
  it("キーワード無し・分類無しは id 順のまま全件", () => {
    expect(filterErrorLists(catalog, { lang: "ja", keyword: "", categoryKey: null }).map((i) => i.id)).toEqual([
      2, 10, 1,
    ]);
  });

  it("キーワードは部分一致・大文字小文字を区別しない（今の言語の詳細）", () => {
    expect(
      filterErrorLists(catalog, { lang: "en", keyword: "stop", categoryKey: null }).map((i) => i.id)
    ).toEqual([2]);
  });

  it("もう一方の言語の詳細にも当たる", () => {
    expect(
      filterErrorLists(catalog, { lang: "ja", keyword: "BRUSH", categoryKey: null }).map((i) => i.id)
    ).toEqual([1]);
  });

  it("分類とキーワードは AND。外れると0件", () => {
    const key = errorListCategories(catalog).find((c) => c.ja === "制御盤エラー")!.key;
    expect(
      filterErrorLists(catalog, { lang: "ja", keyword: "非常", categoryKey: key })
    ).toEqual([]);
    expect(
      filterErrorLists(catalog, { lang: "ja", keyword: "通信", categoryKey: key }).map((i) => i.id)
    ).toEqual([10]);
  });

  it("大分類は id 順の初出（id 1 の清掃ロボットが先）", () => {
    expect(errorListCategories(catalog).map((c) => c.ja)).toEqual([
      "清掃ロボットエラー",
      "制御盤エラー",
    ]);
  });

  it("クリア（空白だけのキーワードと分類なし）は全件に戻る", () => {
    const key = errorListCategories(catalog)[0]!.key;
    expect(
      filterErrorLists(catalog, { lang: "ja", keyword: "非常", categoryKey: key }).length
    ).toBeGreaterThan(0);
    expect(
      filterErrorLists(catalog, { lang: "ja", keyword: "   ", categoryKey: null }).map((i) => i.id)
    ).toEqual([2, 10, 1]);
  });

  it("見出しは大分類 / 小分類。小分類だけでは分けない塊が続く", () => {
    const filtered = filterErrorLists([c, a], { lang: "ja", keyword: "", categoryKey: null });
    expect(groupErrorLists("ja", filtered).map((g) => g.heading)).toEqual(["清掃ロボットエラー / 安全"]);
    expect(groupErrorLists("ja", filtered)[0].items.map((i) => i.id)).toEqual([1, 2]);
  });
});

describe("filterErrorLists のキーワード対象（詳細の ja / en だけ。分類名には当てない）", () => {
  // 詳細には出ない語を小分類・大分類に入れる
  const x = item({
    id: 5,
    error_category_main_ja: "制御盤エラー",
    error_category_main_en: "PANEL ERROR",
    error_category_sub_ja: "電源",
    error_category_sub_en: "Power",
    error_detail_ja: "落ちた",
    error_detail_en: "Down",
  });
  const y = item({ id: 6, error_detail_ja: "別件", error_detail_en: "Other" });
  // 小分類が「データ」の2行。詳細に「データ」を含むのは 21 だけ
  const dataSub = item({
    id: 20,
    error_category_sub_ja: "データ",
    error_category_sub_en: "Data",
    error_detail_ja: "地図の読み込みに失敗",
    error_detail_en: "Map load failed",
  });
  const dataDetail = item({
    id: 21,
    error_category_sub_ja: "データ",
    error_category_sub_en: "Data",
    error_detail_ja: "データが壊れています",
    error_detail_en: "Corrupted",
  });
  const battery = item({ id: 30, error_detail_ja: "バッテリー残量が少ない", error_detail_en: "Low battery" });
  const can = item({ id: 31, error_detail_ja: "CAN通信 エラー 2", error_detail_en: "CAN bus error 2" });
  const list = [x, y, dataSub, dataDetail, battery, can];
  const ids = (lang: "ja" | "en", keyword: string) =>
    filterErrorLists(list, { lang, keyword, categoryKey: null }).map((i) => i.id);

  it("詳細（日本語）に部分一致で当たる", () => {
    expect(ids("ja", "落ち")).toEqual([5]);
  });

  it("JP の画面でも英語の詳細に当たり、EN の画面でも日本語の詳細に当たる", () => {
    expect(ids("ja", "down")).toEqual([5]);
    expect(ids("en", "落ちた")).toEqual([5]);
    expect(ids("en", "down")).toEqual([5]);
  });

  it("小分類・大分類の名前には当たらない（JP / EN とも）", () => {
    expect(ids("ja", "電源")).toEqual([]);
    expect(ids("en", "power")).toEqual([]);
    expect(ids("ja", "制御盤")).toEqual([]);
    expect(ids("en", "panel")).toEqual([]);
  });

  it("「データ」は小分類の名前では当たらず、詳細に「データ」を含む行だけ", () => {
    expect(ids("ja", "データ")).toEqual([21]);
    expect(ids("en", "data")).toEqual([]);
  });

  it("ひらがな・半角カナでカタカナの詳細に当たる", () => {
    expect(ids("ja", "ばってりー")).toEqual([30]);
    expect(ids("ja", "ﾊﾞｯﾃﾘｰ")).toEqual([30]);
    expect(ids("ja", "でーた")).toEqual([21]);
  });

  it("全角英数で半角の詳細に当たる（大文字・小文字も区別しない）", () => {
    expect(ids("ja", "ＣＡＮ")).toEqual([31]);
    expect(ids("ja", "ｃａｎ")).toEqual([31]);
    expect(ids("en", "ＢＵＳ　ＥＲＲＯＲ　２")).toEqual([31]);
  });

  it("全角スペースは半角スペースと同じに扱う・前後の全角スペースは除く", () => {
    expect(ids("ja", "通信　エラー")).toEqual([31]);
    expect(ids("ja", "　ばってりー　")).toEqual([30]);
    expect(ids("ja", "　　")).toEqual([5, 6, 20, 21, 30, 31]);
  });

  it("分類と AND — 選んだ大分類の外の行は、キーワードに当たっても返さない", () => {
    const panel = errorListCategories(list).find((c) => c.ja === "制御盤エラー")!.key;
    const robot = errorListCategories(list).find((c) => c.ja === "清掃ロボットエラー")!.key;
    expect(filterErrorLists(list, { lang: "ja", keyword: "ばってりー", categoryKey: panel })).toEqual([]);
    expect(
      filterErrorLists(list, { lang: "ja", keyword: "ばってりー", categoryKey: robot }).map((i) => i.id)
    ).toEqual([30]);
    expect(
      filterErrorLists(list, { lang: "ja", keyword: "down", categoryKey: panel }).map((i) => i.id)
    ).toEqual([5]);
    expect(filterErrorLists(list, { lang: "ja", keyword: "down", categoryKey: robot })).toEqual([]);
  });
});

describe("normalizeErrorListKeyword", () => {
  it.each([
    ["ばってりー", "バッテリー"],
    ["ﾊﾞｯﾃﾘｰ", "バッテリー"],
    ["ＣＡＮ１２", "can12"],
    ["a　b", "a b"],
    ["バッテリー", "バッテリー"],
  ])("%s → %s", (input, expected) => {
    expect(normalizeErrorListKeyword(input)).toBe(expected);
  });
});

describe("groupErrorLists", () => {
  it("同じ小分類でも大分類が違えば別の塊（「データ」）", () => {
    const robot = item({
      id: 20,
      error_category_main_ja: "清掃ロボットエラー",
      error_category_main_en: "ROBOT ERROR",
      error_category_sub_ja: "データ",
      error_category_sub_en: "Data",
    });
    const setting = item({
      id: 21,
      error_category_main_ja: "清掃ロボット設定エラー",
      error_category_main_en: "ROBOT SETTING ERROR",
      error_category_sub_ja: "データ",
      error_category_sub_en: "Data",
    });
    const groups = groupErrorLists("ja", [robot, setting]);
    expect(groups.map((g) => g.heading)).toEqual([
      "清掃ロボットエラー / データ",
      "清掃ロボット設定エラー / データ",
    ]);
    expect(groups.map((g) => g.items.map((i) => i.id))).toEqual([[20], [21]]);
    expect(groupErrorLists("en", [robot, setting]).map((g) => g.heading)).toEqual([
      "ROBOT ERROR / Data",
      "ROBOT SETTING ERROR / Data",
    ]);
  });
});

describe("shouldCloseOnBackdrop（背景のクリック）", () => {
  it("押したのも離したのも背景のときだけ閉じる", () => {
    expect(shouldCloseOnBackdrop(true, true)).toBe(true);
  });

  it("キーワード入力の中で押して背景で離したときは閉じない", () => {
    expect(shouldCloseOnBackdrop(false, true)).toBe(false);
  });

  it("背景で押して窓の中で離したときも閉じない", () => {
    expect(shouldCloseOnBackdrop(true, false)).toBe(false);
    expect(shouldCloseOnBackdrop(false, false)).toBe(false);
  });
});

describe("trapFocusIndex（Tab の閉じ込め）", () => {
  it("最後で Tab は最初へ", () => {
    expect(trapFocusIndex(5, 4, false)).toBe(0);
  });

  it("最初で Shift+Tab は最後へ", () => {
    expect(trapFocusIndex(5, 0, true)).toBe(4);
  });

  it("途中はブラウザの既定に任せる（null）", () => {
    expect(trapFocusIndex(5, 2, false)).toBeNull();
    expect(trapFocusIndex(5, 2, true)).toBeNull();
    expect(trapFocusIndex(5, 0, false)).toBeNull();
    expect(trapFocusIndex(5, 4, true)).toBeNull();
  });

  it("フォーカスが窓の外（-1）なら、Tab は最初・Shift+Tab は最後へ", () => {
    expect(trapFocusIndex(5, -1, false)).toBe(0);
    expect(trapFocusIndex(5, -1, true)).toBe(4);
  });

  it("絞り込みで数が変わっても、押した時点の数で折り返す", () => {
    expect(trapFocusIndex(3, 2, false)).toBe(0);
    expect(trapFocusIndex(3, 0, true)).toBe(2);
    expect(trapFocusIndex(1, 0, false)).toBe(0);
    expect(trapFocusIndex(1, 0, true)).toBe(0);
  });

  it("フォーカスできる要素が無ければ null", () => {
    expect(trapFocusIndex(0, -1, false)).toBeNull();
  });
});
