// Design tokens for BreatheFlow. Dark is the primary surface (most
// breathwork apps are dark to preserve the user's adapted, post-session
// state). Accent is a single muted aqua; variations are shifted by hue
// only, so density/chroma stays coherent across palettes.

const BF_THEMES = {
  dark: {
    bg: 'oklch(0.17 0.015 245)',
    bgSoft: 'oklch(0.22 0.018 245)',
    card: 'oklch(0.23 0.018 245)',
    cardSoft: 'oklch(0.27 0.018 245)',
    line: 'oklch(0.32 0.012 245 / 0.6)',
    fg: 'oklch(0.96 0.006 90)',
    fgMuted: 'oklch(0.78 0.012 240)',
    fgFaint: 'oklch(0.56 0.012 240)',
  },
  // Re-tuned 2026-04-25 for v1.29 light-mode rebuild. Goals:
  //   1. Body / muted text pass WCAG AA (≥4.5:1) against the surface they sit
  //      on. Faint passes AA-large (≥3:1). The previous fgFaint (L=0.62) was
  //      ~2.8:1 — invisible. The previous bg/card pair (0.985 vs 1.0) had
  //      virtually no separation; cards and surfaces blended together.
  //   2. Use a clear two-step: tinted bg + pure-white card so cards have
  //      built-in elevation without needing shadows.
  //   3. Lines visible without screaming — borders show structure but don't
  //      compete with text.
  // Tokens stay in oklch so accent-hue shifts produce consistent perceived
  // weight; only the light branch was retuned.
  light: {
    bg: 'oklch(0.965 0.005 245)',     // tinted off-white surface
    bgSoft: 'oklch(0.93 0.006 245)',  // nested rows / sections slightly darker
    card: 'oklch(1 0 0)',             // pure white cards float above bg
    cardSoft: 'oklch(0.97 0.005 245)',
    line: 'oklch(0.84 0.006 245)',    // visible separator on both bg and card
    fg: 'oklch(0.20 0.012 245)',      // body text, ~13:1 on card / ~12:1 on bg
    fgMuted: 'oklch(0.42 0.014 245)', // secondary copy, ~6:1 on card
    fgFaint: 'oklch(0.52 0.012 245)', // hints / labels, ~3.8:1 on card (AA-large)
  },
};

// Accents share L=0.78 C=0.08 — shifting only H keeps perceived weight even.
const BF_ACCENTS = {
  aqua:   { h: 200, label: 'Aqua' },
  moss:   { h: 155, label: 'Moss' },
  lilac:  { h: 300, label: 'Lilac' },
  ember:  { h: 45,  label: 'Ember' },
  rose:   { h: 15,  label: 'Rose' },
};
const bfAccent = (k, l = 0.78, c = 0.08) => `oklch(${l} ${c} ${BF_ACCENTS[k].h})`;

// Shared font stacks. Instrument Serif for hero numerals + section leads,
// Geist for everything functional, Geist Mono for timers/metrics.
// Font pairings user can switch between
const BF_FONT_PAIRINGS = {
  serif:    { label: 'Editorial',  labelKey: 'settings.typography_editorial', display: "'Instrument Serif', Georgia, serif", ui: "'Geist', system-ui, sans-serif" },
  humanist: { label: 'Humanist',   labelKey: 'settings.typography_humanist',  display: "'Fraunces', Georgia, serif",         ui: "'Manrope', system-ui, sans-serif" },
  modern:   { label: 'Modern',     labelKey: 'settings.typography_modern',    display: "'Geist', system-ui, sans-serif",     ui: "'Geist', system-ui, sans-serif" },
  soft:     { label: 'Soft',       labelKey: 'settings.typography_soft',      display: "'DM Serif Display', serif",          ui: "'DM Sans', system-ui, sans-serif" },
};
// Display helper — routes label through i18n catalog, falls back to English.
function bfFontPairingLabel(p) {
  if (!p) return '';
  if (p.labelKey && window.BFI18n) return window.BFI18n.t(p.labelKey) || p.label;
  return p.label || '';
}
let BF_FONTS = {
  serif: "'Instrument Serif', 'Cormorant Garamond', Georgia, serif",
  sans:  "'Geist', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
  mono:  "'Geist Mono', 'SF Mono', ui-monospace, Menlo, monospace",
};
const bfSetFontPairing = (key) => {
  const p = BF_FONT_PAIRINGS[key] || BF_FONT_PAIRINGS.serif;
  BF_FONTS.serif = p.display;
  BF_FONTS.sans = p.ui;
};

