// Micara bridge — design system Astryx personnalisé (thème iOS)
//
// COPIE de front/src/theme.jsx (refonte bridge t_fa529748) : le bridge est
// l'app téléchargée par les clients → il doit porter le design system en dur
// (zéro dépendance réseau au chargement). Les tokens par défaut (DEFAULT_TOKENS)
// sont IDENTIQUES au front — un test de dérive (test-theme-drift.mjs) vérifie
// que les deux copies ne divergent pas.
//
// Différences volontaires avec front/src/theme.jsx :
//  - ThemeRoot charge les tokens du thème ACTIF via window.bridge.theme()
//    (IPC main.js → GET {serverUrl}/api/theme) au lieu de fetch('/api/theme') :
//    le renderer est chargé en file://, un fetch relatif ne peut pas marcher,
//    et le main fait déjà toutes les requêtes HTTP du bridge.
//  - Pas d'éditeur /admin (parseColor, withAlpha, contrastOn, computeDerived
//    restent pour buildTheme ; SOURCE_TOKEN_NAMES et setThemeTokens non repris).
import { useEffect, useMemo, useState } from 'react';
import { defineTheme, expandRadiusScale, Theme } from '@astryxdesign/core/theme';
import { ICON_REGISTRY } from './icons';

export const THEME_NAME = 'micara';

// Soft (pastel) par teinte : fond = teinte à ~18 % sur blanc (clair) / noir
// (sombre) ; texte = variante lisible (sombre en clair, claire en sombre).
// Les 9 premières sont consommées par les variantes Badge non-sémantiques
// d'Astryx (blue, cyan, green, orange, pink, purple, red, teal, yellow) ;
// mint et indigo restent disponibles en tokens pour de futures variantes.
const HUE_BG = {
  red: ['#FFDCDA', '#2E0C0A'],
  orange: ['#FFECD1', '#2E1D02'],
  yellow: ['#FFF6D1', '#2E2702'],
  green: ['#DAF5E1', '#092610'],
  mint: ['#D1F5F3', '#122929'],
  teal: ['#DAF1F5', '#0C2528'],
  cyan: ['#DAF0FB', '#12262E'],
  blue: ['#D1E7FF', '#02182E'],
  indigo: ['#E1E1F8', '#111129'],
  purple: ['#F1E0F9', '#22102C'],
  pink: ['#FFD9E0', '#2E0A11'],
};
const HUE_TEXT = {
  red: ['#D70015', '#FF6961'],
  orange: ['#C93400', '#FFB340'],
  yellow: ['#B25000', '#FFD60A'],
  green: ['#248A3D', '#30D158'],
  mint: ['#009E94', '#63E6E2'],
  teal: ['#007C86', '#40CBE0'],
  cyan: ['#007A9C', '#64D2FF'],
  blue: ['#0040DD', '#0A84FF'],
  indigo: ['#4338DC', '#5E5CE6'],
  purple: ['#8944AB', '#BF5AF2'],
  pink: ['#D7006B', '#FF375F'],
};
// Couleurs pures iOS (clair/sombre) — bordures et icônes colorées
const HUE_STRONG = {
  red: ['#FF3B30', '#FF453A'],
  orange: ['#FF9500', '#FF9F0A'],
  yellow: ['#FFCC00', '#FFD60A'],
  green: ['#34C759', '#30D158'],
  mint: ['#00C7BE', '#63E6E2'],
  teal: ['#30B0C7', '#40CBE0'],
  cyan: ['#32ADE6', '#64D2FF'],
  blue: ['#007AFF', '#0A84FF'],
  indigo: ['#5856D6', '#5E5CE6'],
  purple: ['#AF52DE', '#BF5AF2'],
  pink: ['#FF2D55', '#FF375F'],
};

