ll := import("@platforma-sdk/workflow-tengo:ll")
maps := import("@platforma-sdk/workflow-tengo:maps")
slices := import("@platforma-sdk/workflow-tengo:slices")
validation := import("@platforma-sdk/workflow-tengo:validation")
fmt := import("fmt")
objects := import("@platforma-sdk/workflow-tengo:objects")
oop := import("@platforma-sdk/workflow-tengo:oop")
pframesSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")



_newExpression := undefined
_newWindowExpression := undefined
axis := undefined
col := undefined
lit := undefined
allHorizontal := undefined
anyHorizontal := undefined
and := undefined
or := undefined
when := undefined
rank := undefined




_validateOptions := func(options, schema, functionName) {
	if len(options) > 1 {
		ll.panic("%s accepts at most one options map argument.", functionName)
	}
	if len(options) == 0 {
		return {}
	}

	opsMap := options[0]
	ll.assert(is_map(opsMap), "Options argument to %s(), if provided, must be an options map.", functionName)

	validation.assertType(opsMap, schema, fmt.sprintf("%s options validation error", functionName))
	return opsMap
}


_isExpression := func(item) {
	return ll.isMap(item) && ll.methodExists(item, "_getExpression")
}


_isAggregation := func(item) {
	return ll.isMap(item) && ll.methodExists(item, "_getAggregation")
}


_mapToExpressionStructList := func(itemsList, stringInterpretation, ctx) {
	if !is_array(itemsList) {
		itemsList = [itemsList] // Allow single item to be passed
	}
	if stringInterpretation != "col" && stringInterpretation != "lit" {
		ll.panic("Invalid stringInterpretation ('%v') for _mapToExpressionStructList. Must be 'col' or 'lit'.", stringInterpretation)
		return undefined // Unreachable
	}
	return slices.map(itemsList, func(item) {
		if is_string(item) {
			if stringInterpretation == "col" {
				return col(item)._getExpression(ctx)
			} else { // Must be "lit" due to the check above
				return lit(item)._getExpression(ctx)
			}
		} else if _isExpression(item) {
			return item._getExpression(ctx)
		} else {
			ll.panic("Invalid item type in list: %T. Must be a string (interpreted as '%s') or an expression object.", item, stringInterpretation)
			return undefined // Unreachable
		}
	})
}

_mapToSingleExpressionStruct := func(item, stringInterpretation, ctx) {
	if _isExpression(item) {
		return item._getExpression(ctx)
	} else if is_int(item) || is_float(item) || is_bool(item) { // These are always literals
		return lit(item)._getExpression(ctx)
	} else if is_string(item) {
		if stringInterpretation == "col" {
			return col(item)._getExpression(ctx)
		} else if stringInterpretation == "lit" {
			return lit(item)._getExpression(ctx)
		} else { // stringInterpretation is undefined or an invalid value
			ll.panic("String item '%v' provided, but its interpretation as 'col' or 'lit' was not specified or is not applicable in this context. (stringInterpretation: '%v')", item, stringInterpretation)
			return undefined
		}
	} else {
		ll.panic("Invalid item type: %T. Expected an expression, literal (number, bool, null), or string (with 'col'/'lit' interpretation).", item)
		return undefined
	}
}







_newWindowExpression = func(expression, aggregation, currentAlias) {
	self := undefined
	self = ll.toStrict({




		_getExpression: func(ctx) {
			ll.assert(is_map(ctx), "ctx must be a map")
			ll.assert(!is_undefined(expression), "Using this operation as a window expression is not allowed. Use it as a group by expression instead. %v", aggregation)

			valueExpr := expression(ctx)
			if is_undefined(currentAlias) {
				return valueExpr
			}
			return {type: "alias", value: valueExpr, name: currentAlias}
		},

		_getAggregation: func(ctx) {
			if aggregation == undefined {
				ll.panic("Using this operation as a group by expression is not allowed, most probably because you applied .over() to it. %v", expression)
			}
			if currentAlias == undefined {
				ll.panic("Alias is not defined for this window expression.")
			}
			aggStruct := aggregation(ctx)
			return maps.deepMerge(aggStruct, {name: currentAlias})
		},







		alias: func(newAlias) {
			ll.assert(is_string(newAlias) && newAlias != "", "alias must be a non-empty string")
			return _newWindowExpression(expression, aggregation, newAlias)
		},






		over: func(partitionBy) {
			ll.assert(is_array(partitionBy) || is_string(partitionBy) || _isExpression(partitionBy), "partitionBy must be an array of strings/expressions or a single string/expression")


			newExpr := func(ctx) {
				baseExpr := expression(ctx)

				if len(baseExpr.partitionBy) > 0 {
					ll.warn("partitionBy already set on this window expression, overriding.")
				}

				return maps.deepMerge(baseExpr, {
					partitionBy: _mapToExpressionStructList(partitionBy, "col", ctx)
				})
			}

			return _newWindowExpression(newExpr, undefined, currentAlias)
		}
	})
	return self
}





