/* global window */
// ============================================================================
// MELR · STAR (FEM/GEF) — moteur de calcul de l'allocation des ressources
// ----------------------------------------------------------------------------
// Modélise le Système Transparent d'Allocation des Ressources du Fonds pour
// l'Environnement Mondial (FEM-8 et GEF-9). Formule et coefficients OFFICIELS :
//   • FEM-8 : GEF/C.62/04 « Updating the STAR » (mai 2022) ;
//   • GEF-9 : GEF/C.71/06 « Updating the STAR for GEF-9 » (7 mai 2026, en
//     vigueur au 1er juillet 2026) — poids PIB -0.21 + indice de décaissement.
//
//   Score pays = CPI^cpiExp × GBI^gbiExp × GDP^gdpWeight  (exposants par cycle)
//   FEM-8 : GDP^(-0.16) ; GEF-9 : GDP^(-0.21).
//
//   CPI (Country Performance Index) = 0.20·PPI + 0.65·CEPIA + 0.15·BFI (inchangé)
//   PPI (Portfolio Performance Index) : FEM-8 = 0.40·PIR + 0.60·TER ;
//     GEF-9 = 0.30·PIR + 0.50·TER + 0.20·Décaissement (poids par cycle).
//   CEPIA = critère 11 du CPIA (durabilité environnementale, Banque mondiale)
//   BFI   = moyenne du « Cluster D » du CPIA (gestion du secteur public)
//
//   Poids de l'indice PIB : -0.04 (FEM-5) → -0.08 (FEM-6) → -0.12 (FEM-7)
//                           → -0.16 (FEM-8) → -0.21 (GEF-9, confirmé
//   GEF/C.71/06 « Updating the STAR for GEF-9 », 7 mai 2026, Annexe II §46-47).
//
// HONNÊTETÉ DU MODÈLE : sans la base complète de tous les pays, on ne peut
// pas reproduire l'allocation NORMALISÉE en dollars (le FEM la calcule par
// reconstitution, avec le GBI et une normalisation internes). Ce moteur
// fournit :
//   1. le CPI (fidèle, à partir des données publiques Banque mondiale) ;
//   2. la décomposition du CPI (part CEPIA / BFI / PPI) ;
//   3. la SENSIBILITÉ exacte : variation du score pour une variation d'un
//      facteur — exacte car le score est une fonction puissance (élasticités
//      invariantes d'échelle). C'est l'apport analytique le plus solide.
// L'allocation officielle en dollars reste saisie (publiée par le FEM/cycle).
// ============================================================================