// Built-in breathing patterns. Durations in seconds per phase. A cycle =
// sum of all phase durations. `holdOut` is the hold after exhale (optional).
//
// `name`/`desc`/`tag` stay as English strings — they are the persisted
// identity (used in session history JSON, analytics, share copy). For
// display, always route through bfPatternName/bfPatternDesc/bfPatternTag
// which look up the *Key via BFI18n and fall back to the English value.
//
// `audience` controls visibility across the Adult/Kids split:
//   'all'    — visible in both Adult and Kids libraries
//   'adult'  — visible in Adult only; HARD-BLOCKED from Kids mode and from
//              family sessions that include a kid profile (covers both
//              content-mature patterns like Tantra and medically-risky ones
//              like Wim Hof hyperventilation)
//   'kids'   — visible in Kids only; hidden from the Adult library so it
//              doesn't clutter grown-up flows
//
// Overrides live in the config table as `pattern.<id>.audience` (admin
// panel) — flip a pattern without a deploy. See bfPatternsForProfile.
const BF_PATTERNS = [
  { id: 'box',       name: 'Box Breathing',     tag: 'Focus',   nameKey: 'pattern.box.name',      descKey: 'pattern.box.desc',      tagKey: 'flow.tag_focus',   audience: 'adult', inhale: 4, holdIn: 4, exhale: 4, holdOut: 4, desc: 'Even four-count square. Used by Navy SEALs for composure under pressure.' },
  { id: 'vagus',     name: 'Vagus Nerve',       tag: 'Calm',    nameKey: 'pattern.vagus.name',    descKey: 'pattern.vagus.desc',    tagKey: 'flow.tag_calm',    audience: 'adult', inhale: 4, holdIn: 0, exhale: 6, holdOut: 0, desc: 'Long exhales stimulate the vagus nerve — the body\u2019s rest-and-digest switch.' },
  { id: 'calm',      name: '4\u00b77\u00b78 Calm',          tag: 'Sleep',   nameKey: 'pattern.calm.name',     descKey: 'pattern.calm.desc',     tagKey: 'flow.tag_sleep',   audience: 'adult', inhale: 4, holdIn: 7, exhale: 8, holdOut: 0, desc: 'Longer exhale engages the parasympathetic brake. Great pre-sleep.' },
  { id: 'coherent',  name: 'Coherent Breath',   tag: 'Balance', nameKey: 'pattern.coherent.name', descKey: 'pattern.coherent.desc', tagKey: 'flow.tag_balance', audience: 'adult', inhale: 5.5, holdIn: 0, exhale: 5.5, holdOut: 0, desc: 'Six breaths per minute \u2014 the resonant frequency of the heart.' },
  // ─── Kids patterns ────────────────────────────────────────────────
  // Shorter phases, light holds, playful framing. Balloon is the first-
  // session default in Kids mode: 4·2·4·2 — four-in, two-hold, four-out,
  // two-hold is gentle enough for a child but distinct from adult Box
  // (which is 4·4·4·4). Dragon adds a longer exhale for "breathing out
  // fire" (4·0·6·0, no breath-holds). Both default to the Balloon skin.
  { id: 'balloon',   name: 'Balloon Breath',    tag: 'Play',    nameKey: 'pattern.balloon.name',  descKey: 'pattern.balloon.desc',  tagKey: 'flow.tag_play',    audience: 'kids',  inhale: 4, holdIn: 2, exhale: 4, holdOut: 2, desc: 'Blow up your balloon, hold it, let it drift back down. Four in, two hold, four out, two hold.' },
  { id: 'dragon',    name: 'Dragon Breath',     tag: 'Play',    nameKey: 'pattern.dragon.name',   descKey: 'pattern.dragon.desc',   tagKey: 'flow.tag_play',    audience: 'kids',  inhale: 4, holdIn: 0, exhale: 6, holdOut: 0, desc: 'Big dragon breath in, slow fire out. Four in, six out.' },
];

