/**
 * MGT2 Commerce – Logique métier
 *
 * Toutes les fonctions retournent des objets détaillés pour alimenter
 * la carte de chat et le dialogue. Les jets de dés utilisent l'API Roll
 * de FoundryVTT (asynchrone).
 */

import {
  PASSAGE_COSTS,
  PASSENGER_TRAFFIC,
  CARGO_TRAFFIC,
  GOODS_TABLE,
  CARGO_LOT_SIZES,
  getModifiedPrice,
} from './data/tradeTables.js';
import { parseUWP, starportModifier, populationModifier, zoneModifier } from './uwpParser.js';

/**
 * Évalue une formule de dés et retourne { formula, total, rolls }.
 * Affiche le jet dans le chat FoundryVTT si whisper=false.
 * @param {string} formula
 * @returns {Promise<{formula: string, total: number}>}
 */
async function rollDice(formula) {
  if (formula === '0') return { formula: '0', total: 0 };
  const roll = await new Roll(formula).evaluate();
  return { formula, total: roll.total };
}

// ─── Passagers ────────────────────────────────────────────────────────────────

/**
 * Calcule le trafic de passagers entre deux mondes.
 *
 * @param {object} params
 * @param {string} params.uwpDep         UWP du monde de départ
 * @param {string} params.uwpDest        UWP du monde de destination
 * @param {string} params.zoneDep        Zone du départ  ('normal'|'amber'|'red')
 * @param {string} params.zoneDest       Zone de destination
 * @param {number} params.parsecs        Distance en parsecs (1–6)
 * @param {number} params.skillEffect    Effet du test de compétence (Mondanités/Courtier/Sens de rue)
 * @param {number} params.stewardLevel   Niveau de compétence Intendant (chef)
 * @returns {Promise<object>}
 */
export async function calculatePassengers(params) {
  const {
    uwpDep, uwpDest,
    zoneDep = 'normal', zoneDest = 'normal',
    parsecs = 1,
    skillEffect = 0,
    stewardLevel = 0,
  } = params;

  const dep  = parseUWP(uwpDep,  zoneDep);
  const dest = parseUWP(uwpDest, zoneDest);

  const errors = [];
  if (!dep.valid)  errors.push(`Départ : ${dep.error}`);
  if (!dest.valid) errors.push(`Destination : ${dest.error}`);
  if (errors.length) return { success: false, errors };

  // Modificateurs communs aux deux mondes
  function worldMods(w) {
    return populationModifier(w.population, 'passenger')
         + starportModifier(w.starport, 'passenger')
         + zoneModifier(w.zone, 'passenger');
  }

  // Modificateur de distance (au-delà du 1er parsec)
  const distanceMod = -(Math.max(1, parsecs) - 1);

  const categories = ['sup', 'inter', 'eco', 'inf'];
  const categoryMods = { sup: -4, inter: 0, eco: 0, inf: 1 };
  const results = [];

  for (const cat of categories) {
    const baseMods = worldMods(dep) + worldMods(dest) + skillEffect + stewardLevel
                   + categoryMods[cat] + distanceMod;
    const diceRoll = await rollDice('2d6');
    const total    = Math.max(1, diceRoll.total + baseMods);
    const numKey   = String(Math.min(20, Math.max(1, total)));
    const numFormula = PASSENGER_TRAFFIC[numKey] ?? '0';
    const numRoll    = await rollDice(numFormula);

    const costEntry = PASSAGE_COSTS[Math.min(parsecs, 6) - 1];
    const pricePerPax = costEntry ? costEntry[cat] : 0;

    results.push({
      category:    cat,
      label:       { sup: 'Supérieur', inter: 'Intermédiaire', eco: 'Éco', inf: 'Inférieur' }[cat],
      modifiers:   baseMods,
      diceResult:  diceRoll.total,
      trafficTotal: total,
      countFormula: numFormula,
      count:        numRoll.total,
      pricePerPax,
      revenue:      numRoll.total * pricePerPax,
    });
  }

  const totalRevenue = results.reduce((s, r) => s + r.revenue, 0);

  return {
    success: true,
    type: 'passengers',
    dep:  { uwp: dep.raw, starport: dep.starport, population: dep.population, tradeCodes: dep.tradeCodes },
    dest: { uwp: dest.raw, starport: dest.starport, population: dest.population, tradeCodes: dest.tradeCodes },
    parsecs,
    categories: results,
    totalRevenue,
  };
}

