// ============================================================================
// TERRITORY-ENGINE — TIS-style territory model + overlap math.
//
// CISAC TIS gives every country a 4-digit code, every region/bloc a code,
// and lets you say "Europe" in one tag. We mirror that hierarchy:
//
//   2136  WORLD
//   2100  EUROPE
//     2110  WESTERN EUROPE  (FR, DE, IT, …)
//     2120  NORTHERN EUROPE (GB, IE, …)
//     2130  SOUTHERN EUROPE
//   2076  AMERICAS
//     2021  NORTH AMERICA
//     2419  LATIN AMERICA
//   ISO-α2 country codes are LEAVES.
//
// The engine answers four questions:
//   expand(node) → ISO-α2 set            // resolve a region/bloc to leaves
//   resolveSelection(picks) → ISO set    // includes − excludes − leaf-list
//   overlap(setA, setB) → ISO[] | null   // intersection of two selections
//   conflicts(rows, right, period) → []  // hard-block detection
//
// Every selection is a struct:
//   { mode: 'world' | 'include' | 'exclude' | 'list',
//     codes: ['EU', 'US', 'JP'], excludes: ['DE'] }
//
// Custom blocs (Tier 1, Latin, etc.) live in REF.territoryGroups OR are
// user-defined in TerritoryEngine.customBlocs. We resolve customs first,
// fall through to TIS.
//
// EXPORT: window.TerritoryEngine
// ============================================================================
(function () {
  // ── TIS-style hierarchy. Codes are 4-digit numerics. We carry a few
  // common ones; the rest live in REF.territoryGroups (CISAC table 6).
  const TIS = {
    '2136': { code: '2136', label: 'WORLD',           kind: 'world',  parent: null,   children: ['2100','2076','2002','2009','2017','2080','2118'] },
    '2100': { code: '2100', label: 'Europe',          kind: 'region', parent: '2136', children: ['2110','2120','2130','2140','2150'] },
    '2110': { code: '2110', label: 'Western Europe',  kind: 'subreg', parent: '2100', leaves: ['FR','DE','BE','NL','LU','AT','CH','LI','MC'] },
    '2120': { code: '2120', label: 'Northern Europe', kind: 'subreg', parent: '2100', leaves: ['GB','IE','SE','NO','DK','FI','IS','EE','LV','LT'] },
    '2130': { code: '2130', label: 'Southern Europe', kind: 'subreg', parent: '2100', leaves: ['ES','IT','PT','GR','MT','CY','SM','VA','AD'] },
    '2140': { code: '2140', label: 'Eastern Europe',  kind: 'subreg', parent: '2100', leaves: ['PL','CZ','SK','HU','RO','BG','RU','UA','BY','MD'] },
    '2150': { code: '2150', label: 'Balkans',         kind: 'subreg', parent: '2100', leaves: ['HR','SI','RS','BA','ME','MK','AL','XK'] },
    '2076': { code: '2076', label: 'Americas',        kind: 'region', parent: '2136', children: ['2021','2419'] },
    '2021': { code: '2021', label: 'North America',   kind: 'subreg', parent: '2076', leaves: ['US','CA','MX','GL','BM'] },
    '2419': { code: '2419', label: 'Latin America',   kind: 'subreg', parent: '2076', leaves: ['BR','AR','CL','CO','PE','VE','EC','UY','PY','BO','GT','HN','SV','NI','CR','PA','DO','CU','HT','PR'] },
    '2002': { code: '2002', label: 'Africa',          kind: 'region', parent: '2136', leaves: ['ZA','NG','KE','EG','MA','GH','EH','ET','TZ','UG','RW','SN','CI','DZ','TN','LY','SD','ZM','ZW','BW','NA','MZ','CM','AO','MG','MU'] },
    '2009': { code: '2009', label: 'Oceania',         kind: 'region', parent: '2136', leaves: ['AU','NZ','FJ','PG','SB','VU','WS','TO','NC','PF'] },
    '2017': { code: '2017', label: 'Asia',            kind: 'region', parent: '2136', children: ['2030','2031','2034','2142'] },
    '2030': { code: '2030', label: 'East Asia',       kind: 'subreg', parent: '2017', leaves: ['JP','KR','KP','CN','TW','HK','MO','MN'] },
    '2031': { code: '2031', label: 'Southeast Asia',  kind: 'subreg', parent: '2017', leaves: ['ID','MY','SG','TH','VN','PH','MM','KH','LA','BN','TL'] },
    '2034': { code: '2034', label: 'South Asia',      kind: 'subreg', parent: '2017', leaves: ['IN','PK','BD','LK','NP','BT','MV','AF'] },
    '2142': { code: '2142', label: 'Middle East',     kind: 'subreg', parent: '2017', leaves: ['IL','SA','AE','QA','KW','BH','OM','YE','JO','LB','SY','IQ','IR','TR'] },
    '2080': { code: '2080', label: 'Caribbean',       kind: 'region', parent: '2136', leaves: ['JM','TT','BB','BS','LC','GD','VC','DM','AG','KN','AI','VG','TC','KY'] },
    '2118': { code: '2118', label: 'Antarctica',      kind: 'region', parent: '2136', leaves: ['AQ'] },
  };

  // ── Common pre-fab blocs / economic groupings users frequently need.
  const BLOCS = {
    EU:        { code: 'EU',       label: 'European Union', kind: 'bloc', leaves: ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE'] },
    EEA:       { code: 'EEA',      label: 'European Economic Area', kind: 'bloc', leaves: ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK','SI','ES','SE','IS','LI','NO'] },
    GSA:       { code: 'GSA',      label: 'GSA (DE/AT/CH)', kind: 'bloc', leaves: ['DE','AT','CH','LI'] },
    NORDICS:   { code: 'NORDICS',  label: 'Nordics', kind: 'bloc', leaves: ['SE','NO','DK','FI','IS'] },
    BENELUX:   { code: 'BENELUX',  label: 'Benelux', kind: 'bloc', leaves: ['BE','NL','LU'] },
    BALTICS:   { code: 'BALTICS',  label: 'Baltics', kind: 'bloc', leaves: ['EE','LV','LT'] },
    UK_IE:     { code: 'UK_IE',    label: 'UK + Ireland', kind: 'bloc', leaves: ['GB','IE'] },
    DACH:      { code: 'DACH',     label: 'DACH', kind: 'bloc', leaves: ['DE','AT','CH'] },
    ANZ:       { code: 'ANZ',      label: 'Australia + NZ', kind: 'bloc', leaves: ['AU','NZ'] },
    LATAM_BIG: { code: 'LATAM_BIG',label: 'LATAM (top 6)', kind: 'bloc', leaves: ['BR','MX','AR','CL','CO','PE'] },
    MENA:      { code: 'MENA',     label: 'MENA', kind: 'bloc', leaves: ['AE','SA','EG','MA','TN','DZ','LB','JO','IQ','KW','OM','QA','BH','YE','LY','SY','IL','TR'] },
    APAC:      { code: 'APAC',     label: 'Asia-Pacific', kind: 'bloc', leaves: ['JP','KR','CN','HK','TW','SG','MY','TH','VN','PH','ID','IN','AU','NZ'] },
    G7:        { code: 'G7',       label: 'G7', kind: 'bloc', leaves: ['US','CA','GB','FR','DE','IT','JP'] },
    BRICS:     { code: 'BRICS',    label: 'BRICS', kind: 'bloc', leaves: ['BR','RU','IN','CN','ZA'] },
    NAFTA:     { code: 'NAFTA',    label: 'USMCA (NAFTA)', kind: 'bloc', leaves: ['US','CA','MX'] },
    MERCOSUR:  { code: 'MERCOSUR', label: 'Mercosur', kind: 'bloc', leaves: ['BR','AR','PY','UY','BO'] },
    ASEAN:     { code: 'ASEAN',    label: 'ASEAN', kind: 'bloc', leaves: ['ID','MY','SG','TH','VN','PH','MM','KH','LA','BN'] },
    GCC:       { code: 'GCC',      label: 'GCC', kind: 'bloc', leaves: ['SA','AE','QA','KW','BH','OM'] },
  };

  // ── World ISO-α2 fallback list (rough; REF.territories is the source of truth).
  const ALL_ISO = [
    'AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ',
    'BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS','BT','BV','BW','BY','BZ',
    'CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CW','CX','CY','CZ',
    'DE','DJ','DK','DM','DO','DZ',
    'EC','EE','EG','EH','ER','ES','ET',
    'FI','FJ','FK','FM','FO','FR',
    'GA','GB','GD','GE','GF','GG','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY',
    'HK','HM','HN','HR','HT','HU',
    'ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT',
    'JE','JM','JO','JP',
    'KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ',
    'LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY',
    'MA','MC','MD','ME','MF','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ',
    'NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ',
    'OM',
    'PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY',
    'QA',
    'RE','RO','RS','RU','RW',
    'SA','SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ',
    'TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR','TT','TV','TW','TZ',
    'UA','UG','UM','US','UY','UZ',
    'VA','VC','VE','VG','VI','VN','VU',
    'WF','WS',
    'YE','YT',
    'ZA','ZM','ZW','XK',
  ];

  function allIso() {
    const REF = window.REF;
    if (REF && REF.ready && Array.isArray(REF.territories) && REF.territories.length) {
      return REF.territories.map(t => t.iso_alpha_2).filter(Boolean);
    }
    return ALL_ISO.slice();
  }

  // ── Custom blocs (user-defined). Persisted in localStorage.
  const customBlocs = {};
  try {
    const saved = localStorage.getItem('astro_custom_blocs');
    if (saved) Object.assign(customBlocs, JSON.parse(saved));
  } catch {}
  function saveCustomBlocs() {
    try { localStorage.setItem('astro_custom_blocs', JSON.stringify(customBlocs)); } catch {}
  }

  // ── Resolve a single code → ISO-α2 set.
  function expand(code) {
    if (!code) return new Set();
    const c = String(code).toUpperCase();
    if (c === 'WW' || c === '2136' || c === 'WORLD') return new Set(allIso());
    // Custom bloc?
    if (customBlocs[c]) return new Set(customBlocs[c].leaves);
    // Pre-fab bloc?
    if (BLOCS[c]) return new Set(BLOCS[c].leaves);
    // TIS code? (4-digit numeric)
    if (TIS[c]) {
      const n = TIS[c];
      if (n.leaves) return new Set(n.leaves);
      if (n.children) {
        const set = new Set();
        n.children.forEach(ch => expand(ch).forEach(iso => set.add(iso)));
        return set;
      }
    }
    // ISO-α2?
    if (/^[A-Z]{2}$/.test(c)) return new Set([c]);
    // ISO-α3? Try REF lookup
    const REF = window.REF;
    if (REF && REF.ready && /^[A-Z]{3}$/.test(c)) {
      const t = REF.territoryByIso3 && REF.territoryByIso3.get(c);
      if (t) return new Set([t.iso_alpha_2]);
    }
    return new Set();
  }

  // ── Resolve a selection struct → ISO set + label.
  function resolveSelection(sel) {
    if (!sel) return { isos: new Set(), label: '—' };
    const mode = sel.mode || 'list';
    let isos;
    if (mode === 'world') {
      isos = new Set(allIso());
    } else if (mode === 'include' || mode === 'list') {
      isos = new Set();
      (sel.codes || []).forEach(c => expand(c).forEach(iso => isos.add(iso)));
    } else if (mode === 'exclude') {
      isos = new Set(allIso());
      (sel.codes || []).forEach(c => expand(c).forEach(iso => isos.delete(iso)));
    } else {
      isos = new Set();
    }
    // Apply explicit excludes (works in any mode).
    (sel.excludes || []).forEach(c => expand(c).forEach(iso => isos.delete(iso)));

    // Build a compact label.
    const label = labelFor(sel, isos);
    return { isos, label };
  }

  function labelFor(sel, isos) {
    if (!sel) return '—';
    if (sel.mode === 'world' && (!sel.excludes || !sel.excludes.length)) return 'World';
    if (sel.mode === 'world' && sel.excludes && sel.excludes.length)
      return 'World ex ' + sel.excludes.slice(0, 3).join('·') + (sel.excludes.length > 3 ? ` +${sel.excludes.length - 3}` : '');
    const codes = sel.codes || [];
    if (sel.mode === 'exclude') {
      return 'World ex ' + codes.slice(0, 3).join('·') + (codes.length > 3 ? ` +${codes.length - 3}` : '');
    }
    if (codes.length === 0) return '—';
    if (codes.length === 1) {
      const c = codes[0];
      const l = labelOf(c);
      return l;
    }
    if (codes.length <= 3) return codes.map(labelOf).join(' · ');
    return `${codes.length} territories · ${(isos || resolveSelection(sel).isos).size} ISO`;
  }

  function labelOf(code) {
    if (!code) return '';
    const c = String(code).toUpperCase();
    if (c === 'WW') return 'World';
    if (customBlocs[c]) return customBlocs[c].label || c;
    if (BLOCS[c]) return BLOCS[c].label;
    if (TIS[c]) return TIS[c].label;
    const REF = window.REF;
    if (REF && REF.ready && REF.territoryByIso2) {
      const t = REF.territoryByIso2.get(c);
      if (t) return t.common_name || t.iso_alpha_2;
    }
    return c;
  }

  function flagOf(iso) {
    const REF = window.REF;
    if (REF && REF.ready && REF.territoryByIso2) {
      const t = REF.territoryByIso2.get(iso);
      if (t && t.emoji_flag) return t.emoji_flag;
    }
    if (!/^[A-Z]{2}$/.test(iso)) return '';
    const A = 0x1F1E6;
    return String.fromCodePoint(A + (iso.charCodeAt(0) - 65)) + String.fromCodePoint(A + (iso.charCodeAt(1) - 65));
  }

  // ── Overlap between two selections — returns ISO[] of countries in both.
  function overlap(selA, selB) {
    const a = resolveSelection(selA).isos;
    const b = resolveSelection(selB).isos;
    const out = [];
    a.forEach(iso => { if (b.has(iso)) out.push(iso); });
    return out;
  }

  // ── Period overlap (date strings YYYY-MM-DD or null = open).
  function periodsOverlap(aStart, aEnd, bStart, bEnd) {
    const aS = aStart || '0000-01-01';
    const aE = aEnd   || '9999-12-31';
    const bS = bStart || '0000-01-01';
    const bE = bEnd   || '9999-12-31';
    return aS <= bE && bS <= aE;
  }

  // ── Conflict detection on a list of rows.
  // Each row: { id, selection, rights:[], start, end }
  // Returns conflicts: [{ a, b, isos:[], rights:[], why }]
  function findConflicts(rows) {
    const out = [];
    for (let i = 0; i < rows.length; i++) {
      for (let j = i + 1; j < rows.length; j++) {
        const a = rows[i], b = rows[j];
        if (!periodsOverlap(a.start, a.end, b.start, b.end)) continue;
        const sharedRights = (a.rights || ['ALL']).filter(r => (b.rights || ['ALL']).includes(r) || (b.rights || []).includes('ALL') || (a.rights || []).includes('ALL'));
        if (!sharedRights.length) continue;
        const isos = overlap(a.selection, b.selection);
        if (!isos.length) continue;
        out.push({
          a: a.id, b: b.id,
          isos, rights: sharedRights,
          why: `Overlap on ${isos.length} territor${isos.length === 1 ? 'y' : 'ies'} × ${sharedRights.join('+')} during ${[a.start, b.start].sort()[1] || '?'} → ${[a.end, b.end].filter(Boolean).sort()[0] || 'open'}`,
        });
      }
    }
    return out;
  }

  // ── Reverse lookup: given an ISO, what TIS regions / blocs contain it?
  function regionsContaining(iso) {
    const out = [];
    Object.values(TIS).forEach(n => {
      if (n.leaves && n.leaves.includes(iso)) out.push(n);
    });
    Object.values(BLOCS).forEach(b => {
      if (b.leaves.includes(iso)) out.push(b);
    });
    Object.values(customBlocs).forEach(b => {
      if (b.leaves.includes(iso)) out.push(b);
    });
    return out;
  }

  // ── List blocs / TIS nodes for a picker.
  function listBlocs() {
    return [
      ...Object.values(customBlocs).map(b => ({ ...b, kind: 'custom' })),
      ...Object.values(BLOCS),
    ];
  }
  function listTis() { return Object.values(TIS); }

  // Save / load custom bloc.
  function saveCustomBloc(code, label, leaves) {
    customBlocs[code] = { code, label, leaves: leaves.slice(), kind: 'custom' };
    saveCustomBlocs();
  }
  function deleteCustomBloc(code) {
    delete customBlocs[code];
    saveCustomBlocs();
  }

  // ── Convert a legacy `[{code, label, ...}]` array (agreement-style) to
  // a unified selection. Best-effort.
  function fromLegacyRows(rows) {
    if (!rows || !rows.length) return { mode: 'list', codes: [] };
    if (rows.length === 1 && (rows[0].code === 'WW' || rows[0].code === '2136')) {
      return { mode: 'world', codes: [], excludes: rows[0].excluded || [] };
    }
    return { mode: 'list', codes: rows.map(r => r.code).filter(Boolean), excludes: [] };
  }

  // ── Public API.
  window.TerritoryEngine = {
    TIS, BLOCS, customBlocs,
    expand, resolveSelection, labelFor, labelOf, flagOf,
    overlap, periodsOverlap, findConflicts,
    regionsContaining, listBlocs, listTis,
    saveCustomBloc, deleteCustomBloc,
    fromLegacyRows, allIso,
  };
})();
