// src/lib/gsc/language.ts — attribute a GSC query to a project language.
// Pure (no "server-only") so it is unit-testable. Layered signal, in priority
// order: (1) URL locale segment of the top ranking page, (2) the dominant
// country mapped to a configured language, (3) the project's primary language.
// A query whose only markets are known but map to NO configured language is
// dropped (returned as null) — that's how foreign-market traffic stops leaking
// in as primary-language keywords.
import type { GscQueryRow } from "./types";

// alpha-2 → the ISO-639 language(s) the country predominantly searches in, most
// common first. A language proxy used when URLs aren't locale-segmented (an
// approximation — multilingual countries list several so config can pick one).
// Unknown countries carry NO signal and fall through to the primary language;
// known countries with no configured language trigger a drop, so the Maghreb
// keeps French alongside Arabic (French is widely used for search there).
const COUNTRY_LANGUAGES: Record<string, string[]> = {
  // Francophone
  FR: ["fr"],
  MC: ["fr"],
  SN: ["fr"],
  CI: ["fr"],
  TG: ["fr"],
  CM: ["fr"],
  ML: ["fr"],
  BF: ["fr"],
  NE: ["fr"],
  GA: ["fr"],
  CG: ["fr"],
  CD: ["fr"],
  // Anglophone
  US: ["en"],
  GB: ["en"],
  IE: ["en"],
  AU: ["en"],
  NZ: ["en"],
  ZA: ["en"],
  IN: ["en"],
  NG: ["en"],
  PH: ["en"],
  SG: ["en"],
  // Hispanophone
  ES: ["es"],
  MX: ["es"],
  AR: ["es"],
  CO: ["es"],
  CL: ["es"],
  PE: ["es"],
  VE: ["es"],
  EC: ["es"],
  GT: ["es"],
  CU: ["es"],
  // Lusophone
  PT: ["pt"],
  BR: ["pt"],
  AO: ["pt"],
  MZ: ["pt"],
  // Germanophone / Italophone / Dutch
  DE: ["de"],
  AT: ["de"],
  IT: ["it"],
  SM: ["it"],
  NL: ["nl"],
  // Single-language markets (useful so foreign traffic is dropped, not kept)
  SE: ["sv"],
  NO: ["no"],
  DK: ["da"],
  FI: ["fi"],
  PL: ["pl"],
  RU: ["ru"],
  JP: ["ja"],
  CN: ["zh"],
  TW: ["zh"],
  KR: ["ko"],
  TR: ["tr"],
  GR: ["el"],
  VN: ["vi"],
  TH: ["th"],
  ID: ["id"],
  CZ: ["cs"],
  HU: ["hu"],
  RO: ["ro"],
  // Multilingual — config picks among these (most-common first)
  BE: ["nl", "fr"],
  CH: ["de", "fr", "it"],
  CA: ["en", "fr"],
  LU: ["fr", "de"],
  // North Africa: Arabic primary, French widely used for search
  MA: ["ar", "fr"],
  TN: ["ar", "fr"],
  DZ: ["ar", "fr"],
};

/** One country's impression weight for a query (alpha-2 country code). */
export type QueryCountryImpressions = { country: string; impressions: number };

/** The configured language whose locale segment appears in the page path
 *  (e.g. `/fr/blog` → "fr" when "fr" is configured), or null. */
export function localeSegmentLang(
  pageUrl: string,
  configured: string[],
): string | null {
  let path: string;
  try {
    path = new URL(pageUrl).pathname;
  } catch {
    path = pageUrl;
  }
  const segs = path.toLowerCase().split("/").filter(Boolean);
  for (const lang of configured) {
    if (segs.includes(lang.toLowerCase())) return lang;
  }
  return null;
}

/** Map a country to a configured language:
 *  - a string  → a configured language for this (known) country;
 *  - null      → the country is known but none of its languages are configured
 *                (caller treats this as a drop signal);
 *  - undefined → the country is unknown to us (no signal). */
export function countryToConfiguredLang(
  alpha2: string,
  configured: string[],
): string | null | undefined {
  const langs = COUNTRY_LANGUAGES[alpha2.toUpperCase()];
  if (!langs) return undefined;
  return langs.find((l) => configured.includes(l)) ?? null;
}

/** A target country whose language matches `lang` (for the `country` column of
 *  newly-created keywords), or null when none of the targets fit. */
export function countryForLang(
  lang: string,
  targetCountries: string[],
): string | null {
  for (const c of targetCountries) {
    if (COUNTRY_LANGUAGES[c.toUpperCase()]?.includes(lang))
      return c.toUpperCase();
  }
  return null;
}

/** The country (alpha-2) to tag a GSC query with, from its per-country
 *  impression breakdown: the country where it gets the MOST impressions,
 *  PREFERRING the site's target countries (a target with data always wins over
 *  a non-target), else the overall dominant country (truthful — e.g. a query
 *  ranking only in Thailand → TH, not the first target), else `fallback`.
 *  This is what stops every query collapsing onto countries[0]. */
export function resolveGscCountry(
  breakdown: QueryCountryImpressions[] | undefined,
  targetCountries: string[],
  fallback: string,
): string {
  if (!breakdown || breakdown.length === 0) return fallback;
  const targets = new Set(targetCountries.map((c) => c.toUpperCase()));
  let bestTarget: { c: string; i: number } | null = null;
  let bestOverall: { c: string; i: number } | null = null;
  for (const { country, impressions } of breakdown) {
    const c = country.toUpperCase();
    if (!bestOverall || impressions > bestOverall.i)
      bestOverall = { c, i: impressions };
    if (targets.has(c) && (!bestTarget || impressions > bestTarget.i))
      bestTarget = { c, i: impressions };
  }
  return (bestTarget ?? bestOverall)?.c ?? fallback;
}

function attributeOne(
  queryLower: string,
  configured: string[],
  primary: string,
  topPageByQuery?: Map<string, string>,
  queryCountries?: Map<string, QueryCountryImpressions[]>,
): string | null {
  // 1. URL locale segment (precise — sites that localize by path).
  const page = topPageByQuery?.get(queryLower);
  if (page) {
    const seg = localeSegmentLang(page, configured);
    if (seg) return seg;
  }
  // 2. Dominant country → configured language (the geographic approximation).
  const countries = queryCountries?.get(queryLower);
  if (countries && countries.length > 0) {
    const sorted = [...countries].sort((a, b) => b.impressions - a.impressions);
    let sawKnown = false;
    for (const c of sorted) {
      const lang = countryToConfiguredLang(c.country, configured);
      if (lang) return lang; // configured language found (dominant wins)
      if (lang === null) sawKnown = true; // known country, not configured
      // undefined → unknown country, no signal, keep scanning
    }
    if (sawKnown) return null; // ranks only in known non-configured markets → drop
  }
  // 3. Fallback: the project's primary language (no usable URL/country signal).
  return primary;
}

/** Resolve each query's project language. Returns queryLower → language, or
 *  null when the query should be dropped (foreign market, not configured). */
export function resolveQueryLanguages(input: {
  rows: GscQueryRow[];
  configured: string[];
  primary: string;
  topPageByQuery?: Map<string, string>;
  queryCountries?: Map<string, QueryCountryImpressions[]>;
}): Map<string, string | null> {
  const { rows, configured, primary, topPageByQuery, queryCountries } = input;
  const out = new Map<string, string | null>();
  for (const row of rows) {
    const q = row.query.toLowerCase();
    if (out.has(q)) continue;
    out.set(
      q,
      attributeOne(q, configured, primary, topPageByQuery, queryCountries),
    );
  }
  return out;
}
