import { QuantumElement , QuantumElementCreationOptions , QuantumElementType } from '../document-element' ; import { ref , Ref , reactive , shallowRef , watch , watchEffect , computed , ComputedRef , toRaw } from 'vue' ; import { Vector2 } from '../../vectors' ; import arrayUtils from '../../array-utils' ; import { assert } from '../../assert' ; import { watchImmediate } from '../../reactivity-utils' ; import type { Expression } from '@cortex-js/compute-engine' ; export const ElementType = 'scope-element' ; export class ScopeElement extends QuantumElement { typeName : typeof ElementType = ElementType ; /**
     * Name of the scope, used for named imports
     */ readonly name : Ref < string > = ref ( '' ) ; /**
     * A closed scope does not auto-import variables from its parent scope
     */ readonly closed : Ref < boolean > = ref ( false ) ; private readonly _variableMap = reactive ( new Map < string , ScopedVariable [ ] > ( ) ) ; /**
     * Variables defined in this scope
     * 0th variable is always the import-variable
     */ public get variableMap ( ) : ReadonlyMap < string , ScopedVariable [ ] > { return this . _variableMap ; } constructor ( options ) { super ( options ) ; } setName ( value ) { this . name . value = value ; } private createVariableArray ( name ) { const importerVariable = reactive ( { position : computed ( ( ) => this . position . value ) , index : 0 , data : shallowRef ( ) , getters : [ ] , } ) ; const newVariableArray = reactive ( [ importerVariable ] ) ; watch ( [ ( ) => newVariableArray . length , ( ) => importerVariable . getters . length ] , ( [ variableArrayLength , gettersLength ] ) => { if ( variableArrayLength <= 1 && gettersLength == 0 ) { this . _variableMap . delete ( name ) ; } } ) ; this . _variableMap . set ( name , newVariableArray ) ; return newVariableArray ; } addVariable ( name , position ) { const variable = reactive ( { position : position , index : - 1 , data : shallowRef < any > ( null ) , getters : [ ] , } ) ; const variableArray = this . variableMap . get ( name ) ?? this . createVariableArray ( name ) ; watchImmediate ( ( ) => variable . position , ( value ) => { if ( variable . index >= 0 ) { assert ( variableArray [ variable . index ] == variable , `Expected variable ${ variable } to be in ${ variableArray } at index ${ variable . index }` ) ; const prev = arrayUtils . at ( variableArray , variable . index - 1 ) ; const next = arrayUtils . at ( variableArray , variable . index + 1 ) ; if ( isInRange ( value , { start : prev ?. position , end : next ?. position } ) ) { } removeVariable ( variableArray , variable ) ; } const { index } = arrayUtils . getBinaryInsertIndex ( variableArray , ( v ) => v . position . compareTo ( value ) ) ; const prev = arrayUtils . at ( variableArray , index - 1 ) ; if ( prev ?. getters ) { variable . getters = prev . getters . filter ( ( v ) => value . compareTo ( v . position ) < 0 ) ; variable . getters . forEach ( ( v ) => { v . variable = variable ; } ) ; prev . getters = prev . getters . filter ( ( v ) => ! ( value . compareTo ( v . position ) < 0 ) ) ; } for ( let i = index ; i < variableArray . length ; i ++ ) { variableArray [ i ] . index = i + 1 ; } variableArray . splice ( index , 0 , variable ) ; variable . index = index ; } ) ; function setData ( data ) { variable . data = data ; } function remove ( ) { removeVariable ( variableArray , variable ) ; } return { setData , remove , } ; } addGetter ( name , position ) { const getter = reactive ( { position : position , variable : undefined , } ) ; const data = computed ( ( ) => getter . variable ?. data ) ; const variableArray = this . variableMap . get ( name ) ?? this . createVariableArray ( name ) ; watchImmediate ( ( ) => getter . position , ( value ) => { if ( getter . variable ) { const nextVariable = arrayUtils . at ( variableArray , getter . variable . index + 1 ) ; if ( isInRange ( value , { start : getter . variable . position , end : nextVariable ?. position , } ) ) { return ; } arrayUtils . remove ( getter . variable . getters , getter ) ; getter . variable = undefined ; } const { index } = arrayUtils . getBinaryInsertIndex ( variableArray , ( v ) => v . position . compareTo ( value ) ) ; const variable = arrayUtils . at ( variableArray , index - 1 ) ; assert ( variable , `Getter position ${ getter . position } outside of block ${ this . position }` ) ; variable . getters . push ( getter ) ; getter . variable = variable ; } ) ; function remove ( ) { if ( ! getter . variable ) return ; arrayUtils . remove ( getter . variable . getters , getter ) ; getter . variable = undefined ; } return { data , remove , } ; } } export const ScopeElementType = { typeName : ElementType , elementType : ScopeElement , serializeElement : ( element ) => { const serializedElement = { id : element . id , typeName : ElementType , name : element . name . value , position : { x : element . position . value . x , y : element . position . value . y } , size : { x : element . size . value . x , y : element . size . value . y } , resizable : element . resizable . value , closed : element . closed . value , } ; return serializedElement ; } , deserializeElement : ( elementData ) => { const creationOptions = { id : elementData . id , position : new Vector2 ( elementData . position . x , elementData . position . y ) , size : new Vector2 ( elementData . size . x , elementData . size . y ) , resizable : elementData . resizable , } ; const element = new ScopeElement ( creationOptions ) ; return { element : element , onAddedCallback : ( ) => { } , } ; } , } ; export interface UseScopedVariable { /**
     * - `undefined` is equivalent to the variable not existing
     * - `null` is a variable without data
     * - anything else is data
     */ setData ( data ) : void ; remove ( ) : void ; } export interface UseScopedGetter { /**
     * - `undefined` is equivalent to the variable not existing
     * - `null` is a variable without data
     * - anything else is data
     */ data : ComputedRef < Expression | null | undefined > ; remove ( ) : void ; } interface ScopedVariable { /**
     * Variable position
     */ readonly position : Vector2 ; /**
     * Variable index in array
     */ index : number ; /**
     * Shallow ref variable data
     *
     */ data : any ; /**
     * Variable getters
     */ getters : ScopedGetter [ ] ; } interface ScopedGetter { /**
     * Getter position
     */ readonly position : Vector2 ; /**
     * Getter variable
     */ variable : ScopedVariable | undefined ; } function removeVariable ( variableArray , variable ) { if ( variable . index < 0 ) return ; if ( variable . getters . length > 0 ) { const prev = arrayUtils . at ( variableArray , variable . index - 1 ) ; assert ( prev , 'Expected prev variable to exist' ) ; prev . getters = prev . getters . concat ( variable . getters ) ; variable . getters . forEach ( ( v ) => ( v . variable = prev ) ) ; variable . getters = [ ] ; } variableArray . splice ( variable . index , 1 ) ; for ( let i = variable . index ; i < variableArray . length ; i ++ ) { variableArray [ i ] . index = i ; } variable . index = - 1 ; } function isInRange ( value , range ) { return ( ! range . start || range . start . compareTo ( value ) <= 0 ) && ( ! range . end || value . compareTo ( range . end ) < 0 ) ; }