// Neutral starting point for "Build your own" — opens the preset editor
// so the user can tune each phase by ±1s. Intentionally boring defaults
// (box 4s) so the editor is the point, not the preset.
const BF_CUSTOM_PATTERN = {
  id: 'custom', name: 'Custom', tag: 'Focus',
  nameKey: 'pattern.custom.name', descKey: 'pattern.custom.desc', tagKey: 'flow.tag_focus',
  inhale: 4, holdIn: 4, exhale: 4, holdOut: 4,
  desc: 'Tune each phase, then Save as… to keep it in My patterns.',
};

// Display-time helpers. Route the built-in fields through BFI18n with an
// English fallback so a missing key still paints something readable, and
// user-saved patterns (no *Key) still work.
//
// BFI18n.t() returns the key string itself when the key is missing from
// the catalog (see src/i18n.js). That's a debugging convenience but lethal
// for UX — a user briefly seeing "pattern.balloon.name" on their home
// screen is a bug. The `=== key` check below treats that echo as a miss
// and falls back to the raw English `name`/`desc` fields.
function bfPatternName(p) {
  if (!p) return '';
  if (p.nameKey && window.BFI18n) {
    const v = window.BFI18n.t(p.nameKey);
    if (v && v !== p.nameKey) return v;
  }
  return p.name || '';
}
function bfPatternDesc(p) {
  if (!p) return '';
  if (p.descKey && window.BFI18n) {
    const v = window.BFI18n.t(p.descKey);
    if (v && v !== p.descKey) return v;
  }
  return p.desc || '';
}
// Known tag → i18n key fallback. Used when a pattern lacks an explicit
// tagKey (e.g. user-saved customs from before tagKey existed, or any path
// that spreads the pattern through a code path that drops it).
const BF_TAG_KEY_BY_TAG = {
  Focus:   'flow.tag_focus',
  Calm:    'flow.tag_calm',
  Sleep:   'flow.tag_sleep',
  Balance: 'flow.tag_balance',
  Energy:  'flow.tag_energy',
  Play:    'flow.tag_play',
};
function bfPatternTag(p) {
  if (!p) return '';
  const i18n = window.BFI18n;
  if (p.tagKey && i18n) {
    const v = i18n.t(p.tagKey);
    if (v && v !== p.tagKey) return v;
  }
  // Fallback: derive key from the English tag string so built-ins still
  // localize even if tagKey was dropped somewhere upstream.
  const derivedKey = p.tag && BF_TAG_KEY_BY_TAG[p.tag];
  if (derivedKey && i18n) {
    const v = i18n.t(derivedKey);
    if (v && v !== derivedKey) return v;
  }
  return p.tag || '';
}

// ─── Audience filtering ────────────────────────────────────────────────
// Returns the effective audience for a pattern, honouring admin overrides
// stored in the config table (`pattern.<id>.audience`). If BFConfig hasn't
// loaded or there's no override, falls back to the pattern's declared
// audience or 'all' as the permissive default.
function bfPatternAudience(p) {
  if (!p) return 'all';
  try {
    if (window.BFConfig && window.BFConfig.get) {
      const override = window.BFConfig.get('pattern.' + p.id);
      if (override && typeof override === 'object' && override.audience) {
        return override.audience;
      }
    }
  } catch (e) {}
  return p.audience || 'all';
}

// Returns the list of patterns visible for the given profile. Filtering
// honours the kid-safety hard rule and the adult-curation soft rule:
//   - Kid profile (kind='kid')  → 'all' + 'kids'  (hard-blocks 'adult')
//   - Adult / null profile      → 'all' + 'adult' (hides 'kids' curation)
//
// User-saved custom patterns (no `audience` field) are treated as 'all' so
// legacy rows don't disappear. Admin overrides are applied via
// bfPatternAudience so a previously-safe pattern can be reclassified live.
function bfPatternsForProfile(profile, options) {
  const opts = options || {};
  const source = Array.isArray(opts.source) ? opts.source : BF_PATTERNS;
  const isKid = !!(profile && profile.kind === 'kid');
  return source.filter(function (p) {
    const a = bfPatternAudience(p);
    if (isKid) return a === 'all' || a === 'kids';
    return a === 'all' || a === 'adult';
  });
}

