/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-af8d194 | Cube Dev Team */
import { parseStyle } from "../utils/styles.js";
import { colorInitialValueToRgb, getEffectiveDefinition, normalizePropertyDefinition } from "../properties/index.js";
import { isDevEnv } from "../utils/is-dev-env.js";
import { SheetManager } from "./sheet-manager.js";

//#region src/tasty/injector/injector.ts
/**
* Generate sequential class name with format t{number}
*/
function generateClassName(counter) {
	return `t${counter}`;
}
var StyleInjector = class {
	sheetManager;
	config;
	cleanupScheduled = false;
	globalRuleCounter = 0;
	constructor(config = {}) {
		this.config = config;
		this.sheetManager = new SheetManager(config);
	}
	/**
	* Allocate a className for a cacheKey without injecting styles yet.
	* This allows separating className allocation (render phase) from style injection (insertion phase).
	*/
	allocateClassName(cacheKey, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		if (registry.cacheKeyToClassName.has(cacheKey)) return {
			className: registry.cacheKeyToClassName.get(cacheKey),
			isNewAllocation: false
		};
		const className = generateClassName(registry.classCounter++);
		const placeholderRuleInfo = {
			className,
			ruleIndex: -1,
			sheetIndex: -1
		};
		registry.rules.set(className, placeholderRuleInfo);
		registry.cacheKeyToClassName.set(cacheKey, className);
		return {
			className,
			isNewAllocation: true
		};
	}
	/**
	* Inject styles from StyleResult objects
	*/
	inject(rules, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		if (rules.length === 0) return {
			className: "",
			dispose: () => {}
		};
		const cacheKey = options?.cacheKey;
		let className;
		let isPreAllocated = false;
		if (cacheKey && registry.cacheKeyToClassName.has(cacheKey)) {
			className = registry.cacheKeyToClassName.get(cacheKey);
			const existingRuleInfo = registry.rules.get(className);
			isPreAllocated = existingRuleInfo.ruleIndex === -1 && existingRuleInfo.sheetIndex === -1;
			if (!isPreAllocated) {
				const currentRefCount = registry.refCounts.get(className) || 0;
				registry.refCounts.set(className, currentRefCount + 1);
				if (registry.metrics) registry.metrics.hits++;
				return {
					className,
					dispose: () => this.dispose(className, registry)
				};
			}
		} else className = generateClassName(registry.classCounter++);
		const rulesToInsert = rules.map((rule) => {
			let newSelector = rule.selector;
			if (rule.needsClassName) {
				const selectorParts = newSelector ? newSelector.split("|||") : [""];
				const classPrefix = `.${className}.${className}`;
				newSelector = selectorParts.map((part) => {
					const classSelector = part ? `${classPrefix}${part}` : classPrefix;
					if (rule.rootPrefix) return `${rule.rootPrefix} ${classSelector}`;
					return classSelector;
				}).join(", ");
			}
			return {
				...rule,
				selector: newSelector,
				needsClassName: void 0,
				rootPrefix: void 0
			};
		});
		const colorPropRegex = /^\s*(--[a-z0-9-]+-color)\s*:/i;
		for (const rule of rulesToInsert) {
			if (!rule.declarations) continue;
			const parts = rule.declarations.split(/;+\s*/);
			for (const part of parts) {
				if (!part) continue;
				const match = colorPropRegex.exec(part);
				if (match) {
					const propName = match[1];
					if (!this.isPropertyDefined(propName, { root })) this.property(propName, {
						syntax: "<color>",
						initialValue: "transparent",
						root
					});
				}
			}
		}
		const ruleInfo = this.sheetManager.insertRule(registry, rulesToInsert, className, root);
		if (!ruleInfo) {
			if (registry.metrics) registry.metrics.misses++;
			return {
				className,
				dispose: () => {}
			};
		}
		registry.refCounts.set(className, 1);
		if (isPreAllocated) registry.rules.set(className, ruleInfo);
		else {
			registry.rules.set(className, ruleInfo);
			if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
		}
		if (registry.metrics) {
			registry.metrics.totalInsertions++;
			registry.metrics.misses++;
		}
		return {
			className,
			dispose: () => this.dispose(className, registry)
		};
	}
	/**
	* Inject global styles (rules without a generated tasty class selector)
	* This ensures we don't reserve a tasty class name (t{number}) for global rules,
	* which could otherwise collide with element-level styles and break lookups.
	*/
	injectGlobal(rules, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		if (!rules || rules.length === 0) return { dispose: () => {} };
		const key = `global:${this.globalRuleCounter++}`;
		const info = this.sheetManager.insertGlobalRule(registry, rules, key, root);
		if (registry.metrics) registry.metrics.totalInsertions++;
		return { dispose: () => {
			if (info) this.sheetManager.deleteGlobalRule(registry, key);
		} };
	}
	/**
	* Inject raw CSS text directly without parsing
	* This is a low-overhead alternative to createGlobalStyle for raw CSS
	* The CSS is inserted into a separate style element to avoid conflicts with tasty's chunking
	*/
	injectRawCSS(css, options) {
		const root = options?.root || document;
		return this.sheetManager.injectRawCSS(css, root);
	}
	/**
	* Get raw CSS text for SSR
	*/
	getRawCSSText(options) {
		const root = options?.root || document;
		return this.sheetManager.getRawCSSText(root);
	}
	/**
	* Dispose of a className
	*/
	dispose(className, registry) {
		const currentRefCount = registry.refCounts.get(className);
		if (currentRefCount == null || currentRefCount <= 0) return;
		const newRefCount = currentRefCount - 1;
		registry.refCounts.set(className, newRefCount);
		if (newRefCount === 0) {
			if (registry.metrics) registry.metrics.totalUnused++;
			this.sheetManager.checkCleanupNeeded(registry);
		}
	}
	/**
	* Force bulk cleanup of unused styles
	*/
	cleanup(root) {
		const registry = this.sheetManager.getRegistry(root || document);
		this.sheetManager.forceCleanup(registry);
	}
	/**
	* Get CSS text from all sheets (for SSR)
	*/
	getCssText(options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		return this.sheetManager.getCssText(registry);
	}
	/**
	* Get CSS only for the provided tasty classNames (e.g., ["t0","t3"])
	*/
	getCssTextForClasses(classNames, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		const cssChunks = [];
		for (const cls of classNames) {
			const info = registry.rules.get(cls);
			if (info) {
				const styleSheet = registry.sheets[info.sheetIndex]?.sheet?.sheet;
				if (styleSheet) {
					const start = Math.max(0, info.ruleIndex);
					const end = Math.min(styleSheet.cssRules.length - 1, info.endRuleIndex ?? info.ruleIndex);
					if (start >= 0 && end >= start && start < styleSheet.cssRules.length) for (let i = start; i <= end; i++) {
						const rule = styleSheet.cssRules[i];
						if (rule) cssChunks.push(rule.cssText);
					}
				} else if (info.cssText && info.cssText.length) cssChunks.push(...info.cssText);
			}
		}
		return cssChunks.join("\n");
	}
	/**
	* Get cache performance metrics
	*/
	getMetrics(options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		return this.sheetManager.getMetrics(registry);
	}
	/**
	* Reset cache performance metrics
	*/
	resetMetrics(options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		this.sheetManager.resetMetrics(registry);
	}
	/**
	* Define a CSS @property custom property.
	*
	* Accepts tasty token syntax for the property name:
	* - `$name` → defines `--name`
	* - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')
	* - `--name` → defines `--name` (legacy format)
	*
	* Example:
	* @property --rotation { syntax: "<angle>"; inherits: false; initial-value: 45deg; }
	*
	* Note: No caching or dispose — this defines a global property.
	*
	* If the same property is registered with different options, a warning is emitted
	* but the original definition is preserved (CSS @property cannot be redefined).
	*/
	property(name, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		const effectiveResult = getEffectiveDefinition(name, {
			syntax: options?.syntax,
			inherits: options?.inherits,
			initialValue: options?.initialValue
		});
		if (!effectiveResult.isValid) {
			if (isDevEnv()) console.warn(`[Tasty] property(): ${effectiveResult.error}. Got: "${name}"`);
			return;
		}
		const cssName = effectiveResult.cssName;
		const definition = effectiveResult.definition;
		const normalizedDef = normalizePropertyDefinition(definition);
		const existingDef = registry.injectedProperties.get(cssName);
		if (existingDef !== void 0) {
			if (existingDef !== normalizedDef) {
				if (isDevEnv()) console.warn(`[Tasty] @property ${cssName} was already defined with a different declaration. The new declaration will be ignored. Original: ${existingDef}, New: ${normalizedDef}`);
			}
			return;
		}
		const parts = [];
		if (definition.syntax != null) {
			let syntax = String(definition.syntax).trim();
			if (!/^['"]/u.test(syntax)) syntax = `"${syntax}"`;
			parts.push(`syntax: ${syntax};`);
		}
		const inherits = definition.inherits ?? true;
		parts.push(`inherits: ${inherits ? "true" : "false"};`);
		if (definition.initialValue != null) {
			let initialValueStr;
			if (typeof definition.initialValue === "number") initialValueStr = String(definition.initialValue);
			else initialValueStr = parseStyle(definition.initialValue).output;
			parts.push(`initial-value: ${initialValueStr};`);
		}
		const declarations = parts.join(" ").trim();
		const rule = {
			selector: `@property ${cssName}`,
			declarations
		};
		if (!this.sheetManager.insertGlobalRule(registry, [rule], `property:${name}`, root)) return;
		registry.injectedProperties.set(cssName, normalizedDef);
		if (effectiveResult.isColor) {
			const rgbCssName = `${cssName}-rgb`;
			if (!this.isPropertyDefined(rgbCssName, { root })) this.property(rgbCssName, {
				syntax: "<number>+",
				inherits: definition.inherits,
				initialValue: colorInitialValueToRgb(definition.initialValue),
				root
			});
		}
	}
	/**
	* Check whether a given @property name was already injected by this injector.
	*
	* Accepts tasty token syntax:
	* - `$name` → checks `--name`
	* - `#name` → checks `--name-color`
	* - `--name` → checks `--name` (legacy format)
	*/
	isPropertyDefined(name, options) {
		const root = options?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		const effectiveResult = getEffectiveDefinition(name, {});
		if (!effectiveResult.isValid) return false;
		return registry.injectedProperties.has(effectiveResult.cssName);
	}
	/**
	* Inject keyframes and return object with toString() and dispose()
	*
	* Keyframes are cached by content (steps). If the same content is injected
	* multiple times with different provided names, the first injected name is reused.
	*
	* If the same name is provided with different content (collision), a unique
	* name is generated to avoid overwriting the existing keyframes.
	*/
	keyframes(steps, nameOrOptions) {
		const isStringName = typeof nameOrOptions === "string";
		const providedName = isStringName ? nameOrOptions : nameOrOptions?.name;
		const root = isStringName ? document : nameOrOptions?.root || document;
		const registry = this.sheetManager.getRegistry(root);
		if (Object.keys(steps).length === 0) return {
			toString: () => "",
			dispose: () => {}
		};
		const contentHash = JSON.stringify(steps);
		const existing = registry.keyframesCache.get(contentHash);
		if (existing) {
			existing.refCount++;
			return {
				toString: () => existing.name,
				dispose: () => this.disposeKeyframes(contentHash, registry)
			};
		}
		let actualName;
		if (providedName) {
			const existingContentForName = registry.keyframesNameToContent.get(providedName);
			if (existingContentForName && existingContentForName !== contentHash) actualName = `${providedName}-k${registry.keyframesCounter++}`;
			else {
				actualName = providedName;
				registry.keyframesNameToContent.set(providedName, contentHash);
			}
		} else actualName = `k${registry.keyframesCounter++}`;
		const info = this.sheetManager.insertKeyframes(registry, steps, actualName, root);
		if (!info) return {
			toString: () => "",
			dispose: () => {}
		};
		registry.keyframesCache.set(contentHash, {
			name: actualName,
			refCount: 1,
			info
		});
		if (registry.metrics) {
			registry.metrics.totalInsertions++;
			registry.metrics.misses++;
		}
		return {
			toString: () => actualName,
			dispose: () => this.disposeKeyframes(contentHash, registry)
		};
	}
	/**
	* Dispose keyframes
	*/
	disposeKeyframes(contentHash, registry) {
		const entry = registry.keyframesCache.get(contentHash);
		if (!entry) return;
		entry.refCount--;
		if (entry.refCount <= 0) {
			this.sheetManager.deleteKeyframes(registry, entry.info);
			registry.keyframesCache.delete(contentHash);
			for (const [name, hash] of registry.keyframesNameToContent.entries()) if (hash === contentHash) {
				registry.keyframesNameToContent.delete(name);
				break;
			}
			if (registry.metrics) {
				registry.metrics.totalUnused++;
				registry.metrics.stylesCleanedUp++;
			}
		}
	}
	/**
	* Destroy all resources for a root
	*/
	destroy(root) {
		const targetRoot = root || document;
		this.sheetManager.cleanup(targetRoot);
	}
};

//#endregion
export { StyleInjector };
//# sourceMappingURL=injector.js.map