"use strict";

// Side-effect import — must come first. Initializes react-native-css-interop's
// `darkMode` flag to `'class'` at module load so its MutationObserver doesn't
// throw "Cannot manually set color scheme, as dark mode is type 'media'" the
// first time Bloom toggles the dark class on <html>. See ./init-css-interop.
import "./init-css-interop.js";
import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Platform, View, useColorScheme as useRNColorScheme } from 'react-native';
import { useControllableState } from "../hooks/useControllableState.js";
import { FontLoader } from '../fonts/FontLoader';
import { applyDarkClass, applyColorPresetVars } from "./apply-dark-class.js";
import { buildTheme } from "./build-theme.js";
import { buildScopeVars, getVariableContextProvider } from "./color-scope/style-builder.js";
import { applyNativeRootVars } from './native-root-vars';
import { readPersistedTheme, readPersistedThemeSync, removePersistedTheme, writePersistedTheme } from "./persistence.js";
import { setColorSchemeSafe } from "./set-color-scheme-safe.js";
import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
import { jsx as _jsx } from "react/jsx-runtime";
const DEFAULT_PRESET = 'oxy';
const DEFAULT_MODE = 'system';
/**
 * `BloomThemeContext` is a `globalThis`-guarded singleton so that every physical
 * copy of this module returns the SAME React context object.
 *
 * Why this is required: `package.json`'s `exports["./theme"]` ships a
 * `react-native` → `./src/...` condition alongside the `import` → `./lib/module/...`
 * and `require` → `./lib/commonjs/...` forks, so up to three physical copies of the
 * theme module can coexist in a single bundle. A bundler can resolve them through
 * DIFFERENT conditions across import paths — e.g. an app importing `@oxyhq/bloom/theme`
 * lands on the `src` copy while `@oxyhq/bloom/toast`'s `Toast.tsx` imports
 * `../theme/use-theme` relatively and lands on a different physical copy. With a plain
 * module-level `createContext(...)`, each copy creates its OWN context object: the
 * mounted `<BloomThemeProvider>` writes into context A while `useTheme`/`useBloomTheme`/
 * `Toast`/`Dialog` read context B, so `useContext` returns `null` and they throw
 * "must be used within a <BloomThemeProvider>" even when correctly wrapped. This is the
 * same src-vs-lib dual-instance class react-native-css guards its `StyleCollection`/
 * root-variable families against. Anchoring the context on `globalThis` (keyed by the
 * `__oxyhq_bloom_theme_context__` property) collapses all copies onto one object. The
 * `??=` runs `createContext` at most once per process.
 */
