import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";

const TABLE_CARACTERISTIQUES_DERIVEES = {
  // xp: coût pour passer du niveau inférieur à ce niveau
  1: { xp: 3, niveau: -5, taille: 40, poids: "moins de 1kg", poidsMin: 0, poidsMax: 1, plusdom: -5, sconst: 0.5, sust: 0.1 },
  2: { xp: 3, niveau: -4, taille: 70, poids: "1-5", poidsMin: 1, poidsMax: 5, plusdom: -4, sconst: 0.5, sust: 0.3 },
  3: { xp: 4, niveau: -3, taille: 100, poids: "6-10", poidsMin: 6, poidsMax: 10, plusdom: -3, sconst: 1, sust: 0.5, beaute: 'hideux' },
  4: { xp: 4, niveau: -2, taille: 120, poids: "11-20", poidsMin: 11, poidsMax: 20, plusdom: -3, sconst: 1, sust: 1, beaute: 'repoussant' },
  5: { xp: 5, niveau: -1, taille: 135, poids: "21-30", poidsMin: 21, poidsMax: 30, plusdom: -2, sconst: 1, sust: 1, beaute: 'franchement très laid' },
  6: { xp: 5, niveau: 0, taille: 145, poids: "31-40", poidsMin: 31, poidsMax: 40, plusdom: -1, sconst: 2, sust: 2, beaute: 'laid' },
  7: { xp: 6, niveau: 0, taille: 150, poids: "41-50", poidsMin: 41, poidsMax: 50, plusdom: -1, sconst: 2, sust: 2, beaute: 'très désavantagé' },
  8: { xp: 6, niveau: 0, taille: 155, poids: "51-60", poidsMin: 51, poidsMax: 60, plusdom: 0, sconst: 2, sust: 2, beaute: 'désavantagé' },
  9: { xp: 7, niveau: 0, taille: 160, poids: "61-65", poidsMin: 61, poidsMax: 65, plusdom: 0, sconst: 3, sust: 2, beaute: 'pas terrible' },
  10: { xp: 7, niveau: 0, taille: 170, poids: "66-70", poidsMin: 66, poidsMax: 70, plusdom: 0, sconst: 3, sust: 3, beaute: 'commun' },
  11: { xp: 8, niveau: 1, taille: 175, poids: "71-75", poidsMin: 71, poidsMax: 75, plusdom: 0, sconst: 3, sust: 3, beaute: 'pas mal' },
  12: { xp: 8, niveau: 1, taille: 180, poids: "76-80", poidsMin: 76, poidsMax: 80, plusdom: +1, sconst: 4, sust: 3, beaute: 'avantagé' },
  13: { xp: 9, niveau: 2, taille: 185, poids: "81-90", poidsMin: 81, poidsMax: 90, plusdom: +1, sconst: 4, sust: 3, beaute: 'mignon' },
  14: { xp: 9, niveau: 2, taille: 190, poids: "91-100", poidsMin: 91, poidsMax: 100, plusdom: +2, sconst: 4, sust: 4, beaute: 'beau' },
  15: { xp: 10, niveau: 3, taille: 200, poids: "101-110", poidsMin: 101, poidsMax: 110, plusdom: +2, sconst: 5, sust: 4, beaute: 'très beau' },
  16: { xp: 20, niveau: 3, taille: 210, poids: "111-120", poidsMin: 111, poidsMax: 120, plusdom: +3, sconst: 5, sust: 4, beaute: 'éblouissant' },
  17: { xp: 30, niveau: 4, taille: 220, poids: "121-131", poidsMin: 121, poidsMax: 131, plusdom: +3, sconst: 5, sust: 5 },
  18: { xp: 40, niveau: 4, taille: 230, poids: "131-141", poidsMin: 131, poidsMax: 141, plusdom: +4, sconst: 6, sust: 5 },
  19: { xp: 50, niveau: 5, taille: 240, poids: "141-150", poidsMin: 141, poidsMax: 150, plusdom: +4, sconst: 6, sust: 5 },
  20: { xp: 60, niveau: 5, taille: 250, poids: "151-160", poidsMin: 151, poidsMax: 160, plusdom: +4, sconst: 6, sust: 6 },
  21: { xp: 70, niveau: 6, taille: 260, poids: "161-180", poidsMin: 161, poidsMax: 180, plusdom: +5, sconst: 7, sust: 6 },
  22: { xp: 80, niveau: 6, taille: 270, poids: "181-200", poidsMin: 181, poidsMax: 200, plusdom: +5, sconst: 7, sust: 7 },
  23: { xp: 90, niveau: 7, taille: 280, poids: "201-300", poidsMin: 201, poidsMax: 300, plusdom: +6, sconst: 7, sust: 8 },
  24: { xp: 100, niveau: 7, taille: 290, poids: "301-400", poidsMin: 301, poidsMax: 400, plusdom: +6, sconst: 8, sust: 9 },
  25: { xp: 110, niveau: 8, taille: 300, poids: "401-500", poidsMin: 401, poidsMax: 500, plusdom: +7, sconst: 8, sust: 10 },
  26: { xp: 120, niveau: 8, taille: 310, poids: "501-600", poidsMin: 501, poidsMax: 600, plusdom: +7, sconst: 8, sust: 11 },
  27: { xp: 130, niveau: 9, taille: 320, poids: "601-700", poidsMin: 601, poidsMax: 700, plusdom: +8, sconst: 9, sust: 12 },
  28: { xp: 140, niveau: 9, taille: 330, poids: "701-800", poidsMin: 701, poidsMax: 800, plusdom: +8, sconst: 9, sust: 13 },
  29: { xp: 150, niveau: 10, taille: 340, poids: "801-900", poidsMin: 801, poidsMax: 900, plusdom: +9, sconst: 9, sust: 14 },
  30: { xp: 160, niveau: 10, taille: 350, poids: "901-1000", poidsMin: 901, poidsMax: 1000, plusdom: +9, sconst: 10, sust: 15 },
  31: { xp: 170, niveau: 11, taille: 360, poids: "1001-1500", poidsMin: 1001, poidsMax: 1500, plusdom: +10, sconst: 10, sust: 16 },
  32: { xp: 180, niveau: 11, taille: 370, poids: "1501-2000", poidsMin: 1501, poidsMax: 2000, plusdom: +11, sconst: 10, sust: 17 },
};