_newExpression = func(expression) {
	self := undefined // forward declaration


	binaryOp := func(opType, other) {
		return _newExpression(
			func(ctx) {
				lhsExpr := self._getExpression(ctx)
				rhsExpr := undefined
				if _isExpression(other) {
					rhsExpr = other._getExpression(ctx)
				} else if is_int(other) || is_float(other) || is_bool(other) || is_string(other) {
					rhsExpr = lit(other)._getExpression(ctx)
				} else {
					ll.panic(opType + " expects an expression object or a literal (number, boolean, null) as argument. Got type: %T", other)
				}
				return {type: opType, lhs: lhsExpr, rhs: rhsExpr}
			}
		)
	}


	unaryOp := func(opType) {
		return _newExpression(
			func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {type: opType, value: valueExpr}
			}
		)
	}


	unaryStrOp := func(opType) {
		return _newExpression(
			func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {type: opType, value: valueExpr}
			}
		)
	}


	aggOp := func(aggType) {
		return _newWindowExpression(
			func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {type: "aggregate", aggregation: aggType, value: valueExpr, partitionBy: []}
			},
			func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {aggregation: aggType, expression: valueExpr}
			},
			undefined // Alias of underlying col, window expression itself is typically aliased later
		)
	}

	aggOpBy := func(aggType, by) {
		return _newWindowExpression(
			undefined,
			func(ctx) {
				baseExpr := self._getExpression(ctx)
				return {aggregation: aggType, expression: baseExpr, by: _mapToExpressionStructList(by, "col", {})}
			},
			undefined // Alias of underlying col, window expression itself is typically aliased later
		)
	}

	self = ll.toStrict({

		_getExpression: func(ctx) {
			ll.assert(is_map(ctx), "ctx must be a map")
			return expression(ctx)
		},






		alias: func(newAlias) {
			ll.assert(is_string(newAlias) && newAlias != "", "alias must be a non-empty string")
			return _newExpression(func (ctx) {
				valueExpr := self._getExpression(ctx)
				return {type: "alias", value: valueExpr, name: newAlias}
			})
		},



		gt: func(other) { return binaryOp("gt", other) },

		ge: func(other) { return binaryOp("ge", other) },

		eq: func(other) { return binaryOp("eq", other) },

		lt: func(other) { return binaryOp("lt", other) },

		le: func(other) { return binaryOp("le", other) },

		neq: func(other) { return binaryOp("neq", other) },



		plus: func(other) { return binaryOp("plus", other) },

		minus: func(other) { return binaryOp("minus", other) },

		multiply: func(other) { return binaryOp("multiply", other) },

		truediv: func(other) { return binaryOp("truediv", other) },

		floordiv: func(other) { return binaryOp("floordiv", other) },


		log10: func() { return unaryOp("log10") },

		log: func() { return unaryOp("log") },

		log2: func() { return unaryOp("log2") },

		abs: func() { return unaryOp("abs") },

		sqrt: func() { return unaryOp("sqrt") },

		negate: func() { return unaryOp("negate") },

		floor: func() { return unaryOp("floor") },

		round: func() { return unaryOp("round") },

		ceil: func() { return unaryOp("ceil") },










		cast: func(dtype, ...options) {
			ops := _validateOptions(options, {
				"__options__,closed": "",
				"strict,?": "bool"
			}, "cast")
			strictVal := false
			if !is_undefined(ops.strict) {
				strictVal = ops.strict
			}
			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {type: "cast", value: valueExpr, dtype: dtype, strict: strictVal}
			})
		},



		and: func(other) {
			ll.assert(_isExpression(other), "and method expects an expression object as argument")
			return allHorizontal(self, other) // Refer to top-level function
		},

		or: func(other) {
			ll.assert(_isExpression(other), "or method expects an expression object as argument")
			return anyHorizontal(self, other) // Refer to top-level function
		},

		not: func() {
			return unaryOp("not")
		},



		isNull: func() { return unaryOp("is_na") }, // Polars: is_null(), TS: is_na

		isNotNull: func() { return unaryOp("is_not_na") }, // Polars: is_not_null(), TS: is_not_na
































		structField: func(fieldPath, ...options) {

			ll.assert(!is_undefined(fieldPath), "structField expects a field path (string or array of strings)")

			fields := undefined
			if is_string(fieldPath) {
				fields = fieldPath
			} else if is_array(fieldPath) {
				ll.assert(len(fieldPath) > 0, "structField field path array cannot be empty")
				for fieldName in fieldPath {
					ll.assert(is_string(fieldName), "All field names in path must be strings, got: %T", fieldName)
				}
				if len(fieldPath) == 1 {
					fields = fieldPath[0]
				} else {
					fields = fieldPath
				}
			} else {
				ll.panic("structField fieldPath must be a string or array of strings, got: %T", fieldPath)
			}


			ops := _validateOptions(options, {
				"__options__,closed": "",
				"default,?": "any",
				"dtype,?": "string"
			}, "structField")


			return _newExpression(func(ctx) {
				structExpr := {
					type: "struct_field",
					struct: self._getExpression(ctx),
					fields: fields
				}


				if !is_undefined(ops.default) {
					structExpr.default = ops.default
				}
				if !is_undefined(ops.dtype) {
					structExpr.dtype = ops.dtype
				}

				return structExpr
			})
		},



		strToUpper: func() { return unaryStrOp("to_upper") },

		strToLower: func() { return unaryStrOp("to_lower") },

		strLenChars: func() { return unaryStrOp("str_len")},











		strSlice: func(start, ...args) {
			ll.assert(len(args) <= 1, "strSlice expects at most one additional argument.")

			length := undefined
			end := undefined
			if len(args) > 0 {
				arg := args[0]
				if is_map(arg) {
					ops := _validateOptions([arg], {
						"__options__,closed": "",
						"length,?": "any",
						"end,?": "any"
					}, "strSlice")
					if ops.length != undefined && ops.end != undefined {
						ll.panic("strSlice cannot have both 'length' and 'end' options.")
					}
					if !is_undefined(ops.length) {
						length = ops.length
					}
					if !is_undefined(ops.end) {
						end = ops.end
					}
				} else {
					length = arg
				}
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				expr := {
					type: "substring",
					value: valueExpr,
					start: _mapToSingleExpressionStruct(start, "col", ctx)
				}

				if !is_undefined(length) {
					expr.length = _mapToSingleExpressionStruct(length, "col", ctx)
				}
				if !is_undefined(end) {
					expr.end = _mapToSingleExpressionStruct(end, "col", ctx)
				}

				return expr
			})
		},










		strReplace: func(pattern, replacement, ...options) {
			ops := _validateOptions(options, {
				"__options__,closed": "",
				"replaceAll,?": "bool",
				"literal,?": "bool"
			}, "strReplace")
			replaceAllVal := false
			literalVal := false

			if !is_undefined(ops.replaceAll) {
				replaceAllVal = ops.replaceAll
			}
			if !is_undefined(ops.literal) {
				literalVal = ops.literal
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_replace",
					value: valueExpr,
					pattern: _mapToSingleExpressionStruct(pattern, "lit", ctx),
					replacement: _mapToSingleExpressionStruct(replacement, "lit", ctx),
					replaceAll: replaceAllVal,
					literal: literalVal
				}
			})
		},









		strReplaceAll: func(pattern, replacement, ...options) {
			opts := {}
			if len(options) > 0 {
				opts = options[0]
			}
			opts.replaceAll = true
			return self.strReplace(pattern, replacement, opts)
		},









		strContains: func(pattern, ...options) {
			ops := _validateOptions(options, {
				"__options__,closed": "",
				"literal,?": "bool",
				"strict,?": "bool"
			}, "strContains")
			literalVal := false
			strictVal := true

			if !is_undefined(ops.literal) {
				literalVal = ops.literal
			}
			if !is_undefined(ops.strict) {
				strictVal = ops.strict
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_contains",
					value: valueExpr,
					pattern: _mapToSingleExpressionStruct(pattern, "lit", ctx),
					literal: literalVal,
					strict: strictVal
				}
			})
		},








		strContainsAny: func(patterns, ...options) {
			ll.assert(is_array(patterns), "patterns must be an array of strings")
			for pattern in patterns {
				ll.assert(is_string(pattern), "all patterns must be strings")
			}

			ops := _validateOptions(options, {
				"__options__,closed": "",
				"asciiCaseInsensitive,?": "bool"
			}, "strContainsAny")
			asciiCaseInsensitiveVal := false
			if !is_undefined(ops.asciiCaseInsensitive) {
				asciiCaseInsensitiveVal = ops.asciiCaseInsensitive
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_contains_any",
					value: valueExpr,
					patterns: patterns,
					asciiCaseInsensitive: asciiCaseInsensitiveVal
				}
			})
		},








		strCountMatches: func(pattern, ...options) {
			ops := _validateOptions(options, {
				"__options__,closed": "",
				"literal,?": "bool"
			}, "strCountMatches")
			literalVal := false

			if !is_undefined(ops.literal) {
				literalVal = ops.literal
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_count_matches",
					value: valueExpr,
					pattern: _mapToSingleExpressionStruct(pattern, "lit", ctx),
					literal: literalVal
				}
			})
		},








		strExtract: func(pattern, ...options) {
			ops := _validateOptions(options, {
				"__options__,closed": "",
				"groupIndex,?": "number"
			}, "strExtract")
			groupIndexVal := 0

			if !is_undefined(ops.groupIndex) {
				groupIndexVal = ops.groupIndex
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_extract",
					value: valueExpr,
					pattern: _mapToSingleExpressionStruct(pattern, "lit", ctx),
					groupIndex: groupIndexVal
				}
			})
		},






		strStartsWith: func(prefix) {
			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_starts_with",
					value: valueExpr,
					prefix: _mapToSingleExpressionStruct(prefix, "lit", ctx)
				}
			})
		},






		strEndsWith: func(suffix) {
			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "str_ends_with",
					value: valueExpr,
					suffix: _mapToSingleExpressionStruct(suffix, "lit", ctx)
				}
			})
		},









		strDistance: func(string2, metric, returnSimilarity) {
			ll.assert(_isExpression(string2), "string2 must be an expression object")
			ll.assert(is_string(metric), "metric must be a string")

			returnSimilarityVal := false
			if returnSimilarity != undefined {
				ll.assert(is_bool(returnSimilarity), "returnSimilarity must be a boolean")
				returnSimilarityVal = returnSimilarity
			}

			return _newExpression(func(ctx) {
				lhsExpr := self._getExpression(ctx)
				rhsExpr := string2._getExpression(ctx)
				return {
					type: "string_distance",
					string1: lhsExpr,
					string2: rhsExpr,
					metric: metric,
					returnSimilarity: returnSimilarityVal
				}
			})
		},









		fuzzyFilter: func(patternExpr, metric, bound) {
			ll.assert(_isExpression(patternExpr), "patternExpr must be an expression object")
			ll.assert(is_string(metric), "metric must be a string")
			ll.assert(is_int(bound) || is_float(bound), "bound must be a number")

			return _newExpression(func(ctx) {
				lhsExpr := self._getExpression(ctx)
				rhsExpr := patternExpr._getExpression(ctx)
				return {
					type: "fuzzy_string_filter",
					value: lhsExpr,
					pattern: rhsExpr,
					metric: metric,
					bound: bound
				}
			})
		},









		hash: func(hashType, encoding, bits) {
			ll.assert(is_string(hashType), "hashType must be a string")
			ll.assert(is_string(encoding), "encoding must be a string")
			if bits != undefined {
				ll.assert(is_int(bits), "bits must be a number")
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				hashExpr := {
					type: "hash",
					value: valueExpr,
					hashType: hashType,
					encoding: encoding
				}
				if bits != undefined {
					hashExpr.bits = bits
				}
				return hashExpr
			})
		},







		fillNull: func(fillValueExpr) {
			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "fill_null",
					input: valueExpr,
					fillValue: _mapToSingleExpressionStruct(fillValueExpr, "lit", ctx)
				}
			})
		},






		fillNaN: func(fillValueExpr) {
			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "fill_nan",
					input: valueExpr,
					fillValue: _mapToSingleExpressionStruct(fillValueExpr, "lit", ctx)
				}
			})
		},



		sum: func() { return aggOp("sum") },

		mean: func() { return aggOp("mean") },

		median: func() { return aggOp("median") },

		min: func() { return aggOp("min") },

		max: func() { return aggOp("max") },

		std: func() { return aggOp("std") },

		var: func() { return aggOp("var") },

		count: func() { return aggOp("count") },

		first: func() { return aggOp("first") },

		last: func() { return aggOp("last") },

		nUnique: func() { return aggOp("n_unique") },


		maxBy: func(...by) { return aggOpBy("max_by", by) },

		minBy: func(...by) { return aggOpBy("min_by", by) },







		rank: func(...descendingArgs) {
			ll.assert(len(descendingArgs) <= 1, "rank method expects at most one optional boolean argument for descending.")
			descendingVal := false
			if len(descendingArgs) == 1 {
				ll.assert(is_bool(descendingArgs[0]), "descending argument for rank must be a boolean")
				descendingVal = descendingArgs[0]
			}
			return _newWindowExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "rank",
					orderBy: [valueExpr],
					partitionBy: [], // To be set by .over()
					descending: descendingVal
				}
			}, undefined)
		},












		cumsum: func(...args) {
			additionalOrderByVal := []
			descendingVal := false
			actualArgs := args

			if len(args) > 0 && is_map(args[len(args)-1]) && !(_isExpression(args[len(args)-1])) {
				ops := _validateOptions([args[len(args)-1]], {
					"__options__,closed": "",
					"orderBy,?": "any",
					"descending,?": "bool"
				}, "cumsum")
				actualArgs = args[0:len(args)-1]
				if !is_undefined(ops.orderBy) {
					if is_array(ops.orderBy) {
						additionalOrderByVal = ops.orderBy
					} else {
						additionalOrderByVal = [ops.orderBy]
					}
				}
				if !is_undefined(ops.descending) {
					descendingVal = ops.descending
				}
			}


			if len(additionalOrderByVal) == 0 && len(actualArgs) > 0 {
				if is_array(actualArgs[0]) || is_string(actualArgs[0]) || _isExpression(actualArgs[0]) {
					if is_array(actualArgs[0]) {
						additionalOrderByVal = actualArgs[0]
					} else {
						additionalOrderByVal = [actualArgs[0]]
					}
					if len(actualArgs) > 1 && is_bool(actualArgs[1]) {
						descendingVal = actualArgs[1]
					}
				} else if is_bool(actualArgs[0]) && len(additionalOrderByVal) == 0 { // only if not set by ops.orderBy
					descendingVal = actualArgs[0]
				} else if len(actualArgs) > 0 {
					ll.panic("Invalid arguments to cumsum. Expected order expressions or options map.")
				}
			}

			return _newWindowExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "cumsum",
					value: valueExpr,
					additionalOrderBy: _mapToExpressionStructList(additionalOrderByVal, "col", ctx),
					partitionBy: [],
					descending: descendingVal
				}
			}, undefined, undefined)
		},


		toString: func() {
			return self.cast("string")
		},






		inSet: func(...values) {
			ll.assert(len(values) > 0, "inSet method requires at least one value.")

			set := []
			for v in values {
				ll.assert(
					is_undefined(v) || is_bool(v) || is_int(v) || is_float(v) || is_string(v),
					"Invalid argument in inSet: Expected primitive value, got %T", v)
				set = append(set, v)
			}
			uniqueSet := slices.sortUnique(set)

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "in_set",
					value: valueExpr,
					set: uniqueSet
				}
			})
		},






		matchesEcmaRegex: func(ecmaRegex) {
			ll.assert(is_string(ecmaRegex), "ecma_regex must be a string.")

			if len(ecmaRegex) == 0 {
				return self.eq("")
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "matches_ecma_regex",
					value: valueExpr,
					ecmaRegex: ecmaRegex
				}
			})
		},










		containsFuzzyMatch: func(reference, maxEdits, ...args) {
			ll.assert(is_string(reference) && len(reference) > 0, "pattern must be a non-empty string.")
			ll.assert(is_int(maxEdits) && maxEdits > 0, "maxEdits must be a positive integer.")

			wildcard := undefined
			substitutionsOnly := undefined
			if len(args) > 0 {
				ops := _validateOptions(args, {
					"__options__,closed": "",
					"wildcard,?": "string",
					"substitutionsOnly,?": "bool"
				}, "containsFuzzyMatch")
				if !is_undefined(ops.wildcard) {
					ll.assert(len(ops.wildcard) == 1, "wildcard must be a single character.")
				}
				wildcard = ops.wildcard
				substitutionsOnly = ops.substitutionsOnly
			}

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return objects.deleteUndefined({
					type: "contains_fuzzy_match",
					value: valueExpr,
					reference: reference,
					maxEdits: maxEdits,
					wildcard: wildcard,
					substitutionsOnly: substitutionsOnly
				})
			})
		},



















		replaceEcmaRegex: func(ecmaRegex, replacement) {
			ll.assert(is_string(ecmaRegex) && len(ecmaRegex) > 0, "ecmaRegex must be a non-empty string.")
			ll.assert(is_string(replacement), "replacement must be a string.")

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "replace_ecma_regex",
					value: valueExpr,
					ecmaRegex: ecmaRegex,
					replacement: replacement
				}
			})
		},
































		extractEcmaRegex: func(ecmaRegex) {
			ll.assert(is_string(ecmaRegex) && len(ecmaRegex) > 0, "ecmaRegex must be a non-empty string.")

			return _newExpression(func(ctx) {
				valueExpr := self._getExpression(ctx)
				return {
					type: "extract_ecma_regex",
					value: valueExpr,
					ecmaRegex: ecmaRegex
				}
			})
		}
	})
	return self
}





