import type { ReactNode } from 'react';
import type { SystemId } from '@/lib/rules';
import { abilityModifier } from '@/lib/rules';
import { normalizeMonsterDefenses, normalizeMonsterDefensesPf2e } from '@/lib/mechanics';
import type { CombatantDamageDefenses } from '@/lib/schemas/encounter';
import type { CompendiumEntry, Monster, Spell, MagicItem } from '@/lib/compendium/types';
import {
  loadMonsters,
  loadSpells,
  loadItems,
  loadWeapons5e,
  loadArmor5e,
  loadFeats5e,
  loadConditions5e,
  loadClasses,
  loadPf2e,
  crLabel,
} from '@/lib/compendium';
import type { RulesetClass } from '@/lib/ruleset/normalize';
import { MonsterDetail } from './MonsterDetail';
import {
  Spell5eDetail,
  Item5eDetail,
  Weapon5eDetail,
  Armor5eDetail,
  Feat5eDetail,
  Condition5eDetail,
  Pf2eDetail,
  ClassDetail,
} from './details';

export type Entry = CompendiumEntry;

export interface FilterDef {
  key: string;
  label: string;
  kind: 'eq' | 'gte' | 'lte';
  get: (e: Entry) => string | number | (string | number)[] | undefined;
  options: (data: Entry[]) => { value: string; label: string }[];
}

export interface CategoryDef {
  id: string;
  system: SystemId;
  label: string;
  load: () => Promise<Entry[]>;
  searchKeys: string[];
  meta: (e: Entry) => string;
  detail: (e: Entry) => ReactNode;
  filters: FilterDef[];
  /** enables "add to character" for spells/items */
  linkAs?: 'spell' | 'item';
  /** enables "add to combat" for monsters/creatures */
  toCombatant?: (e: Entry) => { name: string; ac: number; hp: number; initBonus: number; cr?: number; level?: number; damageDefenses?: CombatantDamageDefenses };
  /** optional numeric sort axis (e.g. CR, spell level) in addition to name */
  numericSort?: { label: string; get: (e: Entry) => number };
}

export function filterMatches(def: FilterDef, entry: Entry, value: string): boolean {
  const g = def.get(entry);
  if (Array.isArray(g)) return g.map(String).includes(value);
  if (def.kind === 'eq') return String(g) === value;
  const n = Number(g);
  if (!Number.isFinite(n)) return false;
  return def.kind === 'gte' ? n >= Number(value) : n <= Number(value);
}

// ---- filter option helpers ----
function distinctStrings(data: Entry[], get: (e: Entry) => unknown): { value: string; label: string }[] {
  const set = new Set<string>();
  for (const e of data) {
    const v = get(e);
    if (Array.isArray(v)) v.forEach((x) => x && set.add(String(x)));
    else if (v !== undefined && v !== null && String(v).trim() !== '') set.add(String(v));
  }
  return [...set].sort((a, b) => a.localeCompare(b)).map((v) => ({ value: v, label: v }));
}
function numericOptions(data: Entry[], get: (e: Entry) => unknown, fmt: (n: number) => string): { value: string; label: string }[] {
  const set = new Set<number>();
  for (const e of data) {
    const v = get(e);
    if (typeof v === 'number' && Number.isFinite(v)) set.add(v);
  }
  return [...set].sort((a, b) => a - b).map((n) => ({ value: String(n), label: fmt(n) }));
}

const traitFilter = (): FilterDef => ({
  key: 'trait',
  label: 'Trait',
  kind: 'eq',
  get: (e) => e.trait as string[] | undefined,
  options: (d) => distinctStrings(d, (e) => e.trait),
});
const rarityFilter = (): FilterDef => ({
  key: 'rarity',
  label: 'Rarity',
  kind: 'eq',
  get: (e) => e.rarity as string | undefined,
  options: (d) => distinctStrings(d, (e) => e.rarity),
});
const levelMin = (): FilterDef => ({
  key: 'levelMin',
  label: 'Min level',
  kind: 'gte',
  get: (e) => e.level as number | undefined,
  options: (d) => numericOptions(d, (e) => e.level, (n) => `Lvl ≥ ${n}`),
});
const levelMax = (): FilterDef => ({
  key: 'levelMax',
  label: 'Max level',
  kind: 'lte',
  get: (e) => e.level as number | undefined,
  options: (d) => numericOptions(d, (e) => e.level, (n) => `Lvl ≤ ${n}`),
});

