/**
 * Identifiers created when the platform admin provisions a venue: URL slugs and the short join code
 * printed on QR posters (pintrush.com/join/<CODE>). Pure helpers; uniqueness is checked against the
 * database by the caller.
 */

/** Join codes: 4–8 upper-case letters or digits (a subset of the protocol's VenueCode). */
export const JOIN_CODE_PATTERN = /^[A-Z0-9]{4,8}$/;
export const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const SLUG_MAX = 48;

/** "The Harbor Tap!" → "the-harbor-tap"; diacritics folded; never empty. */
export function slugify(name: string, fallback = 'venue'): string {
  const slug = name
    .normalize('NFKD')
    .replace(/[̀-ͯ]/g, '')
    .toLowerCase()
    .replace(/&/g, ' and ')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, SLUG_MAX)
    .replace(/-+$/g, '');
  return slug.length > 0 ? slug : fallback;
}

/** First free slug among base, base-2, base-3, … */
export function uniqueSlug(base: string, taken: ReadonlySet<string>): string {
  if (!taken.has(base)) return base;
  for (let n = 2; n < 10_000; n++) {
    const suffix = `-${n}`;
    const candidate = `${base.slice(0, SLUG_MAX - suffix.length).replace(/-+$/g, '')}${suffix}`;
    if (!taken.has(candidate)) return candidate;
  }
  throw new Error('No free slug');
}

/** Upper-cased, trimmed join code as typed by the admin. */
export function normalizeJoinCode(input: string): string {
  return input.trim().toUpperCase();
}

const CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no 0/O, 1/I: easier to read off a TV
const FILLER_WORDS = new Set(['THE', 'A', 'AN', 'AND', 'OF', 'BAR', 'PUB', 'TAVERN', 'GRILL', 'TAP', 'TAPROOM', 'LOUNGE', 'CO']);

/**
 * Join-code candidates for a venue name, most memorable first: the distinctive word of the name
 * ("The Harbor Tap" → HARBOR), then that word with digits, then random codes. `random` is
 * injectable (returns [0, 1)) so tests are deterministic.
 */
export function joinCodeCandidates(name: string, count = 12, random: () => number = Math.random): string[] {
  const words = name
    .normalize('NFKD')
    .replace(/[̀-ͯ]/g, '')
    .toUpperCase()
    .split(/[^A-Z0-9]+/)
    .filter(Boolean);
  const distinctive = words.filter((w) => !FILLER_WORDS.has(w));
  const pick = (alphabet: string) => alphabet[Math.floor(random() * alphabet.length)] ?? alphabet[0]!;
  const out: string[] = [];
  const push = (code: string) => {
    if (JOIN_CODE_PATTERN.test(code) && !out.includes(code)) out.push(code);
  };

  // Prefer a word long enough to stand alone ("QA Test Lounge" → TEST, not QA00).
  const word = (distinctive.find((w) => w.length >= 4) ?? distinctive[0] ?? words[0] ?? '').slice(0, 8);
  if (word.length >= 4) push(word.slice(0, 6));
  const initials = (distinctive.length >= 2 ? distinctive : words).map((w) => w[0]).join('');
  if (initials.length >= 4) push(initials.slice(0, 8));
  const stem = (word.length >= 2 ? word : initials).slice(0, 5);
  for (let i = 0; out.length < count && i < count * 4; i++) {
    if (stem.length >= 2 && i < count * 2) {
      let digits = '';
      while (stem.length + digits.length < Math.max(4, stem.length + 2)) digits += String(Math.floor(random() * 10));
      push(`${stem}${digits}`.slice(0, 8));
    } else {
      let code = '';
      for (let j = 0; j < 6; j++) code += pick(CODE_ALPHABET);
      push(code);
    }
  }
  return out.slice(0, count);
}