// Hard safety check — call before launching a session to ensure a kid
// profile can never run an adult-audience pattern even if the id arrives
// from a stale URL, deep-link, or localStorage. Returns true if safe.
function bfPatternAllowedForProfile(pattern, profile) {
  if (!pattern) return false;
  const a = bfPatternAudience(pattern);
  const isKid = !!(profile && profile.kind === 'kid');
  if (isKid) return a === 'all' || a === 'kids';
  return true;  // adult profile: everything allowed (curation handled at UI layer)
}

const bfCycleSeconds = (p) => p.inhale + p.holdIn + p.exhale + p.holdOut;

// Visualizer default per pattern: patterns without holds (Coherent, Vagus)
// feel wrong in the geometric/square style — the corners imply pauses that
// don't exist. Prefer flower for rhythmic no-hold patterns, circle as a
// universal fallback, and only let geometric stand when the pattern itself
// has four distinct phases.
//
// Kids patterns (audience:'kids') override everything — they always render
// in a kid-themed skin regardless of user preference. Balloon Breath gets
// the balloon, Dragon Breath gets the dragon (sparks-on-exhale). That gives
// Kids mode its unmistakable visual identity and ensures the copy matches
// what's on screen.
const bfDefaultViz = (pattern, userPreference) => {
  if (pattern && pattern.audience === 'kids') {
    return pattern.id === 'dragon' ? 'dragon' : 'balloon';
  }
  const allFourPhases = pattern.inhale > 0 && pattern.holdIn > 0 && pattern.exhale > 0 && pattern.holdOut > 0;
  const noHold = (pattern.holdIn === 0 && pattern.holdOut === 0);
  // Geometric (square) only fits patterns with 4 real phases, e.g. Box.
  // Force a pattern-suitable shape otherwise: flower for no-hold rhythms,
  // circle as the universal fallback.
  if (userPreference === 'geometric' && !allFourPhases) {
    return noHold ? 'flower' : 'circle';
  }
  return userPreference || (noHold ? 'flower' : 'circle');
};

// Format seconds as m:ss
const bfFmt = (s) => {
  s = Math.max(0, Math.round(s));
  const m = Math.floor(s / 60);
  const r = s % 60;
  return `${m}:${r.toString().padStart(2, '0')}`;
};

// Test whether a theme object is the light variant. Used by visualizers and
// other components that need to flip rgba(255,...) ↔ rgba(0,...) without
// threading darkMode through every level of the tree. Falls back to dark
// when the theme object is unrecognized (custom themes, early-boot calls
// before BF_THEMES is hydrated).
function bfIsLightTheme(theme) {
  return !!(theme && theme === BF_THEMES.light);
}

