/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-e634e55 | Cube Dev Team */
import { Lru } from "../parser/lru.js";
import { falseCondition, getConditionUniqueId, trueCondition } from "./conditions.js";

//#region src/tasty/pipeline/simplify.ts
/**
* Condition Simplification Engine
*
* Simplifies condition trees by applying boolean algebra rules,
* detecting contradictions, merging ranges, and deduplicating terms.
*
* This is critical for:
* 1. Detecting invalid combinations (A & !A → FALSE)
* 2. Reducing CSS output size
* 3. Producing cleaner selectors
*/
const simplifyCache = new Lru(5e3);
/**
* Simplify a condition tree aggressively.
*
* This applies all possible simplification rules:
* - Boolean algebra (identity, annihilator, idempotent, absorption)
* - Contradiction detection (A & !A → FALSE)
* - Tautology detection (A | !A → TRUE)
* - Range intersection for numeric queries
* - Attribute value conflict detection
* - Deduplication and sorting
*/
function simplifyCondition(node) {
	const key = getConditionUniqueId(node);
	const cached = simplifyCache.get(key);
	if (cached) return cached;
	const result = simplifyInner(node);
	simplifyCache.set(key, result);
	return result;
}
function simplifyInner(node) {
	if (node.kind === "true" || node.kind === "false") return node;
	if (node.kind === "state") return node;
	if (node.kind === "compound") {
		const simplifiedChildren = node.children.map((c) => simplifyInner(c));
		if (node.operator === "AND") return simplifyAnd(simplifiedChildren);
		else return simplifyOr(simplifiedChildren);
	}
	return node;
}
function simplifyAnd(children) {
	let terms = [];
	for (const child of children) {
		if (child.kind === "false") return falseCondition();
		if (child.kind === "true") continue;
		if (child.kind === "compound" && child.operator === "AND") terms.push(...child.children);
		else terms.push(child);
	}
	if (terms.length === 0) return trueCondition();
	if (terms.length === 1) return terms[0];
	if (hasContradiction(terms)) return falseCondition();
	if (hasRangeContradiction(terms)) return falseCondition();
	if (hasAttributeConflict(terms)) return falseCondition();
	if (hasContainerStyleConflict(terms)) return falseCondition();
	terms = removeImpliedNegations(terms);
	terms = deduplicateTerms(terms);
	terms = mergeRanges(terms);
	terms = sortTerms(terms);
	terms = applyAbsorptionAnd(terms);
	if (terms.length === 0) return trueCondition();
	if (terms.length === 1) return terms[0];
	return {
		kind: "compound",
		operator: "AND",
		children: terms
	};
}
function simplifyOr(children) {
	let terms = [];
	for (const child of children) {
		if (child.kind === "true") return trueCondition();
		if (child.kind === "false") continue;
		if (child.kind === "compound" && child.operator === "OR") terms.push(...child.children);
		else terms.push(child);
	}
	if (terms.length === 0) return falseCondition();
	if (terms.length === 1) return terms[0];
	if (hasTautology(terms)) return trueCondition();
	terms = deduplicateTerms(terms);
	terms = sortTerms(terms);
	terms = applyAbsorptionOr(terms);
	if (terms.length === 0) return falseCondition();
	if (terms.length === 1) return terms[0];
	return {
		kind: "compound",
		operator: "OR",
		children: terms
	};
}
/**
* Check if any term contradicts another (A & !A)
*/
function hasContradiction(terms) {
	const uniqueIds = /* @__PURE__ */ new Set();
	for (const term of terms) {
		if (term.kind !== "state") continue;
		const id = term.uniqueId;
		const negatedId = term.negated ? id.slice(1) : `!${id}`;
		if (uniqueIds.has(negatedId)) return true;
		uniqueIds.add(id);
	}
	return false;
}
/**
* Check for tautologies (A | !A)
*/
function hasTautology(terms) {
	const uniqueIds = /* @__PURE__ */ new Set();
	for (const term of terms) {
		if (term.kind !== "state") continue;
		const id = term.uniqueId;
		const negatedId = term.negated ? id.slice(1) : `!${id}`;
		if (uniqueIds.has(negatedId)) return true;
		uniqueIds.add(id);
	}
	return false;
}
/**
* Check for range contradictions in media/container queries
* e.g., @media(w < 400px) & @media(w > 800px) → FALSE
*
* Also handles negated conditions:
* - Single-bound negations are inverted (not (w < 600px) → w >= 600px)
* - Range negations create excluded ranges that are checked against positive bounds
*/
function hasRangeContradiction(terms) {
	const mediaByDim = /* @__PURE__ */ new Map();
	const containerByDim = /* @__PURE__ */ new Map();
	for (const term of terms) {
		if (term.kind !== "state") continue;
		if (term.type === "media" && term.subtype === "dimension") {
			const key = term.dimension || "width";
			if (!mediaByDim.has(key)) mediaByDim.set(key, {
				positive: [],
				negated: []
			});
			const group = mediaByDim.get(key);
			if (term.negated) group.negated.push(term);
			else group.positive.push(term);
		}
		if (term.type === "container" && term.subtype === "dimension") {
			const key = `${term.containerName || "_"}:${term.dimension || "width"}`;
			if (!containerByDim.has(key)) containerByDim.set(key, {
				positive: [],
				negated: []
			});
			const group = containerByDim.get(key);
			if (term.negated) group.negated.push(term);
			else group.positive.push(term);
		}
	}
	for (const group of mediaByDim.values()) if (rangesAreImpossibleWithNegations(group.positive, group.negated)) return true;
	for (const group of containerByDim.values()) if (rangesAreImpossibleWithNegations(group.positive, group.negated)) return true;
	return false;
}
/**
* Check if conditions are impossible, including negated conditions.
*
* For negated single-bound conditions:
*   not (w < 600px) → w >= 600px (inverted to lower bound)
*   not (w >= 800px) → w < 800px (inverted to upper bound)
*
* For negated range conditions:
*   not (400px <= w < 800px) → excludes [400, 800)
*   If the effective bounds fall entirely within an excluded range, it's impossible.
*/
function rangesAreImpossibleWithNegations(positive, negated) {
	const bounds = computeEffectiveBounds(positive);
	const excludedRanges = [];
	for (const cond of negated) {
		const hasLower = cond.lowerBound?.valueNumeric != null;
		const hasUpper = cond.upperBound?.valueNumeric != null;
		if (hasLower && hasUpper) excludedRanges.push({
			lower: cond.lowerBound.valueNumeric,
			lowerInclusive: cond.lowerBound.inclusive,
			upper: cond.upperBound.valueNumeric,
			upperInclusive: cond.upperBound.inclusive
		});
		else if (hasUpper) {
			const value = cond.upperBound.valueNumeric;
			const inclusive = !cond.upperBound.inclusive;
			if (bounds.lowerBound === null || value > bounds.lowerBound) {
				bounds.lowerBound = value;
				bounds.lowerInclusive = inclusive;
			} else if (value === bounds.lowerBound && !inclusive) bounds.lowerInclusive = false;
		} else if (hasLower) {
			const value = cond.lowerBound.valueNumeric;
			const inclusive = !cond.lowerBound.inclusive;
			if (bounds.upperBound === null || value < bounds.upperBound) {
				bounds.upperBound = value;
				bounds.upperInclusive = inclusive;
			} else if (value === bounds.upperBound && !inclusive) bounds.upperInclusive = false;
		}
	}
	if (bounds.lowerBound !== null && bounds.upperBound !== null) {
		if (bounds.lowerBound > bounds.upperBound) return true;
		if (bounds.lowerBound === bounds.upperBound && (!bounds.lowerInclusive || !bounds.upperInclusive)) return true;
	}
	if (bounds.lowerBound !== null && bounds.upperBound !== null && excludedRanges.length > 0) {
		for (const excluded of excludedRanges) if (boundsWithinExcludedRange(bounds, excluded)) return true;
	}
	return false;
}
/**
* Compute effective bounds from positive (non-negated) conditions
*/
function computeEffectiveBounds(conditions) {
	let lowerBound = null;
	let lowerInclusive = false;
	let upperBound = null;
	let upperInclusive = false;
	for (const cond of conditions) {
		if (cond.lowerBound?.valueNumeric != null) {
			const value = cond.lowerBound.valueNumeric;
			const inclusive = cond.lowerBound.inclusive;
			if (lowerBound === null || value > lowerBound) {
				lowerBound = value;
				lowerInclusive = inclusive;
			} else if (value === lowerBound && !inclusive) lowerInclusive = false;
		}
		if (cond.upperBound?.valueNumeric != null) {
			const value = cond.upperBound.valueNumeric;
			const inclusive = cond.upperBound.inclusive;
			if (upperBound === null || value < upperBound) {
				upperBound = value;
				upperInclusive = inclusive;
			} else if (value === upperBound && !inclusive) upperInclusive = false;
		}
	}
	return {
		lowerBound,
		lowerInclusive,
		upperBound,
		upperInclusive
	};
}
/**
* Check if effective bounds fall entirely within an excluded range.
*
* For example:
*   Effective: [400, 800)
*   Excluded:  [400, 800)
*   → bounds fall entirely within excluded range → impossible
*/
function boundsWithinExcludedRange(bounds, excluded) {
	if (bounds.lowerBound === null || bounds.upperBound === null) return false;
	let lowerOk = false;
	if (bounds.lowerBound > excluded.lower) lowerOk = true;
	else if (bounds.lowerBound === excluded.lower) lowerOk = excluded.lowerInclusive || !bounds.lowerInclusive;
	let upperOk = false;
	if (bounds.upperBound < excluded.upper) upperOk = true;
	else if (bounds.upperBound === excluded.upper) upperOk = excluded.upperInclusive || !bounds.upperInclusive;
	return lowerOk && upperOk;
}
/**
* Check for attribute value conflicts
* e.g., [data-theme="dark"] & [data-theme="light"] → FALSE
* e.g., [data-theme="dark"] & ![data-theme] → FALSE
*/
function hasAttributeConflict(terms) {
	const modifiersByAttr = /* @__PURE__ */ new Map();
	for (const term of terms) {
		if (term.kind !== "state" || term.type !== "modifier") continue;
		const attr = term.attribute;
		if (!modifiersByAttr.has(attr)) modifiersByAttr.set(attr, {
			positive: [],
			negated: []
		});
		const group = modifiersByAttr.get(attr);
		if (term.negated) group.negated.push(term);
		else group.positive.push(term);
	}
	for (const [attr, group] of modifiersByAttr) {
		const positiveValues = group.positive.filter((m) => m.value !== void 0).map((m) => m.value);
		if (new Set(positiveValues).size > 1) return true;
		const hasPositiveValue = group.positive.some((m) => m.value !== void 0);
		const hasNegatedBoolean = group.negated.some((m) => m.value === void 0);
		if (hasPositiveValue && hasNegatedBoolean) return true;
		for (const pos of group.positive) if (pos.value !== void 0) {
			for (const neg of group.negated) if (neg.value === pos.value) return true;
		}
	}
	return false;
}
/**
* Check for container style query conflicts
* e.g., style(--variant: danger) & style(--variant: success) → FALSE
* e.g., style(--variant: danger) & not style(--variant) → FALSE
*/
function hasContainerStyleConflict(terms) {
	const styleByProp = /* @__PURE__ */ new Map();
	for (const term of terms) {
		if (term.kind !== "state" || term.type !== "container" || term.subtype !== "style") continue;
		const key = `${term.containerName || "_"}:${term.property}`;
		if (!styleByProp.has(key)) styleByProp.set(key, {
			positive: [],
			negated: []
		});
		const group = styleByProp.get(key);
		if (term.negated) group.negated.push(term);
		else group.positive.push(term);
	}
	for (const [, group] of styleByProp) {
		const positiveValues = group.positive.filter((c) => c.propertyValue !== void 0).map((c) => c.propertyValue);
		if (new Set(positiveValues).size > 1) return true;
		const hasPositiveValue = group.positive.some((c) => c.propertyValue !== void 0);
		const hasNegatedExistence = group.negated.some((c) => c.propertyValue === void 0);
		if (hasPositiveValue && hasNegatedExistence) return true;
		for (const pos of group.positive) if (pos.propertyValue !== void 0) {
			for (const neg of group.negated) if (neg.propertyValue === pos.propertyValue) return true;
		}
	}
	return false;
}
/**
* Remove negations that are implied by positive terms.
*
* Key optimizations:
* 1. style(--variant: danger) implies NOT style(--variant: success)
*    → If we have style(--variant: danger) & not style(--variant: success),
*      the negation is redundant and can be removed.
*
* 2. [data-theme="dark"] implies NOT [data-theme="light"]
*    → Same logic for attribute selectors.
*
* This produces cleaner CSS:
*   Before: @container style(--variant: danger) and (not style(--variant: success))
*   After:  @container style(--variant: danger)
*/
function removeImpliedNegations(terms) {
	const positiveContainerStyles = /* @__PURE__ */ new Map();
	const positiveModifiers = /* @__PURE__ */ new Map();
	for (const term of terms) {
		if (term.kind !== "state" || term.negated) continue;
		if (term.type === "container" && term.subtype === "style") {
			const key = `${term.containerName || "_"}:${term.property}`;
			if (term.propertyValue !== void 0) positiveContainerStyles.set(key, term.propertyValue);
		}
		if (term.type === "modifier" && term.value !== void 0) positiveModifiers.set(term.attribute, term.value);
	}
	return terms.filter((term) => {
		if (term.kind !== "state" || !term.negated) return true;
		if (term.type === "container" && term.subtype === "style") {
			const key = `${term.containerName || "_"}:${term.property}`;
			const positiveValue = positiveContainerStyles.get(key);
			if (positiveValue !== void 0) {
				if (term.propertyValue === void 0) return true;
				if (term.propertyValue !== positiveValue) return false;
			}
		}
		if (term.type === "modifier") {
			const positiveValue = positiveModifiers.get(term.attribute);
			if (positiveValue !== void 0 && term.value !== void 0) {
				if (term.value !== positiveValue) return false;
			}
		}
		return true;
	});
}
function deduplicateTerms(terms) {
	const seen = /* @__PURE__ */ new Set();
	const result = [];
	for (const term of terms) {
		const id = getConditionUniqueId(term);
		if (!seen.has(id)) {
			seen.add(id);
			result.push(term);
		}
	}
	return result;
}
/**
* Merge compatible range conditions
* e.g., @media(w >= 400px) & @media(w <= 800px) → @media(400px <= w <= 800px)
*/
function mergeRanges(terms) {
	const mediaByDim = /* @__PURE__ */ new Map();
	const containerByDim = /* @__PURE__ */ new Map();
	terms.forEach((term, index) => {
		if (term.kind !== "state") return;
		if (term.type === "media" && term.subtype === "dimension" && !term.negated) {
			const key = term.dimension || "width";
			if (!mediaByDim.has(key)) mediaByDim.set(key, {
				conditions: [],
				indices: []
			});
			const group = mediaByDim.get(key);
			group.conditions.push(term);
			group.indices.push(index);
		}
		if (term.type === "container" && term.subtype === "dimension" && !term.negated) {
			const key = `${term.containerName || "_"}:${term.dimension || "width"}`;
			if (!containerByDim.has(key)) containerByDim.set(key, {
				conditions: [],
				indices: []
			});
			const group = containerByDim.get(key);
			group.conditions.push(term);
			group.indices.push(index);
		}
	});
	const indicesToRemove = /* @__PURE__ */ new Set();
	const mergedTerms = [];
	for (const [dim, group] of mediaByDim) if (group.conditions.length > 1) {
		const merged = mergeMediaRanges(group.conditions);
		if (merged) {
			group.indices.forEach((i) => indicesToRemove.add(i));
			mergedTerms.push(merged);
		}
	}
	for (const [, group] of containerByDim) if (group.conditions.length > 1) {
		const merged = mergeContainerRanges(group.conditions);
		if (merged) {
			group.indices.forEach((i) => indicesToRemove.add(i));
			mergedTerms.push(merged);
		}
	}
	const result = [];
	terms.forEach((term, index) => {
		if (!indicesToRemove.has(index)) result.push(term);
	});
	result.push(...mergedTerms);
	return result;
}
function mergeMediaRanges(conditions) {
	if (conditions.length === 0) return null;
	let lowerBound;
	let upperBound;
	for (const cond of conditions) {
		if (cond.lowerBound) {
			if (!lowerBound || (cond.lowerBound.valueNumeric ?? -Infinity) > (lowerBound.valueNumeric ?? -Infinity)) lowerBound = cond.lowerBound;
		}
		if (cond.upperBound) {
			if (!upperBound || (cond.upperBound.valueNumeric ?? Infinity) < (upperBound.valueNumeric ?? Infinity)) upperBound = cond.upperBound;
		}
	}
	const base = conditions[0];
	const merged = {
		kind: "state",
		type: "media",
		subtype: "dimension",
		negated: false,
		raw: buildMergedRaw(base.dimension || "width", lowerBound, upperBound),
		uniqueId: "",
		dimension: base.dimension,
		lowerBound,
		upperBound
	};
	const parts = [
		"media",
		"dim",
		merged.dimension
	];
	if (lowerBound) {
		parts.push(lowerBound.inclusive ? ">=" : ">");
		parts.push(lowerBound.value);
	}
	if (upperBound) {
		parts.push(upperBound.inclusive ? "<=" : "<");
		parts.push(upperBound.value);
	}
	merged.uniqueId = parts.join(":");
	return merged;
}
function mergeContainerRanges(conditions) {
	if (conditions.length === 0) return null;
	let lowerBound;
	let upperBound;
	for (const cond of conditions) {
		if (cond.lowerBound) {
			if (!lowerBound || (cond.lowerBound.valueNumeric ?? -Infinity) > (lowerBound.valueNumeric ?? -Infinity)) lowerBound = cond.lowerBound;
		}
		if (cond.upperBound) {
			if (!upperBound || (cond.upperBound.valueNumeric ?? Infinity) < (upperBound.valueNumeric ?? Infinity)) upperBound = cond.upperBound;
		}
	}
	const base = conditions[0];
	const merged = {
		kind: "state",
		type: "container",
		subtype: "dimension",
		negated: false,
		raw: buildMergedRaw(base.dimension || "width", lowerBound, upperBound),
		uniqueId: "",
		containerName: base.containerName,
		dimension: base.dimension,
		lowerBound,
		upperBound
	};
	const parts = [
		"container",
		"dim",
		merged.containerName || "_",
		merged.dimension
	];
	if (lowerBound) {
		parts.push(lowerBound.inclusive ? ">=" : ">");
		parts.push(lowerBound.value);
	}
	if (upperBound) {
		parts.push(upperBound.inclusive ? "<=" : "<");
		parts.push(upperBound.value);
	}
	merged.uniqueId = parts.join(":");
	return merged;
}
function buildMergedRaw(dimension, lowerBound, upperBound) {
	if (lowerBound && upperBound) {
		const lowerOp = lowerBound.inclusive ? "<=" : "<";
		const upperOp = upperBound.inclusive ? "<=" : "<";
		return `@media(${lowerBound.value} ${lowerOp} ${dimension} ${upperOp} ${upperBound.value})`;
	} else if (upperBound) return `@media(${dimension} ${upperBound.inclusive ? "<=" : "<"} ${upperBound.value})`;
	else if (lowerBound) return `@media(${dimension} ${lowerBound.inclusive ? ">=" : ">"} ${lowerBound.value})`;
	return "@media()";
}
function sortTerms(terms) {
	return [...terms].sort((a, b) => {
		const idA = getConditionUniqueId(a);
		const idB = getConditionUniqueId(b);
		return idA.localeCompare(idB);
	});
}
/**
* Apply absorption law for AND: A & (A | B) → A
*/
function applyAbsorptionAnd(terms) {
	const simpleIds = /* @__PURE__ */ new Set();
	for (const term of terms) if (term.kind !== "compound") simpleIds.add(getConditionUniqueId(term));
	return terms.filter((term) => {
		if (term.kind === "compound" && term.operator === "OR") {
			for (const child of term.children) if (simpleIds.has(getConditionUniqueId(child))) return false;
		}
		return true;
	});
}
/**
* Apply absorption law for OR: A | (A & B) → A
*/
function applyAbsorptionOr(terms) {
	const simpleIds = /* @__PURE__ */ new Set();
	for (const term of terms) if (term.kind !== "compound") simpleIds.add(getConditionUniqueId(term));
	return terms.filter((term) => {
		if (term.kind === "compound" && term.operator === "AND") {
			for (const child of term.children) if (simpleIds.has(getConditionUniqueId(child))) return false;
		}
		return true;
	});
}

//#endregion
export { simplifyCondition };
//# sourceMappingURL=simplify.js.map