/* ========================================================================
   travel-plans.jsx — Travel eSIM plan catalogue and plan store (Story 4)
   Plans are records attached to a travel eSIM, not properties of it. One
   travel eSIM per person; plans stack on it. Loads after estate-data.jsx.
   Field names mirror the supplier contract so the API swap is clean.
   ======================================================================== */
const TD = window.MILO_DATA;
const TE = window.MP_ESTATE;
const { useState: uT, useEffect: eT } = React;

/* ---------- Catalogue areas ----------
   Every country in the world resolves. Each belongs to exactly one zone, and
   the two global areas cover named sets of zones. A short excluded list has no
   coverage at all, which is what makes the no-plans state honest.
   Flags are derived from the ISO code, so no emoji table is stored. */
const ZONE_OF = {
  europe: "AL AD AT BE BA BG HR CY CZ DK EE FO FI FR DE GI GR GG HU IS IE IM IT JE XK LV LI LT LU MT MD MC ME NL MK NO PL PT RO SM RS SK SI ES SE CH TR UA GB VA",
  apac: "AU BD BT BN KH CN FJ HK IN ID JP KZ KG LA MO MY MV MN MM NP NZ PK PG PH WS SG KR LK TW TJ TH TO TM UZ VU VN",
  namerica: "US CA MX",
  latam: "AR AW BS BB BZ BO BR KY CL CO CR CW DM DO EC SV GD GT GY HT HN JM NI PA PY PE PR LC SR TT UY VE",
  middleeast: "AE BH EG IL IQ JO KW LB OM PS QA SA YE",
  africa: "DZ AO BJ BW BF BI CM CV CF TD KM CG CD CI DJ GQ ER SZ ET GA GM GH GN GW KE LS LR LY MG MW ML MR MU MA MZ NA NE NG RW ST SN SC SL SO ZA SS SD TZ TG TN UG ZM ZW"
};
/* Markets our supplier does not sell into. They resolve in search and then
   honestly report that nothing covers them. */
