// src/lib/pages/mapping.ts — matching textuel keyword ↔ page (pur, sans wasm ni DB).

import { normalizeUrl } from "@/lib/content/stats-logic";

/** Une page candidate pour le mapping (sous-ensemble de site_pages). */
export type PageRef = {
  id: string;
  url: string;
  title: string | null;
  h1: string | null;
};

const SCORE_PATH = 40;
const SCORE_H1 = 30;
const SCORE_TITLE = 30;
const SCORE_THRESHOLD = 50;

/** Retire les diacritiques (NFD) et passe en minuscules. */
function foldAccents(s: string): string {
  return s
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase();
}

/**
 * Slug local minimal : lowercase, accents strippés, non-alphanum → '-', runs
 * collapsés, trim des '-'. Écart assumé avec `wasm.slugify` (translittération
 * plus riche) — `mapping.ts` reste pur, donc cette version suffit pour un
 * `path.includes(slug)`.
 */
export function localSlug(s: string): string {
  return foldAccents(s)
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

/**
 * Normalisation URL partagée — wrapper de `normalizeUrl` (content/stats-logic),
 * UN SEUL point de vérité pour tout le module Pages.
 */
export function normalizePageUrl(u: string): string {
  return normalizeUrl(u);
}

/** Pathname d'une URL, replié (accents/minuscules) ; chaîne vide si URL invalide. */
function foldedPath(url: string): string {
  try {
    return foldAccents(new URL(url).pathname);
  } catch {
    return "";
  }
}

/**
 * Découpe une chaîne foldée en tokens alphanumériques (frontières de mot) :
 * tout run de caractères non-`[a-z0-9]` est un séparateur. Couvre `/`, `-`, `_`,
 * espaces, ponctuation. Vide → `[]`.
 */
function wordTokens(folded: string): string[] {
  return folded.split(/[^a-z0-9]+/).filter(Boolean);
}

/**
 * `true` si `needle` (non vide) apparaît comme sous-séquence CONTIGUË dans
 * `haystack`. Frontières de mot garanties : le match est token-à-token, jamais
 * sur une sous-chaîne (« or » ne matche pas « portfolio »/« bonjour »).
 */
function containsContiguous(haystack: string[], needle: string[]): boolean {
  if (needle.length === 0 || needle.length > haystack.length) return false;
  for (let i = 0; i + needle.length <= haystack.length; i++) {
    let ok = true;
    for (let j = 0; j < needle.length; j++) {
      if (haystack[i + j] !== needle[j]) {
        ok = false;
        break;
      }
    }
    if (ok) return true;
  }
  return false;
}

/**
 * Meilleure page pour un terme : +40 si les tokens du slug du terme apparaissent
 * en séquence contiguë dans les tokens du path, +30 si les mots du terme
 * (insensible casse/accents) apparaissent en séquence contiguë dans le H1, +30
 * dans le title. Matching à frontières de mot — un terme court (« or ») ne
 * matche plus par sous-chaîne (« portfolio », « bonjour »).
 * Seuil 50. Meilleur score gagne ; égalité → URL la plus courte. Aucun ≥ 50 → null.
 */
export function textualMatch(
  term: string,
  pages: PageRef[],
): { pageId: string; score: number } | null {
  // Tokens du slug : localSlug folde et sépare tout par '-'.
  const slugTokens = localSlug(term).split("-").filter(Boolean);
  // Mots du terme foldé (frontières de mot) pour H1/title.
  const termTokens = wordTokens(foldAccents(term));

  let best: { pageId: string; score: number; urlLength: number } | null = null;

  for (const page of pages) {
    let score = 0;
    if (containsContiguous(wordTokens(foldedPath(page.url)), slugTokens)) {
      score += SCORE_PATH;
    }
    if (
      page.h1 &&
      containsContiguous(wordTokens(foldAccents(page.h1)), termTokens)
    ) {
      score += SCORE_H1;
    }
    if (
      page.title &&
      containsContiguous(wordTokens(foldAccents(page.title)), termTokens)
    ) {
      score += SCORE_TITLE;
    }
    if (score < SCORE_THRESHOLD) continue;

    if (
      best === null ||
      score > best.score ||
      (score === best.score && page.url.length < best.urlLength)
    ) {
      best = { pageId: page.id, score, urlLength: page.url.length };
    }
  }

  return best ? { pageId: best.pageId, score: best.score } : null;
}
