/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-1fc5ad1 | Cube Dev Team */
import { isDevEnv } from "../utils/is-dev-env.js";
import { hasStylesGenerated } from "../config.js";

//#region src/tasty/states/index.ts
/**
* Advanced State Mapping - Predefined States Management
*
* This module handles global and local predefined states for the Tasty styling system.
* See ADVANCED_STATE_MAPPING.md for full specification.
*/
const BUILTIN_STATES = new Set([
	"@starting",
	"@keyframes",
	"@properties",
	"@supports",
	"@inherit"
]);
const RESERVED_PREFIXES = [
	"@media",
	"@root",
	"@own",
	"@(",
	"@starting",
	"@keyframes",
	"@properties",
	"@supports",
	"@inherit"
];
let globalPredefinedStates = {};
const emittedWarnings = /* @__PURE__ */ 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);
	}
}
/**
* Configure global predefined states
*/
function setGlobalPredefinedStates(states) {
	if (hasStylesGenerated()) {
		const newStateNames = Object.keys(states).join(", ");
		warnOnce(`dynamic-states:${newStateNames}`, `[Tasty] Cannot update predefined states after styles have been generated.\nThe new definition(s) for ${newStateNames} will be ignored.`);
		return;
	}
	for (const [name, value] of Object.entries(states)) {
		if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(name)) {
			warnOnce(`invalid-state-name:${name}`, `[Tasty] Invalid predefined state name '${name}'. Must start with '@' followed by a letter.`);
			continue;
		}
		if (BUILTIN_STATES.has(name)) {
			warnOnce(`reserved-state:${name}`, `[Tasty] Cannot define predefined state '${name}'. This name is reserved for built-in functionality.`);
			continue;
		}
		if (name === "@media" || name === "@root" || name === "@own" || name.startsWith("@(")) {
			warnOnce(`reserved-prefix:${name}`, `[Tasty] Cannot define predefined state '${name}'. This prefix is reserved for built-in functionality.`);
			continue;
		}
		const crossRefs = extractPredefinedStateRefs(value);
		if (crossRefs.length > 0) {
			warnOnce(`cross-ref:${name}`, `[Tasty] Predefined state '${name}' references another predefined state '${crossRefs[0]}'.\nPredefined states cannot reference each other. Use the full definition instead.`);
			continue;
		}
		if (globalPredefinedStates[name] && globalPredefinedStates[name] !== value) warnOnce(`duplicate-state:${name}`, `[Tasty] Duplicate predefined state '${name}' in configure(). The last definition will be used.`);
		globalPredefinedStates[name] = value;
	}
}
/**
* Get global predefined states
*/
function getGlobalPredefinedStates() {
	return globalPredefinedStates;
}
/**
* Regex to match predefined state references in a string
* Matches @name that is NOT followed by ( or : and is a complete word
* Uses word boundary and negative lookahead
*/
const PREDEFINED_STATE_PATTERN = /@([A-Za-z][A-Za-z0-9-]*)(?![A-Za-z0-9-:(])/g;
/**
* Extract predefined state references from a string
*/
function extractPredefinedStateRefs(value) {
	const matches = value.matchAll(PREDEFINED_STATE_PATTERN);
	const refs = [];
	for (const match of matches) {
		const stateName = "@" + match[1];
		if (!BUILTIN_STATES.has(stateName) && !refs.includes(stateName)) refs.push(stateName);
	}
	return refs;
}
/**
* Check if a state key is a predefined state reference
*/
function isPredefinedStateRef(stateKey) {
	if (!stateKey.startsWith("@")) return false;
	if (BUILTIN_STATES.has(stateKey)) return false;
	for (const prefix of RESERVED_PREFIXES) if (stateKey === prefix || stateKey.startsWith(prefix)) {
		if (stateKey === "@media" || stateKey.startsWith("@media(") || stateKey.startsWith("@media:")) return false;
		if (stateKey === "@root" || stateKey.startsWith("@root(")) return false;
		if (stateKey === "@own" || stateKey.startsWith("@own(")) return false;
		if (stateKey.startsWith("@(")) return false;
	}
	return /^@[A-Za-z][A-Za-z0-9-]*$/.test(stateKey);
}
/**
* Extract local predefined states from a styles object
* Local predefined states are top-level keys starting with @ that have string values
* and are valid predefined state names (not built-in like @media, @root, etc.)
*/
function extractLocalPredefinedStates(styles) {
	const localStates = {};
	if (!styles || typeof styles !== "object") return localStates;
	for (const [key, value] of Object.entries(styles)) if (key.startsWith("@") && typeof value === "string") {
		if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(key)) continue;
		if (BUILTIN_STATES.has(key)) continue;
		if (key === "@media" || key === "@root" || key === "@own" || key.startsWith("@(")) continue;
		const crossRefs = extractPredefinedStateRefs(value);
		if (crossRefs.length > 0) {
			warnOnce(`local-cross-ref:${key}`, `[Tasty] Predefined state '${key}' references another predefined state '${crossRefs[0]}'.\nPredefined states cannot reference each other. Use the full definition instead.`);
			continue;
		}
		localStates[key] = value;
	}
	return localStates;
}
/**
* Create a state parser context from styles
*/
function createStateParserContext(styles, isSubElement) {
	return {
		localPredefinedStates: styles ? extractLocalPredefinedStates(styles) : {},
		globalPredefinedStates: getGlobalPredefinedStates(),
		isSubElement
	};
}
/**
* Resolve a predefined state reference to its value
* Returns the resolved value or null if not found
*/
function resolvePredefinedState(stateKey, ctx) {
	if (ctx.localPredefinedStates[stateKey]) return ctx.localPredefinedStates[stateKey];
	if (ctx.globalPredefinedStates[stateKey]) return ctx.globalPredefinedStates[stateKey];
	warnOnce(`undefined-state:${stateKey}`, `[Tasty] Undefined predefined state '${stateKey}'.\nDefine it in configure({ states: { '${stateKey}': '...' } }) or in the component's styles.`);
	return null;
}
/**
* Expand dimension shorthands in a condition string
* w -> width, h -> height, is -> inline-size, bs -> block-size
*/
function expandDimensionShorthands(condition) {
	let result = condition;
	result = result.replace(/\bw\b/g, "width");
	result = result.replace(/\bh\b/g, "height");
	result = result.replace(/\bis\b/g, "inline-size");
	result = result.replace(/\bbs\b/g, "block-size");
	return result;
}
/**
* Convert tasty units in a string (e.g., 40x -> calc(var(--gap) * 40))
*/
function expandTastyUnits(value) {
	return value.replace(/(\d+(?:\.\d+)?)\s*x\b/g, (_, num) => {
		return `calc(var(--gap) * ${num})`;
	});
}
/**
* Parse an advanced state key and return its type and components
*/
function parseAdvancedState(stateKey, ctx) {
	const raw = stateKey;
	if (stateKey === "@starting") return {
		type: "starting",
		condition: "",
		raw
	};
	if (stateKey.startsWith("@media:")) {
		const mediaType = stateKey.slice(7);
		if (![
			"all",
			"screen",
			"print",
			"speech"
		].includes(mediaType)) warnOnce(`unknown-media-type:${mediaType}`, `[Tasty] Unknown media type '${mediaType}'. Valid types: all, screen, print, speech.`);
		return {
			type: "media",
			condition: "",
			mediaType,
			raw
		};
	}
	if (stateKey.startsWith("@media(")) {
		const endParen = findMatchingParen(stateKey, 6);
		if (endParen === -1) {
			warnOnce(`unclosed-media:${stateKey}`, `[Tasty] Unclosed media query '${stateKey}'. Missing closing parenthesis.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		let condition = stateKey.slice(7, endParen);
		if (!condition.trim()) {
			warnOnce(`empty-media:${stateKey}`, `[Tasty] Empty media query condition '${stateKey}'.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		condition = expandDimensionShorthands(condition);
		condition = expandTastyUnits(condition);
		return {
			type: "media",
			condition,
			raw
		};
	}
	if (stateKey.startsWith("@root(")) {
		const endParen = findMatchingParen(stateKey, 5);
		if (endParen === -1) {
			warnOnce(`unclosed-root:${stateKey}`, `[Tasty] Unclosed root state '${stateKey}'. Missing closing parenthesis.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		const condition = stateKey.slice(6, endParen);
		if (!condition.trim()) {
			warnOnce(`empty-root:${stateKey}`, `[Tasty] Empty root state condition '${stateKey}'.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		return {
			type: "root",
			condition,
			raw
		};
	}
	if (stateKey.startsWith("@own(")) {
		const endParen = findMatchingParen(stateKey, 4);
		if (endParen === -1) {
			warnOnce(`unclosed-own:${stateKey}`, `[Tasty] Unclosed own state '${stateKey}'. Missing closing parenthesis.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		const condition = stateKey.slice(5, endParen);
		if (!condition.trim()) {
			warnOnce(`empty-own:${stateKey}`, `[Tasty] Empty own state condition '${stateKey}'.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		if (!ctx.isSubElement) {
			warnOnce(`own-outside-subelement:${stateKey}`, `[Tasty] @own(${condition}) used outside sub-element context.\n@own() is equivalent to '${condition}' at the root level. Did you mean to use it inside a sub-element?`);
			return {
				type: "modifier",
				condition,
				raw
			};
		}
		return {
			type: "own",
			condition,
			raw
		};
	}
	if (stateKey.startsWith("@(")) {
		const endParen = findMatchingParen(stateKey, 1);
		if (endParen === -1) {
			warnOnce(`unclosed-container:${stateKey}`, `[Tasty] Unclosed container query '${stateKey}'. Missing closing parenthesis.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		const content = stateKey.slice(2, endParen);
		if (!content.trim()) {
			warnOnce(`empty-container:${stateKey}`, `[Tasty] Empty container query '${stateKey}'.`);
			return {
				type: "modifier",
				condition: stateKey,
				raw
			};
		}
		const commaIndex = findTopLevelComma(content);
		let containerName;
		let condition;
		if (commaIndex !== -1) {
			containerName = content.slice(0, commaIndex).trim();
			condition = content.slice(commaIndex + 1).trim();
		} else condition = content.trim();
		if (condition.startsWith("$")) {
			const styleCondition = parseStyleQuery(condition);
			if (!styleCondition) {
				warnOnce(`invalid-style-query:${stateKey}`, `[Tasty] Invalid style query '${condition}' in container query.`);
				return {
					type: "modifier",
					condition: stateKey,
					raw
				};
			}
			condition = styleCondition;
		} else if (/^[a-zA-Z][\w-]*\s*\(/.test(condition)) {} else {
			condition = expandDimensionShorthands(condition);
			condition = expandTastyUnits(condition);
		}
		return {
			type: "container",
			condition,
			containerName,
			raw
		};
	}
	if (isPredefinedStateRef(stateKey)) {
		const resolved = resolvePredefinedState(stateKey, ctx);
		if (resolved) return {
			...parseAdvancedState(resolved, ctx),
			raw
		};
		return {
			type: "modifier",
			condition: stateKey,
			raw
		};
	}
	return {
		type: "modifier",
		condition: stateKey,
		raw
	};
}
/**
* Parse a style query condition (e.g., $compact, $variant=compact, $variant="very compact")
*/
function parseStyleQuery(condition) {
	const query = condition.slice(1);
	if (/[<>]/.test(query)) {
		warnOnce(`style-query-comparison:${condition}`, `[Tasty] Style queries only support equality. '${condition}' is invalid. Use '${condition.split(/[<>]/)[0]}=...' instead.`);
		return null;
	}
	const eqIndex = query.indexOf("=");
	if (eqIndex === -1) return `style(--${query})`;
	const propName = query.slice(0, eqIndex).trim();
	let value = query.slice(eqIndex + 1).trim();
	if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) {} else value = `"${value}"`;
	value = expandTastyUnits(value);
	return `style(--${propName}: ${value})`;
}
/**
* Find the index of the first comma at parentheses depth 0.
* Returns -1 if no top-level comma is found.
* This prevents splitting on commas inside function calls like scroll-state(a, b).
*/
function findTopLevelComma(s) {
	let depth = 0;
	for (let i = 0; i < s.length; i++) if (s[i] === "(") depth++;
	else if (s[i] === ")") depth--;
	else if (s[i] === "," && depth === 0) return i;
	return -1;
}
function findMatchingParen(str, startIndex) {
	let depth = 1;
	let i = startIndex + 1;
	while (i < str.length && depth > 0) {
		if (str[i] === "(") depth++;
		else if (str[i] === ")") depth--;
		i++;
	}
	return depth === 0 ? i - 1 : -1;
}

//#endregion
export { createStateParserContext, expandDimensionShorthands, expandTastyUnits, extractLocalPredefinedStates, extractPredefinedStateRefs, findTopLevelComma, getGlobalPredefinedStates, parseAdvancedState, resolvePredefinedState, setGlobalPredefinedStates };
//# sourceMappingURL=index.js.map