const NO_COVERAGE = "AF BY CU IR KP RU SY".split(" ");
const COUNTRY_NAMES = {
  AF: "Afghanistan", AL: "Albania", DZ: "Algeria", AD: "Andorra", AO: "Angola", AR: "Argentina", AW: "Aruba", AU: "Australia", AT: "Austria",
  BS: "Bahamas", BH: "Bahrain", BD: "Bangladesh", BB: "Barbados", BY: "Belarus", BE: "Belgium", BZ: "Belize", BJ: "Benin", BT: "Bhutan",
  BO: "Bolivia", BA: "Bosnia and Herzegovina", BW: "Botswana", BR: "Brazil", BN: "Brunei", BG: "Bulgaria", BF: "Burkina Faso", BI: "Burundi",
  KH: "Cambodia", CM: "Cameroon", CA: "Canada", CV: "Cape Verde", KY: "Cayman Islands", CF: "Central African Republic", TD: "Chad",
  CL: "Chile", CN: "China", CO: "Colombia", KM: "Comoros", CG: "Congo", CD: "Congo (DRC)", CR: "Costa Rica", CI: "Côte d'Ivoire",
  HR: "Croatia", CU: "Cuba", CW: "Curaçao", CY: "Cyprus", CZ: "Czechia", DK: "Denmark", DJ: "Djibouti", DM: "Dominica",
  DO: "Dominican Republic", EC: "Ecuador", EG: "Egypt", SV: "El Salvador", GQ: "Equatorial Guinea", ER: "Eritrea", EE: "Estonia",
  SZ: "Eswatini", ET: "Ethiopia", FO: "Faroe Islands", FJ: "Fiji", FI: "Finland", FR: "France", GA: "Gabon", GM: "Gambia",
  DE: "Germany", GH: "Ghana", GI: "Gibraltar", GR: "Greece", GD: "Grenada", GT: "Guatemala", GG: "Guernsey", GN: "Guinea",
  GW: "Guinea-Bissau", GY: "Guyana", HT: "Haiti", HN: "Honduras", HK: "Hong Kong", HU: "Hungary", IS: "Iceland", IN: "India",
  ID: "Indonesia", IR: "Iran", IQ: "Iraq", IE: "Ireland", IM: "Isle of Man", IL: "Israel", IT: "Italy", JM: "Jamaica",
  JP: "Japan", JE: "Jersey", JO: "Jordan", KZ: "Kazakhstan", KE: "Kenya", XK: "Kosovo", KW: "Kuwait", KG: "Kyrgyzstan",
  LA: "Laos", LV: "Latvia", LB: "Lebanon", LS: "Lesotho", LR: "Liberia", LY: "Libya", LI: "Liechtenstein", LT: "Lithuania",
  LU: "Luxembourg", MO: "Macao", MG: "Madagascar", MW: "Malawi", MY: "Malaysia", MV: "Maldives", ML: "Mali", MT: "Malta",
  MR: "Mauritania", MU: "Mauritius", MX: "Mexico", MD: "Moldova", MC: "Monaco", MN: "Mongolia", ME: "Montenegro", MA: "Morocco",
  MZ: "Mozambique", MM: "Myanmar", NA: "Namibia", NP: "Nepal", NL: "Netherlands", NZ: "New Zealand", NI: "Nicaragua", NE: "Niger",
  NG: "Nigeria", KP: "North Korea", MK: "North Macedonia", NO: "Norway", OM: "Oman", PK: "Pakistan", PS: "Palestine", PA: "Panama",
  PG: "Papua New Guinea", PY: "Paraguay", PE: "Peru", PH: "Philippines", PL: "Poland", PT: "Portugal", PR: "Puerto Rico",
  QA: "Qatar", RO: "Romania", RU: "Russia", RW: "Rwanda", LC: "Saint Lucia", WS: "Samoa", SM: "San Marino", ST: "Sao Tome and Principe",
  SA: "Saudi Arabia", SN: "Senegal", RS: "Serbia", SC: "Seychelles", SL: "Sierra Leone", SG: "Singapore", SK: "Slovakia",
  SI: "Slovenia", SO: "Somalia", ZA: "South Africa", KR: "South Korea", SS: "South Sudan", ES: "Spain", LK: "Sri Lanka",
  SD: "Sudan", SR: "Suriname", SE: "Sweden", CH: "Switzerland", SY: "Syria", TW: "Taiwan", TJ: "Tajikistan", TZ: "Tanzania",
  TH: "Thailand", TG: "Togo", TO: "Tonga", TT: "Trinidad and Tobago", TN: "Tunisia", TR: "Turkey", TM: "Turkmenistan",
  UG: "Uganda", UA: "Ukraine", AE: "United Arab Emirates", GB: "United Kingdom", US: "United States", UY: "Uruguay",
  UZ: "Uzbekistan", VU: "Vanuatu", VA: "Vatican City", VE: "Venezuela", VN: "Vietnam", YE: "Yemen", ZM: "Zambia", ZW: "Zimbabwe"
};
const flagOf = (cc) => String.fromCodePoint(...cc.toUpperCase().split("").map((c) => 127397 + c.charCodeAt(0)));

/* Per-zone single-country base price, with a few dearer markets called out. */
const ZONE_BASE = { europe: 4, namerica: 5, apac: 6, latam: 6, middleeast: 7, africa: 8 };
const COUNTRY_BASE_OVERRIDE = { CH: 5, NO: 5, JP: 6, SG: 6, KR: 6, AU: 6, NZ: 6, HK: 6, BR: 7, AE: 7, IL: 8, ZA: 7, MU: 9, MV: 10 };

const AREA_COUNTRIES = {};
Object.keys(ZONE_OF).forEach((zone) => {
  ZONE_OF[zone].split(" ").forEach((cc) => {
    AREA_COUNTRIES[cc.toLowerCase()] = { name: COUNTRY_NAMES[cc] || cc, flag: flagOf(cc), zone, code: cc };
  });
});
NO_COVERAGE.forEach((cc) => {
  AREA_COUNTRIES[cc.toLowerCase()] = { name: COUNTRY_NAMES[cc] || cc, flag: flagOf(cc), zone: null, code: cc };
});
const zoneMembers = (zone) => ZONE_OF[zone].toLowerCase().split(" ");

