import type { EmojiEntry } from './emoji';
import type { ImagePack } from './types';

export interface Emote {
  shortcode: string;
  mxc: string;
}

export type EmoteCandidate =
  | { kind: 'emoji'; emoji: string; shortcode: string }
  | { kind: 'custom'; shortcode: string; mxc: string };

export interface EmoteQuery {
  start: number;
  query: string;
}

const SHORTCODE = /^[\w+-]+$/;

export function emoteQueryAt(text: string, caret: number): EmoteQuery | null {
  const before = text.slice(0, caret);
  const colon = before.lastIndexOf(':');
  if (colon < 0 || (colon > 0 && !/\s/.test(before[colon - 1]))) return null;
  const query = before.slice(colon + 1);
  if (query.length < 2 || query.length > 40 || !SHORTCODE.test(query)) return null;
  return { start: colon, query };
}

export function customEmotes(packs: ImagePack[]): Emote[] {
  const seen = new Map<string, Emote>();
  for (const pack of packs) {
    for (const image of pack.images) {
      if (image.usage.length && !image.usage.includes('Emoticon')) continue;
      if (!seen.has(image.shortcode)) seen.set(image.shortcode, { shortcode: image.shortcode, mxc: image.media_id });
    }
  }
  return [...seen.values()];
}

function score(code: string, q: string): number {
  const c = code.toLowerCase();
  if (c === q) return 0;
  if (c.startsWith(q)) return 1;
  if (c.split(/[_-]/).some((part) => part.startsWith(q))) return 2;
  return c.includes(q) ? 3 : -1;
}

export function rankEmotes(query: string, customs: Emote[], emoji: EmojiEntry[], limit = 8): EmoteCandidate[] {
  const q = query.toLowerCase();
  const scored: { candidate: EmoteCandidate; score: number; length: number }[] = [];
  for (const emote of customs) {
    const s = score(emote.shortcode, q);
    if (s >= 0) scored.push({ candidate: { kind: 'custom', ...emote }, score: s, length: emote.shortcode.length });
  }
  for (const entry of emoji) {
    let best: { code: string; s: number } | null = null;
    for (const code of entry.shortcodes) {
      const s = score(code, q);
      if (s >= 0 && (!best || s < best.s || (s === best.s && code.length < best.code.length))) best = { code, s };
    }
    if (best) scored.push({ candidate: { kind: 'emoji', emoji: entry.emoji, shortcode: best.code }, score: best.s, length: best.code.length });
  }
  return scored
    .sort((a, b) => a.score - b.score || a.length - b.length)
    .slice(0, limit)
    .map((s) => s.candidate);
}

/** When the caret sits just after a closing colon of a known emoji shortcode, swap the shortcode for the emoji. */
export function completeShortcode(text: string, caret: number, emoji: EmojiEntry[]): { text: string; caret: number } | null {
  const match = /(^|\s):([\w+-]+):$/.exec(text.slice(0, caret));
  if (!match) return null;
  const code = match[2].toLowerCase();
  const entry = emoji.find((e) => e.shortcodes.includes(code));
  if (!entry) return null;
  const start = caret - code.length - 2;
  return { text: text.slice(0, start) + entry.emoji + text.slice(caret), caret: start + entry.emoji.length };
}

export function resolveEmotes(body: string, customs: Emote[]): Emote[] {
  const used = new Set([...body.matchAll(/:([\w+-]+):/g)].map((m) => m[1]));
  return customs.filter((e) => used.has(e.shortcode));
}
