// src/lib/mangools/normalize.ts — normalisation PURE Mangools → formes internes.
// Aucune I/O, aucun import server-only : réutilisable partout (upserts keywords,
// pipeline sémantique) sans dépendance runtime. Ne consomme que les types Task 3
// et les formes cibles du repo (SourceMetrics / MonthlyVolume / SerpSnapshot).
import { isNonCompetitorDomain } from "../keywords/serp-filter";
import type { SerpCompetitor, SerpSnapshot } from "../keywords/serp-types";
import type { MonthlyVolume, SourceMetrics } from "../keywords/types";
import type {
  MangoolsKeywordItem,
  MangoolsSerpItem,
  MangoolsSerpResponse,
} from "./types";

/** Fenêtre de calcul de la croissance : les 13 derniers points de `msv`
 *  (convention YoY même-mois — 13 points cadrent exactement 12 mois, donc le
 *  1er point tombe sur le même mois calendaire un an avant le dernier). */
const GROWTH_WINDOW_POINTS = 13;
/** La série `trend` (sparkline) est capée aux 24 derniers mois. */
const TREND_MAX_MONTHS = 24;
/** Contrat `SerpSnapshot.competitors` : top 10 organique max. */
const MAX_COMPETITORS = 10;

/** `[année, moisIdx0, volume]` → `"YYYY-MM"` (mois 1-based, zero-padded). */
function msvMonth(entry: [number, number, number]): string {
  const [year, monthIdx0] = entry;
  return `${year}-${String(monthIdx0 + 1).padStart(2, "0")}`;
}

/** Croissance % en convention YoY même-mois (13 points, décision Bastien
 *  2026-07-25) : fenêtre = les 13 derniers points de `msv` ; growthPct =
 *  (dernier − premier) / premier × 100, arrondie à 1 décimale — le 1er point de
 *  la fenêtre étant le même mois calendaire un an avant le dernier.
 *  `null` si : moins de 13 points ; base (1er point) nulle ; OU 1er et dernier
 *  point de la fenêtre PAS sur le même mois calendaire (gap dans `msv` — les
 *  données réelles en ont : ne jamais comparer deux mois différents en silence). */
function growthFromMsv(msv: MangoolsKeywordItem["msv"]): number | null {
  if (!msv || msv.length < GROWTH_WINDOW_POINTS) return null;
  const window = msv.slice(-GROWTH_WINDOW_POINTS);
  const firstEntry = window[0];
  const lastEntry = window[window.length - 1];
  // Garde YoY : même mois calendaire (monthIdx0) exigé aux deux bornes, sinon
  // un gap fait comparer deux mois distincts → on refuse plutôt qu'induire.
  if (firstEntry[1] !== lastEntry[1]) return null;
  const first = firstEntry[2];
  const last = lastEntry[2];
  if (first === 0) return null;
  return Math.round(((last - first) / first) * 1000) / 10;
}

/** Métriques de référence (vol/kd/cpc) d'un mot-clé Mangools. `competitiveDensity`
 *  et `numberOfResults` sont toujours `null` (non fournis par Mangools). */
export function metricsFromMangools(
  item: MangoolsKeywordItem,
  now: string,
): SourceMetrics {
  return {
    volume: item.sv ?? 0,
    kd: item.seo ?? 0,
    cpc: item.cpc ?? 0,
    growthPct: growthFromMsv(item.msv),
    competitiveDensity: null,
    numberOfResults: null,
    updatedAt: now,
  };
}

/** Série de volume mensuel (sparkline) : `msv` → `{ month, volume }`, 24 derniers
 *  mois max, ordre chronologique (l'ordre source est déjà chronologique). `[]`
 *  si `msv` est absent. */
export function trendFromMangools(item: MangoolsKeywordItem): MonthlyVolume[] {
  if (!item.msv) return [];
  return item.msv.slice(-TREND_MAX_MONTHS).map((entry) => ({
    month: msvMonth(entry),
    volume: entry[2],
  }));
}

/** `pageAuthority` d'un item SERP. Moz expose deux autorités dans `m.moz.v` :
 *  `upa` (URL Page Authority, per-URL) et `pda` (Domain Authority). On mappe
 *  `pageAuthority` sur `upa` — la métrique per-URL la plus proche d'une « Page
 *  Authority » Moz ; `pda` est une autorité de domaine, hors sujet ici. `0` si
 *  absente ou non numérique. */
function mozPageAuthority(item: MangoolsSerpItem): number {
  const v = (item.m?.moz as { v?: Record<string, unknown> } | undefined)?.v;
  const upa = v?.upa;
  return typeof upa === "number" ? upa : 0;
}

/** Snapshot SERP concurrentiel depuis une réponse SERPChecker. Ne garde que les
 *  items `ORGANIC` rankés (→ `SerpCompetitor`), écarte ceux sans `serpRank`
 *  (rank inconnu, non triable), filtre les domaines non-concurrents via
 *  `isNonCompetitorDomain`, trie par position croissante puis cape au top 10
 *  (contrat `SerpSnapshot.competitors`). `features` = types d'items
 *  non-organiques distincts (PAA, etc.). Les champs slice 2/3 (crawl) restent
 *  `null` jusqu'au crawl on-page. */
export function serpSnapshotFromMangools(
  resp: MangoolsSerpResponse,
  importedAt: string,
): SerpSnapshot {
  const competitors: SerpCompetitor[] = resp.items
    .filter((i) => i.type === "ORGANIC")
    .filter((i) => i.serpRank != null)
    .filter((i) => i.domain && !isNonCompetitorDomain(i.domain))
    .map((i) => ({
      position: i.serpRank ?? 0,
      domain: i.domain ?? "",
      url: i.url ?? "",
      backlinks: i.m?.majestic?.v?.ExtBackLinks ?? 0,
      refDomains: i.m?.majestic?.v?.RefDomains ?? 0,
      pageAuthority: mozPageAuthority(i),
      searchTraffic: 0,
      urlKeywords: 0,
      title: null,
      metaDescription: null,
      lang: null,
      images: null,
      imagesWithoutAlt: null,
      imageAlts: null,
      missingAltSrcs: null,
      hasJsonLd: null,
      jsonLdTypes: null,
      crawledAt: null,
    }))
    .sort((a, b) => a.position - b.position)
    .slice(0, MAX_COMPETITORS);

  const features = [
    ...new Set(
      resp.items.filter((i) => i.type !== "ORGANIC").map((i) => i.type),
    ),
  ];

  return { source: "mangools", importedAt, features, competitors };
}
