/**
 * SPCAMO_Translation.js
 * ─────────────────────────────────────────────────────────────────────────────
 * Cross-game translation tables for SP1, SP2, SPCAMO SPWW2, and SPCAMO SPMBT.
 *
 *   // Reverse lookups (SPCAMO → source game):
 *   SPMBT_TO_SP2_NATION[spmbtIdx]       → SP2 nation index (or undefined)
 *   SPWW2_TO_SP1_NATION[spww2Idx]       → SP1 nation index (or undefined)
 *
 *   // Human-readable nation names by source-game index:
 *   SP2_NATION_NAMES[sp2NationIdx]      → e.g. "West Germany"
 *   SP1_NATION_NAMES[sp1NationIdx]      → e.g. "Germany"
 */

'use strict';

// ─── SP2 → SPMBT Nation Translation ──────────────────────────────────────────
// Index: SP2 nation index  →  Value: corresponding SPMBT nation index.

const SP2_TO_SPMBT_NATION = [
  44, //  0  West Germany   → Germany
   1, //  1  Egypt          → Egypt
   2, //  2  Poland         → Poland
   3, //  3  Syria          → Syria
   4, //  4  Israel         → Israel
   5, //  5  Japan          → Japan
   6, //  6  France         → France
   7, //  7  United Kingdom → Great Britain
   8, //  8  PLO            → PLO
   9, //  9  Jordan         → Jordan
  10, // 10  Iran           → Iran
  11, // 11  Russia         → Russia
  12, // 12  USA            → USA
  13, // 13  USMC           → USMC
  14, // 14  China          → China
  15, // 15  Australia      → Australia
  16, // 16  Gulf States    → Gulf States
  17, // 17  Iraq           → Iraq
  18, // 18  India          → India
  19, // 19  North Korea    → North Korea
  20, // 20  South Korea    → South Korea
  21, // 21  Taiwan         → Taiwan
  22, // 22  Mujahideen     → Mujahideen
  23, // 23  North Vietnam  → Vietnam
  24, // 24  South Vietnam  → South Vietnam
  25, // 25  Pakistan       → Pakistan
  26, // 26  Afghanistan    → Afghanistan
  27, // 27  Belgium        → Belgium
  28, // 28  Netherlands    → Netherlands
  29, // 29  Norway         → Norway
  30, // 30  Canada         → Canada
  31, // 31  Greece         → Greece
  32, // 32  Turkey         → Turkey
  33, // 33  Spain          → Spain
  34, // 34  Italy          → Italy
  35, // 35  Denmark        → Denmark
  36, // 36  East Germany   → East Germany
  37, // 37  Czechoslovakia → Czechoslovakia
  38, // 38  Hungary        → Hungary
  39, // 39  Romania        → Romania
  40, // 40  Libya          → Libya
  41, // 41  Yugoslavia     → Yugoslavia/Serbia
  42, // 42  Green          → Green
  43, // 43  Red            → Red
];

// ─── SP1 → SPWW2 Nation Translation ──────────────────────────────────────────
// Index: SP1 nation index  →  Value: corresponding SPWW2 nation index.

const SP1_TO_SPWW2_NATION = [
  16, //  0  Germany     → Germany
  35, //  1  Finland     → Finland
  34, //  2  Italy       → Italy
  39, //  3  Romania     → Romania
  38, //  4  Hungary     → Hungary
   5, //  5  Japan       → Japan
   6, //  6  France      → France
   7, //  7  UK          → Great Britain
  27, //  8  Belgium     → Belgium
  28, //  9  Netherlands → Netherlands
   2, // 10  Poland      → Poland
  11, // 11  USSR        → Soviet Union
  12, // 12  US Army     → USA
  13, // 13  USMC        → USMC
  21, // 14  China       → Nationalist China
  31, // 15  Greece      → Greece
  29, // 16  Norway      → Norway
  20, // 17  Yugoslavia  → Yugoslavia
];

// ─── Human-readable nation name tables ───────────────────────────────────

