import { useEffect, useMemo, useState } from 'react';
import Fuse from 'fuse.js';
import type { CompendiumKind, Monster, Spell, MagicItem } from '@/lib/compendium/types';
import { loadMonsters, loadSpells, loadItems, crLabel } from '@/lib/compendium';
import { abilityModifier } from '@/lib/rules';
import { newId } from '@/lib/ids';
import { createRng } from '@/lib/rng';
import { rollDice } from '@/lib/dice/notation';
import { addCombatant } from '@/lib/combat/engine';
import { encountersRepo } from '@/lib/db/repositories';
import { useUiStore } from '@/stores/uiStore';
import { Page, PageHeader } from '@/components/ui/Page';
import { Button } from '@/components/ui/Button';
import { Input, Select } from '@/components/ui/Input';
import { cn } from '@/lib/cn';
import { MonsterDetail } from './MonsterDetail';

type AnyEntry = Monster | Spell | MagicItem;

const TABS: { kind: CompendiumKind; label: string }[] = [
  { kind: 'monsters', label: 'Bestiary' },
  { kind: 'spells', label: 'Spells' },
  { kind: 'items', label: 'Magic Items' },
];

interface Filters {
  type?: string;
  school?: string;
  level?: number;
  rarity?: string;
  crMin?: number;
  crMax?: number;
}

function distinct<T>(items: T[], get: (x: T) => string | undefined): string[] {
  return [...new Set(items.map(get).filter((v): v is string => !!v && v.trim() !== ''))].sort((a, b) =>
    a.localeCompare(b),
  );
}

function crNumLabel(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);
}

function applyFilters(kind: CompendiumKind, data: AnyEntry[], f: Filters): AnyEntry[] {
  if (kind === 'monsters') {
    return (data as Monster[]).filter((m) => {
      const cr = m.cr ?? 0;
      return (
        (!f.type || m.type === f.type) &&
        (f.crMin === undefined || cr >= f.crMin) &&
        (f.crMax === undefined || cr <= f.crMax)
      );
    });
  }
  if (kind === 'spells') {
    return (data as Spell[]).filter(
      (s) => (f.level === undefined || s.level_int === f.level) && (!f.school || s.school === f.school),
    );
  }
  return (data as MagicItem[]).filter(
    (i) => (!f.rarity || i.rarity === f.rarity) && (!f.type || i.type === f.type),
  );
}

export function CompendiumPage() {
  const [kind, setKind] = useState<CompendiumKind>('monsters');
  const [data, setData] = useState<AnyEntry[]>([]);
  const [loading, setLoading] = useState(true);
  const [query, setQuery] = useState('');
  const [filters, setFilters] = useState<Filters>({});
  const [selected, setSelected] = useState<AnyEntry | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setSelected(null);
    const loader = kind === 'monsters' ? loadMonsters : kind === 'spells' ? loadSpells : loadItems;
    void loader().then((d) => {
      if (cancelled) return;
      setData(d);
      setLoading(false);
    });
    return () => {
      cancelled = true;
    };
  }, [kind]);

  const filtered = useMemo(() => applyFilters(kind, data, filters), [kind, data, filters]);

  const fuse = useMemo(
    () => new Fuse(filtered, { keys: ['name', 'type', 'school', 'rarity'], threshold: 0.35, ignoreLocation: true }),
    [filtered],
  );

  const results = useMemo(() => {
    const list = query.trim() ? fuse.search(query.trim()).map((r) => r.item) : filtered;
    return list.slice(0, 300);
  }, [query, fuse, filtered]);

  const switchTab = (k: CompendiumKind) => {
    setKind(k);
    setQuery('');
    setFilters({});
  };

  return (
    <Page>
      <PageHeader title="Compendium" subtitle="SRD reference for D&D 5e — searchable bestiary, spells, and items." />

      <div className="mb-4 flex gap-1">
        {TABS.map((t) => (
          <button
            key={t.kind}
            onClick={() => switchTab(t.kind)}
            className={cn(
              'rounded-md px-3 py-1.5 text-sm transition-colors',
              kind === t.kind ? 'bg-accent text-accent-ink' : 'bg-elevated text-muted hover:text-ink',
            )}
          >
            {t.label}
          </button>
        ))}
      </div>

      <div className="grid gap-4 lg:grid-cols-[320px_1fr]">
        <div>
          <Input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder={`Search ${kind}…`}
            aria-label="Search compendium"
          />

          <CompendiumFilters kind={kind} data={data} filters={filters} onChange={setFilters} />

          <p className="mt-1 text-xs text-muted">
            {loading ? 'Loading…' : `${results.length}${results.length === 300 ? '+' : ''} results`}
          </p>
          <ul className="mt-2 max-h-[65vh] space-y-1 overflow-auto">
            {results.map((entry) => (
              <li key={(entry as { slug: string }).slug}>
                <button
                  onClick={() => setSelected(entry)}
                  className={cn(
                    'flex w-full items-center justify-between rounded-md border px-3 py-2 text-left text-sm',
                    selected === entry ? 'border-accent bg-elevated' : 'border-line bg-panel hover:border-accent/40',
                  )}
                >
                  <span className="truncate text-ink">{entry.name}</span>
                  <EntryMeta kind={kind} entry={entry} />
                </button>
              </li>
            ))}
          </ul>
        </div>

        <div className="rounded-lg border border-line bg-panel p-5">
          {selected ? (
            <DetailView kind={kind} entry={selected} />
          ) : (
            <p className="text-sm text-muted">Select an entry to view details.</p>
          )}
        </div>
      </div>
    </Page>
  );
}