const AREA_ZONES = {
  europe:     { name: "Europe Zone",        members: zoneMembers("europe") },
  apac:       { name: "Asia Pacific Zone",  members: zoneMembers("apac") },
  namerica:   { name: "North America Zone", members: zoneMembers("namerica") },
  latam:      { name: "Latin America Zone", members: zoneMembers("latam") },
  middleeast: { name: "Middle East Zone",   members: zoneMembers("middleeast") },
  africa:     { name: "Africa Zone",        members: zoneMembers("africa") },
  /* The two global areas are named for the size of the real supplier list, so
     their stated reach is pinned, not derived from the demo member set. */
  global90:   { name: "Global 90",  zones: ["europe", "apac", "namerica", "latam"], countries: 90 },
  global175:  { name: "Global 175", zones: ["europe", "apac", "namerica", "latam", "middleeast", "africa"], countries: 175 }
};
Object.keys(AREA_ZONES).forEach((k) => {
  const z = AREA_ZONES[k];
  if (z.zones) z.members = z.zones.reduce((a, n) => a.concat(zoneMembers(n)), []);
  if (z.countries == null) z.countries = z.members.length;
});

/* Fictional pricing. Never supplier or proposal figures. */
const ZONE_PRICE = { europe: 5, apac: 8, namerica: 7, latam: 8, middleeast: 9, africa: 10, global90: 11, global175: 14 };
function baseFor(area) {
  if (ZONE_PRICE[area] != null) return ZONE_PRICE[area];
  const c = AREA_COUNTRIES[area];
  if (!c || !c.zone) return null;
  return COUNTRY_BASE_OVERRIDE[c.code] || ZONE_BASE[c.zone];
}
const ONE_OFF_SHAPE = [
  { allowance_gb: 1, duration_days: 7, factor: 1 },
  { allowance_gb: 3, duration_days: 15, factor: 2 },
  { allowance_gb: 5, duration_days: 30, factor: 3, premium: true },
  { allowance_gb: 10, duration_days: 30, factor: 5, premium: true }
];
const RECURRING_SHAPE = [
  { allowance_gb: 5, factor: 2.5 },
  { allowance_gb: 10, factor: 4, premium: true },
  { allowance_gb: 20, factor: 6.5 }
];

function areaLabel(key) {
  return AREA_ZONES[key] ? AREA_ZONES[key].name : (AREA_COUNTRIES[key] || {}).name || key;
}
function areaCountryCount(key) { return AREA_ZONES[key] ? AREA_ZONES[key].countries : 1; }
function areaMembers(key) { return AREA_ZONES[key] ? AREA_ZONES[key].members : [key]; }
function areaFlag(key) { return AREA_ZONES[key] ? null : (AREA_COUNTRIES[key] || {}).flag; }
const isZone = (key) => !!AREA_ZONES[key];

/* ---------- Generated catalogue ---------- */
const AREA_KEYS = Object.keys(AREA_ZONES).concat(Object.keys(AREA_COUNTRIES).filter((k) => AREA_COUNTRIES[k].zone));
const CATALOGUE = [];
AREA_KEYS.forEach((area) => {
  const b = baseFor(area);
  if (b == null) return;
  const ref = area.toUpperCase();
  ONE_OFF_SHAPE.forEach((s, i) => {
    CATALOGUE.push({ catalogue_ref: "TS-" + ref + "-O" + (i + 1), type: "one_off", area,
      allowance_gb: s.allowance_gb, duration_days: s.duration_days, price: Math.round(b * s.factor), premium: false });
    if (s.premium) CATALOGUE.push({ catalogue_ref: "TS-" + ref + "-O" + (i + 1) + "P", type: "one_off", area,
      allowance_gb: s.allowance_gb, duration_days: s.duration_days, price: Math.round(b * s.factor * 1.5), premium: true });
  });
  RECURRING_SHAPE.forEach((s, i) => {
    CATALOGUE.push({ catalogue_ref: "TS-" + ref + "-R" + (i + 1), type: "recurring", area,
      allowance_gb: s.allowance_gb, term_months: 12, price: Math.round(b * s.factor), premium: false });
    if (s.premium) CATALOGUE.push({ catalogue_ref: "TS-" + ref + "-R" + (i + 1) + "P", type: "recurring", area,
      allowance_gb: s.allowance_gb, term_months: 12, price: Math.round(b * s.factor * 1.5), premium: true });
  });
});
const byRef = (ref) => CATALOGUE.find((c) => c.catalogue_ref === ref);