const SP1_NATION_NAMES = [
  'Germany',      //  0
  'Finland',      //  1
  'Italy',        //  2
  'Romania',      //  3
  'Hungary',      //  4
  'Japan',        //  5
  'France',       //  6
  'UK',           //  7
  'Belgium',      //  8
  'Netherlands',  //  9
  'Poland',       // 10
  'USSR',         // 11
  'US Army',      // 12
  'USMC',         // 13
  'China',        // 14
  'Greece',       // 15
  'Norway',       // 16
  'Yugoslavia',   // 17
];

const SP2_NATION_NAMES = [
  'West Germany',   //  0
  'Egypt',          //  1
  'Poland',         //  2
  'Syria',          //  3
  'Israel',         //  4
  'Japan',          //  5
  'France',         //  6
  'UK',             //  7
  'PLO',            //  8
  'Jordan',         //  9
  'Iran',           // 10
  'Russia',         // 11
  'USA',            // 12
  'USMC',           // 13
  'China',          // 14
  'Australia',      // 15
  'Gulf States',    // 16
  'Iraq',           // 17
  'India',          // 18
  'North Korea',    // 19
  'South Korea',    // 20
  'Taiwan',         // 21
  'Mujahideen',     // 22
  'North Vietnam',  // 23
  'South Vietnam',  // 24
  'Pakistan',       // 25
  'Afghanistan',    // 26
  'Belgium',        // 27
  'Netherlands',    // 28
  'Norway',         // 29
  'Canada',         // 30
  'Greece',         // 31
  'Turkey',         // 32
  'Spain',          // 33
  'Italy',          // 34
  'Denmark',        // 35
  'East Germany',   // 36
  'Czechoslovakia', // 37
  'Hungary',        // 38
  'Romania',        // 39
  'Libya',          // 40
  'Yugoslavia',     // 41
  'Green',          // 42
  'Red',            // 43
];

// ─── Reverse lookups ─────────────────────────────────────────────────────────
// Built automatically from the forward maps above.

// SPMBT index → SP2 index (undefined if no SP2 equivalent)
const SPMBT_TO_SP2_NATION = {};
SP2_TO_SPMBT_NATION.forEach((spmbt, sp2) => { SPMBT_TO_SP2_NATION[spmbt] = sp2; });

// SPWW2 index → SP1 index (undefined if no SP1 equivalent)
const SPWW2_TO_SP1_NATION = {};
SP1_TO_SPWW2_NATION.forEach((spww2, sp1) => { SPWW2_TO_SP1_NATION[spww2] = sp1; });

// ─── Helper functions ─────────────────────────────────────────────────────────

/**
 * Translate a SP2 nation index to its SPMBT equivalent.
 * Returns null if the index is out of range.
 */
function translateSP2NationToSPMBT(sp2Idx) {
  const r = SP2_TO_SPMBT_NATION[sp2Idx];
  return r !== undefined ? r : null;
}

/**
 * Translate a SP1 nation index to its SPWW2 equivalent.
 * Returns null if the index is out of range.
 */
function translateSP1NationToSPWW2(sp1Idx) {
  const r = SP1_TO_SPWW2_NATION[sp1Idx];
  return r !== undefined ? r : null;
}

/**
 * Translate a SPMBT nation index back to its SP2 equivalent.
 * Returns null if there is no SP2 counterpart.
 */
function translateSPMBTNationToSP2(spmbtIdx) {
  const r = SPMBT_TO_SP2_NATION[spmbtIdx];
  return r !== undefined ? r : null;
}

/**
 * Translate a SPWW2 nation index back to its SP1 equivalent.
 * Returns null if there is no SP1 counterpart.
 */
function translateSPWW2NationToSP1(spww2Idx) {
  const r = SPWW2_TO_SP1_NATION[spww2Idx];
  return r !== undefined ? r : null;
}

