import { BooleanExpression , RewriteMapping as OptimizedMapping , SimpleAttribute , SimpleTagname , isAndExpression , isBooleanExpression , isNotExpression , isOrExpression , isSimpleTagname , } from "@opticss/template-api" ; import { Maybe , ObjectDictionary , assertNever , maybe , objectValues } from "@opticss/util" ; import { inspect } from "util" ; import { Style } from "../BlockTree" ; import { ClassRewrite , IndexedClassRewrite } from "./ClassRewrite" ; export type ClassExpressionMap = ObjectDictionary < BooleanExpression < number > | undefined > ; export class IndexedClassMapping implements IndexedClassRewrite < Style > { inputs : Style [ ] ; staticClasses : string [ ] ; private map : ClassExpressionMap ; private _inputMap : Map < Style , number > ; constructor ( inputs , staticClasses , map ) { this . inputs = inputs ; this . staticClasses = staticClasses . sort ( ) ; this . _inputMap = new Map < Style , number > ( ) ; inputs . forEach ( ( i , n ) => this . _inputMap . set ( i , n ) ) ; this . map = map ; } dynamicClass ( name ) { return this . map [ name ] ; } get dynamicClasses ( ) : string [ ] { return Object . keys ( this . map ) ; } public indexOf ( input ) { let index = this . _inputMap . get ( input ) ; return maybe ( index , "internal error: style not found" ) ; } static fromOptimizer ( classRewrite , classMap ) { let indexSet = new Set < number > ( ) ; let dynClasses = classRewrite . dynamicAttributes . class ; objectValues ( dynClasses ) . forEach ( expr => indexesUsed ( indexSet , expr ! ) ) ; let usedIndexes = [ ... indexSet ] . sort ( ( a , b ) => a < b ? - 1 : 1 ) ; let adjustments = new Array < number > ( ) ; usedIndexes . reduce ( ( [ missing , last ] , n ) => { missing = missing + ( n - last - 1 ) ; adjustments [ n ] = missing ; return [ missing , n ] ; } , [ 0 , - 1 ] ) ; function renumberer ( i , n , arr ) { if ( typeof i === "number" ) { arr [ n ] = i - adjustments [ i ] ; } else { renumber ( renumberer , i ) ; } } let inputs = classRewrite . inputs . filter ( ( _ , n ) => indexSet . has ( n ) ) . map ( ( _ , n , inputs ) => processExpressionLiteral ( n , inputs , classMap ) ) ; objectValues ( classRewrite . dynamicAttributes . class ) . forEach ( expr => renumber ( renumberer , expr ! ) ) ; return new IndexedClassMapping ( inputs , classRewrite . staticAttributes . class , classRewrite . dynamicAttributes . class ) ; } } export class RewriteMapping implements ClassRewrite < Style > { /**
     * output attributes that are always on the element independent of any dynamic changes.
     */ staticClasses : string [ ] ; /**
     * The numbers in the boolean expressions represents indexes into the inputAttributes array.
     */ private _dynamicClasses : Map < string , BooleanExpression < Style > > ; constructor ( staticClasses ? , dynamicClasses ? ) { this . staticClasses = staticClasses || [ ] ; this . _dynamicClasses = dynamicClasses || new Map < string , BooleanExpression < Style > > ( ) ; } get dynamicClasses ( ) : Array < string > { return [ ... this . _dynamicClasses . keys ( ) ] ; } dynamicClass ( name ) { return this . _dynamicClasses . get ( name ) ! ; } static fromOptimizer ( classRewrite , classMap ) { let staticClasses = classRewrite . staticAttributes . class ; let dynamicClasses = classRewrite . dynamicAttributes . class ; let dynMap = new Map < string , BooleanExpression < Style > > ( ) ; if ( dynamicClasses ) { for ( let className of Object . keys ( dynamicClasses ) ) { let expression = dynamicClasses [ className ] ; if ( expression ) { dynMap . set ( className , processExpression ( expression , classRewrite . inputs , classMap ) ) ; } } } let styleRewrite = new RewriteMapping ( staticClasses || [ ] , dynMap ) ; return styleRewrite ; } } function indexesUsed ( indexes , expression ) { if ( typeof expression === "number" ) { indexes . add ( expression ) ; } else if ( isAndExpression ( expression ) ) { expression . and . forEach ( e => indexesUsed ( indexes , e ) ) ; } else if ( isOrExpression ( expression ) ) { expression . or . forEach ( e => indexesUsed ( indexes , e ) ) ; } else if ( isNotExpression ( expression ) ) { indexesUsed ( indexes , expression . not ) ; } else { assertNever ( expression ) ; } } type ExprItem = number | BooleanExpression < number > ; type Renumberer = ( i : ExprItem , n : number , arr : ExprItem [ ] ) => void ; function renumber ( renumberer , expression ) { if ( isAndExpression ( expression ) ) { expression . and . forEach ( renumberer ) ; } else if ( isOrExpression ( expression ) ) { expression . or . forEach ( renumberer ) ; } else if ( isNotExpression ( expression ) ) { let not = [ expression . not ] ; not . forEach ( renumberer ) ; expression . not = not [ 0 ] ; } else { assertNever ( expression ) ; } } function processExpression ( expression , inputs , classMap ) { if ( isAndExpression ( expression ) ) { return { and : expression . and . map ( e => isBooleanExpression ( e ) ? processExpression ( e , inputs , classMap ) : processExpressionLiteral ( e , inputs , classMap ) ) } ; } else if ( isOrExpression ( expression ) ) { return { or : expression . or . map ( e => isBooleanExpression ( e ) ? processExpression ( e , inputs , classMap ) : processExpressionLiteral ( e , inputs , classMap ) ) } ; } else if ( isNotExpression ( expression ) ) { return { not : isBooleanExpression ( expression . not ) ? processExpression ( expression . not , inputs , classMap ) : processExpressionLiteral ( expression . not , inputs , classMap ) } ; } else { return assertNever ( expression ) ; } } function processExpressionLiteral ( expression , inputs , classMap ) { let input = inputs [ expression ] ; if ( isSimpleTagname ( input ) ) { throw new Error ( "i really just can't handle tag names rn thx" ) ; } else { if ( input . name !== "class" ) { throw new Error ( `expected a class but got ${ inspect ( input ) }, you should have known better.` ) ; } if ( ! classMap . has ( input . value ) ) { throw new Error ( `wth. no class ${ input . value } exists on this element.` ) ; } return classMap . get ( input . value ) ! ; } }