/**
 * @license MIT
 * author: Cube Dev Team
 * @cube-dev/ui-kit v0.0.0-canary-5e810c3
 * Released under the MIT license.
 */

/**
 * Tasty Configuration Module
 *
 * Centralizes all tasty configuration, including:
 * - Style injector settings (nonce, cleanup thresholds, etc.)
 * - Global predefined states for advanced state mapping
 * - stylesGenerated flag that locks configuration after first style generation
 *
 * Configuration must be done BEFORE any styles are generated.
 * After the first `inject()` call, configuration is locked and attempts to
 * reconfigure will emit a warning and be ignored.
 */
import { StyleInjector } from './injector/injector.js';
import { clearPipelineCache } from './pipeline/index.js';
import { setGlobalPredefinedStates } from './states/index.js';
import { normalizeHandlerDefinition, registerHandler, resetHandlers, } from './styles/predefined.js';
import { isDevEnv } from './utils/is-dev-env.js';
import { CUSTOM_UNITS, getGlobalFuncs, getGlobalParser, resetGlobalPredefinedTokens, setGlobalPredefinedTokens, } from './utils/styles.js';
// Warnings tracking to avoid duplicates
const emittedWarnings = new Set();
const devMode = isDevEnv();
/**
 * Emit a warning only once
 */
function warnOnce(key, message) {
    if (devMode && !emittedWarnings.has(key)) {
        emittedWarnings.add(key);
        console.warn(message);
    }
}
// ============================================================================
// Configuration State
// ============================================================================
// Track whether styles have been generated (locks configuration)
let stylesGenerated = false;
// Current configuration (null until first configure() or auto-configured on first use)
let currentConfig = null;
// Global keyframes storage (null = no keyframes configured, empty object checked via hasGlobalKeyframes)
let globalKeyframes = null;
// Global properties storage (null = no properties configured)
let globalProperties = null;
/**
 * Internal properties required by tasty core features.
 * These are always injected when styles are first generated.
 * Keys use tasty token syntax (#name for colors, $name for other properties).
 */
export const INTERNAL_PROPERTIES = {
    // Used by dual-fill feature to enable CSS transitions on the second fill color
    '#tasty-second-fill': {
        syntax: '<color>',
        inherits: false,
        initialValue: 'transparent',
    },
};
// Global injector instance key
const GLOBAL_INJECTOR_KEY = '__TASTY_GLOBAL_INJECTOR__';
/**
 * Detect if we're running in a test environment
 */
export function isTestEnvironment() {
    // Check Node.js environment
    if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') {
        return true;
    }
    // Check for Jest globals (safely)
    if (typeof global !== 'undefined') {
        const g = global;
        if (g.jest || g.expect || g.describe || g.it) {
            return true;
        }
    }
    // Check for jsdom environment (common in tests)
    if (typeof window !== 'undefined' &&
        window.navigator?.userAgent?.includes('jsdom')) {
        return true;
    }
    // Check for other test runners
    if (typeof globalThis !== 'undefined') {
        const gt = globalThis;
        if (gt.vitest || gt.mocha) {
            return true;
        }
    }
    return false;
}
/**
 * Create default configuration with optional test environment detection
 */
function createDefaultConfig(isTest) {
    return {
        maxRulesPerSheet: 8192,
        unusedStylesThreshold: 500,
        bulkCleanupDelay: 5000,
        idleCleanup: true,
        forceTextInjection: isTest ?? false,
        devMode: isDevEnv(),
        bulkCleanupBatchRatio: 0.5,
        unusedStylesMinAgeMs: 10000,
    };
}
// ============================================================================
// stylesGenerated Flag Management
// ============================================================================
/**
 * Mark that styles have been generated (called by injector on first inject)
 * This locks the configuration - no further changes allowed.
 * Also injects internal and global properties.
 */
export function markStylesGenerated() {
    if (stylesGenerated)
        return; // Already marked, skip
    stylesGenerated = true;
    const injector = getGlobalInjector();
    // Inject internal properties required by tasty core features
    for (const [token, definition] of Object.entries(INTERNAL_PROPERTIES)) {
        injector.property(token, definition);
    }
    // Inject global properties if any were configured
    // Properties are permanent and only need to be injected once
    if (globalProperties && Object.keys(globalProperties).length > 0) {
        for (const [token, definition] of Object.entries(globalProperties)) {
            injector.property(token, definition);
        }
    }
}
/**
 * Check if styles have been generated (configuration is locked)
 */