// ═══════════════════════════════════════════════════════════════════════════
// UNIT IMPORT / CONVERSION ENGINE
// ═══════════════════════════════════════════════════════════════════════════
//
// Entry point:  importUnitsFromJSON(jsonObj, obatTable, options)
//   jsonObj    — parsed JSON from SP1/SP2/SPCAMO export
//   obatTable  — SPWW2_OBAT or SPMBT_OBAT (from SPCAMO_UnitsWW2/MBT.js)
//   options    — { topN: 3 }  number of candidate matches to return per unit
//
// Returns: ImportResult
//   {
//     sourceGame,       // 'SP1' | 'SP2'
//     scenario,         // scenario metadata
//     nationMap,        // { sp1Idx → spww2Idx, ... } resolved nation → OBAT key
//     units: [          // one entry per source unit
//       {
//         source,       // original JSON unit object
//         candidates,   // [ { obatID, slot, unit[], score, reasons[] }, ... ]
//         chosen,       // index into candidates (default 0), set by UI
//       }
//     ],
//     leaders,          // raw leaders array from JSON
//   }
//
// ─── SP1 unit class name → SPWW2 classID mapping ─────────────────────────
// SP1 uses plain English class names; SPWW2 uses numeric IDs.
// This maps the exact strings that appear in SP1 JSON exports.

const SP1_CLASS_TO_SPWW2_ID = {
  // Infantry types
  'Infantry':           1,
  'Inf-AT':             2,
  'Cavalry':            3,
  'Machine Gun':        4,
  'Machinegun':         4,
  'MG Section':         4,
  'Mortar':             5,
  'AT Gun':             6,
  'Anti-Tank Gun':      6,
  'Flak':               8,
  'Field Gun':          9,
  'Artillery':          9,   // generic — refined by scoring
  'O/M Artillery':      10,
  'Off-Map Artillery':  10,
  'Armored Car':        11,
  'Armoured Car':       11,
  'Light Tank':         12,
  'Medium Tank':        13,
  'Heavy Tank':         14,
  'CS-Tank':            15,
  'Close Support Tank': 15,
  'Flame Tank':         17,
  'Assault Gun':        18,
  'TD':                 19,
  'Tank Destroyer':     19,
  'Engineer':           20,
  'SP Artillery':       21,
  'SP Flak':            22,
  'APC':                24,
  'Halftrack':          24,
  'Half-Track':         24,
  'Heavy Halftrack':    24,
  'APC (Track)':        25,
  'Truck':              27,
  'Medium Truck':       27,
  'Heavy Truck':        28,
  'Fighter-Bomber':     44,
  'Aircraft':           44,
  'Bomber':             62,
  'Rocket':             31,
  'Rocket Artillery':   31,
  'Command Tank':       13,   // treat as Medium Tank; refined by name/scoring
  'Fort':               0,
  'Fortification':      0,
  'Sniper':             45,
  'HQ':                 55,
  'Scout':              58,
  'Motorcycle':         49,
};

// ─── Name-similarity scorer ───────────────────────────────────────────────
// Simple but effective: tokenise both names, measure token overlap,
// also reward shared leading characters (model designation matching).

function _tokenise(name) {
  return name.toLowerCase()
    .replace(/[^a-z0-9]/g, ' ')
    .split(/\s+/)
    .filter(Boolean);
}

function _nameSimilarity(a, b) {
  if (!a || !b) return 0;
  const ta = new Set(_tokenise(a));
  const tb = new Set(_tokenise(b));
  if (ta.size === 0 || tb.size === 0) return 0;
  let shared = 0;
  ta.forEach(t => { if (tb.has(t)) shared++; });
  // Jaccard-style over union
  const union = new Set([...ta, ...tb]).size;
  return shared / union;
}

// ─── Weapon-set similarity ────────────────────────────────────────────────
// Compare the set of weapon names from the source unit against the OBAT entry.
// Returns 0–1.

function _weaponSimilarity(srcWeapons, obatWeapons) {
  if (!srcWeapons || !srcWeapons.length) return 0.5; // neutral
  if (!obatWeapons || !obatWeapons.length) return 0;
  // Build token sets
  const srcTokens = new Set(
    srcWeapons.flatMap(w => _tokenise(w.name || w.id || ''))
  );
  const obatTokens = new Set(
    obatWeapons.flatMap(w => _tokenise(w[1] || ''))  // w[1] = weapon name in positional array
  );
  if (srcTokens.size === 0) return 0.5;
  let hits = 0;
  srcTokens.forEach(t => { if (obatTokens.has(t)) hits++; });
  return hits / srcTokens.size;
}