// Tokens par défaut de la marque Micara (paire [clair, sombre] = light-dark).
export const DEFAULT_TOKENS = {
  // --- Accent : bleu iOS ---
  '--color-accent': ['#007AFF', '#0A84FF'],
  '--color-accent-muted': ['#007AFF1F', '#0A84FF3D'], // 12 % / 24 % bleu
  '--color-on-accent': ['#FFFFFF', '#FFFFFF'],
  '--color-text-accent': ['#007AFF', '#0A84FF'],
  '--color-icon-accent': ['#007AFF', '#0A84FF'],
  // --- Neutre (fills génériques iOS) ---
  '--color-neutral': ['rgba(120,120,128,0.16)', 'rgba(120,120,128,0.32)'],
  // --- Surfaces (iOS systemBackground / systemGroupedBackground) ---
  '--color-background-body': ['#F2F2F7', '#000000'],
  // secondarySystemBackground pour les champs de saisie (TextInput iOS : gris
  // clair #F2F2F7 au lieu de blanc pur, #1C1C1E en sombre) — consommé par la
  // coquille des inputs Astryx (--color-background-surface).
  '--color-background-surface': ['#F2F2F7', '#1C1C1E'],
  '--color-background-card': ['#FFFFFF', '#1C1C1E'],
  '--color-background-muted': ['rgba(120,120,128,0.12)', 'rgba(120,120,128,0.24)'],
  '--color-background-popover': ['#FFFFFF', '#2C2C2E'],
  '--color-overlay': ['rgba(0,0,0,0.4)', 'rgba(0,0,0,0.5)'],
  '--color-overlay-hover': ['rgba(0,0,0,0.05)', 'rgba(255,255,255,0.05)'],
  '--color-overlay-pressed': ['rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)'],
  // --- Texte (iOS labels) ---
  '--color-text-primary': ['#000000', '#FFFFFF'],
  '--color-text-secondary': ['rgba(60,60,67,0.6)', 'rgba(235,235,245,0.6)'],
  '--color-text-disabled': ['rgba(60,60,67,0.3)', 'rgba(235,235,245,0.3)'],
  '--color-on-dark': ['#FFFFFF', '#FFFFFF'],
  '--color-on-light': ['#000000', '#000000'],
  // --- Icônes (mêmes labels) ---
  '--color-icon-primary': ['#000000', '#FFFFFF'],
  '--color-icon-secondary': ['rgba(60,60,67,0.6)', 'rgba(235,235,245,0.6)'],
  '--color-icon-disabled': ['rgba(60,60,67,0.3)', 'rgba(235,235,245,0.3)'],
  // --- Bordures (séparateurs iOS) ---
  '--color-border': ['rgba(60,60,67,0.29)', 'rgba(84,84,88,0.6)'],
  '--color-border-emphasized': ['#C6C6C8', '#38383A'],
  // --- Statuts (couleurs système iOS) ---
  '--color-success': ['#34C759', '#30D158'],
  '--color-on-success': ['#FFFFFF', '#FFFFFF'],
  '--color-success-muted': ['rgba(52,199,89,0.18)', 'rgba(48,209,88,0.24)'],
  '--color-error': ['#FF3B30', '#FF453A'],
  '--color-on-error': ['#FFFFFF', '#FFFFFF'],
  '--color-error-muted': ['rgba(255,59,48,0.18)', 'rgba(255,69,58,0.24)'],
  '--color-warning': ['#FF9500', '#FF9F0A'],
  '--color-on-warning': ['#000000', '#000000'], // texte sombre sur orange (contraste)
  '--color-warning-muted': ['rgba(255,149,0,0.18)', 'rgba(255,159,10,0.24)'],
  // --- Soft (badges) + strong par teinte ---
  ...Object.entries(HUE_BG).flatMap(([hue, bg]) => [
    [`--color-background-${hue}`, bg],
    [`--color-border-${hue}`, HUE_STRONG[hue]],
    [`--color-icon-${hue}`, HUE_STRONG[hue]],
    [`--color-text-${hue}`, HUE_TEXT[hue]],
  ]).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}),
  // --- Radius : généreux style iOS (8 / 10 / 18 / 20) ---
  ...expandRadiusScale({ base: 4, multiplier: 1.5 }), // inner 6, element 12, container 18, page 24, chat 24
  '--radius-inner': '8px',
  // Éléments (inputs, boutons) : ~10px comme les champs de formulaire iOS
  '--radius-element': '10px',
  '--radius-page': '20px',
  // --- Taille des éléments : champs ~44px comme les TextField iOS (Apple HIG,
  // cible tactile minimale 44pt). Consommé par TextInput/Button/Select md via
  // --size-element-md — token FIXE (pas édictable dans l'éditeur, comme les
  // ancres --radius-none/full). ---
  '--size-element-md': '44px',
  // --- Ombres douces iOS ---
  '--shadow-low': [
    '0px 1px 2px rgba(0,0,0,0.05), 0px 2px 8px rgba(0,0,0,0.04)',
    '0px 1px 2px rgba(0,0,0,0.4), 0px 2px 8px rgba(0,0,0,0.3)',
  ],
  '--shadow-med': [
    '0px 2px 4px rgba(0,0,0,0.06), 0px 4px 16px rgba(0,0,0,0.05)',
    '0px 2px 4px rgba(0,0,0,0.45), 0px 4px 16px rgba(0,0,0,0.35)',
  ],
  '--shadow-high': [
    '0px 4px 8px rgba(0,0,0,0.07), 0px 8px 32px rgba(0,0,0,0.06)',
    '0px 4px 8px rgba(0,0,0,0.5), 0px 8px 32px rgba(0,0,0,0.4)',
  ],
  // --- Typo : polices système (zéro dépendance réseau) ---
  '--font-family-body': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
  '--font-family-heading': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
  '--font-family-code': '"SF Mono", Monaco, Consolas, monospace',
};