// ─── Cargaison ────────────────────────────────────────────────────────────────

/**
 * Calcule le trafic de cargaison et le courrier disponibles.
 *
 * @param {object} params
 * @param {string} params.uwpDep
 * @param {string} params.uwpDest
 * @param {string} params.zoneDep
 * @param {string} params.zoneDest
 * @param {number} params.parsecs
 * @param {number} params.skillEffect   Effet du test Courtier/Sens de rue
 * @param {number} params.navyRank      Rang le plus élevé dans la Marine (pour courrier)
 * @param {number} params.scoutRank     Rang le plus élevé dans les Éclaireurs (pour courrier)
 * @param {number} params.socMod        MD SOC le plus élevé des Voyageurs (pour courrier)
 * @param {boolean} params.armed        Le vaisseau est-il armé ?
 * @returns {Promise<object>}
 */
export async function calculateCargo(params) {
  const {
    uwpDep, uwpDest,
    zoneDep = 'normal', zoneDest = 'normal',
    parsecs = 1,
    skillEffect = 0,
    navyRank = 0, scoutRank = 0, socMod = 0,
    armed = false,
  } = params;

  const dep  = parseUWP(uwpDep,  zoneDep);
  const dest = parseUWP(uwpDest, zoneDest);

  const errors = [];
  if (!dep.valid)  errors.push(`Départ : ${dep.error}`);
  if (!dest.valid) errors.push(`Destination : ${dest.error}`);
  if (errors.length) return { success: false, errors };

  const distanceMod = -(Math.max(1, parsecs) - 1);

  function worldCargoBonuses(w) {
    return populationModifier(w.population, 'cargo')
         + starportModifier(w.starport, 'cargo')
         + zoneModifier(w.zone, 'cargo')
         + (w.techLevel >= 9 ? 2 : 0)
         + (w.techLevel <= 6 ? -1 : 0);
  }

  const baseMod = worldCargoBonuses(dep) + worldCargoBonuses(dest)
                + skillEffect + distanceMod;

  const lotTypes = [
    { key: 'access', label: 'Lots accessoires', sizeMod:  2 },
    { key: 'minor',  label: 'Lots mineurs',     sizeMod:  0 },
    { key: 'major',  label: 'Lots majeurs',      sizeMod: -4 },
  ];

  const lots = [];
  let trafficMd = 0; // On garde le MD de trafic pour le calcul du courrier (avg)

  for (const lt of lotTypes) {
    const dice = await rollDice('2d6');
    const total = Math.max(1, dice.total + baseMod + lt.sizeMod);
    trafficMd = baseMod; // conserve le MD de base pour le courrier
    const numKey     = String(Math.min(20, Math.max(1, total)));
    const numFormula = CARGO_TRAFFIC[numKey] ?? '0';
    const numRoll    = await rollDice(numFormula);

    const tonsDice  = CARGO_LOT_SIZES[lt.key].formula;
    const tonsEntry = await rollDice(tonsDice);
    const costEntry = PASSAGE_COSTS[Math.min(parsecs, 6) - 1];
    const ratePerTon = costEntry ? costEntry.freight : 0;

    lots.push({
      key:     lt.key,
      label:   lt.label,
      modifiers: baseMod + lt.sizeMod,
      diceResult: dice.total,
      trafficTotal: total,
      countFormula: numFormula,
      count:    numRoll.total,
      tonsPerLot: tonsEntry.total,
      ratePerTon,
      revenue: numRoll.total * tonsEntry.total * ratePerTon,
    });
  }

  // ── Courrier ──────────────────────────────────────────────────────────────
  // MD courrier = MD trafic cargaison converti + autres modificateurs
  const mailMd = _cargoMdToMailMd(trafficMd)
               + (armed ? 2 : 0)
               + (dep.techLevel <= 5 ? -4 : 0)
               + Math.max(navyRank, scoutRank)
               + socMod;

  const mailDice = await rollDice('2d6');
  const mailTotal = mailDice.total + mailMd;
  let mail = null;
  if (mailTotal >= 12) {
    const mailCount = await rollDice('1d6');
    mail = {
      available: true,
      count: mailCount.total,
      revenuePerContainer: 25000,
      revenue: mailCount.total * 25000,
    };
  } else {
    mail = { available: false, count: 0, revenue: 0 };
  }

  const totalRevenue = lots.reduce((s, l) => s + l.revenue, 0) + mail.revenue;

  return {
    success: true,
    type: 'cargo',
    dep:  { uwp: dep.raw, starport: dep.starport, techLevel: dep.techLevel },
    dest: { uwp: dest.raw, starport: dest.starport, techLevel: dest.techLevel },
    parsecs,
    lots,
    mail,
    totalRevenue,
  };
}