// ─── Availability filter ──────────────────────────────────────────────────
// Accepts unit if scenario year falls within [availFirstYear, availLastYear].
// Year is stored as 2-digit offset (add 1900). Scenario year is 4-digit.

function _yearInRange(unit, scenarioYear) {
  const firstY = unit[15] + 1900;  // availFirstYear
  const lastY  = unit[17] + 1900;  // availLastYear
  return scenarioYear >= firstY && scenarioYear <= lastY;
}

// ─── Core match function ──────────────────────────────────────────────────
//
// For a single source unit, search the OBAT for the best matches.
// Returns array of candidate objects sorted best-first.

function _findCandidates(srcUnit, obatID, obatSlots, scenarioYear, topN) {
  // SP1 units have unitClass (string) needing translation; SP2 units have unitClassIdx (number) directly
  const srcClassID  = (typeof srcUnit.unitClassIdx === 'number')
    ? srcUnit.unitClassIdx
    : (SP1_CLASS_TO_SPWW2_ID[srcUnit.unitClass] ?? -1);
  const srcName     = srcUnit.name || '';
  const srcWeapons  = srcUnit.weapons || [];

  const scored = [];

  for (const [slotNo, unit] of Object.entries(obatSlots)) {
    // unit is positional array:
    // [0]=slot [1]=name [2]=nation [3]=classID [4]=cost ... [28]=weapons [29]=armor

    const reasons = [];
    let score = 0;

    // ── Class match (0–40 points) ──────────────────────────────────────────
    const unitClassID = unit[3];
    if (srcClassID !== -1) {
      if (unitClassID === srcClassID) {
        score += 40;
        reasons.push('class match');
      } else {
        // Partial credit for closely related classes
        const related = _relatedClasses(srcClassID);
        if (related.includes(unitClassID)) {
          score += 15;
          reasons.push('related class');
        }
      }
    }

    // ── Availability filter (hard gate, −1000 penalty) ────────────────────
    if (!_yearInRange(unit, scenarioYear)) {
      score -= 1000;
      reasons.push('out of era');
    }

    // ── Name similarity (0–35 points) ─────────────────────────────────────
    const nameSim = _nameSimilarity(srcName, unit[1]);
    score += Math.round(nameSim * 35);
    if (nameSim > 0.3) reasons.push(`name ~${Math.round(nameSim*100)}%`);

    // ── Weapon similarity (0–25 points) ───────────────────────────────────
    const wpnSim = _weaponSimilarity(srcWeapons, unit[28]);
    score += Math.round(wpnSim * 25);
    if (wpnSim > 0.2) reasons.push(`weapons ~${Math.round(wpnSim*100)}%`);

    scored.push({ obatID, slot: Number(slotNo), unit, score, reasons });
  }

  // Sort descending, return top N (only positive-scoring)
  return scored
    .filter(c => c.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, topN);
}

// ─── Related class groups ─────────────────────────────────────────────────
// Returns classIDs that are acceptable substitutes for a given classID.

function _relatedClasses(classID) {
  const groups = [
    [1, 2, 20, 40, 51, 64, 65, 66, 67, 68, 140],  // infantry variants
    [3, 149, 150, 148],                             // cavalry
    [4, 65],                                        // MG
    [5, 38, 152, 153],                              // mortar variants
    [6, 157, 158, 2],                               // AT gun variants
    [9, 10, 21, 154, 155, 156],                     // artillery variants
    [12, 13, 14, 15, 16, 102, 103, 104, 105, 120],  // tank variants
    [13, 15, 16, 102, 104, 120, 131],               // medium/CS tank
    [11, 108, 32],                                  // armoured car
    [24, 25, 125],                                  // APC/halftrack
    [44, 62, 50],                                   // aircraft
    [31, 137, 151],                                 // rocket
    [20, 141, 145, 166],                            // engineer variants
  ];
  const result = new Set();
  groups.forEach(g => {
    if (g.includes(classID)) g.forEach(id => { if (id !== classID) result.add(id); });
  });
  return [...result];
}