function pf2eMeta(e: Entry): string {
  if (typeof e.level === 'number') return `Lvl ${e.level}`;
  // action cost is the meaningful label for actions/activities
  if (e.actions) return String(e.actions);
  const rarity = e.rarity ? String(e.rarity) : '';
  return rarity && rarity !== 'common' ? rarity : '';
}

const actionCostFilter = (): FilterDef => ({
  key: 'actions',
  label: 'Action cost',
  kind: 'eq',
  get: (e) => e.actions as string | undefined,
  options: (d) => distinctStrings(d, (e) => e.actions),
});

function pf2eCategory(id: string, label: string, file: string, opts: { level?: boolean; linkAs?: 'spell' | 'item' } = {}): CategoryDef {
  const filters: FilterDef[] = [traitFilter(), rarityFilter()];
  if (opts.level !== false) filters.unshift(levelMin(), levelMax());
  return {
    id: `pf2e-${id}`,
    system: 'pf2e',
    label,
    load: () => loadPf2e(file),
    searchKeys: ['name', 'trait'],
    meta: pf2eMeta,
    detail: (e) => <Pf2eDetail entry={e} />,
    filters,
    ...(opts.linkAs ? { linkAs: opts.linkAs } : {}),
    ...(opts.level !== false ? { numericSort: { label: 'Level', get: (e: Entry) => Number(e.level) } } : {}),
  };
}

function classCategory(system: SystemId): CategoryDef {
  return {
    id: `${system}-classes`, system, label: 'Classes',
    load: () => loadClasses(system) as unknown as Promise<Entry[]>,
    searchKeys: ['name'],
    meta: (e) => { const c = e as unknown as RulesetClass; return c.system === 'pf2e' ? `${c.hitDie} HP` : `d${c.hitDie}`; },
    detail: (e) => <ClassDetail c={e as unknown as RulesetClass} />,
    filters: [],
  };
}