export const CARACS = {
  TAILLE: 'taille',
  APPARENCE: 'apparence',
  CONSTITUTION: 'constitution',
  FORCE: 'force',
  AGILITE: 'agilite',
  DEXTERITE: 'dexterite',
  VUE: 'vue',
  OUIE: 'ouie',
  ODORATGOUT: 'odoratgout',
  VOLONTE: 'volonte',
  INTELLECT: 'intellect',
  EMPATHIE: 'empathie',
  REVE: 'reve',
  CHANCE: 'chance',
  PROTECTION: 'protection',
  BEAUTE: 'beaute',
  PERCEPTION: 'perception',
  MELEE: 'melee',
  TIR: 'tir',
  LANCER: 'lancer',
  DEROBEE: 'derobee',
  CHANCE_ACTUELLE: 'chance-actuelle',
  REVE_ACTUEL: 'reve-actuel',
  VIE: 'vie'
}

export const LIST_CARAC_PERSONNAGE = {
  [CARACS.TAILLE]: { code: CARACS.TAILLE, label: 'Taille', isCarac: true, path: 'system.carac.taille.value' },
  [CARACS.APPARENCE]: { code: CARACS.APPARENCE, label: 'Apparence', isCarac: true, path: 'system.carac.apparence.value' },
  [CARACS.CONSTITUTION]: { code: CARACS.CONSTITUTION, label: 'Constitution', isCarac: true, path: 'system.carac.constitution.value' },
  [CARACS.FORCE]: { code: CARACS.FORCE, label: 'Force', isCarac: true, path: 'system.carac.force.value' },
  [CARACS.AGILITE]: { code: CARACS.AGILITE, label: 'Agilité', isCarac: true, path: 'system.carac.agilite.value' },
  [CARACS.DEXTERITE]: { code: CARACS.DEXTERITE, label: 'Dextérité', isCarac: true, path: 'system.carac.dexterite.value' },
  [CARACS.VUE]: { code: CARACS.VUE, label: 'Vue', isCarac: true, path: 'system.carac.vue.value' },
  [CARACS.OUIE]: { code: CARACS.OUIE, label: 'Ouïe', isCarac: true, path: 'system.carac.ouie.value' },
  [CARACS.ODORATGOUT]: { code: CARACS.ODORATGOUT, label: 'Odorat-Goût', isCarac: true, path: 'system.carac.odoratgout.value' },
  [CARACS.VOLONTE]: { code: CARACS.VOLONTE, label: 'Volonté', isCarac: true, path: 'system.carac.volonte.value' },
  [CARACS.INTELLECT]: { code: CARACS.INTELLECT, label: 'Intellect', isCarac: true, path: 'system.carac.intellect.value' },
  [CARACS.EMPATHIE]: { code: CARACS.EMPATHIE, label: 'Empathie', isCarac: true, path: 'system.carac.empathie.value' },
  [CARACS.REVE]: { code: CARACS.REVE, label: 'Rêve', isCarac: true, path: 'system.carac.reve.value' },
  [CARACS.CHANCE]: { code: CARACS.CHANCE, label: 'Chance', isCarac: true, path: 'system.carac.chance.value' },
  [CARACS.PROTECTION]: { code: CARACS.PROTECTION, label: 'Protection naturelle', isCarac: false, path: 'system.attributs.protection.value' },
  [CARACS.BEAUTE]: { code: CARACS.BEAUTE, label: 'Beauté', isCarac: false, path: 'system.background.beaute.value' }
}