// ─── Nation resolution ────────────────────────────────────────────────────
// Given source JSON, build the nationMap: { sourceNationIdx → spcamoObatID }
// SP1 JSON carries countryIdx (0-based SP1 nation index).
// SP2 JSON carries nation (string like "USA", "Russia") but no countryIdx.
// This function also injects a synthetic _nationKey onto each unit for uniform lookup.

function _buildNationMap(jsonObj, obatTable) {
  const map = {};
  const allUnits = [
    ...(jsonObj.units?.p1 || []),
    ...(jsonObj.units?.p2 || []),
  ];

  const isSP2 = (jsonObj._meta?.format || '').startsWith('SP2');

  if (isSP2) {
    // SP2: units have .nation (string) — look up in SP2_NATION_NAMES → SP2_TO_SPMBT_NATION
    const seenNations = new Set(allUnits.map(u => u.nation).filter(Boolean));
    seenNations.forEach(nationStr => {
      const sp2Idx = SP2_NATION_NAMES.findIndex(
        n => n.toLowerCase() === nationStr.toLowerCase()
      );
      if (sp2Idx < 0) { map['_n_' + nationStr] = null; return; }
      const spmbtIdx = SP2_TO_SPMBT_NATION[sp2Idx];
      if (spmbtIdx === undefined) { map['_n_' + nationStr] = null; return; }
      map['_n_' + nationStr] = obatTable[spmbtIdx] ? spmbtIdx : null;
    });
    // Inject _nationKey onto each unit for uniform lookup
    allUnits.forEach(u => { u._nationKey = '_n_' + (u.nation || ''); });
  } else {
    // SP1: units have .countryIdx — look up via SP1_TO_SPWW2_NATION
    const seenCountryIdx = new Set(allUnits.map(u => u.countryIdx).filter(x => x != null));
    seenCountryIdx.forEach(cidx => {
      const spww2Idx = SP1_TO_SPWW2_NATION[cidx];
      if (spww2Idx === undefined) { map[cidx] = null; return; }
      map[cidx] = obatTable[spww2Idx] ? spww2Idx : null;
    });
    // Inject _nationKey for uniform lookup
    allUnits.forEach(u => { u._nationKey = u.countryIdx; });
  }

  return map;
}

// ─── Main entry point ─────────────────────────────────────────────────────

function importUnitsFromJSON(jsonObj, obatTable, options = {}) {
  const topN = options.topN ?? 3;
  const sourceGame = jsonObj._meta?.format || 'SP1';
  const scenario   = jsonObj.scenario || {};
  const scenarioYear = scenario.year || 1944;

  const nationMap = _buildNationMap(jsonObj, obatTable);

  const allSrcUnits = [
    ...(jsonObj.units?.p1 || []).map(u => ({ ...u, _side: 'P1' })),
    ...(jsonObj.units?.p2 || []).map(u => ({ ...u, _side: 'P2' })),
  ];

  const units = allSrcUnits.map(srcUnit => {
    const nkey   = srcUnit._nationKey;
    const obatID = nationMap[nkey];

    if (obatID == null) {
      return {
        source:     srcUnit,
        candidates: [],
        chosen:     -1,
        error:      `No OBAT mapping for nation key ${nkey}`,
      };
    }

    const obatSlots   = obatTable[obatID] || {};
    const candidates  = _findCandidates(srcUnit, obatID, obatSlots, scenarioYear, topN);

    return {
      source:     srcUnit,
      candidates,
      chosen:     candidates.length > 0 ? 0 : -1,
    };
  });

  return {
    sourceGame,
    scenario,
    nationMap,
    units,
    leaders: jsonObj.leaders || [],
  };
}

// ─── Exports ──────────────────────────────────────────────────────────────

if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    importUnitsFromJSON,
    SP1_CLASS_TO_SPWW2_ID,
    _nameSimilarity,
    _weaponSimilarity,
    _findCandidates,
  };
}