/* Register catalogue areas with the estate coverage table so existing travel
   surfaces can label a coverage key without knowing the catalogue. */
AREA_KEYS.forEach((area) => {
  if (!TE.COVERAGE[area]) TE.COVERAGE[area] = { label: areaLabel(area), countries: areaCountryCount(area) };
});

/* ---------- Search: destinations and zones ---------- */
function searchDestinations(q) {
  const s = q.trim().toLowerCase();
  if (!s) return [];
  const hits = [];
  Object.keys(AREA_COUNTRIES).forEach((k) => {
    const i = AREA_COUNTRIES[k].name.toLowerCase().indexOf(s);
    if (i !== -1) hits.push({ rank: i === 0 ? 0 : 1, item: { kind: "country", id: k, label: AREA_COUNTRIES[k].name, flag: AREA_COUNTRIES[k].flag } });
  });
  Object.keys(AREA_ZONES).forEach((k) => {
    const i = AREA_ZONES[k].name.toLowerCase().indexOf(s);
    if (i !== -1) hits.push({ rank: i === 0 ? 0 : 1, item: { kind: "zone", id: k, label: AREA_ZONES[k].name, countries: AREA_ZONES[k].countries } });
  });
  hits.sort((a, b) => a.rank - b.rank || a.item.label.localeCompare(b.item.label));
  return hits.slice(0, 8).map((h) => h.item);
}
/* A plan matches when its area covers every chosen destination. */
function planCovers(area, token) {
  if (token.kind === "country") return areaMembers(area).indexOf(token.id) !== -1;
  if (area === token.id) return true;
  const z = AREA_ZONES[area];
  return !!(z && z.zones && z.zones.indexOf(token.id) !== -1);
}
function matchingPlans(tokens, type) {
  if (!tokens.length) return [];
  return CATALOGUE
    .filter((c) => c.type === type && tokens.every((t) => planCovers(c.area, t)))
    .sort((a, b) => a.price - b.price);
}

/* ---------- Store ---------- */
const TRAVEL_KEY = "mp_travel_plans_v1";
let TS = { seq: 0, purchased: [], overrides: {} };
try { const raw = localStorage.getItem(TRAVEL_KEY); if (raw) TS = { ...TS, ...JSON.parse(raw) }; } catch (e) {}
const tSubs = new Set();
function tSave() { try { localStorage.setItem(TRAVEL_KEY, JSON.stringify(TS)); } catch (e) {} }
function tNotify() { tSave(); tSubs.forEach((cb) => cb()); }
function useTravel() {
  const [, force] = uT(0);
  eT(() => { const cb = () => force((n) => n + 1); tSubs.add(cb); return () => tSubs.delete(cb); }, []);
  return TS;
}

const DAY = 86400000;
const nowD = () => new Date();
const addDays = (d, n) => new Date(d.getTime() + n * DAY);
const iso = (d) => (d ? new Date(d).toISOString() : null);

/* ---------- Seeded plans ----------
   Every travel eSIM in the placeholder data carries one plan, derived from
   the fields it already holds. SIM 205 is awaiting its first use. */
function seedPlans() {
  return TE.travelSims().filter((s) => s.id < 250).map((sim) => {
    const oneOff = sim.plan_type === "One-Off";
    const awaiting = sim.id === 205;
    const want = (c) => c.type === (oneOff ? "one_off" : "recurring") && c.allowance_gb === sim.allowance_gb;
    const cat = CATALOGUE.find((c) => want(c) && c.area === sim.coverage) || CATALOGUE.find(want) || CATALOGUE[0];
    const days = oneOff ? (sim.id === 205 ? 30 : 15) : null;
    return {
      id: "seed-" + sim.id,
      esim_id: sim.id,
      catalogue_ref: cat.catalogue_ref,
      type: oneOff ? "one_off" : "recurring",
      area: sim.coverage,
      allowance_gb: sim.allowance_gb,
      duration_days: days,
      term_months: oneOff ? null : 12,
      price: cat.price,
      premium: false,
      purchased_at: iso(sim.provisioned || addDays(nowD(), -30)),
      first_use_at: awaiting ? null : iso(sim.provisioned || addDays(nowD(), -25)),
      expires_at: awaiting ? null : iso(sim.expires),
      renews_on: iso(sim.renews_on),
      cancelled: false,
      used: awaiting ? 0 : sim.used
    };
  });
}

