
# var conv = require('../../vendor/colors')
import * as selparser from './selparse'
import {conv} from '../../vendor/colors'
import {fonts,colors,variants} from './theme.imba'
import * as theme from  './theme.imba'

const extensions = {}
let ThemeInstance = null
const ThemeCache = new WeakMap

# {string: "hsla(0,0,0,var(--alpha,1))",h:0,s:0,l:0}
# {string: "hsla(0,100%,100%,var(--alpha,1))",h:0,s:0,l:100}

# export const properties =

export const layouts =
	group: do(o)
		o.display = 'flex'
		o.jc = 'flex-start'
		o.flw = 'wrap'
		# unique variable for this?
		o['--u_sx'] = "calc(var(--u_cg,0) * 0.5)"
		o['--u_sy'] = "calc(var(--u_rg,0) * 0.5)"
		# this should be added as ultra low specificity
		o.margin = "calc(var(--u_sy) * -1) calc(var(--u_sx) * -1)"
		o["&>*"] = {margin: "var(--u_sy) var(--u_sx)"}
	
	vflex: do(o)
		o.display = 'flex'
		o.fld = 'column'
	
	hflex: do(o)
		o.display = 'flex'
		o.fld = 'row'

	hgrid: do(o)
		o.display = 'grid'
		o.gaf = 'column'
		o.gac = '1fr'
	
	vgrid: do(o)
		o.display = 'grid'
		o.gaf = 'row'

export const validTypes = {
	ease: 'linear|ease|ease-in|ease-out|ease-in-out|step-start|step-end|stepsƒ|cubic-bezierƒ'
}

for own k,v of validTypes
	let o = {}
	for item in v.split('|')
		o[item] = 1
	validTypes[k] = o