// --- fonds dégradés + grain (thème Rombo, lot J) ------------------------------
// Astryx applique --color-background-body en background-color, où un dégradé
// est invalide. Cette règle ajoute background-image sur TOUT thème : une
// valeur couleur y est ignorée (invalid at computed-value time), une valeur
// dégradé s'affiche par-dessus la couleur de fond. Le grain (texture film,
// style rombo.co) se pose via <html data-grain="true"> — activé par ThemeRoot
// quand le fond du thème actif est un dégradé.
if (typeof document !== 'undefined') {
  const style = document.createElement('style');
  style.textContent = `html[data-astryx-theme]{background-color:var(--color-background-body);background-image:var(--color-background-body);background-size:cover}@media (prefers-color-scheme: dark){html[data-astryx-theme]{background-image:var(--color-background-body-dark,var(--color-background-body))}}[data-astryx-theme]{background-image:var(--color-background-body);background-size:cover}@media (prefers-color-scheme: dark){[data-astryx-theme]{background-image:var(--color-background-body-dark,var(--color-background-body))}}html[data-astryx-theme][data-grain="true"]::before{content:"";position:fixed;inset:0;z-index:2147483647;pointer-events:none;opacity:.07;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E")}`;
  document.head.appendChild(style);
}

// --- couleurs dérivées (éditeur simplifié, lot J) -----------------------------

const HEX_RE_UTIL = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
const RGB_RE_UTIL = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(0|1|0?\.\d+)\s*)?\)$/;

// hex #RGB/#RGBA/#RRGGBB/#RRGGBBAA ou rgb()/rgba() → {r,g,b,a} | null
export function parseColor(str) {
  if (typeof str !== 'string') return null;
  const s = str.trim();
  if (HEX_RE_UTIL.test(s)) {
    let h = s.slice(1);
    if (h.length === 3 || h.length === 4) h = [...h].map((c) => c + c).join('');
    const n = parseInt(h.slice(0, 6), 16);
    return {
      r: (n >> 16) & 255,
      g: (n >> 8) & 255,
      b: n & 255,
      a: h.length === 8 ? Math.round(parseInt(h.slice(6, 8), 16) / 2.55) / 100 : 1,
    };
  }
  const m = s.match(RGB_RE_UTIL);
  if (m) return { r: +m[1], g: +m[2], b: +m[3], a: m[4] === undefined ? 1 : +m[4] };
  return null;
}

