import { _getAdministration , action , IArrayDidChange , IArraySplice , IArrayWillChange , IArrayWillSplice , intercept , IObservableArray , observable , observe } from "mobx" ; import { addHiddenFinalProp , addHiddenWritableProp , AnyNode , AnyObjectNode , assertIsType , ComplexType , convertChildNodesToArray , createActionInvoker , createObjectNode , devMode , EMPTY_ARRAY , EMPTY_OBJECT , ExtractCSTWithSTN , fail , flattenTypeErrors , getContextForPath , getStateTreeNode , IAnyStateTreeNode , IAnyType , IChildNodesMap , IHooksGetter , IJsonPatch , isArray , isNode , isPlainObject , isStateTreeNode , IStateTreeNode , isType , IType , IValidationContext , IValidationResult , mobxShallow , normalizeIdentifier , ObjectNode , typeCheckFailure , typecheckInternal , TypeFlags } from "../../internal" ; hidden export interface IMSTArray < IT extends IAnyType > extends IObservableArray < IT [ "Type" ] > { push ( ... items ) : number ; push ( ... items ) : number ; concat ( ... items ) : IT [ "Type" ] [ ] ; concat ( ... items ) : IT [ "Type" ] [ ] ; concat ( ... items ) : IT [ "Type" ] [ ] ; concat ( ... items ) : IT [ "Type" ] [ ] ; splice ( start , deleteCount ? ) : IT [ "Type" ] [ ] ; splice ( start , deleteCount , ... items ) : IT [ "Type" ] [ ] ; splice ( start , deleteCount , ... items ) : IT [ "Type" ] [ ] ; unshift ( ... items ) : number ; unshift ( ... items ) : number ; } hidden export interface IArrayType < IT extends IAnyType > extends IType < IT [ "CreationType" ] [ ] | undefined , IT [ "SnapshotType" ] [ ] , IMSTArray < IT > > { hooks ( hooks ) : IArrayType < IT > ; } internal hidden export class ArrayType < IT extends IAnyType > extends ComplexType < IT [ "CreationType" ] [ ] | undefined , IT [ "SnapshotType" ] [ ] , IMSTArray < IT > > { readonly flags = TypeFlags . Array ; private readonly hookInitializers : Array < IHooksGetter < IMSTArray < IT > > > = [ ] ; constructor ( name , private readonly _subType , hookInitializers = [ ] ) { super ( name ) ; this . hookInitializers = hookInitializers ; } hooks ( hooks ) { const hookInitializers = this . hookInitializers . length > 0 ? this . hookInitializers . concat ( hooks ) : [ hooks ] ; return new ArrayType ( this . name , this . _subType , hookInitializers ) ; } instantiate ( parent , subpath , environment , initialValue ) { return createObjectNode ( this , parent , subpath , environment , initialValue ) ; } initializeChildNodes ( objNode , snapshot = [ ] ) { const subType = ( objNode . type as this ) . _subType ; const result = { } ; snapshot . forEach ( ( item , index ) => { const subpath = "" + index ; result [ subpath ] = subType . instantiate ( objNode , subpath , undefined , item ) ; } ) ; return result ; } createNewInstance ( childNodes ) { return observable . array ( convertChildNodesToArray ( childNodes ) , mobxShallow ) as this [ "T" ] ; } finalizeNewInstance ( node , instance ) { _getAdministration ( instance ) . dehancer = node . unbox ; const type = node . type as this ; type . hookInitializers . forEach ( ( initializer ) => { const hooks = initializer ( instance ) ; Object . keys ( hooks ) . forEach ( ( name ) => { const hook = hooks [ name as keyof typeof hooks ] ! ; const actionInvoker = createActionInvoker ( instance as IAnyStateTreeNode , name , hook ) ; ( ! devMode ( ) ? addHiddenFinalProp : addHiddenWritableProp ) ( instance , name , actionInvoker ) ; } ) ; } ) ; intercept ( instance as IObservableArray < AnyNode > , this . willChange ) ; observe ( instance as IObservableArray < AnyNode > , this . didChange ) ; } describe ( ) { return this . _subType . describe ( ) + "[]" ; } getChildren ( node ) { return node . storedValue . slice ( ) ; } getChildNode ( node , key ) { const index = Number ( key ) ; if ( index < node . storedValue . length ) return node . storedValue [ index ] ; throw fail ( "Not a child: " + key ) ; } willChange ( change ) { const node = getStateTreeNode ( change . object as IStateTreeNode < this > ) ; node . assertWritable ( { subpath : "" + change . index } ) ; const subType = ( node . type as this ) . _subType ; const childNodes = node . getChildren ( ) ; switch ( change . type ) { case "update" : { if ( change . newValue === change . object [ change . index ] ) return null ; const updatedNodes = reconcileArrayChildren ( node , subType , [ childNodes [ change . index ] ] , [ change . newValue ] , [ change . index ] ) ; if ( ! updatedNodes ) { return null ; } change . newValue = updatedNodes [ 0 ] ; } break ; case "splice" : { const { index , removedCount , added } = change ; const addedNodes = reconcileArrayChildren ( node , subType , childNodes . slice ( index , index + removedCount ) , added , added . map ( ( _ , i ) => index + i ) ) ; if ( ! addedNodes ) { return null ; } change . added = addedNodes ; for ( let i = index + removedCount ; i < childNodes . length ; i ++ ) { childNodes [ i ] . setParent ( node , "" + ( i + added . length - removedCount ) ) ; } } break ; } return change ; } getSnapshot ( node ) { return node . getChildren ( ) . map ( ( childNode ) => childNode . snapshot ) ; } processInitialSnapshot ( childNodes ) { const processed = [ ] ; Object . keys ( childNodes ) . forEach ( ( key ) => { processed . push ( childNodes [ key ] . getSnapshot ( ) ) ; } ) ; return processed ; } didChange ( change ) { const node = getStateTreeNode ( change . object as IAnyStateTreeNode ) ; switch ( change . type ) { case "update" : return void node . emitPatch ( { op : "replace" , path : "" + change . index , value : change . newValue . snapshot , oldValue : change . oldValue ? change . oldValue . snapshot : undefined } , node ) ; case "splice" : for ( let i = change . removedCount - 1 ; i >= 0 ; i -- ) node . emitPatch ( { op : "remove" , path : "" + ( change . index + i ) , oldValue : change . removed [ i ] . snapshot } , node ) ; for ( let i = 0 ; i < change . addedCount ; i ++ ) node . emitPatch ( { op : "add" , path : "" + ( change . index + i ) , value : node . getChildNode ( "" + ( change . index + i ) ) . snapshot , oldValue : undefined } , node ) ; return ; } } applyPatchLocally ( node , subpath , patch ) { const target = node . storedValue ; const index = subpath === "-" ? target . length : Number ( subpath ) ; switch ( patch . op ) { case "replace" : target [ index ] = patch . value ; break ; case "add" : target . splice ( index , 0 , patch . value ) ; break ; case "remove" : target . splice ( index , 1 ) ; break ; } } applySnapshot ( node , snapshot ) { typecheckInternal ( this , snapshot ) ; const target = node . storedValue ; target . replace ( snapshot as any ) ; } getChildType ( ) { return this . _subType ; } isValidSnapshot ( value , context ) { if ( ! isArray ( value ) ) { return typeCheckFailure ( context , value , "Value is not an array" ) ; } return flattenTypeErrors ( value . map ( ( item , index ) => this . _subType . validate ( item , getContextForPath ( context , "" + index , this . _subType ) ) ) ) ; } getDefaultSnapshot ( ) { return EMPTY_ARRAY as this [ "C" ] ; } removeChild ( node , subpath ) { node . storedValue . splice ( Number ( subpath ) , 1 ) ; } } ArrayType . prototype . applySnapshot = action ( ArrayType . prototype . applySnapshot ) ; param subtype returns export function array < IT extends IAnyType > ( subtype ) { assertIsType ( subtype , 1 ) ; return new ArrayType < IT > ( subtype . name + "[]" , subtype ) ; } function reconcileArrayChildren < TT > ( parent , childType , oldNodes , newValues , newPaths ) { let nothingChanged = true ; for ( let i = 0 ; ; i ++ ) { const hasNewNode = i <= newValues . length - 1 ; const oldNode = oldNodes [ i ] ; let newValue = hasNewNode ? newValues [ i ] : undefined ; const newPath = "" + newPaths [ i ] ; if ( isNode ( newValue ) ) newValue = newValue . storedValue ; if ( ! oldNode && ! hasNewNode ) { break ; } else if ( ! hasNewNode ) { nothingChanged = false ; oldNodes . splice ( i , 1 ) ; if ( oldNode instanceof ObjectNode ) { oldNode . createObservableInstanceIfNeeded ( ) ; } oldNode . die ( ) ; i -- ; } else if ( ! oldNode ) { if ( isStateTreeNode ( newValue ) && getStateTreeNode ( newValue ) . parent === parent ) { throw fail ( `Cannot add an object to a state tree if it is already part of the same or another state tree. Tried to assign an object to '${ parent . path }/${ newPath }', but it lives already at '${ getStateTreeNode ( newValue ) . path }'` ) ; } nothingChanged = false ; const newNode = valueAsNode ( childType , parent , newPath , newValue ) ; oldNodes . splice ( i , 0 , newNode ) ; } else if ( areSame ( oldNode , newValue ) ) { oldNodes [ i ] = valueAsNode ( childType , parent , newPath , newValue , oldNode ) ; } else { let oldMatch = undefined ; for ( let j = i ; j < oldNodes . length ; j ++ ) { if ( areSame ( oldNodes [ j ] , newValue ) ) { oldMatch = oldNodes . splice ( j , 1 ) [ 0 ] ; break ; } } nothingChanged = false ; const newNode = valueAsNode ( childType , parent , newPath , newValue , oldMatch ) ; oldNodes . splice ( i , 0 , newNode ) ; } } return nothingChanged ? null : oldNodes ; } /**
 * Convert a value to a node at given parent and subpath. Attempts to reuse old node if possible and given.
 */ function valueAsNode ( childType , parent , subpath , newValue , oldNode ? ) { typecheckInternal ( childType , newValue ) ; function getNewNode ( ) { if ( isStateTreeNode ( newValue ) ) { const childNode = getStateTreeNode ( newValue ) ; childNode . assertAlive ( EMPTY_OBJECT ) ; if ( childNode . parent !== null && childNode . parent === parent ) { childNode . setParent ( parent , subpath ) ; return childNode ; } } if ( oldNode ) { return childType . reconcile ( oldNode , newValue , parent , subpath ) ; } return childType . instantiate ( parent , subpath , undefined , newValue ) ; } const newNode = getNewNode ( ) ; if ( oldNode && oldNode !== newNode ) { if ( oldNode instanceof ObjectNode ) { oldNode . createObservableInstanceIfNeeded ( ) ; } oldNode . die ( ) ; } return newNode ; } /**
 * Check if a node holds a value.
 */ function areSame ( oldNode , newValue ) { if ( ! oldNode . isAlive ) { return false ; } if ( isStateTreeNode ( newValue ) ) { const newNode = getStateTreeNode ( newValue ) ; return newNode . isAlive && newNode === oldNode ; } if ( oldNode . snapshot === newValue ) { return true ; } if ( ! ( oldNode instanceof ObjectNode ) ) { return false ; } const oldNodeType = oldNode . getReconciliationType ( ) ; return ( oldNode . identifier !== null && oldNode . identifierAttribute && isPlainObject ( newValue ) && oldNodeType . is ( newValue ) && ( oldNodeType as any ) . isMatchingSnapshotId ( oldNode , newValue ) ) ; } param type returns export function isArrayType < Items extends IAnyType = IAnyType > ( type ) { return isType ( type ) && ( type . flags & TypeFlags . Array ) > 0 ; }