_matcherToAxisSpec := func(ctx, processedMatcher) {
	ll.assert(!is_undefined(ctx.specDistiller), "specDistiller must be defined")
	ll.assert(is_map(ctx.distilledAxesSpecs), "distilledAxesSpecs must be a map")

	distilledMatcher := ctx.specDistiller.distill(processedMatcher)
	discriminativeDomains := ctx.specDistiller.getDiscriminativeDomainsSet(distilledMatcher.name)
	if ((!is_undefined(distilledMatcher.domain) && len(distilledMatcher.domain) > 0) != (!is_undefined(discriminativeDomains) && len(discriminativeDomains) > 0)) ||
		(!is_undefined(distilledMatcher.domain) && !is_undefined(discriminativeDomains) && len(distilledMatcher.domain) != len(discriminativeDomains)) {
		ll.panic("Axis matcher %v is not specific enough. Expected discriminative domains %v, got %v", distilledMatcher.name, discriminativeDomains, distilledMatcher.domain)
	}

	axisSpec := undefined
	for spec in ctx.distilledAxesSpecs[distilledMatcher.name] {
		if !is_undefined(distilledMatcher.type) {
			if distilledMatcher.type != spec.type {
				continue
			}
		}
		if !is_undefined(distilledMatcher.domain) && len(distilledMatcher.domain) > 0 {
			if is_undefined(spec.domain) || len(spec.domain) < len(distilledMatcher.domain) {
				continue
			}
			domainMatches := true
			for domainKey, domainValue in distilledMatcher.domain {
				if spec.domain[domainKey] != domainValue {
					domainMatches = false
					break
				}
			}
			if !domainMatches {
				continue
			}
		}
		if !is_undefined(distilledMatcher.contextDomain) && len(distilledMatcher.contextDomain) > 0 {
			if is_undefined(spec.contextDomain) || len(spec.contextDomain) < len(distilledMatcher.contextDomain) {
				continue
			}
			contextDomainMatches := true
			for domainKey, domainValue in distilledMatcher.contextDomain {
				if spec.contextDomain[domainKey] != domainValue {
					contextDomainMatches = false
					break
				}
			}
			if !contextDomainMatches {
				continue
			}
		}
		axisSpec = spec
		break
	}
	ll.assert(!is_undefined(axisSpec),
		"axis spec not found for matcher %v, available options: %v",
		processedMatcher, ctx.distilledAxesSpecs)
	return axisSpec
}