/* Replay stored purchases that created a new travel eSIM. */
function ensurePurchasedSims() {
  TS.purchased.forEach((p) => {
    if (!p.new_esim) return;
    if (TD.SIMS.some((s) => s.id === p.esim_id)) return;
    const sim = {
      id: p.esim_id, group: "travel", serial: p.serial, msisdn: "Data only",
      status: "Active", sim_type: "eSIM", plan: "small", coverage: p.area,
      allowance_gb: p.allowance_gb, plan_type: p.type === "one_off" ? "One-Off" : "Recurring",
      used: 0, prev_used: 0, data_active: true, activation_date: null,
      provisioned: new Date(p.purchased_at), install_state: "ready",
      expires: null, renews_on: p.renews_on ? new Date(p.renews_on) : null,
      user_id: p.user_id
    };
    TD.SIMS.push(sim);
    const u = TD.USERS.find((x) => x.id === p.user_id);
    if (u && u.sim_ids.indexOf(sim.id) === -1) u.sim_ids.push(sim.id);
    /* It is billed from the moment it exists, so it joins the travel line. */
    const line = (TD.SUBSCRIPTIONS || []).find((x) => x.id === 4917);
    if (line && line.sim_ids.indexOf(sim.id) === -1) line.sim_ids.push(sim.id);
  });
}

function allPlans() {
  const rows = seedPlans().concat(TS.purchased.map((p) => ({ ...p })));
  return rows.map((p) => {
    const o = TS.overrides[p.id];
    return o ? { ...p, ...o } : p;
  });
}
const plansFor = (simId) => allPlans().filter((p) => p.esim_id === simId)
  .sort((a, b) => new Date(b.purchased_at) - new Date(a.purchased_at));

function expiresAt(p) {
  if (p.type !== "one_off") return null;
  if (!p.first_use_at) return null;
  if (p.expires_at) return new Date(p.expires_at);
  return addDays(new Date(p.first_use_at), p.duration_days || 30);
}
function planStatus(p) {
  if (p.type === "one_off") {
    if (!p.first_use_at) return "awaiting";
    const e = expiresAt(p);
    return e && e < nowD() ? "expired" : "active";
  }
  if (p.cancelled) {
    const end = p.renews_on ? new Date(p.renews_on) : null;
    return end && end < nowD() ? "expired" : "not_renewing";
  }
  return "active";
}
const PLAN_STATUS = {
  active: { label: "Active", tone: "ok" },
  awaiting: { label: "Purchased, awaiting first use", tone: "pending" },
  not_renewing: { label: "Active, not renewing", tone: "pending" },
  expired: { label: "Expired", tone: "off" }
};
const isLivePlan = (p) => planStatus(p) !== "expired";

/* The plan a travel surface speaks for: the soonest-ending live plan. */
function primaryPlan(simId) {
  const live = plansFor(simId).filter(isLivePlan);
  if (!live.length) return plansFor(simId)[0] || null;
  const active = live.filter((p) => planStatus(p) !== "awaiting");
  const pool = active.length ? active : live;
  return pool.slice().sort((a, b) => {
    const A = expiresAt(a) || (a.renews_on ? new Date(a.renews_on) : new Date(8e15));
    const B = expiresAt(b) || (b.renews_on ? new Date(b.renews_on) : new Date(8e15));
    return A - B;
  })[0];
}
/* One reading of a travel eSIM, used by every travel surface. */
function travelSummary(sim) {
  const p = primaryPlan(sim.id);
  if (!p) return { plan: null, allowance_gb: sim.allowance_gb, used: sim.used, awaiting: false, expires: sim.expires, renews_on: sim.renews_on, count: 0 };
  const rows = plansFor(sim.id);
  return {
    plan: p, allowance_gb: p.allowance_gb, used: p.used || 0,
    awaiting: planStatus(p) === "awaiting",
    expires: expiresAt(p), renews_on: p.renews_on ? new Date(p.renews_on) : null,
    count: rows.filter(isLivePlan).length, total: rows.length,
    area: p.area, type: p.type
  };
}
const travelEsimFor = (userId) => TE.travelSims().find((s) => s.user_id === userId ||
  (TD.USERS.find((u) => u.id === userId) || { sim_ids: [] }).sim_ids.indexOf(s.id) !== -1) || null;