// ─── Accent-tinted style helpers ───────────────────────────────────────
// All inline styles in screens.jsx historically hard-coded oklch values
// tuned for the dark surface (e.g. `oklch(0.78 0.08 ${accentH})`). On the
// light surface those land too pale for chips and gradients. These helpers
// resolve theme-aware values from a single accent hue so callers don't have
// to know the light/dark math.
//
// The accent itself (solid pill bg) stays the same brand-recognizable
// teal in both themes — only its companions (text on tinted bg, the deep
// gradient stops, the off-state borders) need to flip.
function bfAccentSolid(accentH) {
  return `oklch(0.78 0.08 ${accentH})`;
}
// Foreground color paired with bfAccentSolid. Always dark — solid accent is
// light enough that a near-black foreground reads in either theme.
function bfAccentSolidFg() {
  return 'oklch(0.18 0.012 245)';
}
// Translucent accent background (chips, hover states, badges).
function bfAccentTint(theme, accentH, alpha) {
  const a = (alpha == null) ? 0.15 : alpha;
  if (bfIsLightTheme(theme)) {
    return `oklch(0.62 0.12 ${accentH} / ${a})`;
  }
  return `oklch(0.78 0.08 ${accentH} / ${a})`;
}
// Accent-tinted text that pairs with bfAccentTint or transparent.
function bfAccentText(theme, accentH) {
  if (bfIsLightTheme(theme)) {
    return `oklch(0.40 0.13 ${accentH})`;
  }
  return `oklch(0.88 0.08 ${accentH})`;
}
// Accent border for active/selected outlines.
function bfAccentBorder(theme, accentH, alpha) {
  const a = (alpha == null) ? 0.5 : alpha;
  if (bfIsLightTheme(theme)) {
    return `oklch(0.55 0.12 ${accentH} / ${a})`;
  }
  return `oklch(0.78 0.08 ${accentH} / ${a})`;
}
// Hero/featured card gradient (Home top, preset detail header, etc.).
function bfFeaturedGradient(theme, accentH) {
  if (bfIsLightTheme(theme)) {
    return `linear-gradient(160deg, oklch(0.88 0.06 ${accentH}) 0%, oklch(0.94 0.04 ${accentH}) 60%, ${theme.card} 100%)`;
  }
  return `linear-gradient(160deg, oklch(0.3 0.05 ${accentH}) 0%, oklch(0.22 0.03 ${accentH}) 60%, ${theme.card} 100%)`;
}
// Subtler featured gradient (preset list, complete screen radial).
function bfFeaturedSoftGradient(theme, accentH) {
  if (bfIsLightTheme(theme)) {
    return `linear-gradient(180deg, oklch(0.90 0.05 ${accentH} / 0.6) 0%, ${theme.card} 120%)`;
  }
  return `linear-gradient(180deg, oklch(0.28 0.04 ${accentH} / 0.6) 0%, ${theme.card} 120%)`;
}
// Danger/error text (Delete account, error labels). Red hue 25.
function bfDangerText(theme) {
  return bfIsLightTheme(theme) ? 'oklch(0.50 0.18 25)' : 'oklch(0.72 0.17 25)';
}
// Danger pill (filled error chip) — bg + text + border.
function bfDangerPill(theme) {
  if (bfIsLightTheme(theme)) {
    return { bg: 'transparent', fg: 'oklch(0.50 0.18 25)', border: 'oklch(0.62 0.18 25 / 0.4)' };
  }
  return { bg: 'transparent', fg: 'oklch(0.78 0.12 25)', border: 'oklch(0.78 0.12 25 / 0.4)' };
}
// Favorite-heart color (rose-red, hue 15) — slightly distinct from danger red
// so the "loved" affordance reads as warmth, not warning.
function bfHeartColor(theme) {
  return bfIsLightTheme(theme) ? 'oklch(0.55 0.20 15)' : 'oklch(0.78 0.12 15)';
}
// iOS-style toggle off-state track. theme.cardSoft fails here in light mode:
// a near-white track (L=0.97) under a pure-white thumb is invisible. This
// returns a medium grey that gives the white thumb a clear "off" silhouette
// in both themes — comparable contrast to the on-state accent fill.
function bfToggleOffBg(theme) {
  return bfIsLightTheme(theme) ? 'oklch(0.82 0.006 245)' : 'oklch(0.38 0.012 245)';
}

Object.assign(window, { BF_THEMES, BF_ACCENTS, bfAccent, BF_FONTS, BF_FONT_PAIRINGS, bfSetFontPairing, bfFontPairingLabel, BF_PATTERNS, BF_CUSTOM_PATTERN, bfCycleSeconds, bfDefaultViz, bfFmt, bfPatternName, bfPatternDesc, bfPatternTag, bfPatternAudience, bfPatternsForProfile, bfPatternAllowedForProfile, bfIsLightTheme, bfAccentSolid, bfAccentSolidFg, bfAccentTint, bfAccentText, bfAccentBorder, bfFeaturedGradient, bfFeaturedSoftGradient, bfDangerText, bfDangerPill, bfHeartColor, bfToggleOffBg });