axis = func(matcher) {
	processedMatcher := undefined
	if is_string(matcher) {
		processedMatcher = { name: matcher, domain: {} }
	} else if is_map(matcher) {
		processedMatcher = pframesSpec.axisSpecToMatcher(matcher)
	} else {
		ll.panic("Matcher for axis must be a string (axis name) or an AxisSpec-like map (with at least 'name' and 'type' fields).")
	}
	return _newExpression(func(ctx) {
		axisSpec := _matcherToAxisSpec(ctx, processedMatcher)
		return {
			type: "axis",
			spec: axisSpec
		}
	})
}






col = func(name) {
	ll.assert(is_string(name), "col name must be a string")
	return _newExpression(func(ctx) {
		return {
			type: "col",
			name: name
		}
	})
}






lit = func(value) {
	return _newExpression(func(ctx) {
		return {
			type: "const",
			value: value
		}
	})
}








concatStr := func(expressions, ...options) {
	ll.assert(is_array(expressions), "First argument to concatStr must be an array of expressions")
	ll.assert(len(expressions) > 0, "Expression array for concatStr cannot be empty")

	ops := _validateOptions(options, {
		"__options__,closed": "",
		"delimiter,?": "string"
	}, "concatStr")
	delimiterVal := "" // Default to empty string
	if !is_undefined(ops.delimiter) {
		delimiterVal = ops.delimiter
	}

	return _newExpression(func(ctx) {
		return {
			type: "str_join",
			operands: _mapToExpressionStructList(expressions, "lit", ctx),
			delimiter: delimiterVal
		}
	})
}