export const BloomThemeContext = globalThis.__oxyhq_bloom_theme_context__ ??= /*#__PURE__*/createContext(null);
function useThemeState({
  controlledMode,
  controlledPreset,
  defaultMode,
  defaultPreset,
  persistKey,
  storage,
  onModeChange,
  onColorPresetChange
}) {
  // Synchronous read happens once on first render. Succeeds on web with
  // localStorage-backed adapters; async adapters rehydrate via the effect
  // below. Lazy-initialized via `useState` so it runs exactly once per mount.
  const [syncResult] = useState(() => readPersistedThemeSync(persistKey, storage));
  const syncState = syncResult.kind === 'sync' ? syncResult.state : null;
  const initialMode = syncState?.mode ?? defaultMode;
  const initialPreset = syncState?.colorPreset ?? defaultPreset;

  // Hydrated immediately when persistence is off or the adapter resolved
  // synchronously (including a null hit — that's a valid "no value" answer).
  const [hydrated, setHydrated] = useState(syncResult.kind !== 'async');
  const [mode, setModeInternal] = useControllableState({
    value: controlledMode,
    defaultValue: initialMode
  });
  const [colorPreset, setPresetInternal] = useControllableState({
    value: controlledPreset,
    defaultValue: initialPreset
  });

  // Refs let setMode/setColorPreset stay referentially stable. Callbacks
  // memoized only by setters and storage identity won't churn the context
  // value on every theme change.
  const modeRef = useRef(mode);
  modeRef.current = mode;
  const presetRef = useRef(colorPreset);
  presetRef.current = colorPreset;

  // Async hydration for adapters that can't be read synchronously
  // (AsyncStorage, MMKV via JSI fallback, etc).
  useEffect(() => {
    if (hydrated) return;
    if (!persistKey || !storage) {
      setHydrated(true);
      return;
    }
    let cancelled = false;
    readPersistedTheme(persistKey, storage).then(state => {
      if (cancelled) return;
      if (state?.mode && controlledMode === undefined) {
        setModeInternal(state.mode);
        onModeChange?.(state.mode);
      }
      if (state?.colorPreset && controlledPreset === undefined) {
        setPresetInternal(state.colorPreset);
        onColorPresetChange?.(state.colorPreset);
      }
      setHydrated(true);
    });
    return () => {
      cancelled = true;
    };
    // Hydration runs once per storage instance. controlledMode/Preset are
    // captured by closure intentionally — switching controlled-ness mid-flight
    // is unsupported and would invalidate the in-flight hydration anyway.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [persistKey, storage]);
  const setMode = useCallback(next => {
    setModeInternal(next);
    onModeChange?.(next);
    void writePersistedTheme(persistKey, storage, {
      mode: next,
      colorPreset: presetRef.current
    });
  }, [setModeInternal, onModeChange, persistKey, storage]);
  const setColorPreset = useCallback(next => {
    setPresetInternal(next);
    onColorPresetChange?.(next);
    void writePersistedTheme(persistKey, storage, {
      mode: modeRef.current,
      colorPreset: next
    });
  }, [setPresetInternal, onColorPresetChange, persistKey, storage]);
  const resetTheme = useCallback(() => {
    if (controlledMode === undefined) {
      setModeInternal(defaultMode);
      onModeChange?.(defaultMode);
    }
    if (controlledPreset === undefined) {
      setPresetInternal(defaultPreset);
      onColorPresetChange?.(defaultPreset);
    }
    void removePersistedTheme(persistKey, storage);
  }, [controlledMode, controlledPreset, defaultMode, defaultPreset, setModeInternal, setPresetInternal, onModeChange, onColorPresetChange, persistKey, storage]);
  return {
    mode,
    colorPreset,
    setMode,
    setColorPreset,
    resetTheme,
    hydrated
  };
}
export function BloomThemeProvider({
  mode: controlledMode,
  colorPreset: controlledPreset,
  defaultMode = DEFAULT_MODE,
  defaultColorPreset = DEFAULT_PRESET,
  onModeChange,
  onColorPresetChange,
  persistKey,
  storage,
  awaitHydration,
  onHydrating,
  fonts = true,
  onFontsLoading,
  nativeWrapperStyle,
  children
}) {
  const rnScheme = useRNColorScheme();
  const {
    mode,
    colorPreset,
    setMode,
    setColorPreset,
    resetTheme,
    hydrated
  } = useThemeState({
    controlledMode,
    controlledPreset,
    defaultMode,
    defaultPreset: defaultColorPreset,
    persistKey,
    storage,
    onModeChange,
    onColorPresetChange
  });
  const isAdaptive = mode === 'adaptive';
  const effectiveMode = isAdaptive ? 'system' : mode;
  const resolved = effectiveMode === 'system' ? rnScheme === 'dark' ? 'dark' : 'light' : effectiveMode;

  // Apply native color scheme, dark class, and CSS vars whenever the resolved
  // mode or preset changes. `useIsomorphicLayoutEffect` runs before paint on
  // both native and web, eliminating the previous render-time side effect.
  useIsomorphicLayoutEffect(() => {
    setColorSchemeSafe(effectiveMode);
    applyDarkClass(resolved);
    applyColorPresetVars(colorPreset, resolved);
  }, [effectiveMode, resolved, colorPreset]);

  // Native only: publish the preset vars into react-native-css's GLOBAL root
  // variable family so they reach the ENTIRE app tree — navigator screens and
  // every portal/modal/bottom-sheet that paints outside the provider's React
  // subtree (and therefore never sees the `VariableContextProvider` vars below).
  // Web writes the same vars to `document.documentElement` above, so this no-ops
  // there. Runs before paint via `useIsomorphicLayoutEffect`, matching the
  // sibling `applyColorPresetVars` side effect.
  useIsomorphicLayoutEffect(() => {
    applyNativeRootVars(colorPreset, resolved);
  }, [colorPreset, resolved]);
  const contextValue = useMemo(() => ({
    theme: buildTheme(colorPreset, resolved, isAdaptive),
    mode,
    colorPreset,
    setMode,
    setColorPreset,
    resetTheme
  }), [colorPreset, resolved, isAdaptive, mode, setMode, setColorPreset, resetTheme]);
  const shouldAwait = awaitHydration ?? Boolean(persistKey && storage);
  const isGated = shouldAwait && !hydrated;

  // The full `--name -> value` record for the active preset. On native this is
  // handed to `VariableContextProvider` so descendants resolve `var(--primary)`
  // etc. through react-native-css's real VariableContext. Web writes these to
  // `document.documentElement` via `applyColorPresetVars`, so it's unused there.
  const nativeVars = useMemo(() => Platform.OS === 'web' ? null : buildScopeVars(colorPreset, resolved), [colorPreset, resolved]);
  const VariableProvider = getVariableContextProvider();
  const content = /*#__PURE__*/_jsx(FontLoader, {
    enabled: fonts,
    fallback: onFontsLoading,
    children: isGated ? onHydrating ?? null : children
  });
  if (Platform.OS === 'web') {
    return /*#__PURE__*/_jsx(BloomThemeContext.Provider, {
      value: contextValue,
      children: content
    });
  }

  // The flex wrapper is still needed for layout; the preset vars now flow
  // through `VariableContextProvider` rather than an (inert) inline-vars style.
  // If NativeWind isn't installed the wrapper still renders so we never crash.
  const nativeTree = /*#__PURE__*/_jsx(View, {
    style: [{
      flex: 1
    }, nativeWrapperStyle],
    children: content
  });
  return /*#__PURE__*/_jsx(BloomThemeContext.Provider, {
    value: contextValue,
    children: VariableProvider && nativeVars ? /*#__PURE__*/_jsx(VariableProvider, {
      value: nativeVars,
      children: nativeTree
    }) : nativeTree
  });
}
//# sourceMappingURL=BloomThemeProvider.js.map