// couleur → même couleur avec une nouvelle opacité (#RRGGBBAA pour un hex,
// rgba() pour une source rgba()) — l'alpha existant est REMPLACÉ.
export function withAlpha(color, alpha) {
  const c = parseColor(color);
  if (!c) return null;
  const a = Math.max(0, Math.min(1, alpha));
  if (HEX_RE_UTIL.test(color.trim())) {
    const to2 = (n) => n.toString(16).padStart(2, '0');
    return `#${to2(c.r)}${to2(c.g)}${to2(c.b)}${to2(Math.round(a * 255))}`;
  }
  return `rgba(${c.r},${c.g},${c.b},${a})`;
}

// luminance relative WCAG + contraste binaire : > 0.6 → texte sombre (noir),
// sinon blanc — utilisé pour on-accent (le jaune canari Rombo → texte noir).
export function contrastOn(color) {
  const c = parseColor(color);
  if (!c) return '#FFFFFF';
  const f = (v) => {
    const x = v / 255;
    return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
  };
  const l = 0.2126 * f(c.r) + 0.7152 * f(c.g) + 0.0722 * f(c.b);
  return l > 0.6 ? '#000000' : '#FFFFFF';
}

// Dérivées → overrides calculées à partir du thème effectif (défauts + tokens
// enregistrés). Chaque règle reçoit le token source [clair, sombre] et renvoie
// la paire dérivée ; en cas de source illisible (ex. fond dégradé), on garde
// la valeur par défaut du code (DEFAULT_TOKENS). Les règles peuvent être
// distinctes par mode (ex. muted : 12 % en clair, 24 % en sombre).
export function computeDerived(tokens) {
  const defs = DEFAULT_TOKENS;
  const pair = (name) => {
    const v = tokens[name];
    return Array.isArray(v) && v.length === 2 ? v : defs[name];
  };
  // outName = token dérivé (défaut du code en repli), sourceName = token source
  const map = (outName, sourceName, ruleL, ruleD) => {
    const [l, d] = pair(sourceName);
    const dl = defs[outName];
    const out = (src, fallback, fn) => {
      const v = fn(src);
      return v === null || v === undefined ? fallback : v;
    };
    return [
      out(l, Array.isArray(dl) ? dl[0] : dl, ruleL),
      out(d, Array.isArray(dl) ? dl[1] : dl, ruleD),
    ];
  };
  const copy = (v) => (parseColor(v) ? v.trim() : null);
  const alpha = (a) => (v) => withAlpha(v, a);
  const halfAlpha = (v) => {
    const c = parseColor(v);
    return c ? withAlpha(v, c.a * 0.5) : null;
  };
  const contrast = (v) => (parseColor(v) ? contrastOn(v) : null);

  const textDisabled = map('--color-text-disabled', '--color-text-secondary', halfAlpha, halfAlpha);
  return {
    '--color-accent-muted': map('--color-accent-muted', '--color-accent', alpha(0.12), alpha(0.24)),
    '--color-on-accent': map('--color-on-accent', '--color-accent', contrast, contrast),
    '--color-text-accent': map('--color-text-accent', '--color-accent', copy, copy),
    '--color-icon-accent': map('--color-icon-accent', '--color-accent', copy, copy),
    '--color-text-disabled': textDisabled,
    '--color-icon-primary': map('--color-icon-primary', '--color-text-primary', copy, copy),
    '--color-icon-secondary': map('--color-icon-secondary', '--color-text-secondary', copy, copy),
    '--color-icon-disabled': textDisabled, // même calcul (icône = texte)
    '--color-success-muted': map('--color-success-muted', '--color-success', alpha(0.18), alpha(0.24)),
    '--color-error-muted': map('--color-error-muted', '--color-error', alpha(0.18), alpha(0.24)),
    '--color-warning-muted': map('--color-warning-muted', '--color-warning', alpha(0.18), alpha(0.24)),
  };
}