minHorizontal := func(expressions) {
	ll.assert(is_array(expressions), "Argument to minHorizontal must be an array of expressions")
	ll.assert(len(expressions) > 0, "Expression array for minHorizontal cannot be empty")
	return _newExpression(func(ctx) {
		return {
			type: "min",
			operands: _mapToExpressionStructList(expressions, "col", ctx)
		}
	})
}






maxHorizontal := func(expressions) {
	ll.assert(is_array(expressions), "Argument to maxHorizontal must be an array of expressions")
	ll.assert(len(expressions) > 0, "Expression array for maxHorizontal cannot be empty")
	return _newExpression(func(ctx) {
		return {
			type: "max",
			operands: _mapToExpressionStructList(expressions, "col", ctx)
		}
	})
}






allHorizontal = func(...args){
    return _newExpression(func(ctx) {
        processedOperands := []
        for arg in args {
            exprStruct := undefined
            if _isExpression(arg) {
                exprStruct = arg._getExpression(ctx)
            } else if is_string(arg) {
                exprStruct = col(arg)._getExpression(ctx)
            } else {
                ll.panic("Invalid argument type for allHorizontal: %T. Must be an expression object or string.", arg)
            }

            if exprStruct.type == "and" && is_array(exprStruct.operands) {
                processedOperands = append(processedOperands, exprStruct.operands...)
            } else {
                processedOperands = append(processedOperands, exprStruct)
            }
        }
        if len(processedOperands) == 0 {
            ll.panic("allHorizontal requires at least one expression.")
        }
        return {
            type: "and",
            operands: processedOperands
        }
    })
}






