import {
atRule,
decl,
rule,
walk,
WalkAction,
type AstNode,
type Rule,
type StyleRule,
} from './ast'
import { type Candidate, type Variant } from './candidate'
import { substituteFunctions } from './css-functions'
import { type DesignSystem } from './design-system'
import GLOBAL_PROPERTY_ORDER from './property-order'
import { asColor, type Utility } from './utilities'
import { compare } from './utils/compare'
import { escape } from './utils/escape'
import type { Variants } from './variants'
export function compileCandidates(
rawCandidates: Iterable<string>,
designSystem: DesignSystem,
{ onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {},
) {
let nodeSorting = new Map<
AstNode,
{ properties: number[]; variants: bigint; candidate: string }
>()
let astNodes: AstNode[] = []
let matches = new Map<string, Candidate[]>()
for (let rawCandidate of rawCandidates) {
if (designSystem.invalidCandidates.has(rawCandidate)) {
onInvalidCandidate?.(rawCandidate)
continue
}
let candidates = designSystem.parseCandidate(rawCandidate)
if (candidates.length === 0) {
onInvalidCandidate?.(rawCandidate)
continue
}
matches.set(rawCandidate, candidates)
}
let variantOrderMap = designSystem.getVariantOrder()
for (let [rawCandidate, candidates] of matches) {
let found = false
for (let candidate of candidates) {
let rules = designSystem.compileAstNodes(candidate)
if (rules.length === 0) continue
try {
substituteFunctions(
rules.map(({ node }) => node),
designSystem.resolveThemeValue,
)
} catch (err) {
continue
}
found = true
for (let { node, propertySort } of rules) {
let variantOrder = 0n
for (let variant of candidate.variants) {
variantOrder |= 1n << BigInt(variantOrderMap.get(variant)!)
}
nodeSorting.set(node, {
properties: propertySort,
variants: variantOrder,
candidate: rawCandidate,
})
astNodes.push(node)
}
}
if (!found) {
onInvalidCandidate?.(rawCandidate)
}
}
astNodes.sort((a, z) => {
let aSorting = nodeSorting.get(a)!
let zSorting = nodeSorting.get(z)!
if (aSorting.variants - zSorting.variants !== 0n) {
return Number(aSorting.variants - zSorting.variants)
}
let offset = 0
while (
aSorting.properties.length < offset &&
zSorting.properties.length < offset &&
aSorting.properties[offset] === zSorting.properties[offset]
) {
offset += 1
}
return (
(aSorting.properties[offset] ?? Infinity) - (zSorting.properties[offset] ?? Infinity) ||
zSorting.properties.length - aSorting.properties.length ||
compare(aSorting.candidate, zSorting.candidate)
)
})
return {
astNodes,
nodeSorting,
}
}
export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) {
let asts = compileBaseUtility(candidate, designSystem)
if (asts.length === 0) return []
let rules: {
node: AstNode
propertySort: number[]
}[] = []
let selector = `.${escape(candidate.raw)}`
for (let nodes of asts) {
let propertySort = getPropertySort(nodes)
if (candidate.important || designSystem.important) {
applyImportant(nodes)
}
let node: StyleRule = {
kind: 'rule',
selector,
nodes,
}
for (let variant of candidate.variants) {
let result = applyVariant(node, variant, designSystem.variants)
if (result === null) return []
}
rules.push({
node,
propertySort,
})
}
return rules
}
export function applyVariant(
node: Rule,
variant: Variant,
variants: Variants,
depth: number = 0,
): null | void {
if (variant.kind === 'arbitrary') {
if (variant.relative && depth === 0) return null
node.nodes = [rule(variant.selector, node.nodes)]
return
}
let { applyFn } = variants.get(variant.root)!
if (variant.kind === 'compound') {
let isolatedNode = atRule('@slot')
let result = applyVariant(isolatedNode, variant.variant, variants, depth + 1)
if (result === null) return null
for (let child of isolatedNode.nodes) {
if (child.kind !== 'rule' && child.kind !== 'at-rule') return null
let result = applyFn(child, variant)
if (result === null) return null
}
{
walk(isolatedNode.nodes, (child) => {
if ((child.kind === 'rule' || child.kind === 'at-rule') && child.nodes.length <= 0) {
child.nodes = node.nodes
return WalkAction.Skip
}
})
node.nodes = isolatedNode.nodes
}
return
}
let result = applyFn(node, variant)
if (result === null) return null
}
function isFallbackUtility(utility: Utility) {
let types = utility.options?.types ?? []
return types.length > 1 && types.includes('any')
}
function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) {
if (candidate.kind === 'arbitrary') {
let value: string | null = candidate.value
if (candidate.modifier) {
value = asColor(value, candidate.modifier)
}
if (value === null) return []
return [[decl(candidate.property, value)]]
}
let utilities = designSystem.utilities.get(candidate.root) ?? []
let asts: AstNode[][] = []
let normalUtilities = utilities.filter((u) => !isFallbackUtility(u))
for (let utility of normalUtilities) {
if (utility.kind !== candidate.kind) continue
let compiledNodes = utility.compileFn(candidate)
if (compiledNodes === undefined) continue
if (compiledNodes === null) return asts
asts.push(compiledNodes)
}
if (asts.length > 0) return asts
let fallbackUtilities = utilities.filter((u) => isFallbackUtility(u))
for (let utility of fallbackUtilities) {
if (utility.kind !== candidate.kind) continue
let compiledNodes = utility.compileFn(candidate)
if (compiledNodes === undefined) continue
if (compiledNodes === null) return asts
asts.push(compiledNodes)
}
return asts
}
function applyImportant(ast: AstNode[]): void {
for (let node of ast) {
if (node.kind === 'at-root') {
continue
}
if (node.kind === 'declaration') {
node.important = true
} else if (node.kind === 'rule' || node.kind === 'at-rule') {
applyImportant(node.nodes)
}
}
}
function getPropertySort(nodes: AstNode[]) {
let propertySort = new Set<number>()
let q: AstNode[] = nodes.slice()
while (q.length > 0) {
let node = q.shift()!
if (node.kind === 'declaration') {
if (node.property === '--tw-sort') {
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.value ?? '')
if (idx !== -1) {
propertySort.add(idx)
break
}
}
let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.property)
if (idx !== -1) propertySort.add(idx)
} else if (node.kind === 'rule' || node.kind === 'at-rule') {
for (let child of node.nodes) {
q.push(child)
}
}
}
return Array.from(propertySort).sort((a, z) => a - z)
}