// Toast — couche de notification globale.
// API : showToast('message', 'success' | 'error' | 'info')
// Stack de 3 max (le plus ancien saute), auto-dismiss 3s, tap pour fermer.
// Monté DANS l'écran de l'iPhone (pas dans document.body) pour rester dans le cadre.

const TOAST_MAX = 3;
const TOAST_DURATION = 3000;
const TOAST_EXIT_MS = 220;

const TS_VT = window.VT || {};
const TS_SURF = TS_VT.surface || '#ffffff';
const TS_INK = TS_VT.ink || '#0a0a0a';
const TS_HAIR2 = TS_VT.hair2 || 'rgba(10,10,10,0.10)';
const TS_SHADOW = TS_VT.shadowCard || '0 2px 8px rgba(10,10,10,0.06), 0 8px 24px rgba(10,10,10,0.04)';

const TOAST_TYPES = {
  success: {
    color: TS_VT.success || '#059669',
    tint: TS_VT.successTint || 'rgba(5,150,105,0.10)',
    icon: <path d="M20 6L9 17l-5-5" />,
  },
  error: {
    color: TS_VT.danger || '#b91c1c',
    tint: 'rgba(185,28,28,0.10)',
    icon: <><path d="M12 4l10 17H2z" /><path d="M12 10v4" /><circle cx="12" cy="17.5" r="0.6" fill="currentColor" /></>,
  },
  info: {
    color: TS_VT.medBlue || '#3d6b8f',
    tint: TS_VT.medBlueTint || 'rgba(61,107,143,0.10)',
    icon: <><circle cx="12" cy="12" r="9" /><path d="M12 11v5" /><circle cx="12" cy="7.8" r="0.6" fill="currentColor" /></>,
  },
};

// ── Store hors React : showToast doit être appelable depuis n'importe où,
// sans contexte ni prop drilling (le codebase n'a pas de provider).
const ToastStore = {
  items: [],
  seq: 0,
  listeners: new Set(),
  timers: new Map(),

  subscribe(fn) {
    this.listeners.add(fn);
    return () => this.listeners.delete(fn);
  },

  emit() {
    const snapshot = this.items;
    this.listeners.forEach((fn) => fn(snapshot));
  },

  // opts : { duration, action: { label, onClick } }
  push(message, type, opts = {}) {
    const id = ++this.seq;
    const kind = TOAST_TYPES[type] ? type : 'info';
    const duration = Number(opts.duration) > 0 ? Number(opts.duration) : TOAST_DURATION;
    const next = [...this.items, {
      id, message: String(message), type: kind, leaving: false,
      action: opts.action || null,
    }];

    // Au-delà de 3, on retire les plus anciens — et on tue leurs timers pour
    // ne pas laisser un dismiss orphelin se déclencher sur un id recyclé.
    while (next.length > TOAST_MAX) {
      const dropped = next.shift();
      this.clearTimer(dropped.id);
    }

    this.items = next;
    this.emit();
    this.timers.set(id, setTimeout(() => this.dismiss(id), duration));
    return id;
  },

  dismiss(id) {
    const target = this.items.find((t) => t.id === id);
    if (!target || target.leaving) return;
    this.clearTimer(id);
    this.items = this.items.map((t) => (t.id === id ? { ...t, leaving: true } : t));
    this.emit();
    this.timers.set(id, setTimeout(() => {
      this.items = this.items.filter((t) => t.id !== id);
      this.timers.delete(id);
      this.emit();
    }, TOAST_EXIT_MS));
  },

  clearTimer(id) {
    const t = this.timers.get(id);
    if (t) clearTimeout(t);
    this.timers.delete(id);
  },
};

function showToast(message, type = 'info', opts) {
  return ToastStore.push(message, type, opts);
}

// ── Confirmer une action sans notification ──
// La réussite d'une action ne se dit pas en bas de l'écran : elle se lit sur le
// bouton qui l'a portée, ou dans l'écran qui a changé. Ce petit état sert aux
// actions dont le résultat n'est visible nulle part ailleurs — une copie dans le
// presse-papiers, par exemple : le bouton porte sa confirmation quelques
// secondes, puis redevient un bouton.
// La règle est écrite dans `docs/product/ACTION_FEEDBACK.md`.
const ACTION_DONE_MS = 1600;