anyHorizontal = func(...args){
    return _newExpression(func(ctx) {
        processedOperands := []
        for arg in args {
            exprStruct := undefined
            if _isExpression(arg) {
                exprStruct = arg._getExpression(ctx)
            } else if is_string(arg) {
                exprStruct = col(arg)._getExpression(ctx)
            } else {
                ll.panic("Invalid argument type for anyHorizontal: %T. Must be an expression object or string.", arg)
            }

            if exprStruct.type == "or" && is_array(exprStruct.operands) {
                processedOperands = append(processedOperands, exprStruct.operands...)
            } else {
                processedOperands = append(processedOperands, exprStruct)
            }
        }
        if len(processedOperands) == 0 {
            ll.panic("anyHorizontal requires at least one expression.")
        }
        return {
            type: "or",
            operands: processedOperands
        }
    })
}






and = func(...args) {
	return allHorizontal(args...)
}






or = func(...args) {
	return anyHorizontal(args...)
}




















rank = func(orderByExpressions, ...options) {
	orderByVal := []
	descendingVal := false

	ll.assert(orderByExpressions != undefined, "rank() requires orderByExpressions (single expression/string or array of them).")

	ops := _validateOptions(options, {
		"__options__,closed": "",
		"descending,?": "bool"
	}, "rank")
	if !is_undefined(ops.descending) {
		descendingVal = ops.descending
	}

	return _newWindowExpression(func(ctx) {
		orderByVal := _mapToExpressionStructList(orderByExpressions, "col", ctx)
		ll.assert(len(orderByVal) > 0, "rank() 'orderBy' list cannot be empty after processing arguments.")
		return {
			type: "rank",
			orderBy: orderByVal,
			partitionBy: [],
			descending: descendingVal
		}
	}, undefined, undefined)
}