/** Convertit le MD de trafic de cargaison en MD courrier (table page 237). */
function _cargoMdToMailMd(md) {
  if (md <= -10) return -2;
  if (md <= -5)  return -1;
  if (md <= 4)   return  0;
  if (md <= 9)   return  1;
  return 2;
}

// ─── Commerce spéculatif ─────────────────────────────────────────────────────

/**
 * Détermine les marchandises disponibles chez un fournisseur sur un monde donné.
 * Ne roule pas encore les prix (étape séparée).
 *
 * @param {object} params
 * @param {string} params.uwp
 * @param {string} params.zone
 * @param {boolean} params.blackMarket     Recherche sur le marché noir ?
 * @param {number}  params.brokerSkill     Niveau de compétence Courtier du PJ
 * @param {number}  params.previousAttempts Nombre de tentatives précédentes ce mois (MD –1 chacune)
 * @returns {Promise<object>}
 */
export async function findAvailableGoods(params) {
  const {
    uwp,
    zone = 'normal',
    blackMarket = false,
    brokerSkill = 0,
    previousAttempts = 0,
  } = params;
  const world = parseUWP(uwp, zone);
  if (!world.valid) return { success: false, errors: [world.error] };

  const tradeCodes = world.tradeCodes;

  // Jet de recherche de fournisseur : 2D + Courtier – tentatives précédentes
  // (MGT2 p. 240 : chaque tentative précédente ce mois = MD –1)
  const supplierMod  = brokerSkill - previousAttempts;
  const supplierRoll = await rollDice('2d6');
  const supplierTotal = supplierRoll.total + supplierMod;

  // Marchandises dont la disponibilité correspond au monde
  const eligible = GOODS_TABLE.filter(g => {
    if (blackMarket) {
      // Marché noir : uniquement les marchandises illégales (D66 6x), en respectant la disponibilité
      return g.illegal && (g.availability.includes('all') || g.availability.some(code => tradeCodes.includes(code)));
    }
    if (g.illegal) return false; // exclut les marchandises illégales du marché normal
    if (g.availability.includes('all')) return true;
    return g.availability.some(code => tradeCodes.includes(code));
  });

  // Quantité aléatoire déterminée pour chaque marchandise disponible
  const goods = [];
  for (const g of eligible) {
    const { formula: tonsFormula, modifier: tonsMod } = adjustTonsDice(g.tonsDice, world.population);
    const tonsRoll = await rollDice(tonsFormula);
    const tons     = Math.max(0, tonsRoll.total + tonsMod);
    const buyMod   = highestApplicableMod(g.buyMod, tradeCodes, world.zone);
    const sellMod  = highestApplicableMod(g.sellMod, tradeCodes, world.zone);

    goods.push({
      d66:       g.d66,
      name:      g.name,
      basePrice: g.basePrice,
      illegal:   g.illegal,
      tons,
      buyMod,
      sellMod,
    });
  }

  return {
    success: true,
    type: 'trade-goods',
    world: { uwp: world.raw, tradeCodes, population: world.population },
    supplierRoll: { dice: supplierRoll.total, mod: supplierMod, total: supplierTotal },
    goods,
  };
}

