// ─────────────────────────────────────────────────────────────────────────
// format-prefs.jsx — Formatage respectant la DEVISE et le FUSEAU du profil
//
// Module pur, sans dépendance, sans import ES. Expose des helpers globaux via
// Object.assign(window, {...}) — comme le reste du projet (cf. app.jsx fmtMoney,
// supabase.jsx todayISO). Tout est robuste : si Intl échoue, on retombe sur un
// formatage manuel fr-FR cohérent avec l'existant.
//
// But d'intégration : permettre à l'app de remplacer le `$` codé en dur de
// fmtMoney par fmtMoneyCur(v, state.profile?.currency || 'USD'), et de rendre
// les horloges / regroupements de dates conscients du fuseau IANA de l'user.
// Aucun autre fichier n'est modifié ici (voir integrationNotes).
// ─────────────────────────────────────────────────────────────────────────

// Devises supportées en premier rang (symbole + position). Toute autre devise
// ISO valide reste formatable via Intl ; on a juste un fallback symbole pour
// les trois principales du produit.
const FP_CURRENCIES = {
  USD: { symbol: '$', code: 'USD' },
  EUR: { symbol: '€', code: 'EUR' },
  GBP: { symbol: '£', code: 'GBP' },
};

// Normalise une entrée devise quelconque ('usd', ' eur ', null) → code ISO.
function fpNormCurrency(currency) {
  const c = String(currency || '').trim().toUpperCase();
  if (FP_CURRENCIES[c]) return c;
  // Code ISO 4217 plausible (3 lettres) → on le laisse passer pour Intl.
  if (/^[A-Z]{3}$/.test(c)) return c;
  return 'USD';
}

// (2) currencySymbol(currency) → '$' | '€' | '£' | code ISO si inconnu.
function currencySymbol(currency) {
  const c = fpNormCurrency(currency);
  if (FP_CURRENCIES[c]) return FP_CURRENCIES[c].symbol;
  // Tente de dériver le symbole via Intl pour une devise ISO arbitraire.
  try {
    const parts = new Intl.NumberFormat('fr-FR', {
      style: 'currency', currency: c, currencyDisplay: 'narrowSymbol',
    }).formatToParts(0);
    const sym = parts.find(function (p) { return p.type === 'currency'; });
    if (sym && sym.value) return sym.value;
  } catch (e) { /* Intl peut rejeter un code inconnu — on ignore */ }
  return c;
}

// (1) fmtMoneyCur(value, currency, sign) — montant dans la devise donnée,
// format fr-FR, même règle de décimales que le fmtMoney historique
// (2 décimales sous 100, 0 au-dessus), symbole accolé au nombre.
// sign=true → préfixe +/− (avec le vrai signe moins typographique « − »).
function fmtMoneyCur(value, currency, sign = false) {
  const n = Number(value) || 0;
  const a = Math.abs(n);
  const code = fpNormCurrency(currency);
  const minFrac = a < 100 ? 2 : 0;

  // Nombre formaté fr-FR (espace fine insécable comme séparateur de milliers).
  let formatted;
  try {
    formatted = a.toLocaleString('fr-FR', {
      minimumFractionDigits: minFrac,
      maximumFractionDigits: 2,
    });
  } catch (e) {
    formatted = a.toFixed(minFrac);
  }

  const sym = currencySymbol(code);
  const zeroStr = (0).toLocaleString
    ? (function () { try { return (0).toLocaleString('fr-FR', { minimumFractionDigits: minFrac, maximumFractionDigits: 2 }); } catch (e) { return '0,00'; } })()
    : '0,00';

  if (n === 0) return sym + zeroStr;
  const prefix = sign ? (n > 0 ? '+' + sym : '−' + sym) : sym;
  return prefix + formatted;
}

// ─── Fuseau horaire (IANA) ────────────────────────────────────────────────

// Cache des Intl.DateTimeFormat par (timezone|locale|opts) — instancier un
// DateTimeFormat est coûteux ; on réutilise.
const FP_DTF_CACHE = {};
function fpDTF(timezone, opts) {
  const tz = timezone || undefined;
  const key = (tz || 'local') + '|' + JSON.stringify(opts);
  if (FP_DTF_CACHE[key]) return FP_DTF_CACHE[key];
  let dtf;
  try {
    dtf = new Intl.DateTimeFormat('fr-FR', Object.assign({ timeZone: tz }, opts));
  } catch (e) {
    // Fuseau invalide → on retente sans timeZone (fuseau local navigateur).
    try { dtf = new Intl.DateTimeFormat('fr-FR', opts); }
    catch (e2) { dtf = null; }
  }
  FP_DTF_CACHE[key] = dtf;
  return dtf;
}

// Coerce une entrée date quelconque (Date | ISO string | ms | undefined) → Date.
function fpToDate(date) {
  if (date == null) return new Date();
  if (date instanceof Date) return date;
  if (typeof date === 'number') return new Date(date);
  const d = new Date(date);
  return isNaN(d.getTime()) ? new Date() : d;
}

// (3) tzTimeIn(date, timezone) → 'HH:mm' dans le fuseau IANA donné.
function tzTimeIn(date, timezone) {
  const d = fpToDate(date);
  const dtf = fpDTF(timezone, { hour: '2-digit', minute: '2-digit', hour12: false });
  if (dtf) {
    try { return dtf.format(d); } catch (e) { /* fallback ci-dessous */ }
  }
  const hh = String(d.getHours()).padStart(2, '0');
  const mm = String(d.getMinutes()).padStart(2, '0');
  return hh + ':' + mm;
}

// (3) tzNow(timezone) → 'HH:mm' courant dans le fuseau (raccourci sur l'instant).
function tzNow(timezone) {
  return tzTimeIn(new Date(), timezone);
}