/* ---------- Purchase ---------- */
let nextEsimId = 250;
function purchase({ userId, ref, tokens }) {
  const cat = byRef(ref);
  const user = TD.USERS.find((u) => u.id === Number(userId));
  if (!cat || !user) return null;
  const existing = travelEsimFor(user.id);
  TS.seq += 1;
  while (TD.SIMS.some((s) => s.id === nextEsimId)) nextEsimId += 1;
  const esimId = existing ? existing.id : nextEsimId;
  const plan = {
    id: "p" + TS.seq,
    esim_id: esimId,
    catalogue_ref: cat.catalogue_ref,
    type: cat.type,
    area: cat.area,
    allowance_gb: cat.allowance_gb,
    duration_days: cat.duration_days || null,
    term_months: cat.term_months || null,
    price: cat.price,
    premium: !!cat.premium,
    purchased_at: iso(nowD()),
    first_use_at: null,
    expires_at: null,
    renews_on: cat.type === "recurring" ? iso(new Date(nowD().getFullYear(), nowD().getMonth() + 1, 1)) : null,
    cancelled: false,
    used: 0,
    destinations: (tokens || []).map((t) => t.label),
    new_esim: !existing,
    user_id: user.id,
    serial: existing ? existing.serial : "89442019123400" + String(5300 + TS.seq)
  };
  TS.purchased.push(plan);
  ensurePurchasedSims();
  if (existing && cat.type === "recurring") existing.renews_on = new Date(plan.renews_on);
  tNotify();
  return plan;
}
function cancelRenewal(planId) {
  TS.overrides[planId] = { ...(TS.overrides[planId] || {}), cancelled: true };
  tNotify();
}
/* First data received: starts validity, and confirms the install (Story 3). */
function simulateFirstUse(planId) {
  const p = allPlans().find((x) => x.id === planId);
  if (!p || p.first_use_at) return false;
  const start = nowD();
  TS.overrides[p.id] = {
    ...(TS.overrides[p.id] || {}),
    first_use_at: iso(start),
    expires_at: p.type === "one_off" ? iso(addDays(start, p.duration_days || 30)) : null,
    used: 0.2
  };
  const sim = TD.SIMS.find((s) => s.id === p.esim_id);
  if (sim && window.MP_ESIM && window.MP_ESIM.isReady(sim)) window.MP_ESIM.confirmInstall(sim.id);
  tNotify();
  return true;
}
function firstAwaitingPlan() {
  return allPlans().find((p) => planStatus(p) === "awaiting") || null;
}
/* Expiring-soon preset: pull the soonest live one-off in to five days out. */
function setExpiringSoon() {
  const p = allPlans().filter((x) => x.type === "one_off" && planStatus(x) === "active")
    .sort((a, b) => (expiresAt(a) || 0) - (expiresAt(b) || 0))[0];
  if (!p) return false;
  TS.overrides[p.id] = { ...(TS.overrides[p.id] || {}), expires_at: iso(addDays(nowD(), 5)) };
  const sim = TD.SIMS.find((s) => s.id === p.esim_id);
  if (sim) sim.expires = addDays(nowD(), 5);
  tNotify();
  return true;
}
function resetTravel() {
  TS.purchased = []; TS.overrides = {}; TS.seq = 0; tSave();
  location.reload();
}

ensurePurchasedSims();

window.MP_TRAVEL = {
  CATALOGUE, AREA_COUNTRIES, AREA_ZONES, byRef, areaLabel, areaCountryCount, areaMembers, areaFlag, isZone,
  searchDestinations, matchingPlans, useTravel, plansFor, primaryPlan, travelSummary, travelEsimFor,
  planStatus, PLAN_STATUS, expiresAt, isLivePlan, purchase, cancelRenewal,
  simulateFirstUse, firstAwaitingPlan, setExpiringSoon, resetTravel
};