function useActionDone(durationMs = ACTION_DONE_MS) {
  const [done, setDone] = React.useState(null);
  const timer = React.useRef(null);

  React.useEffect(() => () => window.clearTimeout(timer.current), []);

  const mark = React.useCallback((key = true) => {
    window.clearTimeout(timer.current);
    setDone(key);
    timer.current = window.setTimeout(() => setDone(null), durationMs);
  }, [durationMs]);

  return [done, mark];
}

// ── Une carte ──
function ToastCard({ toast }) {
  const kind = TOAST_TYPES[toast.type] || TOAST_TYPES.info;
  return (
    <div
      onClick={() => ToastStore.dismiss(toast.id)}
      style={{
        pointerEvents: 'auto',
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '11px 14px',
        background: TS_SURF,
        border: `1px solid ${TS_HAIR2}`,
        borderRadius: 16,
        boxShadow: TS_SHADOW,
        cursor: 'pointer',
        animation: `${toast.leaving ? 'toastOut' : 'toastIn'} ${toast.leaving ? TOAST_EXIT_MS : 260}ms cubic-bezier(.16,1,.3,1) forwards`,
      }}>
      <div style={{
        width: 24, height: 24, borderRadius: '50%', background: kind.tint,
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        color: kind.color,
      }}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
          stroke={kind.color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
          {kind.icon}
        </svg>
      </div>
      <span style={{
        flex: 1, minWidth: 0,
        fontSize: 13, fontWeight: 600, color: TS_INK,
        letterSpacing: '-0.1px', lineHeight: 1.3,
      }}>{toast.message}</span>

      {toast.action && (
        <button
          onClick={(e) => {
            // Sans cela, le clic remonterait à la carte qui ferme le toast.
            e.stopPropagation();
            toast.action.onClick();
            ToastStore.dismiss(toast.id);
          }}
          className="press"
          style={{
            flexShrink: 0, background: 'none', border: 'none', padding: '2px 2px 2px 6px',
            cursor: 'pointer', fontFamily: 'inherit',
            fontSize: 12.5, fontWeight: 700, color: kind.color,
          }}>{toast.action.label}</button>
      )}
    </div>
  );
}

// ── Le host : à monter une seule fois, dans le conteneur de l'écran ──
function ToastHost({ bottom = 84 }) {
  const [items, setItems] = React.useState(ToastStore.items);

  React.useEffect(() => ToastStore.subscribe(setItems), []);

  return (
    <div style={{
      position: 'absolute', left: 16, right: 16, bottom,
      zIndex: 900, pointerEvents: 'none',
      display: 'flex', flexDirection: 'column', gap: 8,
    }}>
      <style>{`
        @keyframes toastIn {
          from { opacity: 0; transform: translateY(14px) scale(.97); }
          to   { opacity: 1; transform: translateY(0) scale(1); }
        }
        @keyframes toastOut {
          from { opacity: 1; transform: translateY(0) scale(1); }
          to   { opacity: 0; transform: translateY(6px) scale(.97); }
        }
      `}</style>
      {items.map((t) => <ToastCard key={t.id} toast={t} />)}
    </div>
  );
}

// ── Copier dans le presse-papiers ──
// `navigator.clipboard.writeText` peut ne jamais se conclure : permission en
// attente de réponse, fenêtre sans le focus. Sans délai de garde, le bouton qui
// l'appelle reste muet — ni confirmation, ni refus — et rien ne dit au
// praticien que rien n'a été copié.
const CLIPBOARD_TIMEOUT_MS = 4000;

function copyToClipboard(text) {
  if (!navigator.clipboard?.writeText) {
    return Promise.reject(new Error('presse-papier indisponible'));
  }
  return Promise.race([
    navigator.clipboard.writeText(text),
    new Promise((_, reject) => window.setTimeout(
      () => reject(new Error('presse-papier sans réponse')), CLIPBOARD_TIMEOUT_MS,
    )),
  ]);
}

window.showToast = showToast;
window.useActionDone = useActionDone;
window.copyToClipboard = copyToClipboard;
window.ToastHost = ToastHost;
console.log('%c[VASCO] toast.jsx chargé — build 2026.08.04', 'color:#3d6b8f;font-weight:bold');