(function () {
  // Constantes par cycle (extensible). Chaque cycle porte ses exposants de tête
  // (gdpWeight, cpiExp, gbiExp) ET les poids internes du PPI (pir/ter/disb),
  // ces derniers ayant changé au GEF-9.
  const CYCLES = {
    // GEF-9 : formule OFFICIELLE GEF/C.71/06 « Updating the STAR for GEF-9 »
    // (7 mai 2026 ; prise d'effet 1er juillet 2026). Deux changements vs FEM-8 :
    //   • poids de l'indice PIB : -0.16 → -0.21 (Annexe II §46-47) ;
    //   • PPI : ajout d'un indice de DÉCAISSEMENT → 0.30·PIR + 0.50·TER +
    //     0.20·Décaissement (Annexe II §45 ; PIR et TER réduits de 0,1 chacun).
    // Les poids de tête du CPI (0.20 PPI + 0.65 CEPIA + 0.15 BFI) restent
    // inchangés (Annexe II §37 et §40).
    "GEF-9": { gdpWeight: -0.21, cpiExp: 1.0, gbiExp: 0.8, ppiWeights: { pir: 0.30, ter: 0.50, disb: 0.20 } },
    "GEF-8": { gdpWeight: -0.16, cpiExp: 1.0, gbiExp: 0.8, ppiWeights: { pir: 0.40, ter: 0.60, disb: 0 } },
    "GEF-7": { gdpWeight: -0.12, cpiExp: 1.0, gbiExp: 0.8, ppiWeights: { pir: 0.40, ter: 0.60, disb: 0 } },
    "GEF-6": { gdpWeight: -0.08, cpiExp: 1.0, gbiExp: 0.8, ppiWeights: { pir: 0.40, ter: 0.60, disb: 0 } },
  };
  // Défaut = GEF-9 (cycle courant, formule officielle en vigueur au 1er juillet 2026).
  const DEFAULT_CYCLE = "GEF-9";

  const CPI_WEIGHTS = { ppi: 0.20, cepia: 0.65, bfi: 0.15 };
  // Poids PPI par défaut si un cycle n'en déclare pas (repli FEM-8).
  const DEFAULT_PPI_WEIGHTS = { pir: 0.40, ter: 0.60, disb: 0 };
  const ppiWeightsFor = (cycleKey) =>
    (CYCLES[cycleKey || DEFAULT_CYCLE] || CYCLES[DEFAULT_CYCLE]).ppiWeights || DEFAULT_PPI_WEIGHTS;

  // Indicateurs Banque mondiale (IRAI / CPIA), API ouverte, échelle 1–6.
  // Vérifiés pour le Sénégal (juin 2026) : CEPIA 3,5 ; Cluster D 3,6.
  const WB = {
    cepia: { code: "IQ.CPA.ENVR.XQ", fr: "CEPIA — durabilité environnementale (critère 11)", en: "CEPIA — environmental sustainability (criterion 11)" },
    bfi:   { code: "IQ.CPA.PUBS.XQ", fr: "BFI — gestion du secteur public (Cluster D, moyenne)", en: "BFI — public sector management (Cluster D average)" },
    gdpPerCapita: { code: "NY.GDP.PCAP.CD", fr: "PIB par habitant (US$ courants)", en: "GDP per capita (current US$)" },
    // CPIA GLOBALE (IRAI, moyenne des 16 critères, échelle 1–6). Non utilisée
    // par le STAR lui-même (qui prend CEPIA + BFI), mais récupérée dans le même
    // lot Banque mondiale pour servir de RÉFÉRENTIEL CPIA UNIQUE : l'écran AVD
    // (window.DSA) la lit depuis star_wb_snapshots pour le CI. Une seule source.
    cpiaOverall: { code: "IQ.CPA.IRAI.XQ", fr: "CPIA globale (IRAI) — référentiel partagé STAR + AVD", en: "Overall CPIA (IRAI) — shared STAR + DSA reference", es: "CPIA global (IRAI) — referencia compartida STAR + AVD" },
  };
  // Détail des 5 critères du Cluster D (pour transparence / vérification BFI).
  const CLUSTER_D = [
    { code: "IQ.CPA.PROP.XQ", fr: "Droits de propriété et gouvernance fondée sur les règles", en: "Property rights and rule-based governance" },
    { code: "IQ.CPA.FINQ.XQ", fr: "Qualité de la gestion budgétaire et financière", en: "Quality of budgetary and financial management" },
    { code: "IQ.CPA.REVN.XQ", fr: "Efficacité de la mobilisation des recettes", en: "Efficiency of revenue mobilization" },
    { code: "IQ.CPA.PADM.XQ", fr: "Qualité de l'administration publique", en: "Quality of public administration" },
    { code: "IQ.CPA.TRAN.XQ", fr: "Transparence, redevabilité et corruption", en: "Transparency, accountability and corruption" },
  ];

  // Domaines focaux alloués via le STAR.
  const FOCAL_AREAS = [
    { key: "bd", fr: "Biodiversité",          en: "Biodiversity" },
    { key: "cc", fr: "Changements climatiques", en: "Climate change" },
    { key: "ld", fr: "Dégradation des terres", en: "Land degradation" },
  ];

  const num = (v) => {
    const n = Number(v);
    return v == null || v === "" || Number.isNaN(n) ? null : n;
  };

  // PPI = pir·PIR + ter·TER + disb·Décaissement (échelle 1–6). Les poids
  // dépendent du CYCLE (le GEF-9 introduit l'indice de décaissement à 0.20 et
  // ramène PIR/TER à 0.30/0.50). On ne pondère que les composantes fournies et
  // on renormalise sur les poids présents : une note manquante ne biaise pas le
  // PPI (repli robuste, identique à l'ancien comportement quand une seule note).
  function ppi(inp, cycleKey) {
    const w = ppiWeightsFor(cycleKey);
    const pir = num(inp && inp.pir), ter = num(inp && inp.ter), disb = num(inp && inp.disb);
    const parts = [];
    if (pir  != null && w.pir  > 0) parts.push([w.pir,  pir]);
    if (ter  != null && w.ter  > 0) parts.push([w.ter,  ter]);
    if (disb != null && w.disb > 0) parts.push([w.disb, disb]);
    if (!parts.length) return null;
    const wsum = parts.reduce((s, p) => s + p[0], 0);
    return parts.reduce((s, p) => s + p[0] * p[1], 0) / wsum;
  }

  // CPI = 0.20·PPI + 0.65·CEPIA + 0.15·BFI. Renvoie aussi la contribution
  // (en points) de chaque composante pour la décomposition.
  function cpi(inp, cycleKey) {
    const cepia = num(inp && inp.cepia);
    const bfi   = num(inp && inp.bfi);
    const p     = inp && inp.ppi != null ? num(inp.ppi) : ppi(inp, cycleKey);
    if (cepia == null || bfi == null) return null;
    const pUsed = p == null ? null : p;
    if (pUsed == null) return null;
    const contrib = {
      ppi:   CPI_WEIGHTS.ppi * pUsed,
      cepia: CPI_WEIGHTS.cepia * cepia,
      bfi:   CPI_WEIGHTS.bfi * bfi,
    };
    const value = contrib.ppi + contrib.cepia + contrib.bfi;
    return { value, contrib, inputs: { ppi: pUsed, cepia, bfi } };
  }

  // Score pays = CPI^cpiExp × GBI^gbiExp × GDPindex^gdpWeight.
  // gdpIndex : indice PIB normalisé (1 = neutre par défaut — la normalisation
  // réelle dépend de la cohorte de pays, non disponible ici).
  function countryScore(inp, cycleKey) {
    const c = CYCLES[cycleKey || DEFAULT_CYCLE] || CYCLES[DEFAULT_CYCLE];
    const cpiR = cpi(inp, cycleKey);
    const gbi = num(inp && inp.gbi);
    const gdpIndex = inp && inp.gdpIndex != null ? num(inp.gdpIndex) : 1;
    if (!cpiR || gbi == null || gbi <= 0 || gdpIndex == null || gdpIndex <= 0) return null;
    const score = Math.pow(cpiR.value, c.cpiExp) * Math.pow(gbi, c.gbiExp) * Math.pow(gdpIndex, c.gdpWeight);
    return { score, cpi: cpiR.value, cpiDetail: cpiR, gbi, gdpIndex, cycle: cycleKey || DEFAULT_CYCLE };
  }

  // Sensibilité EXACTE : recalcule le score en remplaçant une entrée, renvoie
  // la variation relative. Pour les facteurs CPIA (cepia/bfi/ppi), seul le CPI
  // bouge → ratio = (cpi_new/cpi_old)^cpiExp. Pour GBI/GDP, exposant direct.
  function sensitivity(baseInp, field, newValue, cycleKey) {
    const base = countryScore(baseInp, cycleKey);
    if (!base) return null;
    const next = { ...baseInp };
    if (field === "ppi" || field === "pir" || field === "ter" || field === "disb") {
      // recalcule le PPI effectif (avec les poids du cycle) puis l'injecte
      const nextPpiInp = { ...baseInp };
      nextPpiInp[field] = newValue;
      next.ppi = field === "ppi" ? num(newValue) : ppi(nextPpiInp, cycleKey);
    } else {
      next[field] = newValue;
    }
    const after = countryScore(next, cycleKey);
    if (!after) return null;
    return {
      before: base.score, after: after.score,
      deltaPct: 100 * (after.score / base.score - 1),
      cpiBefore: base.cpi, cpiAfter: after.cpi,
    };
  }

  window.STAR = {
    CYCLES, DEFAULT_CYCLE, CPI_WEIGHTS, DEFAULT_PPI_WEIGHTS, ppiWeightsFor,
    WB, CLUSTER_D, FOCAL_AREAS,
    ppi, cpi, countryScore, sensitivity,
  };
})();