export const aliases =
	
	c: 'color'
	d: 'display'
	pos: 'position'

	# padding
	p: 'padding'
	pl: 'padding-left'
	pr: 'padding-right'
	pt: 'padding-top'
	pb: 'padding-bottom'
	px: ['pl','pr']
	py: ['pt','pb']
	
	# margins
	m: 'margin'
	ml: 'margin-left'
	mr: 'margin-right'
	mt: 'margin-top'
	mb: 'margin-bottom'
	mx: ['ml','mr']
	my: ['mt','mb']
	
	# add scroll snap shorthands?
	
	w: 'width'
	h: 'height'
	t: 'top'
	b: 'bottom'
	l: 'left'
	r: 'right'
	size: ['width','height']
	
	# justify
	ji: 'justify-items'
	jc: 'justify-content'
	js: 'justify-self'
	j: ['justify-content','justify-items']
	
	# align
	ai: 'align-items'
	ac: 'align-content'
	as: 'align-self'
	a: ['align-content','align-items']

	# justify & align
	jai: ['justify-items','align-items']
	jac: ['justify-content','align-content']
	jas: ['justify-self','align-self']
	ja: ['justify-content','align-content','justify-items','align-items']
	
	# place
	# pi: 'place-items'
	# pc: 'place-content'
	# ps: 'place-self'

	# flex
	fl: 'flex'
	flf: 'flex-flow'
	fld: 'flex-direction'
	flb: 'flex-basis'
	flg: 'flex-grow'
	fls: 'flex-shrink'
	flw: 'flex-wrap'
	
	# fonts
	ff: 'font-family'
	fs: 'font-size'
	fw: 'font-weight'
	ts: 'text-shadow'
	
	# text-decoration
	td: 'text-decoration'
	tdl: 'text-decoration-line'
	tdc: 'text-decoration-color'
	tds: 'text-decoration-style'
	tdt: 'text-decoration-thickness'
	tdsi: 'text-decoration-skip-ink'
	
	# text-emphasis
	te: 'text-emphasis'
	tec: 'text-emphasis-color'
	tes: 'text-emphasis-style'
	tep: 'text-emphasis-position'
	tet: 'text-emphasis-thickness'
		
	# text
	tt: 'text-transform'
	ta: 'text-align'
	va: 'vertical-align'
	ls: 'letter-spacing'
	lh: 'line-height'

	# border
	bd: 'border'
	bdr: 'border-right'
	bdl: 'border-left'
	bdt: 'border-top'
	bdb: 'border-bottom'

	# border-style
	bs: 'border-style'
	bsr: 'border-right-style'
	bsl: 'border-left-style'
	bst: 'border-top-style'
	bsb: 'border-bottom-style'

	# border-width
	bw: 'border-width'
	bwr: 'border-right-width'
	bwl: 'border-left-width'
	bwt: 'border-top-width'
	bwb: 'border-bottom-width'

	# border-color
	bc: 'border-color'
	bcr: 'border-right-color'
	bcl: 'border-left-color'
	bct: 'border-top-color'
	bcb: 'border-bottom-color'

	# border-radius
	rd: 'border-radius'
	rdtl: 'border-top-left-radius'
	rdtr: 'border-top-right-radius'
	rdbl: 'border-bottom-left-radius'
	rdbr: 'border-bottom-right-radius'
	rdt: ['border-top-left-radius','border-top-right-radius']
	rdb: ['border-bottom-left-radius','border-bottom-right-radius']
	rdl: ['border-top-left-radius','border-bottom-left-radius']
	rdr: ['border-top-right-radius','border-bottom-right-radius']
	
	# background
	bg: 'background'
	bgp: 'background-position'
	bgc: 'background-color'
	bgr: 'background-repeat'
	bgi: 'background-image'
	bga: 'background-attachment'
	bgs: 'background-size'
	bgo: 'background-origin'
	bgclip: 'background-clip'
	
	# grid
	g: 'gap'
	rg: 'row-gap'
	cg: 'column-gap'
	gtr: 'grid-template-rows'
	gtc: 'grid-template-columns'
	gta: 'grid-template-areas'
	gar: 'grid-auto-rows'
	gac: 'grid-auto-columns'
	gaf: 'grid-auto-flow'
	gcg: 'grid-column-gap'
	grg: 'grid-row-gap'
	ga: 'grid-area'
	gr: 'grid-row'
	gc: 'grid-column'
	gt: 'grid-template'
	grs: 'grid-row-start'
	gcs: 'grid-column-start'
	gre: 'grid-row-end'
	gce: 'grid-column-end'
	
	# shadow
	bxs: 'box-shadow'
	shadow: 'box-shadow' # DEPRECATED
	
	# overflow
	'of':'overflow'
	'ofx':'overflow-x'
	'ofy':'overflow-y'
	'ofa':'overflow-anchor'
	
	# content
	prefix: 'content@before'
	suffix: 'content@after'
	
	# transforms
	x: 'x'
	y: 'y'
	z: 'z'
	rotate: 'rotate'
	scale: 'scale'
	'scale-x': 'scale-x'
	'scale-y': 'scale-y'
	'skew-x': 'skew-x'
	'skew-y': 'skew-y'
	origin: 'transform-origin'
	
	# others
	ws: 'white-space'
	zi: 'z-index'
	pe: 'pointer-events'
	us: 'user-select'
	o: 'opacity'
	tween: 'transition'
	
	# easing
	e: 'ease'
	eo: 'ease-opacity'
	et: 'ease-transform'
	ec: 'ease-colors'

export const abbreviations = {}
for own k,v of aliases
	if typeof v == 'string'
		abbreviations[v] = k

def isNumber val
	if val._value and val._value._type == "NUMBER" and !val._unit
		return true
	return false

export class Color
	
	def constructor name,h,s,l,a = 1
		name = name
		h = h
		s = s
		l = l
		a = a
		
	def alpha a = 1
		new Color(name,h,s,l,a)

	def clone
		new Color(name,h,s,l,a)

	def mix other, hw = 0.5, sw = 0.5, lw = 0.5
		let	h1 = h + (other.h - h) * hw
		let	s1 = s + (other.s - s) * sw
		let	l1 = l + (other.l - l) * lw
		return new Color(name + other.name,h1,s1,l1)
	
	def toString a = a
		# if typeof a == 'string' and a.match(/%$/)
		#	a = parseFloat(a.slice(0,-1)) / 100
		if typeof a == 'string' and a[0] == '$'
			a = "var(--{a.slice(1)},100%)"
		"hsla({h.toFixed(2)},{s.toFixed(2)}%,{l.toFixed(2)}%,{a})"

	def toVar round = 2
		"{Math.round(h)},{Math.round(s)}%,{Math.round(l)}%"
		# "{h.toFixed(2)},{s.toFixed(2)}%,{l.toFixed(2)}%"

	def c
		toString!


