/**
 * Case-insensitive keyword matching with word boundaries.
 * Hyphens, spaces, and underscores between parts are flexible (co-op, co op, coop),
 * but substrings inside other words (e.g. "coop" inside "Calico Open Source") do not match.
 */

function escapeRegExp(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

/** @deprecated Prefer textMatchesKeyword; kept for callers that need compact tokens. */
export function normalizeForKeywordMatch(value: string): string {
  return value
    .toLowerCase()
    .replace(/[\s\-_]+/g, "")
    .trim();
}

function twoPartSplits(word: string): Array<[string, string]> {
  const splits: Array<[string, string]> = [];
  for (let i = 2; i <= word.length - 2; i++) {
    splits.push([word.slice(0, i), word.slice(i)]);
  }
  return splits;
}

function buildKeywordPattern(keyword: string): RegExp | null {
  const trimmed = keyword.trim();
  if (!trimmed) return null;

  const parts = trimmed
    .toLowerCase()
    .split(/[\s\-_]+/)
    .filter(Boolean);
  if (parts.length === 0) return null;

  const escapedParts = parts.map(escapeRegExp);
  const concatenated = escapedParts.join("");
  const variants = new Set<string>();

  if (parts.length === 1) {
    variants.add(`\\b${escapedParts[0]}\\b`);
    for (const [left, right] of twoPartSplits(parts[0])) {
      variants.add(
        `\\b${escapeRegExp(left)}[\\s\\-_]*${escapeRegExp(right)}\\b`,
      );
    }
  } else {
    const separated = escapedParts.join("[\\s\\-_]*");
    variants.add(`\\b${separated}\\b`);
    variants.add(`\\b${concatenated}\\b`);
  }

  return new RegExp(`(?:${[...variants].join("|")})`, "i");
}

export function textMatchesKeyword(
  haystack: string | null | undefined,
  keyword: string,
): boolean {
  const pattern = buildKeywordPattern(keyword);
  if (!pattern || !haystack) return false;
  return pattern.test(haystack);
}

export function textMatchesAnyKeyword(
  haystack: string | null | undefined,
  keywords: readonly string[],
): boolean {
  return keywords.some((keyword) => textMatchesKeyword(haystack, keyword));
}
