import { GOODS_TABLE } from './data/tradeTables.js';
import {
  NPC_RELATIONS,
  EXPERIENCE_PROFILES,
  ALLIES_ENEMIES_TABLE,
  CHARACTER_QUIRKS_TABLE,
  RANDOM_CLIENT_TABLE,
  RANDOM_MISSION_TABLE,
  RANDOM_TARGET_TABLE,
  RANDOM_OPPOSITION_TABLE,
  ENCOUNTER_CONTEXTS,
} from './data/npcTables.js';

const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const SKILL_PACK_ID = `${MODULE_ID}.competences`;

const CORE_CHARACTERISTICS = ['strength', 'dexterity', 'endurance', 'intellect', 'education', 'social'];
const DEFAULT_PRIORITIES = {
  'Non-combattant': ['intellect', 'education', 'social', 'dexterity', 'endurance', 'strength'],
  'Combattant': ['dexterity', 'endurance', 'strength', 'education', 'intellect', 'social'],
};

const ROLE_HINTS = [
  { match: /médecin/i, skills: ['Médecine', 'Science'], priorities: ['education', 'intellect', 'dexterity'] },
  { match: /scientifique|chercheur/i, skills: ['Science', 'Électronique'], priorities: ['intellect', 'education', 'dexterity'] },
  { match: /diplomate|ambassadeur|attaché culturel/i, skills: ['Diplomatie', 'Langage', 'Persuader'], priorities: ['social', 'education', 'intellect'] },
  { match: /marchand|franc-marchand|courtier/i, skills: ['Courtier', 'Administration', 'Persuader'], priorities: ['social', 'education', 'intellect'] },
  { match: /mercenaire|seigneur de guerre/i, skills: ['Combat Arme', 'Mêlée', 'Reconnaissance'], priorities: ['dexterity', 'endurance', 'strength'] },
  { match: /officier de marine|amiral|capitaine/i, skills: ['Leadership', 'Marin', 'Tactique'], priorities: ['education', 'social', 'intellect'] },
  { match: /explorateur|éclaireur/i, skills: ['Reconnaissance', 'Survie', 'Navigation'], priorities: ['education', 'dexterity', 'endurance'] },
  { match: /interprète|xéno/i, skills: ['Langage', 'Diplomatie', 'Science'], priorities: ['education', 'intellect', 'social'] },
  { match: /cadre de corpo|agent corpo|administrateur|gouverneur|homme d'état|noble/i, skills: ['Administration', 'Leadership', 'Diplomatie'], priorities: ['social', 'education', 'intellect'] },
  { match: /journaliste|enquêteur|inspecteur|agent impérial/i, skills: ['Enquêter', 'Persuader', 'Combat Arme'], priorities: ['intellect', 'education', 'dexterity'] },
  { match: /conspirateur|criminel|contrebandier/i, skills: ['Duperie', 'Sens de la rue', 'Combat Arme'], priorities: ['social', 'dexterity', 'intellect'] },
  { match: /chef religieux|cultiste/i, skills: ['Persuader', 'Leadership', 'Langage'], priorities: ['social', 'education', 'intellect'] },
  { match: /joueur|playboy/i, skills: ['Flambeur', 'Mondanités', 'Persuader'], priorities: ['social', 'intellect', 'education'] },
  { match: /intelligence artificielle/i, skills: ['Électronique', 'Science', 'Profession'], priorities: ['intellect', 'education', 'social'] },
];

function getD66Entry(entries, total) {
  return entries.find((entry) => entry.d66 === total) ?? null;
}

async function rollFormula(formula) {
  const roll = await new Roll(formula).evaluate();
  return { formula, total: roll.total };
}

async function rollD66(entries) {
  const tens = await rollFormula('1d6');
  const ones = await rollFormula('1d6');
  const total = (tens.total * 10) + ones.total;
  return {
    formula: 'D66',
    tens: tens.total,
    ones: ones.total,
    total,
    entry: getD66Entry(entries, total),
  };
}

async function rollFlat(entries) {
  const roll = await rollFormula(`1d${entries.length}`);
  return {
    formula: `1d${entries.length}`,
    total: roll.total,
    entry: entries[roll.total - 1] ?? null,
  };
}

function pickGoodsPool(type) {
  if (type === 'illegal-goods') return GOODS_TABLE.filter((good) => good.illegal);
  if (type === 'trade-goods') return GOODS_TABLE.filter((good) => !good.illegal);
  return [];
}

async function resolveSpecialTarget(entry) {
  if (!entry?.special) return null;

  switch (entry.special) {
    case 'client': {
      const nested = await rollD66(RANDOM_CLIENT_TABLE);
      return {
        label: 'Client tiré',
        roll: nested,
        text: nested.entry?.text ?? '',
      };
    }
    case 'ally-enemy': {
      const nested = await rollD66(ALLIES_ENEMIES_TABLE);
      return {
        label: 'PNJ tiré',
        roll: nested,
        text: nested.entry?.text ?? '',
      };
    }
    case 'trade-goods':
    case 'illegal-goods': {
      const goods = pickGoodsPool(entry.special);
      if (!goods.length) return null;
      const nested = await rollFlat(goods);
      return {
        label: entry.special === 'illegal-goods' ? 'Marchandise illicite' : 'Marchandise tirée',
        roll: nested,
        text: nested.entry?.name ?? '',
      };
    }
    default:
      return null;
  }
}

function getExperiencePool(mode) {
  if (mode === 'combatant') return EXPERIENCE_PROFILES.filter((entry) => entry.category === 'Combattant');
  if (mode === 'noncombatant') return EXPERIENCE_PROFILES.filter((entry) => entry.category === 'Non-combattant');
  return EXPERIENCE_PROFILES;
}

async function generateExperience(mode = 'random') {
  const pool = getExperiencePool(mode);
  const roll = await rollFlat(pool);
  return {
    roll,
    profile: roll.entry,
  };
}

function findRoleHint(roleName, category) {
  const hint = ROLE_HINTS.find((entry) => entry.match.test(roleName));
  return hint ?? {
    skills: ['Profession'],
    priorities: DEFAULT_PRIORITIES[category] ?? DEFAULT_PRIORITIES['Non-combattant'],
  };
}

function toHex(value) {
  return Math.max(0, Math.min(15, value)).toString(16).toUpperCase();
}

function calculateDm(value) {
  return Math.floor((value - 6) / 3);
}

function buildCharacteristicValues(result) {
  const profile = result.experience.profile;
  const hint = findRoleHint(result.role.entry.text, profile.category);
  const values = Object.fromEntries(CORE_CHARACTERISTICS.map((key) => [key, 7]));

  profile.characteristicBonuses.forEach((bonus, index) => {
    const key = hint.priorities[index] ?? hint.priorities.at(-1) ?? CORE_CHARACTERISTICS[0];
    values[key] += Number.parseInt(String(bonus).replace('+', ''), 10);
  });

  return {
    values,
    hint,
    ucp: CORE_CHARACTERISTICS.map((key) => toHex(values[key])).join(''),
  };
}

function buildCharacteristicsData(values) {
  const allKeys = {
    strength: { showMax: true },
    dexterity: { showMax: true },
    endurance: { showMax: true },
    intellect: { showMax: false },
    education: { showMax: false },
    social: { showMax: false },
    morale: { showMax: false, value: 0 },
    luck: { showMax: false, value: 0 },
    sanity: { showMax: false, value: 0 },
    charm: { showMax: false, value: 0 },
    psionic: { showMax: false, value: 0 },
    other: { showMax: false, value: 0 },
  };

  return Object.fromEntries(Object.entries(allKeys).map(([key, config]) => {
    const value = values[key] ?? config.value ?? 0;
    return [key, {
      value,
      max: value,
      dm: calculateDm(value),
      show: true,
      showMax: config.showMax,
    }];
  }));
}

async function getSkillPackIndex() {
  const pack = game.packs.get(SKILL_PACK_ID);
  if (!pack) throw new Error(`Pack de compétences introuvable : ${SKILL_PACK_ID}`);
  const index = await pack.getIndex();
  return { pack, index };
}

function mergeSkillLevels(profileSkills, roleSkills, baseLevel) {
  const levels = new Map();

  for (const skill of profileSkills) levels.set(skill, baseLevel);
  for (const skill of roleSkills) levels.set(skill, Math.max(levels.get(skill) ?? 0, Math.max(1, baseLevel)));

  return levels;
}

async function buildSkillItems(result) {
  const { pack, index } = await getSkillPackIndex();
  const { hint } = buildCharacteristicValues(result);
  const skillLevels = mergeSkillLevels(result.experience.profile.skills, hint.skills, result.experience.profile.skillLevel);
  const items = [];

  for (const [skillName, level] of skillLevels.entries()) {
    const entry = index.contents.find((item) => item.name === skillName);
    if (!entry) continue;
    const document = await pack.getDocument(entry._id);
    const data = document.toObject();
    delete data._id;
    delete data.folder;
    data.system.level = level;
    items.push(data);
  }

  return items;
}

function buildActorDescription(result, actorName, ucp) {
  return [
    `${actorName} — ${result.role.entry.text}`,
    `Relation : ${result.relation.label}`,
    `Particularité : ${result.quirk.entry.text}`,
    `Expérience : ${result.experience.profile.label}`,
    `UCP : ${ucp}`,
  ].join('\n');
}

export function formatSigned(value) {
  return value >= 0 ? `+${value}` : `${value}`;
}

export async function generateQuickNpc(params = {}) {
  const relationKey = params.relation && NPC_RELATIONS[params.relation] ? params.relation : 'contact';
  const relation = { key: relationKey, ...NPC_RELATIONS[relationKey] };
  const role = await rollD66(ALLIES_ENEMIES_TABLE);
  const quirk = await rollD66(CHARACTER_QUIRKS_TABLE);
  const experience = await generateExperience(params.experienceBias ?? 'random');

  return {
    success: true,
    type: 'npc',
    relation,
    role,
    quirk,
    experience,
  };
}

export async function createNpcActor(result, options = {}) {
  const actorName = options.name?.trim() || `PNJ — ${result.role.entry.text}`;
  const { values, ucp } = buildCharacteristicValues(result);
  const items = await buildSkillItems(result);
  const actor = await Actor.create({
    name: actorName,
    type: 'character',
    img: 'icons/svg/mystery-man.svg',
    system: {
      life: {
        value: values.endurance,
        max: values.endurance,
      },
      personal: {
        title: result.role.entry.text,
        species: '',
        speciesText: {},
        age: '',
        ucp,
        traits: [
          { name: result.relation.label },
          { name: result.quirk.entry.text },
        ],
      },
      characteristics: buildCharacteristicsData(values),
      biography: buildActorDescription(result, actorName, ucp),
      notes: buildActorDescription(result, actorName, ucp),
    },
    flags: {
      [MODULE_ID]: {
        generatedNpc: {
          relation: result.relation.key,
          role: result.role.entry.text,
          quirk: result.quirk.entry.text,
          experience: result.experience.profile.label,
        },
      },
    },
  }, { renderSheet: false });

  if (items.length) await actor.createEmbeddedDocuments('Item', items);
  if (options.openSheet !== false) actor.sheet?.render(true);
  return actor;
}

export async function generateClientMission() {
  const client = await rollD66(RANDOM_CLIENT_TABLE);
  const mission = await rollD66(RANDOM_MISSION_TABLE);
  const target = await rollD66(RANDOM_TARGET_TABLE);
  const opposition = await rollD66(RANDOM_OPPOSITION_TABLE);
  const targetResolution = await resolveSpecialTarget(target.entry);

  return {
    success: true,
    type: 'client-mission',
    client,
    mission,
    target,
    targetResolution,
    opposition,
    rewardGuidance: "Le PDF ne fournit pas de table de rémunération détaillée : négociez une récompense légèrement supérieure à ce que les Voyageurs gagneraient via le commerce.",
  };
}

function getEncounterContext(context) {
  return ENCOUNTER_CONTEXTS[context] ?? ENCOUNTER_CONTEXTS.starport;
}

async function resolveEncounterFollowUp(followUp) {
  if (followUp === 'client-mission') return generateClientMission();
  if (followUp === 'npc-contact') return generateQuickNpc({ relation: 'contact', experienceBias: 'random' });
  return null;
}

export async function generateEncounter(params = {}) {
  const context = getEncounterContext(params.context);
  const encounter = await rollD66(context.entries);
  const followUp = params.includeFollowUp === false ? null : await resolveEncounterFollowUp(encounter.entry?.followUp);

  return {
    success: true,
    type: 'encounter',
    context: {
      key: params.context ?? 'starport',
      label: context.label,
    },
    encounter,
    followUp,
  };
}