export function hasStylesGenerated() {
    return stylesGenerated;
}
/**
 * Reset styles generated flag (for testing only)
 */
export function resetStylesGenerated() {
    stylesGenerated = false;
    emittedWarnings.clear();
}
// ============================================================================
// Global Keyframes Management
// ============================================================================
/**
 * Check if any global keyframes are configured.
 * Fast path: returns false if no keyframes were ever set.
 */
export function hasGlobalKeyframes() {
    return globalKeyframes !== null && Object.keys(globalKeyframes).length > 0;
}
/**
 * Get global keyframes configuration.
 * Returns null if no keyframes configured (fast path for zero-overhead).
 */
export function getGlobalKeyframes() {
    return globalKeyframes;
}
/**
 * Set global keyframes (called from configure).
 * Internal use only.
 */
function setGlobalKeyframes(keyframes) {
    if (stylesGenerated) {
        warnOnce('keyframes-after-styles', `[Tasty] Cannot update keyframes after styles have been generated.\n` +
            `The new keyframes will be ignored.`);
        return;
    }
    globalKeyframes = keyframes;
}
// ============================================================================
// Global Properties Management
// ============================================================================
/**
 * Check if any global properties are configured.
 * Fast path: returns false if no properties were ever set.
 */
export function hasGlobalProperties() {
    return globalProperties !== null && Object.keys(globalProperties).length > 0;
}
/**
 * Get global properties configuration.
 * Returns null if no properties configured (fast path for zero-overhead).
 */
export function getGlobalProperties() {
    return globalProperties;
}
/**
 * Set global properties (called from configure).
 * Internal use only.
 */
function setGlobalProperties(properties) {
    if (stylesGenerated) {
        warnOnce('properties-after-styles', `[Tasty] Cannot update properties after styles have been generated.\n` +
            `The new properties will be ignored.`);
        return;
    }
    globalProperties = properties;
}
/**
 * Check if configuration is locked (styles have been generated)
 */
export function isConfigLocked() {
    return stylesGenerated;
}
// ============================================================================
// Configuration API
// ============================================================================
/**
 * Configure the Tasty style system.
 *
 * Must be called BEFORE any styles are generated (before first render that uses tasty).
 * After styles are generated, configuration is locked and calls to configure() will
 * emit a warning and be ignored.
 *
 * @example
 * ```ts
 * import { configure } from '@cube-dev/ui-kit';
 *
 * // Configure before app renders
 * configure({
 *   nonce: 'abc123',
 *   states: {
 *     '@mobile': '@media(w < 768px)',
 *     '@dark': '@root(theme=dark)',
 *   },
 * });
 * ```
 */