export class Tint < Color

	def alpha a = 1
		new Tint(name,h,s,l,a)

	def clone
		new Tint(name,h,s,l,a)

	def toString a = a
		if typeof a == 'string' and a[0] == '$'
			a = "var(--{a.slice(1)},100%)"

		"hsla(var(--{name}),{a})"

export class Length
	
	static def parse value
		let m = String(value).match(/^(\-?[\d\.]+)(\w+|%)?$/)
		return null unless m
		return new self(parseFloat(m[1]),m[2])
	
	def constructor number, unit
		number = number
		unit = unit
	
	def valueOf
		number
		
	def toString
		number + (unit or '')
		
	def clone num = number, u = unit
		new Length(num,u)
		
	def rounded
		clone(Math.round(number))
		
	def c
		toString!
	
	get _unit
		unit
	
	get _number
		number

export class Var
	
	def constructor name, fallback
		name = name
		fallback = fallback
		
	def c
		fallback ? "var(--{name},{fallback.c ? fallback.c! : String(fallback)})" : "var(--{name})"

export class Calc
	
	def constructor expr
		expr = expr
		
	def cpart parts
		let out = '('
		for part in parts
			if typeof part == 'string'
				out += ' ' + part + ' '
			elif typeof part == 'number'
				out += part
			elif part.c isa Function
				out += part.c!
			elif part isa Array
				out += cpart(part)
			
		out += ')'
		return out
	
	def c
		'calc' + cpart(expr)
		

# This has to move into StyleTheme class
let defaultPalette = {
	# should deprecate
	current: {string: "currentColor", c: do 'currentColor' }
	transparent: new Color('transparent',0,0,100,'0%')
	clear: new Color('transparent',100,100,100,'0%')
	black: new Color('black',0,0,0,'100%')
	white: new Color('white',0,0,100,'100%')
}

