/**
 * エラー内容を選ぶ窓の絞り込み（純粋関数。指示書BE §4-1・§8）。
 * キーワードと分類は同時に効く（AND）。文字列は `t()` に通さない。
 */
import type { Lang } from "@/lib/i18n";
import type { ErrorListItem } from "@/lib/types";

export interface ErrorListCategory {
  /** 大分類の日英を組にした識別子（表示には使わない） */
  key: string;
  ja: string;
  en: string;
}

/** `<option value>` に載せるので、分類名に出ない区切りにする（NUL は値から落ちることがある） */
const CATEGORY_KEY_SEP = "\u241e";

export function errorListCategoryKey(item: ErrorListItem): string {
  return `${item.error_category_main_ja}${CATEGORY_KEY_SEP}${item.error_category_main_en}`;
}

/** 大分類を id 順に、初めて出た順で並べる */
export function errorListCategories(items: readonly ErrorListItem[]): ErrorListCategory[] {
  const seen = new Map<string, ErrorListCategory>();
  const sorted = [...items].sort((a, b) => a.id - b.id);
  for (const item of sorted) {
    const key = errorListCategoryKey(item);
    if (!seen.has(key)) {
      seen.set(key, {
        key,
        ja: item.error_category_main_ja,
        en: item.error_category_main_en,
      });
    }
  }
  return [...seen.values()];
}

/** ひらがな（ぁ U+3041 〜 ゖ U+3096）→ カタカナ（ァ U+30A1 〜 ヶ U+30F6）。差は 0x60 */
const HIRAGANA_FIRST = 0x3041;
const HIRAGANA_LAST = 0x3096;
const HIRAGANA_TO_KATAKANA = 0x60;

/**
 * キーワードと詳細の両側に掛ける正規化。
 * 1. NFKC（全角英数 → 半角、半角カナ → 全角カナ（濁点も1文字に）、全角スペース → 半角スペース）
 * 2. 小文字にする
 * 3. ひらがな → カタカナ（`ばってりー` で `バッテリー` に当てるため）
 * ★記号・空白は除かない★（AND 検索にもしない。空白は1つの文字として部分一致に入る）。
 */
export function normalizeErrorListKeyword(text: string): string {
  let out = "";
  for (const ch of text.normalize("NFKC").toLowerCase()) {
    const code = ch.codePointAt(0)!;
    out +=
      code >= HIRAGANA_FIRST && code <= HIRAGANA_LAST
        ? String.fromCodePoint(code + HIRAGANA_TO_KATAKANA)
        : ch;
  }
  return out;
}

/**
 * ★キーワードはエラー内容の詳細だけに当てる★（`error_detail_ja` と `error_detail_en` の両方。★画面の言語によらない★）。
 * ★小分類・大分類の名前には当てない★（分類は分類の選択で絞る）。
 * 部分一致。比べる前に両側を `normalizeErrorListKeyword()` に通す（全角・半角、大文字・小文字、ひらがな・カタカナを区別しない）。
 * キーワードの前後の空白（全角スペースを含む）は除く。空ならキーワードでは絞らない。
 * ★分類とは AND★ — `categoryKey` が null でなければ、その大分類の外の行はキーワードに当たっても返さない
 * （`categoryKey === null` はすべての分類）。並びは渡された順（API の id 順）のまま。
 * `lang` は今は絞り込みに使わない（呼び出し側の形を変えないために受け取る）。
 */
export function filterErrorLists(
  items: readonly ErrorListItem[],
  input: { lang: Lang; keyword: string; categoryKey: string | null }
): ErrorListItem[] {
  const keyword = normalizeErrorListKeyword(input.keyword).trim();
  return items.filter((item) => {
    if (input.categoryKey !== null && errorListCategoryKey(item) !== input.categoryKey) {
      return false;
    }
    if (keyword === "") return true;
    return [item.error_detail_ja, item.error_detail_en].some((text) =>
      normalizeErrorListKeyword(text).includes(keyword)
    );
  });
}

export interface ErrorListGroup {
  heading: string;
  items: ErrorListItem[];
}

/** 見出しは「大分類 / 小分類」。同じ見出しが続いているあいだは1つの塊にする（id 順のまま） */
export function groupErrorLists(lang: Lang, items: readonly ErrorListItem[]): ErrorListGroup[] {
  const groups: ErrorListGroup[] = [];
  for (const item of items) {
    const main = lang === "ja" ? item.error_category_main_ja : item.error_category_main_en;
    const sub = lang === "ja" ? item.error_category_sub_ja : item.error_category_sub_en;
    const heading = `${main} / ${sub}`;
    const last = groups[groups.length - 1];
    if (last && last.heading === heading) last.items.push(item);
    else groups.push({ heading, items: [item] });
  }
  return groups;
}

/** モーダル共通へ移した判定（指示書AY 段階9-2。M2）。import 元を変えないため再 export */
export { shouldCloseOnBackdrop, trapFocusIndex } from "@/lib/ui/modal";