export function configure(config = {}) {
    if (stylesGenerated) {
        warnOnce('configure-after-styles', `[Tasty] Cannot call configure() after styles have been generated.\n` +
            `Configuration must be done before the first render. The configuration will be ignored.`);
        return;
    }
    // Collect merged values from plugins first, then override with direct config
    let mergedStates = {};
    let mergedUnits = {};
    let mergedFuncs = {};
    let mergedHandlers = {};
    let mergedTokens = {};
    // Process plugins in order
    if (config.plugins) {
        for (const plugin of config.plugins) {
            if (plugin.states) {
                mergedStates = { ...mergedStates, ...plugin.states };
            }
            if (plugin.units) {
                mergedUnits = { ...mergedUnits, ...plugin.units };
            }
            if (plugin.funcs) {
                mergedFuncs = { ...mergedFuncs, ...plugin.funcs };
            }
            if (plugin.handlers) {
                mergedHandlers = { ...mergedHandlers, ...plugin.handlers };
            }
            if (plugin.tokens) {
                mergedTokens = { ...mergedTokens, ...plugin.tokens };
            }
        }
    }
    // Direct config overrides plugins
    if (config.states) {
        mergedStates = { ...mergedStates, ...config.states };
    }
    if (config.units) {
        mergedUnits = { ...mergedUnits, ...config.units };
    }
    if (config.funcs) {
        mergedFuncs = { ...mergedFuncs, ...config.funcs };
    }
    if (config.handlers) {
        mergedHandlers = { ...mergedHandlers, ...config.handlers };
    }
    if (config.tokens) {
        mergedTokens = { ...mergedTokens, ...config.tokens };
    }
    // Handle predefined states
    if (Object.keys(mergedStates).length > 0) {
        setGlobalPredefinedStates(mergedStates);
    }
    // Handle parser configuration (merge semantics - extend, not replace)
    const parser = getGlobalParser();
    if (config.parserCacheSize !== undefined) {
        parser.updateOptions({ cacheSize: config.parserCacheSize });
    }
    if (Object.keys(mergedUnits).length > 0) {
        // Merge with existing units
        const currentUnits = parser.getUnits() ?? CUSTOM_UNITS;
        parser.setUnits({ ...currentUnits, ...mergedUnits });
    }
    if (Object.keys(mergedFuncs).length > 0) {
        // Merge with existing funcs
        const currentFuncs = getGlobalFuncs();
        const finalFuncs = { ...currentFuncs, ...mergedFuncs };
        parser.setFuncs(finalFuncs);
        // Also update the global registry so customFunc() continues to work
        Object.assign(currentFuncs, mergedFuncs);
    }
    // Handle keyframes
    if (config.keyframes) {
        setGlobalKeyframes(config.keyframes);
    }
    // Handle properties
    if (config.properties) {
        setGlobalProperties(config.properties);
    }
    // Handle custom handlers
    if (Object.keys(mergedHandlers).length > 0) {
        for (const [name, definition] of Object.entries(mergedHandlers)) {
            const handler = normalizeHandlerDefinition(name, definition);
            registerHandler(handler);
        }
    }
    // Handle predefined tokens
    // Note: Tokens are processed by the classifier, not here.
    // We just store the raw values; the classifier will process them when encountered.
    if (Object.keys(mergedTokens).length > 0) {
        // Store tokens (keys are normalized to lowercase by setGlobalPredefinedTokens)
        const processedTokens = {};
        for (const [key, value] of Object.entries(mergedTokens)) {
            processedTokens[key] = String(value);
        }
        setGlobalPredefinedTokens(processedTokens);
    }
    // Create config without states, parser options, plugins, keyframes, properties, handlers, and tokens (handled separately)
    const { states: _states, parserCacheSize: _parserCacheSize, units: _units, funcs: _funcs, plugins: _plugins, keyframes: _keyframes, properties: _properties, handlers: _handlers, tokens: _tokens, ...injectorConfig } = config;
    const fullConfig = {
        ...createDefaultConfig(),
        ...currentConfig,
        ...injectorConfig,
    };
    // Store the config
    currentConfig = fullConfig;
    // Create/replace the global injector
    const storage = typeof window !== 'undefined' ? window : globalThis;
    storage[GLOBAL_INJECTOR_KEY] = new StyleInjector(fullConfig);
}
/**
 * Get the current configuration.
 * If not configured, returns default configuration.
 */
export function getConfig() {
    if (!currentConfig) {
        currentConfig = createDefaultConfig(isTestEnvironment());
    }
    return currentConfig;
}
/**
 * Get the global injector instance.
 * Auto-configures with defaults if not already configured.
 */
export function getGlobalInjector() {
    const storage = typeof window !== 'undefined' ? window : globalThis;
    if (!storage[GLOBAL_INJECTOR_KEY]) {
        configure();
    }
    return storage[GLOBAL_INJECTOR_KEY];
}
/**
 * Reset configuration (for testing only).
 * Clears the global injector and allows reconfiguration.
 */
export function resetConfig() {
    stylesGenerated = false;
    currentConfig = null;
    globalKeyframes = null;
    globalProperties = null;
    resetGlobalPredefinedTokens();
    resetHandlers();
    clearPipelineCache();
    emittedWarnings.clear();
    const storage = typeof window !== 'undefined' ? window : globalThis;
    delete storage[GLOBAL_INJECTOR_KEY];
}

        