import {
  census,
  COUNTRY_CODE,
  LANGUAGE_CODE,
  MAX_TARGETS_PER_SITE,
  type Census,
  type CensusCountry,
  type CountryChoice,
} from "@/lib/import-countries";
import type { ListPreview } from "@/lib/integrations";
import { formatTarget } from "@/lib/locales";
import type { Target } from "@/lib/sites";

// La décision du wizard « listes KWFinder » : quels marchés sont proposés, quelles cibles sont
// réutilisées, et **combien d'imports partent** pour la sélection de listes en cours.
//
// Isolé du composant pour la même raison que `lib/import-countries.ts` : c'est une machine
// d'état, pas de la mise en page, et elle doit s'éprouver sans navigateur. Elle réutilise
// d'ailleurs ce module tel quel — `census`, `defaultChoice`, les deux expressions de casse et le
// plafond de cibles —, parce que le problème est le même : une source qui porte plusieurs
// marchés, et une cible par marché retenu.
//
// **Ce qui change par rapport au CSV**, et c'est la seule chose : un fichier est un tout qu'on
// cloisonne à la lecture, là où une sélection de listes est déjà découpée. Le recensement est
// donc rendu **par liste**, et le plan ne croise que ce qui existe : une liste entièrement
// française n'est pas envoyée sur une cible américaine, où elle produirait un rapport à zéro
// ligne et rien pour dire pourquoi.

/** Un appel de lancement : une cible, et les listes qui ont vraiment des lignes pour elle. */
export type PlannedListImport = {
  /** Clé stable de rendu et de suivi : le code du pays. */
  key: string;
  /** Cible à réutiliser ; `null` quand elle reste à créer. */
  targetId: string | null;
  /** ISO 3166-1 alpha-2 majuscule. */
  country: string;
  /** ISO 639-1 minuscule. */
  language: string;
  /** Libellé de la cible, tel qu'il s'affiche. */
  label: string;
  /** Identifiants des listes à importer sur cette cible, dans l'ordre de sélection. */
  listIds: string[];
  /** Leurs noms, pour l'affichage — même ordre. */
  listNames: string[];
  /** Lignes que le recensement attribue à ce marché, toutes listes retenues confondues. */
  rows: number;
};

export type ListLaunchPlan = {
  imports: PlannedListImport[];
  /** Nombre de cibles à créer avant de lancer — ce qui compte pour le plafond du site. */
  creations: number;
  /** Motifs qui interdisent le lancement. Vide = le bouton est actif. */
  blockers: string[];
  /**
   * Listes cochées qu'aucun import ne portera : elles n'ont aucune ligne dans les marchés
   * retenus. Nommées à l'écran plutôt que silencieusement omises — l'utilisateur les a cochées.
   */
  droppedLists: { id: string; name: string }[];
};

/** Le recensement d'une sélection de listes : marchés résolus d'un côté, libellés bruts de l'autre. */
export function listsCensus(previews: readonly ListPreview[]): Census {
  return census(previews.flatMap((preview) => preview.countries));
}

/** Les codes ISO présents dans une liste — ce qui décide si elle part sur une cible donnée. */
export function listCountries(preview: ListPreview): Set<string> {
  const codes = new Set<string>();
  for (const entry of preview.countries) {
    if (entry.code !== null) codes.add(entry.code.toUpperCase());
  }
  return codes;
}

/**
 * Ce que « Lancer l'import » va faire, et ce qui l'en empêche.
 *
 * Un appel par pays coché, portant les listes qui contiennent ce pays. L'api enfile ensuite
 * **un import par (liste, cible)** : c'est elle qui déplie, pas l'écran, ce qui évite N appels
 * réseau là où un seul suffit et laisse la dédup arbitrer d'un seul endroit.
 *
 * Le lancement n'est **pas** atomique : chaque appel publie ses imports par son propre commit.
 * Ce plan sert donc aussi de liste d'états à l'écran, un par cible.
 */
export function listLaunchPlan({
  targets,
  previews,
  countries,
  choice,
}: {
  /** Cibles du site, **fusionnées** avec celles créées depuis l'écran. */
  targets: readonly Target[];
  /** Recensement par liste, tel que l'api l'a rendu — l'ordre est celui de la sélection. */
  previews: readonly ListPreview[];
  /** Marchés du recensement fusionné, dédoublonnés par code. */
  countries: readonly CensusCountry[];
  choice: (country: CensusCountry) => CountryChoice;
}): ListLaunchPlan {
  const imports: PlannedListImport[] = [];
  const blockers: string[] = [];

  if (previews.length === 0) {
    return {
      imports: [],
      creations: 0,
      blockers: ["Choisissez au moins une liste."],
      droppedLists: [],
    };
  }

  const codesByList = new Map(previews.map((preview) => [preview.list_id, listCountries(preview)]));
  const carried = new Set<string>();

  // Un couple (pays, langue) ne peut donner qu'une cible : `UNIQUE (site_id, country, language)`.
  // Deux marchés qui y mèneraient se fondent en un seul appel.
  const planned = new Map<string, PlannedListImport>();
  for (const country of countries) {
    const picked = choice(country);
    if (!picked.selected) continue;
    const language = picked.language.trim().toLowerCase();
    if (!LANGUAGE_CODE.test(language) || !COUNTRY_CODE.test(country.code)) {
      blockers.push(`Choisissez une langue pour ${country.label}.`);
      continue;
    }
    const kept = previews.filter((preview) => codesByList.get(preview.list_id)?.has(country.code));
    if (kept.length === 0) continue;
    for (const preview of kept) carried.add(preview.list_id);
    const couple = `${country.code}/${language}`;
    const known = planned.get(couple);
    if (known !== undefined) {
      //  Deux libellés du recensement qui rendent le même couple : les listes s'additionnent
      //  sans se répéter, et les lignes aussi.
      for (const [index, id] of kept.map((preview) => preview.list_id).entries()) {
        if (known.listIds.includes(id)) continue;
        known.listIds.push(id);
        known.listNames.push(kept[index]?.name ?? id);
      }
      known.rows += country.rows;
      continue;
    }
    const existing = targets.find(
      (target) => target.country === country.code && target.language === language,
    );
    const entry: PlannedListImport = {
      key: country.code,
      targetId: existing?.id ?? null,
      country: country.code,
      language,
      label: formatTarget({ country: country.code, language }),
      listIds: kept.map((preview) => preview.list_id),
      listNames: kept.map((preview) => preview.name),
      rows: country.rows,
    };
    planned.set(couple, entry);
    imports.push(entry);
  }

  if (imports.length === 0 && blockers.length === 0) {
    blockers.push("Cochez au moins un marché à importer.");
  }

  const creations = imports.filter((entry) => entry.targetId === null).length;
  if (targets.length + creations > MAX_TARGETS_PER_SITE) {
    blockers.push(
      `Ce site atteindrait le plafond de ${MAX_TARGETS_PER_SITE} cibles (${targets.length} existantes, ${creations} à créer) : décochez des marchés.`,
    );
  }

  return {
    imports,
    creations,
    blockers,
    //  Identifiant **et** nom : deux listes du compte peuvent porter le même nom, et l'écran a
    //  besoin d'une clé stable pour les rendre côte à côte.
    droppedLists: previews
      .filter((preview) => !carried.has(preview.list_id))
      .map((preview) => ({ id: preview.list_id, name: preview.name })),
  };
}