_newWhenThenBuilder := func(currentClauses, currentWhenExprOrUndefined) {
	builderSelf := undefined
	builderSelf = ll.toStrict({





		then: func(thenExpr) {
			ll.assert(currentWhenExprOrUndefined != undefined, ".then() must follow a .when() or initial when() call.")
			ll.assert(_isExpression(thenExpr), ".then() expects an expression object argument.")


			newClausesExpr := func(ctx) {
				existingClauses := currentClauses(ctx)
				newClauses := maps.clone(existingClauses) // Deep copy existing clauses
				return append(newClauses, {
					when: currentWhenExprOrUndefined._getExpression(ctx),
					then: thenExpr._getExpression(ctx)
				})
			}


			return _newWhenThenBuilder(newClausesExpr, undefined)
		},






		when: func(conditionExpr) {
			ll.assert(currentWhenExprOrUndefined == undefined, ".when() must follow a .then() call.")
			ll.assert(_isExpression(conditionExpr), ".when() expects an expression object argument.")



			return _newWhenThenBuilder(currentClauses, conditionExpr)
		},






		otherwise: func(otherwiseExpr) {
			ll.assert(currentWhenExprOrUndefined == undefined, ".otherwise() must follow a .then() call.")
			ll.assert(_isExpression(otherwiseExpr), ".otherwise() expects an expression object argument.")


			return _newExpression(func(ctx) {
				finalClauses := maps.clone(currentClauses(ctx))
				ll.assert(len(finalClauses) > 0, "At least one .when().then() clause is required before .otherwise().")
				return {
					type: "when_then_otherwise",
					conditions: finalClauses,
					otherwise: otherwiseExpr._getExpression(ctx)
				}
			})
		}
	})
	return builderSelf
}











when = func(conditionExpr) {
	ll.assert(_isExpression(conditionExpr), "Initial when() expects an expression object argument.")
	return _newWhenThenBuilder(func(ctx) { return [] }, conditionExpr)
}











rawExp := func(expression) {
	ll.assert(is_map(expression), "rawExpression expects a map as argument")
	return _newExpression(func(ctx) { return expression })
}

_isSelector := func(expression) {
	return _isExpression(expression) && ll.methodExists(expression, "complement")
}

