/**
 * Font Awesome 6 Free catalogue for the icon picker. The metadata (generated by
 * scripts/build-icons.mjs into icons-fa.json) is lazy-loaded on first use; until then the
 * `starterIcons` list serves as an instant fallback.
 */
import { starterIcons, type IconMeta } from './icons';

export type IconStyle = 'solid' | 'regular' | 'brands';
export const ICON_STYLES: IconStyle[] = ['solid', 'regular', 'brands'];

/** Compact entry as stored in icons-fa.json. */
export interface FaIcon {
  /** Icon name without the `fa-` prefix, e.g. "star". */
  n: string;
  /** Free styles the icon is available in. */
  s: string[];
  /** Search terms. */
  t?: string[];
}

/** A pickable icon: `<i class="{library} {name}">`. */
export interface IconEntry {
  name: string;
  library: string;
  label: string;
}

let cache: Promise<FaIcon[]> | null = null;
let loaded: FaIcon[] | null = null;

export function loadFaIcons(): Promise<FaIcon[]> {
  if (!cache) {
    cache = import('./icons-fa.json').then((m) => {
      loaded = (m.default ?? m) as FaIcon[];
      return loaded;
    });
  }
  return cache;
}

/** Synchronously available once loaded (null before). */
export function faIconsIfLoaded(): FaIcon[] | null {
  return loaded;
}

export function libraryClass(style: string): string {
  return `fa-${style}`;
}

export function toEntry(name: string, style: string): IconEntry {
  return { name: `fa-${name}`, library: libraryClass(style), label: name };
}

export function starterEntries(library: IconStyle | '' = ''): IconEntry[] {
  return starterIcons
    .filter((i: IconMeta) => !library || i.library === libraryClass(library))
    .map((i) => ({ name: i.name, library: i.library, label: i.name.replace(/^fa-/, '') }));
}

/**
 * Searches the catalogue by name and terms. Results are ranked: exact name, name prefix, name
 * substring, then term matches. Each style of an icon is a separate entry (filtered by `library`).
 */
export function searchIcons(list: FaIcon[], query: string, library: IconStyle | '' = '', limit = 120): IconEntry[] {
  const q = query.trim().toLowerCase();
  const words = q.split(/\s+/).filter(Boolean);
  const scored: Array<{ icon: FaIcon; score: number }> = [];
  for (const icon of list) {
    if (library && !icon.s.includes(library)) continue;
    if (!q) {
      scored.push({ icon, score: 0 });
      continue;
    }
    let score = 0;
    if (icon.n === q) score = 4;
    else if (icon.n.startsWith(q)) score = 3;
    else if (icon.n.includes(q)) score = 2;
    else if (words.every((w) => icon.n.includes(w) || icon.t?.some((t) => t.includes(w)))) score = 1;
    if (score) scored.push({ icon, score });
  }
  if (q) scored.sort((a, b) => b.score - a.score || a.icon.n.localeCompare(b.icon.n));
  const out: IconEntry[] = [];
  for (const { icon } of scored) {
    for (const style of icon.s) {
      if (library && style !== library) continue;
      out.push(toEntry(icon.n, style));
      if (out.length >= limit) return out;
    }
  }
  return out;
}

/** Counts matches without expanding styles (for "N results" hints). */
export function countIcons(list: FaIcon[], query: string, library: IconStyle | '' = ''): number {
  return searchIcons(list, query, library, Number.MAX_SAFE_INTEGER).length;
}

/** Test helper */
export function __resetIconLibrary(): void {
  cache = null;
  loaded = null;
}