function CompendiumFilters({
  kind,
  data,
  filters,
  onChange,
}: {
  kind: CompendiumKind;
  data: AnyEntry[];
  filters: Filters;
  onChange: (f: Filters) => void;
}) {
  const options = useMemo(() => {
    if (kind === 'monsters') {
      const m = data as Monster[];
      const crs = [...new Set(m.map((x) => x.cr).filter((c): c is number => c !== undefined))].sort((a, b) => a - b);
      return { types: distinct(m, (x) => x.type), crs };
    }
    if (kind === 'spells') {
      const s = data as Spell[];
      const levels = [...new Set(s.map((x) => x.level_int).filter((l): l is number => l !== undefined))].sort((a, b) => a - b);
      return { schools: distinct(s, (x) => x.school), levels };
    }
    const i = data as MagicItem[];
    return { rarities: distinct(i, (x) => x.rarity), itemTypes: distinct(i, (x) => x.type) };
  }, [kind, data]);

  const setKey = <K extends keyof Filters>(key: K, value: Filters[K] | undefined) => {
    const next: Filters = { ...filters };
    if (value === undefined) delete next[key];
    else next[key] = value;
    onChange(next);
  };
  const numOrUndef = (v: string) => (v === '' ? undefined : Number(v));
  const strOrUndef = (v: string) => v || undefined;
  const active = Object.values(filters).some((v) => v !== undefined);

  return (
    <div className="mt-2 flex flex-wrap items-center gap-2">
      {kind === 'monsters' && (
        <>
          <Select className="w-auto py-1 text-xs" aria-label="Filter by type" value={filters.type ?? ''} onChange={(e) => setKey('type', strOrUndef(e.target.value))}>
            <option value="">Any type</option>
            {options.types!.map((t) => <option key={t} value={t}>{t}</option>)}
          </Select>
          <Select className="w-auto py-1 text-xs" aria-label="Minimum CR" value={filters.crMin ?? ''} onChange={(e) => setKey('crMin', numOrUndef(e.target.value))}>
            <option value="">CR ≥ any</option>
            {options.crs!.map((c) => <option key={c} value={c}>CR ≥ {crNumLabel(c)}</option>)}
          </Select>
          <Select className="w-auto py-1 text-xs" aria-label="Maximum CR" value={filters.crMax ?? ''} onChange={(e) => setKey('crMax', numOrUndef(e.target.value))}>
            <option value="">CR ≤ any</option>
            {options.crs!.map((c) => <option key={c} value={c}>CR ≤ {crNumLabel(c)}</option>)}
          </Select>
        </>
      )}
      {kind === 'spells' && (
        <>
          <Select className="w-auto py-1 text-xs" aria-label="Filter by level" value={filters.level ?? ''} onChange={(e) => setKey('level', numOrUndef(e.target.value))}>
            <option value="">Any level</option>
            {options.levels!.map((l) => <option key={l} value={l}>{l === 0 ? 'Cantrip' : `Level ${l}`}</option>)}
          </Select>
          <Select className="w-auto py-1 text-xs" aria-label="Filter by school" value={filters.school ?? ''} onChange={(e) => setKey('school', strOrUndef(e.target.value))}>
            <option value="">Any school</option>
            {options.schools!.map((s) => <option key={s} value={s}>{s}</option>)}
          </Select>
        </>
      )}
      {kind === 'items' && (
        <>
          <Select className="w-auto py-1 text-xs" aria-label="Filter by rarity" value={filters.rarity ?? ''} onChange={(e) => setKey('rarity', strOrUndef(e.target.value))}>
            <option value="">Any rarity</option>
            {options.rarities!.map((r) => <option key={r} value={r}>{r}</option>)}
          </Select>
          <Select className="w-auto py-1 text-xs" aria-label="Filter by item type" value={filters.type ?? ''} onChange={(e) => setKey('type', strOrUndef(e.target.value))}>
            <option value="">Any type</option>
            {options.itemTypes!.map((t) => <option key={t} value={t}>{t}</option>)}
          </Select>
        </>
      )}
      {active && (
        <Button size="sm" variant="ghost" onClick={() => onChange({})}>Clear</Button>
      )}
    </div>
  );
}