// Une couleur « dégradé » (fond texturé) : vraie pour les thèmes style Rombo.
export function isGradientValue(v) {
  return typeof v === 'string' && /(?:linear|radial|conic)-gradient\(/.test(v);
}

// Astryx enveloppe les paires [clair, sombre] dans light-dark(), qui n'accepte
// QUE des couleurs — un dégradé y serait invalide. Pour un fond dégradé, on
// éclate la paire en DEUX tokens à valeur unique (--color-background-body =
// clair, --color-background-body-dark = sombre) ; la règle CSS
// [data-astryx-theme] les applique en background-image selon le mode. Pour une
// paire de couleurs ordinaires, le token n'est pas touché (light-dark() OK).
export function splitGradientTokens(tokens) {
  const out = { ...tokens };
  const bg = out['--color-background-body'];
  if (Array.isArray(bg) && bg.length === 2 && (isGradientValue(bg[0]) || isGradientValue(bg[1]))) {
    out['--color-background-body'] = bg[0];
    out['--color-background-body-dark'] = bg[1];
  }
  return out;
}

// Thème effectif = défauts + overrides enregistrés + dérivées calculées.
// Ordre : les dérivées passent EN DERNIER — elles reflètent les sources
// (défauts OU overrides) et neutralisent toute dérivée enregistrée par
// l'ancien éditeur (87 tokens).
// icons : registre d'icônes heroicons mini (voir icons.jsx) — résolu par le
// mode string de <Icon> dans tout ThemeRoot (y compris les icônes internes
// Astryx : chevrons, funnel, X des Dialog…).
export function buildTheme(tokens = {}) {
  const effective = { ...DEFAULT_TOKENS, ...tokens };
  return defineTheme({
    name: THEME_NAME,
    tokens: splitGradientTokens({ ...effective, ...computeDerived(effective) }),
    icons: ICON_REGISTRY,
  });
}

export const DEFAULT_THEME = buildTheme();

// --- application dynamique ---------------------------------------------
// Le main.js récupère le thème ACTIF du serveur (GET {serverUrl}/api/theme,
// zéro CORS : la requête part du processus principal) et le passe au renderer
// via window.bridge.theme(). Hors-ligne ou serveur muet → thème par défaut
// (l'app reste utilisable, même design system iOS).
let currentTokens = {};

export function getThemeTokens() {
  return currentTokens;
}

// Fournisseur racine : applique le thème Astryx à toute l'app.
export function ThemeRoot({ children }) {
  const [tokens, setTokens] = useState(null); // null = pas encore chargé

  useEffect(() => {
    let cancelled = false;
    const load = () => {
      if (!window.bridge || typeof window.bridge.theme !== 'function') {
        currentTokens = {};
        setTokens({});
        return;
      }
      window.bridge.theme()
        .then((r) => {
          if (cancelled) return;
          currentTokens = (r && r.ok && r.tokens) || {};
          setTokens(currentTokens);
        })
        .catch(() => {
          if (cancelled) return;
          currentTokens = {};
          setTokens({});
        });
    };
    load();
    return () => { cancelled = true; };
  }, []);

  const theme = useMemo(() => buildTheme(tokens || {}), [tokens]);

  // grain (texture film) : actif quand le fond du thème actif est un dégradé —
  // le CSS [data-grain="true"]::before pose l'overlay de bruit (style rombo).
  useEffect(() => {
    const bg = tokens?.['--color-background-body'];
    const v = Array.isArray(bg) ? bg[0] : bg;
    if (typeof document !== 'undefined') {
      document.documentElement.dataset.grain = isGradientValue(v) ? 'true' : 'false';
    }
  }, [tokens]);

  return (
    <Theme theme={theme} mode="system">
      {children}
    </Theme>
  );
}