export const CATEGORIES: CategoryDef[] = [
  // ---------- D&D 5e ----------
  {
    id: '5e-monsters', system: '5e', label: 'Bestiary',
    load: () => loadMonsters() as unknown as Promise<Entry[]>,
    searchKeys: ['name', 'type'],
    meta: (e) => `CR ${crLabel(e as unknown as Monster)}`,
    detail: (e) => <MonsterDetail monster={e as unknown as Monster} />,
    toCombatant: (e) => {
      const m = e as unknown as Monster;
      return {
        name: m.name, ac: m.armor_class ?? 10, hp: m.hit_points ?? 1,
        initBonus: abilityModifier(m.dexterity ?? 10),
        ...(m.cr !== undefined ? { cr: m.cr } : {}),
        damageDefenses: { ...normalizeMonsterDefenses(m), resistFlat: [], weakness: [] },
      };
    },
    numericSort: { label: 'CR', get: (e) => Number(e.cr) },
    filters: [
      { key: 'type', label: 'Type', kind: 'eq', get: (e) => e.type as string, options: (d) => distinctStrings(d, (e) => e.type) },
      { key: 'crMin', label: 'Min CR', kind: 'gte', get: (e) => e.cr as number, options: (d) => numericOptions(d, (e) => e.cr, (n) => `CR ≥ ${crNum(n)}`) },
      { key: 'crMax', label: 'Max CR', kind: 'lte', get: (e) => e.cr as number, options: (d) => numericOptions(d, (e) => e.cr, (n) => `CR ≤ ${crNum(n)}`) },
    ],
  },
  classCategory('5e'),
  {
    id: '5e-spells', system: '5e', label: 'Spells', linkAs: 'spell',
    load: () => loadSpells() as unknown as Promise<Entry[]>,
    searchKeys: ['name', 'school'],
    meta: (e) => { const l = (e as unknown as Spell).level_int; return l === 0 ? 'Cantrip' : `Lv ${l ?? '?'}`; },
    detail: (e) => <Spell5eDetail entry={e as unknown as Spell} />,
    numericSort: { label: 'Level', get: (e) => Number(e.level_int) },
    filters: [
      { key: 'level', label: 'Level', kind: 'eq', get: (e) => e.level_int as number, options: (d) => numericOptions(d, (e) => e.level_int, (n) => (n === 0 ? 'Cantrip' : `Level ${n}`)) },
      { key: 'school', label: 'School', kind: 'eq', get: (e) => e.school as string, options: (d) => distinctStrings(d, (e) => e.school) },
    ],
  },
  {
    id: '5e-items', system: '5e', label: 'Magic Items', linkAs: 'item',
    load: () => loadItems() as unknown as Promise<Entry[]>,
    searchKeys: ['name', 'type'],
    meta: (e) => (e.rarity as string) ?? '',
    detail: (e) => <Item5eDetail entry={e as unknown as MagicItem} />,
    filters: [
      { key: 'rarity', label: 'Rarity', kind: 'eq', get: (e) => e.rarity as string, options: (d) => distinctStrings(d, (e) => e.rarity) },
      { key: 'type', label: 'Type', kind: 'eq', get: (e) => e.type as string, options: (d) => distinctStrings(d, (e) => e.type) },
    ],
  },
  {
    id: '5e-weapons', system: '5e', label: 'Weapons',
    load: () => loadWeapons5e() as unknown as Promise<Entry[]>,
    searchKeys: ['name'],
    meta: (e) => (e.category as string) ?? '',
    detail: (e) => <Weapon5eDetail entry={e as never} />,
    filters: [
      { key: 'category', label: 'Category', kind: 'eq', get: (e) => e.category as string, options: (d) => distinctStrings(d, (e) => e.category) },
    ],
  },
  {
    id: '5e-armor', system: '5e', label: 'Armor',
    load: () => loadArmor5e() as unknown as Promise<Entry[]>,
    searchKeys: ['name'],
    meta: (e) => (e.type as string) ?? '',
    detail: (e) => <Armor5eDetail entry={e as never} />,
    filters: [
      { key: 'type', label: 'Type', kind: 'eq', get: (e) => e.type as string, options: (d) => distinctStrings(d, (e) => e.type) },
    ],
  },
  {
    id: '5e-feats', system: '5e', label: 'Feats',
    load: () => loadFeats5e() as unknown as Promise<Entry[]>,
    searchKeys: ['name'],
    meta: () => 'Feat',
    detail: (e) => <Feat5eDetail entry={e as never} />,
    filters: [],
  },
  {
    id: '5e-conditions', system: '5e', label: 'Conditions',
    load: () => loadConditions5e() as unknown as Promise<Entry[]>,
    searchKeys: ['name'],
    meta: () => '',
    detail: (e) => <Condition5eDetail entry={e as never} />,
    filters: [],
  },

  // ---------- Pathfinder 2e ----------
  {
    ...pf2eCategory('creatures', 'Bestiary', 'creatures'),
    toCombatant: (e) => {
      const d = normalizeMonsterDefensesPf2e(e as { immunity?: unknown; resistance?: unknown; weakness?: unknown });
      const hasDef = d.immune.length || d.conditionImmune.length || d.resistFlat.length || d.weakness.length;
      return {
        name: e.name,
        ac: Number(e.ac) || 10,
        hp: Number(e.hp) || 1,
        initBonus: Number(e.perception) || 0,
        ...(e.level !== undefined ? { level: Number(e.level) } : {}),
        ...(hasDef ? { damageDefenses: { resist: [], vulnerable: [], ...d } } : {}),
      };
    },
  },
  classCategory('pf2e'),
  pf2eCategory('spells', 'Spells', 'spells', { linkAs: 'spell' }),
  pf2eCategory('feats', 'Feats', 'feats'),
  pf2eCategory('equipment', 'Equipment', 'equipment', { linkAs: 'item' }),
  pf2eCategory('weapons', 'Weapons', 'weapons', { linkAs: 'item' }),
  pf2eCategory('armor', 'Armor', 'armor', { linkAs: 'item' }),
  pf2eCategory('ancestries', 'Ancestries', 'ancestries', { level: false }),
  pf2eCategory('heritages', 'Heritages', 'heritages', { level: false }),
  pf2eCategory('backgrounds', 'Backgrounds', 'backgrounds', { level: false }),
  pf2eCategory('archetypes', 'Archetypes', 'archetypes', { level: false }),
  {
    ...pf2eCategory('actions', 'Actions', 'actions', { level: false }),
    filters: [actionCostFilter(), traitFilter()],
  },
  pf2eCategory('conditions', 'Conditions', 'conditions', { level: false }),
  pf2eCategory('deities', 'Deities', 'deities', { level: false }),
];

function crNum(n: number): string {
  if (n === 0.125) return '1/8';
  if (n === 0.25) return '1/4';
  if (n === 0.5) return '1/2';
  return String(n);
}

export function categoriesForSystem(system: SystemId): CategoryDef[] {
  return CATEGORIES.filter((c) => c.system === system);
}