def parseColorString str
	if let m = str.match(/hsl\((\d+), *(\d+\%), *(\d+\%?)/)
		let h = parseInt(m[1])
		let s = parseInt(m[2])
		let l = parseInt(m[3])
		return [h,s,l]
	elif str[0] == '#'
		return conv.rgb.hsl(conv.hex.rgb(str))


def parseColors palette, colors
	for own name,variations of colors
		if typeof variations == 'string'
			palette[name] = variations
			continue

		for own subname,raw of variations
			let path = name + subname

			if palette[raw]
				palette[path] = palette[raw]
			else
				let [h,s,l] = parseColorString(raw)
				let color = palette[path] = new Color(path,h,s,l,'100%')
	return palette

parseColors(defaultPalette,colors)

const VALID_CSS_UNITS = 'cm mm Q in pc pt px em ex ch rem vw vh vmin vmax % s ms fr deg rad grad turn Hz kHz'.split(' ')

export class StyleTheme
	
	static def instance
		ThemeInstance ||= new self
		
	static def propAbbr name
		abbreviations[name] or name

	static def wrap config
		return self.instance() unless config

		let theme = ThemeCache.get(config)
		ThemeCache.set(config,theme = new self(config)) unless theme
		return theme
		
	def constructor ext = {}
		options = theme
		palette = Object.assign({},defaultPalette)
		
		ext = ext.theme if ext.theme

		if ext and ext.colors
			parseColors(palette,ext.colors)

	def expandProperty name
		return aliases[name] or undefined
		
	def expandValue value, config
	
		if value == undefined
			value = config.default

		if config.hasOwnProperty(value)
			value = config[value]

		if typeof value == 'number' and config.NUMBER
			let [step,num,unit] = config.NUMBER.match(/^(\-?[\d\.]+)(\w+|%)?$/)
			return value * parseFloat(num) + unit

		return value
	
	def padding_x [l,r=l]
		{'padding-left': l, 'padding-right': r}
	
	def padding_y [t,b=t]
		{'padding-top': t, 'padding-bottom': b}
		
	def margin_x [l,r=l]
		{'margin-left': l, 'margin-right': r}
	
	def margin_y [t,b=t]
		{'margin-top': t, 'margin-bottom': b}
		
	def ease pars
		$ease(pars,'')
		
	def ease_opacity pars
		$ease(pars,'o')
		
	def ease_transform pars
		$ease(pars,'t')
		
	def ease_colors pars
		$ease(pars,'c')
		
	def $ease pars, k = ''
		pars = pars.slice(0)
		let o = {__ease__: ''}
		let durRegex = /^[\-\+]?(\d*\.)?(\d+)(s|ms)?$/
		if String(pars[0]).match(durRegex)
			o["--e_d{k}"] = pars[0]
			pars.shift!
			
	
		if pars[0] and !String(pars[0]).match(durRegex)
			let ev = $varFallback('ease',[pars[0]])
			o["--e_f{k}"] = ev
			pars.shift!
	
		if String(pars[0]).match(durRegex)
			o["--e_w{k}"] = pars[0]
			pars.shift!
		
			
		return o

		
	def inset [t,r=t,b=t,l=r]
		{position: 'absolute', top: t, right: r, bottom: b, left: l}
		
	def size [w,h=w]
		{width: w, height: h}
		
	def grid params
		if let m = $varFallback('grid',params)
			return m
		return

	def animation ...params
		
		let valids = {
			normal:1,reverse:1,alternate:1,'alternate-reverse':1
			infinite:2
			none:3,forwards:3,backwards:3,both:3
			running:4,paused:4
		}
		let used = {}
		for anim,k in params
			let name = null
			let ease = null
			for part,i in anim
				let str = String(part)
				let typ = valids[str]

				if validTypes.ease[str] and !ease
					ease = yes
				elif typ
					if used[typ]
						name = [i,str]
					used[typ] = yes
				elif str.match(/^[^\d\.]/) and str.indexOf('(') == -1
					if name
						ease = [i,str]
					else
						name = [i,str]
			if name
				anim[name[0]] = new Var("animation-{name[1]}",name[1])
			if ease isa Array
				let fallback = options.variants.easings[ease[1]]
				anim[ease[0]] = new Var("ease-{ease[1]}",fallback)

		return {animation: params}

	def animation_timing_function ...params
		for param,i in params
			let fb = $varFallback('ease',param)
			params[i] = fb if fb
		return params

	def animation_name ...params
		for param,i in params
			let fb = $varFallback('animation',param)
			if fb
				# param[0] = fb[0]
				params[i] = fb
			# params[i] = $varFallback('animation',param)
		return params

		if let m = $varFallback('animation',params)
			return m
		return
		
	def display params
		let out = {display: params}
		for par in params
			if let layout = layouts[String(par)]
				layout.call(this,out,par,params)
		return out

	def position params
		let out = {position: params}
		let str = String(params[0])
		if str == 'abs'
			out.position = 'absolute'
		elif str == 'rel'
			out.position = 'relative'
		return out
		
		
	def width [...params]
		let o = {}
		for param in params
			let opts = param._options or {}
			let u = param._unit
			if u == 'c' or u == 'col' or u == 'cols'
				o['grid-column-end'] = "span {param._number}"
			elif opts.op and String(opts.op) == '>'
				o['min-width'] = param
			elif opts.op and String(opts.op) == '<'
				o['max-width'] = param
			else
				o.width = param
		return o
		
	def height [...params]
		let o = {}
		for param in params
			let opts = param._options or {}
			let u = param._unit
			if u == 'r' or u == 'row' or u == 'rows'
				o['grid-row-end'] = "span {param._number}"
			elif opts.op and String(opts.op) == '>'
				o['min-height'] = param
			elif opts.op and String(opts.op) == '<'
				o['max-height'] = param
			else
				o.height = param
		return o

	def transition ...parts
		let out = {}
		let add = {}

		let signatures = [
			'name | duration'
			'name | duration | delay'
			'name | duration | ease'
			'name | duration | ease | delay'
		]
		
		let groups = {
			styles: ['background-color','border-color','color','fill','stroke','opacity','box-shadow','transform']
			sizes: ['width','height','left','top','right','bottom','margin','padding']
			colors: ['background-color','border-color','color','fill','stroke']
		}
		
		let i = 0
		while i < parts.length
			let part = parts[i]
			let name = String(part[0])
			if name.match(/^[\-\+]?\d?(\.?\d+)(s|ms)?$/)
				part.unshift(name = 'styles')
				
			let ease = part[2]
			let group = groups[name]
			
			if group and parts.length == 1
				part[0] = 'none'
				Object.assign(add,{'transition-property': group.join(',')})
			elif group and parts.length > 1
				# TODO we could do a more advanced version where we 
				# create repeating transition-property and duration etc and seam
				# the pairs together
				let subparts = group.map do [$1].concat(part.slice(1))
				parts.splice(i,1,...subparts)
				continue
			i++

		Object.assign(out,{'transition': parts},add)
		return out
		
	def font params,...rest
		for param,i in params
			yes
		return
		
	def font_family params
		if let m = $varFallback('font',params)
			return m
		return
		
	def text_shadow params
		if let m = $varFallback('text-shadow',params)
			return m
		return

	def grid_template params
		for param,i in params
			if isNumber(param)
				param._resolvedValue = "repeat({param._value},1fr)"
		return

	def grid_template_columns params
		grid_template(params)

	def grid_template_rows params
		grid_template(params)

	def font_size [v]
		let sizes = options.variants['font-size']
		let raw = String(v)
		let size = v
		let lh
		let out = {}
		
		if sizes[raw]
			[size,lh] = sizes[raw]
			size = Length.parse(size)
			lh = Length.parse(lh or '')
		
		if v.param and v.param
			lh = v.param
			
		out['font-size'] = size
		
		if lh
			let lhu = lh._unit
			let lhn = lh._number
			out.lh = lh
			# supprt base unit as well?
			if lhu == 'fs'
				out.lh = new Length(lhn)
			elif lhu
				out.lh = lh
			elif lhn == 0
				out.lh = 'inherit'
			elif lhn and size._unit == 'px'
				let rounded = Math.round(size._number * lhn)
				if rounded % 2 == 1
					rounded++
				out.lh = new Length(rounded,'px')
		
		return out
		
	def line_height [v]
		let uvar = v
		# TODO what if it has u unit?
		if v._number and !v._unit
			uvar = v.clone(v._number,'em')
			
		return {
			'line-height': v
			'--u_lh': uvar
		}
		
	def text_decoration params
		for param,i in params
			let str = String(param)
			if str == 'u'
				param._resolvedValue = 'underline'
			elif str == 's'
				param._resolvedValue = 'line-through'
			
		return [params]

	# TODO allow setting border style and color w/o width?
	# TODO allow size hidden etc?
	def border [...params]
		if params.length == 1 and $parseColor(params[0])
			return [['1px','solid',params[0]]]
		return

	def border_left params
		return border(params)
		
	def border_right params
		return border(params)
	
	def border_top params
		return border(params)
		
	def border_bottom params
		return border(params)
		
	def border_x params
		{'border-left': border(params) or params, 'border-right': border(params) or params}
		
	def border_y params
		{'border-top': border(params) or params, 'border-bottom': border(params) or params}
		
	def border_x_width [l,r=l]
		{blw: l, brw: r}
		
	def border_y_width [t,b=t]
		{btw: t, bbw: b}
		
	def border_x_style [l,r=l]
		{bls: l, brs: r}
		
	def border_y_style [t,b=t]
		{bts: t, bbs: b}
	
	def border_x_color [l,r=l]
		{blc: l, brc: r}
		
	def border_y_color [t,b=t]
		{btc: t, bbc: b}
	
	def gap [v]
		{'gap': v, '--u_rg': v,'--u_cg': v}
			
	def row_gap [v]
		{'row-gap': v, '--u_rg': v}

	def column_gap [v]
		{'column-gap': v, '--u_cg': v}

	def tint [v]
		
		let o = {'--hue': v}
		for i in [0 ... 10]
			o["--hue{i}"] = "/*##*/{v}{i}"
			# new Tint("v{i}")
		return o
		
	def hue [v]
		let o = {'--hue': v}
		for i in [0 ... 10]
			o["--hue{i}"] = "/*##*/{v}{i}"
			# new Tint("v{i}")
		return o

	# def shadow ...params
	#	{}

	def $color name
		let m = name.match(/^(\w+)(\d)(?:\-(\d+))?$/)
		let ns = m and m[1]
		
		# aliased colors
		if ns and typeof palette[ns] == 'string'
			return $color(palette[ns] + name.slice(ns.length))

		if ns == 'tint'
			let newname = "hue" + name.slice(4)
			# TODO show as a compiler warning instead?
			console.warn "{name} renamed to {newname}"
			return new Tint(newname)
			
		if ns == 'hue'
			return new Tint(name)

		if palette[name]
			return palette[name]

		if m
			let nr = parseInt(m[2])
			let fraction = parseInt(m[3]) or 0
			let from = null
			let to = null
			let n0 = nr + 1
			let n1 = nr
			
			if typeof palette[ns] == 'string'
				# proxy to a different color
				return $color(palette[ns] + name.slice(ns.length))

			while n0 > 1 and !from
				from = palette[ns + (--n0)]

			while n1 < 9 and !to
				to = palette[ns + (++n1)]

			let weight = ((nr - n0) + (fraction / 10)) / (n1 - n0)
			let hw = weight
			let sw = weight
			let lw = weight

			if !to
				to = palette.blue9
				hw = 0
			
			if !from
				from = palette.blue1
				hw = 1

			if from and to
				return palette[name] = from.mix(to,hw,sw,lw)
		null

	def $parseColor identifier
		let key = String(identifier)
		if let color = $color(key)
			return color

		if key.match(/^#[a-fA-F0-9]{3,8}/)
			return identifier
			
		elif key.match(/^(rgb|hsl)/)
			return identifier
		
		elif key == 'currentColor'
			return identifier

		return null
		
	def $varFallback name, params, exclude = []
		if params.length == 1
			let str = String(params[0])
			let fallback = params[0]
			exclude.push('none','initial','unset','inherit')
			if exclude.indexOf(str) == -1 and str.match(/^[\w\-]+$/)
				if name == 'font' and fonts[str]
					fallback = fonts[str]
				if name == 'ease' and options.variants.easings[str]
					fallback = options.variants.easings[str]
				# elif name == 'box-shadow' and 
				return [new Var("{name}-{str}",fallback)]
		return
	

	def $value value, index, config
		let key = config
		let orig = value
		let raw = value && value.toRaw ? value.toRaw! : String(value)
		let str = String(value)
		let fallback = no
		let result = null
		let unit = orig._unit
		# console.log 'resolve value',raw
		if typeof config == 'string'
			if aliases[config]
				config = aliases[config]
				
				if config isa Array
					config = config[0]

			if config.match(/^((min-|max-)?(width|height)|top|left|bottom|right|padding|margin|sizing|inset|spacing|sy$|s$|\-\-s[xy])/)
				config = 'sizing'
			elif config.match(/^\-\-[gs][xy]_/)
				config = 'sizing'
			elif config.match(/^(row-|column-)?gap/)
				config = 'sizing'
			elif config.match(/^[mps][trblxy]?$/)
				config = 'sizing'
			elif config.match(/^[trblwh]$/)
				config = 'sizing'
			elif config.match(/^border-.*radius/) or config.match(/^rd[tlbr]{0,2}$/)
				config = 'radius'
				fallback = 'border-radius'
			elif config.match(/^box-shadow/)
				fallback = config = 'box-shadow'
			elif config.match(/^tween|transition/) and options.variants.easings[raw]
				return options.variants.easings[raw]

			config = options.variants[config] or {}
		
		if value == undefined
			value = config.default
		
		if config.hasOwnProperty(raw)
			# should we convert it or rather just link it up?
			value = config[value]
			
		if typeof raw == 'number' and config.NUMBER
			let [step,num,unit] = config.NUMBER.match(/^(\-?[\d\.]+)(\w+|%)?$/)
			return value * parseFloat(num) + unit
		
		elif typeof raw == 'string'
			if let color = $parseColor(raw)
				return color
		
		if fallback
			let okstr = str.match(/^[a-zA-Z\-][\w\-]*$/) and !str.match(/^(none|inherit|unset|initial)$/)
			let oknum = unit and VALID_CSS_UNITS.indexOf(unit) == -1
			if (okstr or oknum) and value.alone
				return new Var("{fallback}-{str}",orig != value ? value : raw)
			
		return value
		
	def transformColors text, {prefix}
		text = text.replace(/\/\*(##?)\*\/(\w+)(?:\/(\d+%?|\$[\w\-]+))?/g) do(m,typ,c,a)
			# console.log "transforming color {m}"

			if let color = $color(c)
				# Need to work around a bug with esbuild css parsing (https://github.com/evanw/esbuild/issues/1421)
				# Was fixed in 0.12.15 so we can remove the prefixing when we upgrade esbuild
				return typ == '##' ? "{color.toVar(a)}" : "{prefix ? 'PREFIX' : ''}{color.toString(a)}"
			return m
		return text
		
# should not happen at root - but create a theme instance

export const StyleExtenders = {
	transform: '''
		--t_x:0;--t_y:0;--t_z:0;--t_rotate:0;
		--t_scale:1;--t_scale-x:1;--t_scale-y:1;
		--t_skew-x:0;--t_skew-y:0;
		transform: translate3d(var(--t_x),var(--t_y),var(--t_z))
		           rotate(var(--t_rotate))
		           skewX(var(--t_skew-x)) skewY(var(--t_skew-y)) 
		           scaleX(var(--t_scale-x)) scaleY(var(--t_scale-y)) scale(var(--t_scale));
	'''

	transition: '''
		transition:      all var(--e_d) var(--e_f) var(--e_w),
			       transform var(--e_dt,var(--e_d)) var(--e_ft,var(--e_f)) var(--e_wt,var(--e_w)),
			           color var(--e_dc,var(--e_d)) var(--e_fc,var(--e_f)) var(--e_wc,var(--e_w)),
			background-color var(--e_dc,var(--e_d)) var(--e_fc,var(--e_f)) var(--e_wc,var(--e_w)),
			         opacity var(--e_do,var(--e_d)) var(--e_fo,var(--e_f)) var(--e_wo,var(--e_w));
	'''
}
	
export const TransformMixin = '''
	--t_x:0;--t_y:0;--t_z:0;--t_rotate:0;--t_scale:1;--t_scale-x:1;--t_scale-y:1;--t_skew-x:0;--t_skew-y:0;
	transform: translate3d(var(--t_x),var(--t_y),var(--t_z)) rotate(var(--t_rotate)) skewX(var(--t_skew-x)) skewY(var(--t_skew-y)) scaleX(var(--t_scale-x)) scaleY(var(--t_scale-y)) scale(var(--t_scale));
'''

export class StyleSheet
	def constructor stack
		#stack = stack
		#parts = []
		#apply = {}
		#register = {}
		transforms = null

	get transitions
		#register.transition
		
	def add part, meta = {}
		#parts.push(part)
		
		if meta.apply
			for own k,v of meta.apply
				let arr = #apply[k] ||= []
				for item in v
					arr.push(item) unless arr.indexOf(item) >= 0
		return
		
	def js root, stack
		let js = []
		
		# if transitions
		# 	js.push root.runtime!.transitions + ".addSelectors({JSON.stringify(transitions)})"
		for own k,v of #register
			js.push root.runtime!.transitions + ".addSelectors({JSON.stringify(v)},'{k}')"
		return js.join('\n')
	
	def parse
		return #string if #string
		
		let js = []
		let parts = #parts.slice(0)
		
		for own k,v of #apply
			let helper = StyleExtenders[k]
			
			let base = {}
			let all = {}
			let groups = {"": base}
			let easing = k == 'transition' or k.match(/^_(off|out|in)_sized/)

			for item in v
				for rule in item.#rules
					# console.log rule
					let ns = rule.#media
					let sel = rule.#string.replace(/:not\((#_|\._0?)+\)/g,'')
					if easing
						sel = sel.replace(/\._(off|out|in|on)_\b/g,'')

					let group = groups[ns] ||= {}
					group[sel] = rule
					all[sel] = yes
			
			# console.log 'groups',groups
			if helper
				for own ns,group of groups
					let sel = Object.keys(group)
					if ns != ''
						sel = sel.filter do !base[$1]
					
					continue if sel.length == 0
					sel.unshift('._ease_') if k == 'transition'
					let str = sel.join(', ') + ' {\n' + helper + '\n}'
					
					if ns
						str = ns + ' {\n' + str + '\n}'
						
					
					parts.unshift(str)
			
			let selectors = Object.keys(all)
			if k == 'transition' and selectors.length
				# transitions = #register.transition = selectors
				parts.unshift('._easing_ {--e_d:300ms;}')
				parts.unshift('._instant_ { transition-duration:0ms !important; }')				
				parts.unshift(':root {--e_d:0ms;--e_f:ease-in-out;--e_w:0ms}')
			if easing
				#register[k] = selectors
			if k == 'ease' and selectors.length
				parts.unshift(':root {--e_d:0ms;--e_f:ease-in-out;--e_w:0ms}')
		
		#string = parts.join('\n\n')

		if #stack.resolveColors!
			#string = #stack.theme!.transformColors(#string, prefix: false)
		
		return #string
		
	def toString
		parse!

export class StyleRule
	
	def constructor parent,selector,content,options = {}
		parent = parent
		selector = selector
		content = content
		options = options
		isKeyFrames = !!selector.match(/\@keyframes \w/)
		isKeyFrame = parent and parent.isKeyFrames
		meta = {}
		
	def root
		parent ? parent.root : self
		
	def apply kind,sel
		let arr = options.apply[kind] ||= []
		arr.push(sel)

	def register kind,sel
		let arr = options.register[kind] ||= []
		arr.push(sel)
		
	def toString o = {}
		let parts = []
		let subrules = []

		if isKeyFrames
			let [context,name] = selector.split(/\s*\@keyframes\s*/)

			context = context.trim!
			name = name.trim!

			let path = [name,context,options.ns].filter(do $1).join('-')
			# what if it is global?
			meta.name = name
			meta.uniqueName = path.replace(/[\s\.\,]+/g,'').replace(/[^\w\-]/g,'_')

			if options.global and !context
				meta.uniqueName = meta.name

			let subprops = {}
			subprops["--animation-{name}"] = "{meta.uniqueName}"

			if context
				subrules.push new StyleRule(null,context,subprops,options)
			elif options.ns and !options.global
				subrules.push new StyleRule(null,".{options.ns}",subprops,{})

		for own key,value of self.content
			continue if value == undefined
			
			let subsel = null
			
			if key.indexOf('&') >= 0
				if isKeyFrames
					let keyframe = key.replace(/&/g,'')
					let rule = new StyleRule(self,keyframe,value,options)
					parts.push(rule.toString(indent: yes))
					continue

				let subsel = selparser.unwrap(selector,key)
				subrules.push new StyleRule(self,subsel,value,options)
				continue
			
			elif key.indexOf('§') >= 0
				# let keys = key.replace(/[\.\~\@\+]/g,'\\$&').split('§')
				let keys = key.split('§')
				let subsel = selparser.unwrap(selector,keys.slice(1).join(' '))
				let obj = {}
				obj[keys[0]] = value
				subrules.push new StyleRule(self,subsel,obj,options)
				continue
			
			elif key[0] == '['
				# better to just check if key contains '.'
				# this is only for a single property
				console.warn "DEPRECATED",key,self
				let o = JSON.parse(key)
				subrules.push new StyleRule(self,selector,value,options)
				continue

			elif key.match(/^(x|y|z|scale|scale-x|scale-y|skew-x|skew-y|rotate)$/)
				unless meta.transform
					meta.transform = yes
					# parts.unshift(TransformMixin)
				parts.push "--t_{key}: {value} !important;"
			elif key.match(/^__ease__$/)
				meta.ease = yes
				# meta.transform = yes
				# parts.unshift(TransformMixin)
				# parts.push "--t_{key}: {value} !important;"
			else
				if key.match(/^(width|height)$/)
					meta.size = yes

				parts.push "{key}: {value};"
		
		let content = parts.join('\n')
		let out = ""
		if o.indent or isKeyFrames
			content = '\n' + content + '\n'

		if isKeyFrame
			out = "{selector} \{{content}\}"

		elif isKeyFrames
			out = "@keyframes {meta.uniqueName} \{{content}\}"
		else
			let sel = isKeyFrame ? selector : selparser.parse(selector,options)
			if meta.transform
				apply('transform',sel)
			if meta.ease
				apply('ease',sel)
			

			if sel and sel.hasTransitionStyles
				# console.log 'has transitions!!'
				apply('transition',sel)
			
			if meta.size
				for typ in ['_off_','_out_','_in_']
					if sel[typ]
						# console.log 'SIZE AND TWEEN!',sel
						apply("{typ}sized",sel)

			out = content.match(/[^\n\s]/) ? selparser.render(sel,content,options) : ""

		for own subrule in subrules
			out += '\n' + subrule.toString()

		return out