_newSelectorExpression := func(expression) {
	self := undefined
	newExpression := _newExpression(expression)
	self = ll.toStrict(oop.inherit(newExpression, {





		complement: func() {
			return _newSelectorExpression(func(ctx) {
				return {
					type: "selector_complement",
					selector: self._getExpression(ctx)
				}
			})
		},
		






		union: func(...others) {
			ll.assert(len(others) > 0, "union requires at least one other selector")
			return _newSelectorExpression(func(ctx) {
				selectors := [self._getExpression(ctx)]
				for other in others {
					ll.assert(_isSelector(other), "union requires selector expressions, got: %T", other)
					selectors = append(selectors, other._getExpression(ctx))
				}
				return {
					type: "selector_union",
					selectors: selectors
				}
			})
		},
		






		intersection: func(...others) {
			ll.assert(len(others) > 0, "intersection requires at least one other selector")
			return _newSelectorExpression(func(ctx) {
				selectors := [self._getExpression(ctx)]
				for other in others {
					ll.assert(_isSelector(other), "intersection requires selector expressions, got: %T", other)
					selectors = append(selectors, other._getExpression(ctx))
				}
				return {
					type: "selector_intersection",
					selectors: selectors
				}
			})
		},
		






		difference: func(...others) {
			ll.assert(len(others) > 0, "difference requires at least one other selector")
			return _newSelectorExpression(func(ctx) {
				selectors := [self._getExpression(ctx)]
				for other in others {
					ll.assert(_isSelector(other), "difference requires selector expressions, got: %T", other)
					selectors = append(selectors, other._getExpression(ctx))
				}
				return {
					type: "selector_difference",
					selectors: selectors
				}
			})
		},
		






		symmetricDifference: func(...others) {
			ll.assert(len(others) > 0, "symmetricDifference requires at least one other selector")
			return _newSelectorExpression(func(ctx) {
				selectors := [self._getExpression(ctx)]
				for other in others {
					ll.assert(_isSelector(other), "symmetricDifference requires selector expressions, got: %T", other)
					selectors = append(selectors, other._getExpression(ctx))
				}
				return {
					type: "selector_symmetric_difference",
					selectors: selectors
				}
			})
		}
	}))
	return self
}





sc := ll.toStrict({






	all: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_all"}
		})
	},
	






	string: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_string"}
		})
	},
	






	numeric: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_numeric"}
		})
	},
	






	integer: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_integer"}
		})
	},
	






	float: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_float"}
		})
	},
	







	startsWith: func(prefix) {
		ll.assert(is_string(prefix), "startsWith requires a string argument, got: %T", prefix)
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_starts_with",
				prefix: prefix
			}
		})
	},
	







	endsWith: func(suffix) {
		ll.assert(is_string(suffix), "endsWith requires a string argument, got: %T", suffix)
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_ends_with",
				suffix: suffix
			}
		})
	},
	







	contains: func(substring) {
		ll.assert(is_string(substring), "contains requires a string argument, got: %T", substring)
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_contains",
				substring: substring
			}
		})
	},
	







	matches: func(pattern) {
		ll.assert(is_string(pattern), "matches requires a string argument, got: %T", pattern)
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_matches",
				pattern: pattern
			}
		})
	},
	







	exclude: func(...columns) {
		ll.assert(len(columns) > 0, "exclude requires at least one column name")
		for col in columns {
			ll.assert(is_string(col), "exclude requires string arguments, got: %T", col)
		}
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_exclude",
				columns: columns
			}
		})
	},
	







	byName: func(...names) {
		ll.assert(len(names) > 0, "byName requires at least one column name")
		for name in names {
			ll.assert(is_string(name), "byName requires string arguments, got: %T", name)
		}
		return _newSelectorExpression(func(ctx) {
			return {
				type: "selector_by_name",
				names: names
			}
		})
	},











	axis: func(matcher) {
		processedMatcher := undefined
		if is_string(matcher) {
			processedMatcher = { name: matcher, domain: {} }
		} else if is_map(matcher) {
			processedMatcher = pframesSpec.axisSpecToMatcher(matcher)
		} else {
			ll.panic("Matcher for axis must be a string (axis name) or an AxisSpec-like map (with at least 'name' and 'type' fields).")
		}
		return _newSelectorExpression(func(ctx) {
			axisSpec := _matcherToAxisSpec(ctx, processedMatcher)
			return {
				type: "selector_axis",
				spec: axisSpec
			}
		})
	},
	






	nested: func() {
		return _newSelectorExpression(func(ctx) {
			return {type: "selector_nested"}
		})
	}
})

export ll.toStrict({
	axis: axis,
	col: col,
	lit: lit,
	concatStr: concatStr,
	minHorizontal: minHorizontal,
	maxHorizontal: maxHorizontal,
	allHorizontal: allHorizontal,
	anyHorizontal: anyHorizontal,
	and: and, // Top-level alias for allHorizontal
	or: or,   // Top-level alias for anyHorizontal
	rank: rank,
	when: when,
	rawExp: rawExp,
	sc: sc,
	_newExpression: _newExpression,
	_isExpression: _isExpression,
	_isSelector: _isSelector,
	_isAggregation: _isAggregation,
	_mapToExpressionStructList: _mapToExpressionStructList,
	_mapToSingleExpressionStruct: _mapToSingleExpressionStruct
})