function EntryMeta({ kind, entry }: { kind: CompendiumKind; entry: AnyEntry }) {
  if (kind === 'monsters') return <span className="text-xs text-muted">CR {crLabel(entry as Monster)}</span>;
  if (kind === 'spells') {
    const lvl = (entry as Spell).level_int;
    return <span className="text-xs text-muted">{lvl === 0 ? 'Cantrip' : `Lv ${lvl ?? '?'}`}</span>;
  }
  return <span className="text-xs text-muted">{(entry as MagicItem).rarity ?? ''}</span>;
}

function DetailView({ kind, entry }: { kind: CompendiumKind; entry: AnyEntry }) {
  if (kind === 'monsters') return <MonsterPane monster={entry as Monster} />;
  if (kind === 'spells') return <SpellPane spell={entry as Spell} />;
  return <ItemPane item={entry as MagicItem} />;
}

function MonsterPane({ monster }: { monster: Monster }) {
  const activeEncounterId = useUiStore((s) => s.activeEncounterId);
  const [msg, setMsg] = useState<string | null>(null);

  const addToCombat = async () => {
    if (!activeEncounterId) return;
    const enc = await encountersRepo.get(activeEncounterId);
    if (!enc) {
      setMsg('Open an encounter in the Combat tab first.');
      return;
    }
    const initiative = rollDice('1d20', createRng()).total + abilityModifier(monster.dexterity ?? 10);
    const hp = monster.hit_points ?? 1;
    await encountersRepo.save(
      addCombatant(enc, {
        id: newId(),
        name: monster.name,
        kind: 'monster',
        monsterRef: monster.slug,
        initiative,
        ac: monster.armor_class ?? 10,
        hp: { current: hp, max: hp, temp: 0 },
        conditions: [],
        notes: '',
      }),
    );
    setMsg(`Added to “${enc.name}” (initiative ${initiative}).`);
  };

  return (
    <div>
      <div className="mb-3 flex items-center gap-2">
        <Button
          variant="primary"
          size="sm"
          disabled={!activeEncounterId}
          onClick={addToCombat}
          title={activeEncounterId ? 'Add to the open encounter' : 'Open an encounter in Combat first'}
        >
          + Add to combat
        </Button>
        {msg && <span className="text-xs text-success" aria-live="polite">{msg}</span>}
      </div>
      <MonsterDetail monster={monster} />
    </div>
  );
}

function SpellPane({ spell: s }: { spell: Spell }) {
  return (
    <div className="space-y-3">
      <header>
        <h2 className="font-display text-2xl font-bold text-accent">{s.name}</h2>
        <p className="text-sm italic text-muted">
          {s.level_int === 0 ? `${s.school} cantrip` : `Level ${s.level_int} ${s.school ?? ''}`}
        </p>
      </header>
      <div className="grid grid-cols-2 gap-x-6 gap-y-1 text-sm text-muted">
        <span><strong className="text-ink">Casting Time</strong> {s.casting_time}</span>
        <span><strong className="text-ink">Range</strong> {s.range}</span>
        <span><strong className="text-ink">Duration</strong> {s.duration}</span>
        <span><strong className="text-ink">Components</strong> {s.components}</span>
      </div>
      <p className="whitespace-pre-wrap text-sm text-ink">{s.desc}</p>
      {s.higher_level && (
        <p className="whitespace-pre-wrap text-sm text-muted">
          <strong className="text-ink">At Higher Levels.</strong> {s.higher_level}
        </p>
      )}
    </div>
  );
}

function ItemPane({ item }: { item: MagicItem }) {
  return (
    <div className="space-y-3">
      <header>
        <h2 className="font-display text-2xl font-bold text-accent">{item.name}</h2>
        <p className="text-sm italic text-muted">
          {[item.type, item.rarity].filter(Boolean).join(', ')}
          {item.requires_attunement ? ` (requires attunement ${item.requires_attunement})` : ''}
        </p>
      </header>
      <p className="whitespace-pre-wrap text-sm text-ink">{item.desc}</p>
    </div>
  );
}