// Variante avec secondes — utile pour une horloge « live » (cf. econ.jsx).
function tzClock(timezone, withSeconds = false) {
  const d = new Date();
  const opts = withSeconds
    ? { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }
    : { hour: '2-digit', minute: '2-digit', hour12: false };
  const dtf = fpDTF(timezone, opts);
  if (dtf) {
    try { return dtf.format(d); } catch (e) { /* fallback */ }
  }
  const hh = String(d.getHours()).padStart(2, '0');
  const mm = String(d.getMinutes()).padStart(2, '0');
  const ss = String(d.getSeconds()).padStart(2, '0');
  return withSeconds ? hh + ':' + mm + ':' + ss : hh + ':' + mm;
}

// (4) localDayInTz(date, timezone) → 'YYYY-MM-DD' : le JOUR civil tel que vécu
// dans le fuseau de l'user. À utiliser pour grouper trades/complétions par
// « jour de l'user » plutôt que par jour du navigateur. Remplace l'usage de
// new Date().getFullYear()/getMonth()/getDate() pour « aujourd'hui ».
function localDayInTz(date, timezone) {
  const d = fpToDate(date);
  // en-CA donne nativement le format YYYY-MM-DD (ISO ordering) — robuste et
  // indépendant de la locale d'affichage fr-FR (qui produirait JJ/MM/AAAA).
  let dtf = FP_DTF_CACHE['__day__|' + (timezone || 'local')];
  if (!dtf) {
    try {
      dtf = new Intl.DateTimeFormat('en-CA', {
        timeZone: timezone || undefined,
        year: 'numeric', month: '2-digit', day: '2-digit',
      });
    } catch (e) {
      try {
        dtf = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
      } catch (e2) { dtf = null; }
    }
    FP_DTF_CACHE['__day__|' + (timezone || 'local')] = dtf;
  }
  if (dtf) {
    try {
      // formatToParts évite toute ambiguïté de séparateur.
      const parts = dtf.formatToParts(d);
      const get = function (t) { const p = parts.find(function (x) { return x.type === t; }); return p ? p.value : null; };
      const y = get('year'), m = get('month'), day = get('day');
      if (y && m && day) return y + '-' + m + '-' + day;
      // certains moteurs renvoient déjà 'YYYY-MM-DD' via format()
      const s = dtf.format(d);
      if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
    } catch (e) { /* fallback ci-dessous */ }
  }
  // Fallback fuseau local navigateur.
  const y = d.getFullYear();
  const mo = String(d.getMonth() + 1).padStart(2, '0');
  const da = String(d.getDate()).padStart(2, '0');
  return y + '-' + mo + '-' + da;
}

// Heure (0-23) dans le fuseau donné — primitive interne pour sessionStatusInTz.
function fpHourInTz(date, timezone) {
  const d = fpToDate(date);
  const dtf = fpDTF(timezone, { hour: '2-digit', hour12: false });
  if (dtf) {
    try {
      const parts = dtf.formatToParts(d);
      const hp = parts.find(function (p) { return p.type === 'hour'; });
      if (hp) {
        let h = parseInt(hp.value, 10);
        if (h === 24) h = 0; // certains moteurs renvoient '24' à minuit
        if (!isNaN(h)) return h;
      }
    } catch (e) { /* fallback */ }
  }
  return d.getHours();
}

// (5) sessionStatusInTz(timezone) — pour un trader, l'état des grandes sessions
// (Tokyo / Londres / New York) projeté dans l'heure courante du fuseau de l'user.
// Pertinent ici : l'app est un journal de trading, donc « quelle session est
// ouverte » est une info de décision légère et utile (cf. exigence UX : que des
// visualisations vraiment utiles). Renvoie un objet { now, sessions[], openNames }.
// Heures de session approximatives en heure de Paris (Europe/Paris) — on calcule
// l'heure de Paris puis on borne ; volontairement simple et lisible.
const FP_SESSIONS = [
  { key: 'tokyo',  name: 'Tokyo',     start: 1,  end: 9  }, // ~01:00–09:00 Paris
  { key: 'london', name: 'Londres',   start: 9,  end: 17 }, // ~09:00–17:00 Paris
  { key: 'ny',     name: 'New York',  start: 14, end: 22 }, // ~14:00–22:00 Paris
];

function sessionStatusInTz(timezone) {
  const now = new Date();
  // Les bornes de session sont définies en heure de Paris (marché de réf.).
  const parisHour = fpHourInTz(now, 'Europe/Paris');
  const userHour = fpHourInTz(now, timezone);
  const inRange = function (h, s, e) {
    return s <= e ? (h >= s && h < e) : (h >= s || h < e);
  };
  const sessions = FP_SESSIONS.map(function (s) {
    const open = inRange(parisHour, s.start, s.end);
    return { key: s.key, name: s.name, open: open, start: s.start, end: s.end };
  });
  const openNames = sessions.filter(function (s) { return s.open; }).map(function (s) { return s.name; });
  return {
    now: tzNow(timezone),          // 'HH:mm' chez l'user
    userHour: userHour,            // entier 0-23 chez l'user
    parisHour: parisHour,          // entier 0-23 à Paris (référence sessions)
    sessions: sessions,            // [{ key, name, open, start, end }]
    openNames: openNames,          // ['Londres', 'New York'] p.ex.
    label: openNames.length ? openNames.join(' + ') : 'Hors session',
  };
}

// ─── Exposition globale (pattern projet) ──────────────────────────────────
Object.assign(window, {
  fmtMoneyCur,
  currencySymbol,
  tzNow,
  tzTimeIn,
  tzClock,
  localDayInTz,
  sessionStatusInTz,
});
