// ============================================================
// checklist-engine.jsx
// Motore deterministico di valutazione checklist per entità
// (RdA, vendor, progetto, contratto, SAL, variante).
//
// Applica CHECKLIST_RULES dal seed-customizing: prende tutte
// le regole matching (per priorità), fa union dei required/optional,
// confronta con i documenti presenti e produce lo stato.
// ============================================================

(function () {
  const SC = () => window.__SEED_CUSTOMIZING || {};

  // -------- path resolver: "entity.amount" -> value --------
  function resolvePath(path, ctx) {
    if (!path) return undefined;
    const parts = path.split('.');
    let cur = ctx;
    for (const p of parts) {
      if (cur == null) return undefined;
      cur = cur[p];
    }
    return cur;
  }

  // -------- condition evaluator --------
  function evalCondition(cond, ctx) {
    if (!cond || !cond.op) return true;
    const val = cond.field ? resolvePath(cond.field, ctx) : undefined;
    switch (cond.op) {
      case 'always': return true;
      case 'never':  return false;
      case 'eq':   return val === cond.value;
      case 'neq':  return val !== cond.value;
      case 'gt':   return Number(val) >  Number(cond.value);
      case 'gte':  return Number(val) >= Number(cond.value);
      case 'lt':   return Number(val) <  Number(cond.value);
      case 'lte':  return Number(val) <= Number(cond.value);
      case 'in':   return Array.isArray(cond.value) && cond.value.includes(val);
      case 'nin':  return Array.isArray(cond.value) && !cond.value.includes(val);
      case 'exists': return val != null;
      case 'and':  return (cond.args || []).every((c) => evalCondition(c, ctx));
      case 'or':   return (cond.args || []).some((c) => evalCondition(c, ctx));
      case 'not':  return !evalCondition(cond.arg || (cond.args && cond.args[0]), ctx);
      default:     return false;
    }
  }

  // -------- build entity ctx --------
  // Arricchisce l'entità con dati derivati dal seed (progetto → categoria, ecc.)
  function buildContext(entity, entityType) {
    const ctx = { entity: { ...entity } };
    const seed = window.__SEED || {};

    if (entityType === 'rda') {
      // se non ha category, cerchiamo dal progetto
      if (!ctx.entity.category && ctx.entity.project) {
        const p = (seed.PROJECTS || []).find((x) => x.id === ctx.entity.project);
        if (p) ctx.entity.category = p.category;
      }
      // atex flag: deducibile dalla categoria o campo esplicito
      if (ctx.entity.atex == null) ctx.entity.atex = false;
    }
    if (entityType === 'vendor') {
      if (!ctx.entity.country) ctx.entity.country = 'IT';
    }
    return ctx;
  }

  // -------- evaluate checklist --------
  // entity: object with id, amount, category, ...
  // entityType: 'rda' | 'vendor' | 'progetto' | ...
  // presentDocs: array of doc codes (es. ['OFFERTA','SPEC_TECH'])
  //
  // returns:
  //  {
  //    matchedRules: [{ruleId, name, priority}],
  //    required: [{code, name, present, ruleOrigin}],
  //    optional: [{code, name, present, ruleOrigin}],
  //    missing:  [{code, name}],
  //    present:  [{code, name}],
  //    score: 0..1,
  //    complete: bool,
  //    allowOverride: bool,
  //  }
  function evaluateChecklist(entity, entityType, presentDocs) {
    const sc = SC();
    const rules = (sc.CHECKLIST_RULES || []).filter((r) => r.entityType === entityType && r.active !== false);
    const docTypesById = {};
    (sc.DOC_TYPES || []).forEach((d) => { docTypesById[d.id] = d; docTypesById[d.code] = d; });

    const ctx = buildContext(entity, entityType);

    const matched = rules
      .filter((r) => evalCondition(r.conditions, ctx))
      .sort((a, b) => (a.priority || 0) - (b.priority || 0));

    // Union required / optional preservando la regola di origine
    const reqMap = new Map();
    const optMap = new Map();
    let allowOverride = true;

    matched.forEach((rule) => {
      if (rule.allowOverride === false) allowOverride = false;
      (rule.required || []).forEach((code) => {
        if (!reqMap.has(code)) reqMap.set(code, { code, name: (docTypesById[code] && docTypesById[code].name) || code, ruleOrigin: rule.name });
      });
      (rule.optional || []).forEach((code) => {
        if (!optMap.has(code) && !reqMap.has(code)) {
          optMap.set(code, { code, name: (docTypesById[code] && docTypesById[code].name) || code, ruleOrigin: rule.name });
        }
      });
    });

    const presentSet = new Set((presentDocs || []).map((x) => (typeof x === 'string' ? x : x.code || x.id)));

    const required = Array.from(reqMap.values()).map((r) => ({ ...r, present: presentSet.has(r.code) }));
    const optional = Array.from(optMap.values()).map((r) => ({ ...r, present: presentSet.has(r.code) }));

    const missing = required.filter((r) => !r.present).map((r) => ({ code: r.code, name: r.name, ruleOrigin: r.ruleOrigin }));
    const present = [
      ...required.filter((r) => r.present),
      ...optional.filter((r) => r.present),
    ];

    const score = required.length === 0 ? 1 : (required.filter((r) => r.present).length / required.length);

    return {
      matchedRules: matched.map((m) => ({ ruleId: m.id, name: m.name, priority: m.priority || 0 })),
      required,
      optional,
      missing,
      present,
      score,
      complete: missing.length === 0,
      allowOverride,
    };
  }

  // -------- legacy name → code mapping --------
  // Mappa i nomi usati oggi nella UI (es. "Specifica tecnica dettagliata")
  // verso i codici canonici di DOC_TYPES, così il vecchio `missing: [...]`
  // del seed RDA può comunque essere confrontato.
  const LEGACY_NAME_TO_CODE = {
    'specifica tecnica dettagliata': 'SPEC_TECH',
    'specifica tecnica': 'SPEC_TECH',
    'offerta firmata': 'OFFERTA',
    'offerta vendor firmata': 'OFFERTA',
    'offerta': 'OFFERTA',
    'dichiarazione ce': 'CE_CONF',
    'dichiarazione conformità ce': 'CE_CONF',
    'valutazione hse': 'HSE_VAL',
    'business case': 'BUS_CASE',
    'autorizzazione cfo': 'AUT_CFO',
    'iso 9001': 'ISO_9001',
    'iatf 16949': 'IATF_16949',
    'durc': 'DURC',
    'visura camerale': 'VISURA',
    'polizza rc': 'ASSIC_RC',
    'certificazione cyber (iec 62443)': 'CYBER_CERT',
    'report fat': 'FAT_REPORT',
    'report sat': 'SAT_REPORT',
    'datasheet': 'DATASHEET',
    'manualistica it/en': 'MANUAL_IT',
    'certificazione atex': 'ATEX_CERT',
    'contratto firmato': 'CONTRACT',
    'po': 'PO_DOC',
    'wbs': 'WBS',
    'risk register': 'RISK_REG',
    'autorizzazione capex': 'CAPEX_AUTH',
  };

  function codeFromLegacyName(name) {
    if (!name) return null;
    const key = String(name).trim().toLowerCase();
    return LEGACY_NAME_TO_CODE[key] || null;
  }

  // Dato un record RdA seed (con .missing array di nomi), ricostruisce
  // i `presentDocs` come TUTTI i DOC_TYPES required dalle regole,
  // meno quelli mappati da legacy name.
  function derivePresentFromLegacyRda(rdaEntity) {
    const ev = evaluateChecklist(rdaEntity, 'rda', []);
    const missingCodes = new Set();
    (rdaEntity.missing || []).forEach((n) => {
      const c = codeFromLegacyName(n);
      if (c) missingCodes.add(c);
    });
    // present = tutti i required che NON sono nel missing mappato
    return ev.required
      .filter((r) => !missingCodes.has(r.code))
      .map((r) => r.code);
  }

  Object.assign(window, {
    evalCondition,
    evaluateChecklist,
    codeFromLegacyName,
    derivePresentFromLegacyRda,
  });
})();