export const LIST_CARAC_AUTRES = {
  [CARACS.PERCEPTION]: { code: 'perception', label: 'Perception', path: 'system.carac.perception.value' },
}

const LIST_CARAC_DERIVEE = {
  [CARACS.MELEE]: { code: CARACS.MELEE, label: 'Mêlée', path: 'system.carac.melee.value' },
  [CARACS.TIR]: { code: CARACS.TIR, label: 'Tir', path: 'system.carac.tir.value' },
  [CARACS.LANCER]: { code: CARACS.LANCER, label: 'Lancer', path: 'system.carac.lancer.value' },
  [CARACS.DEROBEE]: { code: CARACS.DEROBEE, label: 'Dérobée', path: 'system.carac.derobee.value' },
  [CARACS.CHANCE_ACTUELLE]: { code: CARACS.CHANCE_ACTUELLE, label: 'Chance actuelle', path: 'system.carac.lancer.value' },
  [CARACS.REVE_ACTUEL]: { code: CARACS.REVE_ACTUEL, label: 'Rêve actuel', path: 'system.reve.reve.value' },
}

export const LIST_CARAC_ROLL = Object.values(LIST_CARAC_PERSONNAGE).filter(it => it.isCarac && it.code != 'taille')
  .concat(Object.values(LIST_CARAC_AUTRES))
  .concat(Object.values(LIST_CARAC_DERIVEE))

export class RdDCarac {

  static caracDetails(name, options = { onMessage: undefined }) {
    let entry = Misc.findFirstLike(name, LIST_CARAC_ROLL, { mapper: it => it.code, description: 'caractéristique', onMessage: m => { } })
    if (entry) {
      return entry
    }
    return Misc.findFirstLike(name, LIST_CARAC_ROLL, { mapper: it => it.label, description: 'caractéristique', onMessage: options.onMessage })
  }