/**
 * Calcule le prix d'achat ou de vente pour une marchandise donnée.
 *
 * @param {object} params
 * @param {number} params.basePrice
 * @param {number} params.buyMod     MD d'achat applicable (+)
 * @param {number} params.sellMod    MD de vente applicable (−)
 * @param {number} params.brokerSkill  Compétence Courtier du Voyageur
 * @param {string} params.mode       'buy'|'sell'
 * @returns {Promise<object>}
 */
export async function calculatePrice(params) {
  const { basePrice, buyMod = 0, sellMod = 0, brokerSkill = 0, mode = 'buy' } = params;

  // Prix d'achat : 3D + Courtier Voyageur + MD achat – MD vente – Courtier fournisseur (2)
  // Prix de vente : 3D + Courtier Voyageur + MD vente – MD achat – Courtier acheteur (2)
  const supplierBroker = 2; // on suppose toujours 2 (règle de base)

  let modifier;
  if (mode === 'buy') {
    modifier = brokerSkill + buyMod - sellMod - supplierBroker;
  } else {
    modifier = brokerSkill + sellMod - buyMod - supplierBroker;
  }

  const dice = await rollDice('3d6');
  const total = dice.total + modifier;
  const priceEntry = getModifiedPrice(total);

  const percent     = mode === 'buy' ? priceEntry.buy : priceEntry.sell;
  const actualPrice = Math.round(basePrice * percent / 100);

  return {
    success: true,
    mode,
    diceResult: dice.total,
    modifier,
    total,
    percent,
    basePrice,
    actualPrice,
  };
}

// ─── Utilitaires ─────────────────────────────────────────────────────────────

/**
 * Retourne le plus grand modificateur applicable parmi les codes commerciaux du monde.
 * @param {object} modMap   { codeCommercial: modificateur }
 * @param {string[]} codes  Codes commerciaux du monde
 * @param {string} zone     'normal'|'amber'|'red'
 * @returns {number}
 */
export function highestApplicableMod(modMap, codes, zone = 'normal') {
  let max = 0;
  for (const [code, val] of Object.entries(modMap)) {
    if (code === 'ZA' && zone === 'amber' && val > max) max = val;
    else if (code === 'ZR' && zone === 'red' && val > max) max = val;
    else if (codes.includes(code) && val > max) max = val;
  }
  return max;
}

/**
 * Ajuste la formule de tonnes en fonction de la population du monde.
 * Pop ≤ 3 : MD –3 ; Pop ≥ 9 : MD +3 (minimum 1 tonne).
 * Retourne un objet { formula, modifier } — l'appelant applique le modificateur
 * APRÈS le jet et clame à Math.max(0, ...) pour éviter d'utiliser max() dans la
 * formule de dés (non supporté par l'API Roll de FoundryVTT).
 */
function adjustTonsDice(formula, population) {
  if (formula === '0') return { formula: '0', modifier: 0 };
  if (population <= 3) return { formula, modifier: -3 };
  if (population >= 9) return { formula, modifier: +3 };
  return { formula, modifier: 0 };
}

/**
 * Formate un montant en Crédits avec séparateur de milliers.
 * @param {number} amount
 * @returns {string}
 */
export function formatCredits(amount) {
  return `${amount.toLocaleString('fr-FR')} Cr`;
}