  static carac(code) {
    return LIST_CARAC_PERSONNAGE[code]
  }

  static label(code) {
    return RdDCarac.carac(code)?.label ?? '---'
  }

  static caracs(filter = it => it.isCarac) {
    return Object.values(LIST_CARAC_PERSONNAGE).filter(filter)
  }

  static isAgiliteOuDerobee(caracLabel) {
    return RdDCarac.isAgilite(caracLabel)
      || RdDCarac.isDerobee(caracLabel)
  }

  static isDerobee(caracLabel) {
    return Grammar.equalsInsensitive(caracLabel, LIST_CARAC_PERSONNAGE.agilite.code);
  }

  static isAgilite(caracLabel) {
    return Grammar.equalsInsensitive(caracLabel, LIST_CARAC_DERIVEE.derobee.code);
  }

  static isIntellect(caracLabel) {
    return Grammar.toLowerCaseNoAccent(caracLabel) == CARACS.INTELLECT
  }

  static isVolonte(caracLabel) {
    return Grammar.toLowerCaseNoAccent(caracLabel) == CARACS.VOLONTE
  }
  static isChance(caracLabel) {
    return Grammar.toLowerCaseNoAccent(caracLabel)?.match(/chance(( |-)?actuelle)?/);
  }
  static isReve(caracLabel) {
    return Grammar.toLowerCaseNoAccent(caracLabel)?.match(/reve(( |-)?actuel)?/);
  }

  /**
   * L’appel à la chance n’est possible que pour recommencer les jets d’actions physiques :
   * tous les jets de combat, de FORCE, d’AGILITÉ, de DEXTÉRITÉ, de Dérobée, d’APPARENCE,
   * ainsi que de Perception active et volontaire.
   * Le moral ne s'utilise aussi que sur les actions physiques
   */
  static isActionPhysique(caracLabel) {
    return Grammar.toLowerCaseNoAccent(caracLabel)
      ?.match(/(apparence|force|agilite|dexterite|vue|ouie|gout|odorat|empathie|melee|tir|lancer|derobee)/) != null
  }

  static getCaracDerivee(value) {
    return TABLE_CARACTERISTIQUES_DERIVEES[Math.min(Math.max(Math.floor(Number(value)), 1), 32)];
  }

  static computeTotal(carac, beaute = undefined) {
    const total = Object.values(carac ?? {}).filter(c => !c.derivee)
      .map(it => parseInt(it.value))
      .reduce(Misc.sum(), 0);
    const beauteSuperieur10 = Math.max((beaute ?? 10) - 10, 0)
    return total + beauteSuperieur10;
  }

  static computeTotalXp(carac, beaute = undefined) {
    const totalXp = Object.values(carac ?? {}).filter(c => !c.derivee)
      .map(it => RdDCarac.getCaracXp(0, Misc.toInt(it.value)) + Misc.toInt(it.xp))
      .reduce(Misc.sum(), 0);
    const beauteXp = beaute > 10 ? RdDCarac.getCaracXp(10, beaute) : 0
    return totalXp + beauteXp;
  }

  static levelUp(it) {
    it.xpNext = RdDCarac.getCaracNextXp(it.value);
    it.isLevelUp = (it.xp >= it.xpNext);
  }

  /* -------------------------------------------- */
  static calculSConst(constitution) {
    return RdDCarac.getCaracDerivee(constitution).sconst;
  }

  /* -------------------------------------------- */
  static getCaracNextXp(value) {
    value = Number(value);
    const nextValue = value + 1;
    // xp est le coût pour atteindre cette valeur, on regarde donc le coût de la valeur+1
    return RdDCarac.getCaracXp(value, nextValue)
  }

  static getCaracXp(from, to) {
    return Array.from({ length: to - from },
      (_, i) => from + i + 1)
      .map(it => RdDCarac.getCaracDerivee(it)?.xp ?? 200)
      .reduce(Misc.sum(), 0)
  }
}
