/*

 * Copyright (c) 2021 Huawei Device Co., Ltd.

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */



import ts from 'typescript';



import {

  COMPONENT_STATE_DECORATOR,

  COMPONENT_PROVIDE_DECORATOR,

  COMPONENT_LINK_DECORATOR,

  COMPONENT_PROP_DECORATOR,

  COMPONENT_STORAGE_LINK_DECORATOR,

  COMPONENT_STORAGE_PROP_DECORATOR,

  COMPONENT_OBJECT_LINK_DECORATOR,

  COMPONENT_CONSUME_DECORATOR,

  SYNCHED_PROPERTY_NESED_OBJECT,

  SYNCHED_PROPERTY_SIMPLE_TWO_WAY,

  SYNCHED_PROPERTY_SIMPLE_ONE_WAY,

  OBSERVED_PROPERTY_OBJECT,

  OBSERVED_PROPERTY_SIMPLE,

  COMPONENT_BUILD_FUNCTION,

  BASE_COMPONENT_NAME,

  CREATE_CONSTRUCTOR_PARAMS,

  COMPONENT_CONSTRUCTOR_UPDATE_PARAMS,

  COMPONENT_CONSTRUCTOR_INITIAL_PARAMS,

  COMPONENT_CONSTRUCTOR_PURGE_VARIABLE_DEP,

  COMPONENT_CONSTRUCTOR_DELETE_PARAMS,

  COMPONENT_DECORATOR_PREVIEW,

  CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER,

  ABOUT_TO_BE_DELETE_FUNCTION_ID,

  ABOUT_TO_BE_DELETE_FUNCTION_ID__,

  CREATE_CONSTRUCTOR_GET_FUNCTION,

  CREATE_CONSTRUCTOR_DELETE_FUNCTION,

  FOREACH_OBSERVED_OBJECT,

  FOREACH_GET_RAW_OBJECT,

  COMPONENT_BUILDER_DECORATOR,

  COMPONENT_TRANSITION_FUNCTION,

  COMPONENT_CREATE_FUNCTION,

  GEOMETRY_VIEW,

  COMPONENT_STYLES_DECORATOR,

  STYLES,

  INTERFACE_NAME_SUFFIX,

  OBSERVED_PROPERTY_ABSTRACT,

  COMPONENT_LOCAL_STORAGE_LINK_DECORATOR,

  COMPONENT_LOCAL_STORAGE_PROP_DECORATOR,

  COMPONENT_CONSTRUCTOR_LOCALSTORAGE,

  COMPONENT_SET_AND_LINK,

  COMPONENT_SET_AND_PROP,

  COMPONENT_CONSTRUCTOR_UNDEFINED,

  CUSTOM_COMPONENT,

  COMPONENT_CONSTRUCTOR_PARENT,

  NULL,

  INNER_COMPONENT_MEMBER_DECORATORS,

  COMPONENT_RERENDER_FUNCTION,

  RMELMTID,

  ABOUTTOBEDELETEDINTERNAL,

  UPDATEDIRTYELEMENTS,

  BASE_COMPONENT_NAME_PU,

  OBSERVED_PROPERTY_SIMPLE_PU,

  OBSERVED_PROPERTY_OBJECT_PU,

  SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU,

  SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU,

  SYNCHED_PROPERTY_NESED_OBJECT_PU,

  OBSERVED_PROPERTY_ABSTRACT_PU,

  CREATE_LOCAL_STORAGE_LINK,

  CREATE_LOCAL_STORAGE_PROP,

  COMPONENT_UPDATE_STATE_VARS,

  COMPONENT_WATCH_DECORATOR,

  $$,

  COMPONENT_UPDATE_ELMT_ID,

  OLD_ELMT_ID,

  NEW_ELMT_ID,

  UPDATE_RECYCLE_ELMT_ID,

  GET_ENTRYNAME,

  COMPONENT_PARAMS_FUNCTION,

  FUNCTION,

  COMPONENT_PARAMS_LAMBDA_FUNCTION,

  DECORATOR_COMPONENT_FREEZEWHENINACTIVE,

  INIT_ALLOW_COMPONENT_FREEZE,

  DECORATOR_COMPONENT_REUSEMEMOPT,

  SET_IS_ENABLE_MEM_OPT,

  FINALIZE_CONSTRUCTION,

  PROTOTYPE,

  REFLECT,

  CREATE_SET_METHOD,

  COMPONENT_REQUIRE_DECORATOR,

  CONTEXT_STACK,

  COMPONENT_IF_UNDEFINED,

  COMPONENT_POP_FUNCTION,

  PUSH,

  PUV2_VIEW_BASE,

  COMPONENT_LOCAL_BUILDER_DECORATOR,

  DECORATOR_REUSEABLE,

  COMPONENT_ENV_DECORATOR,

  COMPONENT_CUSTOM_ENV_DECORATOR,

  COMPONENT_DECORATOR_COMPONENT_V2,

  COMPONENT_DECORATOR_COMPONENT,

  REUSEPOOL,

  POOLACCEPTS,

  OWNER,

  SHARED,

  PERINSTANCE

} from './pre_define';

import {

  BUILDIN_STYLE_NAMES,

  CUSTOM_BUILDER_METHOD,

  INNER_STYLE_FUNCTION,

  INTERFACE_NODE_SET,

  STYLES_ATTRIBUTE,

  INNER_CUSTOM_BUILDER_METHOD

} from './component_map';

import {

  componentCollection,

  linkCollection,

  localStorageLinkCollection,

  localStoragePropCollection,

  builderParamObjectCollection,

  methodDecoratorCollect

} from './validate_ui_syntax';

import {

  addConstructor,

  getInitConstructor,

  updateConstructor

} from './process_component_constructor';

import {

  ControllerType,

  processMemberVariableDecorators,

  UpdateResult,

  stateObjectCollection,

  PropMapManager,

  decoratorParamSet,

  isSimpleType,

  isSingleKey,

  findDecoratorIndex,

  createResetStateVarsOnReuseForV1

} from './process_component_member';

import {

  processComponentBuild,

  processComponentBlock

} from './process_component_build';

import { isCompatibleVersionOverTarget, isRecycle, isReuseInV2 } from './process_custom_component';

import {

  LogType,

  LogInfo,

  hasDecorator,

  getPossibleBuilderTypeParameter,

  storedFileInfo,

  removeDecorator

} from './utils';

import {

  partialUpdateConfig,

  projectConfig

} from '../main';

import {

  builderTypeParameter,

  initializeMYIDS,

  globalBuilderParamAssignment,

  parseStylesNode,

  transformLog

} from './process_ui_syntax';

import constantDefine from './constant_define';

import processStructComponentV2, { getDecoratorName, StructInfo } from './process_struct_componentV2';



export function processComponentClass(node: ts.StructDeclaration, context: ts.TransformationContext,

  log: LogInfo[], program: ts.Program, ReusePool: { hasReusePool: boolean }): ts.ClassDeclaration {

  const decoratorNode: readonly ts.Decorator[] = ts.getAllDecorators(node);

  const memberNode: ts.ClassElement[] =

    processMembers(node.members, node.name, context, decoratorNode, log,

      program, checkPreview(node), ReusePool);

  return ts.factory.createClassDeclaration(

    ts.getModifiers(node), 

    node.name,

    node.typeParameters, 

    updateHeritageClauses(node, log), 

    memberNode

  );

}



function checkPreview(node: ts.StructDeclaration): boolean {

  let hasPreview: boolean = false;

  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);

  if (node && decorators) {

    for (let i = 0; i < decorators.length; i++) {

      const name: string = decorators[i].getText().replace(/\([^\(\)]*\)/, '').trim();

      if (name === COMPONENT_DECORATOR_PREVIEW) {

        hasPreview = true;

        break;

      }

    }

  }

  return hasPreview;

}



export type BuildCount = {

  count: number;

};

export interface FreezeParamType {

  componentFreezeParam: ts.Expression;

};

export interface ReuseMemOptParamType {

  memOptParam: ts.Expression;

};

export interface ComponentParamType extends FreezeParamType, ReuseMemOptParamType {};

function processMembers(members: ts.NodeArray<ts.ClassElement>, parentComponentName: ts.Identifier,

  context: ts.TransformationContext, decoratorNode: readonly ts.Decorator[], log: LogInfo[],

  program: ts.Program, hasPreview: boolean, ReusePool: { hasReusePool: boolean }): ts.ClassElement[] {

  const buildCount: BuildCount = { count: 0 };

  let ctorNode: any = getInitConstructor(members, parentComponentName);

  const newMembers: ts.ClassElement[] = [];

  const watchMap: Map<string, ts.Node> = new Map();

  const updateParamsStatements: ts.Statement[] = [];

  const stateVarsStatements: ts.Statement[] = [];

  const purgeVariableDepStatements: ts.Statement[] = [];

  const rerenderStatements: ts.Statement[] = [];

  const deleteParamsStatements: ts.PropertyDeclaration[] = [];

  const addStatementsInResetOnReuseV1: ts.Statement[] | undefined = [];

  const checkController: ControllerType = { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()), 

    unassignedControllerSet: new Set() };

  const interfaceNode = ts.factory.createInterfaceDeclaration(undefined,

    parentComponentName.getText() + INTERFACE_NAME_SUFFIX, undefined, undefined, []);

  members.forEach((item: ts.MethodDeclaration) => {

    if (hasDecorator(item, COMPONENT_STYLES_DECORATOR)) {

      methodDecoratorCollect(item);

    }

  });

  members.forEach((item: ts.ClassElement) => {

    let updateItem: ts.ClassElement | undefined;

    if (ts.isPropertyDeclaration(item)) {

      if (isStaticProperty(item)) {

        newMembers.push(item);

        validateDecorators(item, log);

      } else {

        addPropertyMember(item, newMembers, program, parentComponentName.getText(), log);

        const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item,

          ctorNode, watchMap, checkController, log, program, context, hasPreview, interfaceNode,

          addStatementsInResetOnReuseV1);

        if (result.isItemUpdate()) {

          updateItem = result.getProperity();

        } else {

          updateItem = item;

        }

        if (result.getVariableGet()) {

          newMembers.push(result.getVariableGet());

        }

        if (result.getVariableSet()) {

          newMembers.push(result.getVariableSet());

        }

        if (result.isCtorUpdate()) {

          ctorNode = result.getCtor();

        }

        if (result.getUpdateParams()) {

          updateParamsStatements.push(result.getUpdateParams());

        }

        if (result.getStateVarsParams()) {

          stateVarsStatements.push(result.getStateVarsParams());

        }

        if (result.isDeleteParams()) {

          deleteParamsStatements.push(item);

        }

        if (result.getControllerSet()) {

          newMembers.push(result.getControllerSet());

        }

        processPropertyUnchanged(result, purgeVariableDepStatements);

      }

    }

    if (ts.isMethodDeclaration(item) && item.name) {

      updateItem =

        processComponentMethod(item, context, log, buildCount);

    }

    if (updateItem) {

      newMembers.push(updateItem);

    }

  });

  INTERFACE_NODE_SET.add(interfaceNode);

  validateBuildMethodCount(buildCount, parentComponentName, log);

  validateHasControllerAndControllerCount(parentComponentName, checkController, log);

  if (storedFileInfo.getCurrentArkTsFile().recycleComponents.has(parentComponentName.getText())) {

    newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements, true));

  }

  newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements));

  addIntoNewMembers(newMembers, parentComponentName, updateParamsStatements,

    purgeVariableDepStatements, rerenderStatements, stateVarsStatements);

  if (partialUpdateConfig.partialUpdateMode) {

    const componentParamType: ComponentParamType = { componentFreezeParam: undefined, memOptParam: undefined };

    const freezeStatements = decoratorAssignParams(decoratorNode, context, componentParamType)

      ? decoratorComponentParam(componentParamType) : []

    const reuseStatements = decoratorAssignParamsForReusable(decoratorNode, context, componentParamType)

      ? decoratorComponentParamForReusable(componentParamType) : []

    ctorNode = updateConstructor(ctorNode, [], assignParams(parentComponentName.getText()),

      [...freezeStatements, ...reuseStatements], true);

  }

  const currentLinkCollection: Set<string> = linkCollection.get(parentComponentName.getText());

  isReusableComponent(decoratorNode, false) && isCompatibleVersionOverTarget(26) &&

    createResetStateVarsOnReuseForV1(newMembers, addStatementsInResetOnReuseV1, currentLinkCollection);

  newMembers.unshift(addConstructor(ctorNode, watchMap, parentComponentName));

  if (decoratorNode && Array.isArray(decoratorNode) && decoratorNode.length) {

    processComponentReusePool(newMembers, decoratorNode, false, ReusePool, parentComponentName.getText());

  }

  if (componentCollection.entryComponent === parentComponentName.escapedText.toString() &&

    partialUpdateConfig.partialUpdateMode && projectConfig.minAPIVersion > 10) {

    newMembers.push(getEntryNameFunction(componentCollection.entryComponent));

  }

  log.push(...Array.from(PropMapManager.logInfoMap.values()).flat());

  PropMapManager.reset();

  return newMembers;

}



export function isReusableComponent(

  decoratorNode: readonly ts.Decorator[],

  isComponentV2: boolean

): boolean {

  const targetDecorator: string = isComponentV2 ? COMPONENT_DECORATOR_COMPONENT_V2 :

    COMPONENT_DECORATOR_COMPONENT;

  const result: boolean = decoratorNode.some(decorator => {

    return getDecoratorName(decorator) === targetDecorator;

  })

  return result;

}



export function processComponentReusePool(

  newMembers: ts.ClassElement[],

  decoratorNode: readonly ts.Decorator[],

  isComponentV2: boolean,

  ReusePool: { hasReusePool: boolean },

  parentName: string

): void {

  const targetDecorator: string = isComponentV2 ? COMPONENT_DECORATOR_COMPONENT_V2 :

    COMPONENT_DECORATOR_COMPONENT;

  const componentDecorator = decoratorNode.find(decorator => {

    return getDecoratorName(decorator) === targetDecorator;

  });



  if (!componentDecorator || !componentDecorator.expression ||

    !ts.isCallExpression(componentDecorator.expression) ||

    !componentDecorator.expression.arguments ||

    !componentDecorator.expression.arguments.length

  ) {

    return;

  }



  const reuseObject: ts.Expression = componentDecorator.expression.arguments[0];

  if (!ts.isObjectLiteralExpression(reuseObject) ||

    !reuseObject.properties || !reuseObject.properties.length ||

    reuseObject.properties.length < 2) {

    return;

  }



  let shouldContinue: boolean = true;

  let reusePoolProperty: ts.PropertyAssignment;

  let poolAcceptsProperty: ts.PropertyAssignment;

  reuseObject.properties.forEach((property) => {

    if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {

      shouldContinue = false;

      return;

    }

    const currentName: string = property.name.getText();

    if (currentName === REUSEPOOL) {

      reusePoolProperty = property;

      return;

    }

    if (currentName === POOLACCEPTS) {

      poolAcceptsProperty = property;

      return;

    }

  });



  if (!reusePoolProperty || !poolAcceptsProperty) {

    transformLog.errors.push({

      type: LogType.ERROR,

      code: '10905375',

      message: `'${parentName}' must provide both reusePool and poolAccepts. Neither can be omitted when using the global reuse pool.`,

      pos: reuseObject.pos

    });

    return;

  }

  if (!shouldContinue) {

    return;

  }

  if (!ts.isArrayLiteralExpression(poolAcceptsProperty.initializer)) {

    return;

  }

  if (!ts.isStringLiteral(reusePoolProperty.initializer)) {

    transformLog.errors.push({

      type: LogType.ERROR,

      code: '10905378',

      message: 'ReusePool can only accept string literal.',

      pos: reusePoolProperty.initializer.pos

    });

    return;

  }

  checkReusePoolArg(reusePoolProperty.initializer);

  checkPoolAcceptsArg(poolAcceptsProperty.initializer, parentName);

  newMembers.unshift(createReusePoolPropertyDecl(reusePoolProperty.initializer,

    poolAcceptsProperty.initializer));

  ReusePool.hasReusePool = true;

}



function checkReusePoolArg(

  arg: ts.StringLiteral

): void {

  if (!arg) {

    return;

  }

  if (![SHARED, PERINSTANCE].includes(arg.text)) {

    transformLog.errors.push({

      type: LogType.ERROR,

      code: '10905377',

      message: `ReusePool must be either 'shared' or 'perInstance'. The value '${arg.text}' is not valid.`,

      pos: arg.getStart()

    });

    return;

  }

}



function checkPoolAcceptsArg(

  initializer: ts.ArrayLiteralExpression,

  parentName: string

): void {

  if (!initializer || !initializer.elements) {

    return;

  }



  if (!initializer.elements.length) {

    transformLog.errors.push({

      type: LogType.ERROR,

      code: '10905376',

      message: `PoolAccepts cannot be an empty array. Provide at least one '@Reusable' or '@ReusableV2' component.`,

      pos: initializer.elements.pos

    });

    return;

  }



  initializer.elements.forEach(element => {

    if (!ts.isIdentifier(element)) {

      return;

    }

    const compName: string = element.getText();

    if (compName === parentName) {

      transformLog.errors.push({

        type: LogType.ERROR,

        code: '10905374',

        message: `'${compName}' cannot list itself in poolAccepts. The pool is not yet ready when '${compName}' is being constructed.`,

        pos: element.getStart()

      });

      return;

    }



    if (!isRecycle(compName) && !isReuseInV2(compName)) {

      transformLog.errors.push({

        type: LogType.ERROR,

        code: '10905373',

        message: `'${compName}' is not a '@Reusable' or '@ReusableV2' component and cannot be added to poolAccepts.`,

        pos: element.getStart()

      });

      return;

    }

  });

}



function createReusePoolPropertyDecl(

  reusePoolInitializer: ts.StringLiteral,

  poolAccepts: ts.ArrayLiteralExpression

): ts.PropertyDeclaration {

  const modifiers = ts.factory.createModifiersFromModifierFlags(

    ts.ModifierFlags.Protected | ts.ModifierFlags.Readonly

  );

  const name = ts.factory.createIdentifier('__reusePool__Internal');



  const optionsLiteral = ts.factory.createObjectLiteralExpression(

    [

      ts.factory.createPropertyAssignment(

        ts.factory.createIdentifier(REUSEPOOL),

        reusePoolInitializer

      ),

      ts.factory.createPropertyAssignment(

        ts.factory.createIdentifier(POOLACCEPTS),

        poolAccepts

      ),

      ts.factory.createPropertyAssignment(

        ts.factory.createIdentifier(OWNER),

        ts.factory.createThis()

      )

    ]

  );



  const callExpr = ts.factory.createCallExpression(

    ts.factory.createPropertyAccessExpression(

      ts.factory.createIdentifier('__ReusePool__Internal'),

      ts.factory.createIdentifier('create')

    ),

    undefined,

    [optionsLiteral]

  );



  const result = ts.factory.createPropertyDeclaration(

    modifiers,

    name,

    undefined,

    undefined,

    callExpr

  );



  return result;

}



function createPoolAcceptsArrayLiteral(

  poolAccepts: ts.ArrayLiteralExpression

): ts.ArrayLiteralExpression {

  if (!poolAccepts || !poolAccepts.elements || !poolAccepts.elements.length) {

    return ts.factory.createArrayLiteralExpression(

      [],

      false

    );

  }

  const tempArray: ts.Identifier[] = [];

  poolAccepts.elements.forEach(element => {

    if (!ts.isStringLiteral(element)) {

      return;

    }

    const currentName: string = element.text;

    tempArray.push(

      ts.factory.createIdentifier(currentName)

    );

  });

  return ts.factory.createArrayLiteralExpression(

    tempArray,

    false

  );

}



export function decoratorAssignParams(decoratorNode: readonly ts.Decorator[], context: ts.TransformationContext,

  creezeParam: FreezeParamType): boolean {

  if (decoratorNode && Array.isArray(decoratorNode) && decoratorNode.length) {

    return decoratorNode.some((item: ts.Decorator) => {

      if (isFreezeComponents(item, context, creezeParam)) {

        return true;

      } else {

        return false;

      }

    });

  } else {

    return false;

  }

}



export function decoratorAssignParamsForReusable(decoratorNode: readonly ts.Decorator[], context: ts.TransformationContext,

  reuseMemOptParam: ReuseMemOptParamType): boolean {

  if (decoratorNode && Array.isArray(decoratorNode) && decoratorNode.length) {

    return decoratorNode.some((item: ts.Decorator) => {

      return isReuseMemOptComponents(item, context, reuseMemOptParam);

    });

  }

  return false;

}



function isFreezeComponents(decorator: ts.Decorator, context: ts.TransformationContext,

  creezeParam: FreezeParamType): boolean {

  let isComponentAssignParent: boolean = false;

  ts.visitNode(decorator, visitComponentParament);

  function visitComponentParament(decorator: ts.Node): ts.Node {

    if (ts.isPropertyAssignment(decorator) && decorator.name && decorator.name.text &&

      decorator.name.text.toString() === DECORATOR_COMPONENT_FREEZEWHENINACTIVE) {

      isComponentAssignParent = true;

      creezeParam.componentFreezeParam = decorator.initializer;

      return decorator;

    }

    return ts.visitEachChild(decorator, visitComponentParament, context);

  }

  return isComponentAssignParent;

}



function isReuseMemOptComponents(decorator: ts.Decorator, context: ts.TransformationContext,

  reuseMemOptParam: ReuseMemOptParamType): boolean {

  let isComponentAssignParent: boolean = false;

  ts.visitNode(decorator, visitComponentParament);

  function visitComponentParament(decorator: ts.Node): ts.Node {

    if (ts.isPropertyAssignment(decorator) && decorator.name && decorator.name.text &&

      decorator.name.text.toString() === DECORATOR_COMPONENT_REUSEMEMOPT) {

      isComponentAssignParent = true;

      reuseMemOptParam.memOptParam = decorator.initializer;

      return decorator;

    }

    return ts.visitEachChild(decorator, visitComponentParament, context);

  }

  return isComponentAssignParent;

}



export function getEntryNameFunction(entryName: string): ts.MethodDeclaration {

  return ts.factory.createMethodDeclaration(

    [ts.factory.createToken(ts.SyntaxKind.StaticKeyword)],

    undefined,

    ts.factory.createIdentifier(GET_ENTRYNAME),

    undefined,

    undefined,

    [],

    ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),

    ts.factory.createBlock(

      [ts.factory.createReturnStatement(ts.factory.createStringLiteral(entryName))],

      true

    )

  );

}



function assignParams(parentComponentName: string): ts.Statement[] {

  return [ts.factory.createIfStatement(

    ts.factory.createBinaryExpression(

      ts.factory.createTypeOfExpression(ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION)),

      ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),

      ts.factory.createStringLiteral(FUNCTION)

    ),

    ts.factory.createBlock(

      [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(

        ts.factory.createPropertyAccessExpression(

          ts.factory.createThis(),

          ts.factory.createIdentifier(COMPONENT_PARAMS_FUNCTION)

        ),

        ts.factory.createToken(ts.SyntaxKind.EqualsToken),

        ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION)

      ))],

      true

    )

  )];

}



function decoratorComponentParam(freezeParam: FreezeParamType): ts.IfStatement[] {

  return [ts.factory.createIfStatement(

    ts.factory.createBinaryExpression(

      ts.factory.createElementAccessExpression(

        ts.factory.createSuper(),

        ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE)

      ),

      ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),

      ts.factory.createBinaryExpression(

        ts.factory.createTypeOfExpression(ts.factory.createElementAccessExpression(

          ts.factory.createSuper(),

          ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE)

        )),

        ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),

        ts.factory.createStringLiteral(FUNCTION)

      )

    ),

    ts.factory.createBlock(

      [ts.factory.createExpressionStatement(ts.factory.createCallExpression(

        ts.factory.createElementAccessExpression(

          ts.factory.createSuper(),

          ts.factory.createStringLiteral(INIT_ALLOW_COMPONENT_FREEZE)

        ),

        undefined,

        [freezeParam.componentFreezeParam]

      ))],

      true

    ),

    undefined

  )];

}



export function decoratorComponentParamForReusable(reuseMemOptParam: ReuseMemOptParamType): ts.IfStatement[] {

  return [ts.factory.createIfStatement(

    ts.factory.createBinaryExpression(

      ts.factory.createElementAccessExpression(

        ts.factory.createSuper(),

        ts.factory.createStringLiteral(SET_IS_ENABLE_MEM_OPT)

      ),

      ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),

      ts.factory.createBinaryExpression(

        ts.factory.createTypeOfExpression(ts.factory.createElementAccessExpression(

          ts.factory.createSuper(),

          ts.factory.createStringLiteral(SET_IS_ENABLE_MEM_OPT)

        )),

        ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),

        ts.factory.createStringLiteral(FUNCTION)

      )

    ),

    ts.factory.createBlock(

      [ts.factory.createExpressionStatement(ts.factory.createCallExpression(

        ts.factory.createElementAccessExpression(

          ts.factory.createSuper(),

          ts.factory.createStringLiteral(SET_IS_ENABLE_MEM_OPT)

        ),

        undefined,

        [reuseMemOptParam.memOptParam]

      ))],

      true

    ),

    undefined

  )];

}



function isStaticProperty(property: ts.PropertyDeclaration): boolean {

  const modifiers: readonly ts.Modifier[] =

    ts.canHaveModifiers(property) ? ts.getModifiers(property) : undefined;

  return modifiers && modifiers.length && modifiers.some(modifier => {

    return modifier.kind === ts.SyntaxKind.StaticKeyword;

  });

}



function validateDecorators(item: ts.ClassElement, log: LogInfo[]): void {

  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item);

  if (decorators && decorators.length) {

    decorators.map((decorator: ts.Decorator) => {

      const decoratorName: string = decorator.getText();

      if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {

        log.push({

          type: LogType.ERROR,

          message: `The static variable of struct cannot be used together with built-in decorators.`,

          pos: item.getStart(),

          code: '10905313'

        });

      }

    });

  }

}



function processPropertyUnchanged(

  result: UpdateResult,

  purgeVariableDepStatements: ts.Statement[]

): void {

  if (partialUpdateConfig.partialUpdateMode) {

    if (result.getPurgeVariableDepStatement()) {

      purgeVariableDepStatements.push(result.getPurgeVariableDepStatement());

    }

  }

}



function addIntoNewMembers(

  newMembers: ts.ClassElement[],

  parentComponentName: ts.Identifier,

  updateParamsStatements: ts.Statement[],

  purgeVariableDepStatements: ts.Statement[],

  rerenderStatements: ts.Statement[],

  stateVarsStatements: ts.Statement[]

): void {

  if (partialUpdateConfig.partialUpdateMode) {

    newMembers.unshift(

      addInitialParamsFunc(updateParamsStatements, parentComponentName),

      addUpdateStateVarsFunc(stateVarsStatements, parentComponentName),

      addPurgeVariableDepFunc(purgeVariableDepStatements)

    );

    newMembers.push(addRerenderFunc(rerenderStatements));

  } else {

    newMembers.unshift(addUpdateParamsFunc(updateParamsStatements, parentComponentName));

  }

}



export function isRegularProperty(decorators: readonly ts.Decorator[]): boolean {

  if (decorators && decorators.length) {

    if (decorators.length === 1 && decorators[0].getText() === COMPONENT_REQUIRE_DECORATOR) {

      return true;

    }

    return false;

  }

  return true;

}



function addPropertyMember(item: ts.ClassElement, newMembers: ts.ClassElement[],

  program: ts.Program, parentComponentName: string, log: LogInfo[]): void {

  const propertyItem: ts.PropertyDeclaration = item as ts.PropertyDeclaration;

  let decoratorName: string;

  let updatePropertyItem: ts.PropertyDeclaration;

  const type: ts.TypeNode = propertyItem.type;

  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(propertyItem);

  if (isRegularProperty(decorators)) {

    updatePropertyItem = createPropertyDeclaration(propertyItem, type, true);

    newMembers.push(updatePropertyItem);

  } else {

    for (let i = 0; i < decorators.length; i++) {

      let newType: ts.TypeNode;

      decoratorName = decorators[i].getText().replace(/\(.*\)$/, '').trim();

      let isLocalStorage: boolean = false;

      if (!partialUpdateConfig.partialUpdateMode) {

        newType = createTypeReference(decoratorName, type, log, program);

      } else {

        newType = createTypeReferencePU(decoratorName, type, log, program);

      }

      if (

        decoratorName === COMPONENT_LOCAL_STORAGE_LINK_DECORATOR ||

        decoratorName === COMPONENT_LOCAL_STORAGE_PROP_DECORATOR

      ) {

        isLocalStorage = true;

      }

      if (decoratorName === COMPONENT_ENV_DECORATOR) {

        const updateDecorators: readonly ts.Decorator[] = decorators.filter(

          (decorator: ts.Decorator) => {

            return decorator.getText().replace(/\(.*\)$/, '').trim() !== COMPONENT_WATCH_DECORATOR;

          }

        );

        updatePropertyItem = ts.factory.updatePropertyDeclaration(propertyItem,

          ts.concatenateDecoratorsAndModifiers(updateDecorators, ts.getModifiers(propertyItem)),

          propertyItem.name, propertyItem.questionToken, propertyItem.type, undefined);

        continue;

      }

      if (decoratorName === COMPONENT_CUSTOM_ENV_DECORATOR) {

        const updateDecorators: readonly ts.Decorator[] = decorators.filter(

          (decorator: ts.Decorator) => {

            return decorator.getText().replace(/\(.*\)$/, '').trim() !== COMPONENT_WATCH_DECORATOR;

          }

        );

        updatePropertyItem = ts.factory.updatePropertyDeclaration(propertyItem,

          ts.concatenateDecoratorsAndModifiers(updateDecorators, ts.getModifiers(propertyItem)),

          propertyItem.name, propertyItem.questionToken, propertyItem.type, propertyItem.initializer);

        continue;

      }

      const newUpdatePropertyItem = createPropertyDeclaration(

        propertyItem, newType, false, isLocalStorage, parentComponentName);

      if (!updatePropertyItem) {

        updatePropertyItem = newUpdatePropertyItem;

      } else if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName) &&

        ![COMPONENT_WATCH_DECORATOR, COMPONENT_REQUIRE_DECORATOR].includes(decoratorName)) {

        updatePropertyItem = newUpdatePropertyItem;

      }

    }

    if (updatePropertyItem) {

      newMembers.push(updatePropertyItem);

    }

  }

}



function createPropertyDeclaration(propertyItem: ts.PropertyDeclaration, newType: ts.TypeNode | undefined,

  normalVar: boolean, isLocalStorage: boolean = false, parentComponentName: string = null): ts.PropertyDeclaration {

  if (typeof newType === undefined) {

    return undefined;

  }

  let prefix: string = '';

  if (!normalVar) {

    prefix = '__';

  }

  const privateM: ts.ModifierToken<ts.SyntaxKind.PrivateKeyword> =

    ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword);

  const modifiers: readonly ts.Modifier[] =

    ts.canHaveModifiers(propertyItem) ? ts.getModifiers(propertyItem) : undefined;

  return ts.factory.updatePropertyDeclaration(propertyItem,

    ts.concatenateDecoratorsAndModifiers(undefined, modifiers || [privateM]), prefix + propertyItem.name.getText(),

    propertyItem.questionToken, newType, isLocalStorage ?

      createLocalStroageCallExpression(propertyItem, propertyItem.name.getText(),

        parentComponentName) : undefined);

}



function createLocalStroageCallExpression(node: ts.PropertyDeclaration, name: string,

  parentComponentName: string): ts.CallExpression {

  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);

  if (isSingleKey(node)) {

    const localStorageLink: Set<string> = localStorageLinkCollection.get(parentComponentName).get(name);

    const localStorageProp: Set<string> = localStoragePropCollection.get(parentComponentName).get(name);

    let localFuncName: string;

    const index: number = findDecoratorIndex(decorators,

      [COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_PROP_DECORATOR]);

    const localValue: ts.Expression[] = [

      decorators[index].expression.arguments[0],

      node.initializer ? node.initializer : ts.factory.createNumericLiteral(COMPONENT_CONSTRUCTOR_UNDEFINED),

      ts.factory.createThis(),

      ts.factory.createStringLiteral(name || COMPONENT_CONSTRUCTOR_UNDEFINED)

    ];

    if (!partialUpdateConfig.partialUpdateMode) {

      localFuncName = localStorageLink && !localStorageProp ? COMPONENT_SET_AND_LINK :

        COMPONENT_SET_AND_PROP;

    } else {

      localFuncName = localStorageLink && !localStorageProp ? CREATE_LOCAL_STORAGE_LINK :

        CREATE_LOCAL_STORAGE_PROP;

      localValue.splice(-2, 1);

    }

    return ts.factory.createCallExpression(

      ts.factory.createPropertyAccessExpression(

        !partialUpdateConfig.partialUpdateMode ?

          ts.factory.createPropertyAccessExpression(

            ts.factory.createThis(),

            ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_LOCALSTORAGE}_`)

          ) : ts.factory.createThis(),

        ts.factory.createIdentifier(localFuncName)

      ),

      [node.type],

      localValue

    );

  }

  return undefined;

}

export interface builderConditionType {

  isBuilder: boolean,

  isLocalBuilder: boolean

}

export function processComponentMethod(node: ts.MethodDeclaration, context: ts.TransformationContext,

  log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration {

  let updateItem: ts.MethodDeclaration = node;

  const name: string = node.name.getText();

  const customBuilder: ts.Decorator[] = [];

  const builderCondition: builderConditionType = {

    isBuilder: false,

    isLocalBuilder: false

  };

  if (builderParamObjectCollection.get(componentCollection.currentClassName)) {

    storedFileInfo.builderLikeCollection =

      new Set([...builderParamObjectCollection.get(componentCollection.currentClassName), ...CUSTOM_BUILDER_METHOD]);

  } else {

    storedFileInfo.builderLikeCollection = CUSTOM_BUILDER_METHOD;

  }

  if (name === COMPONENT_BUILD_FUNCTION) {

    storedFileInfo.processBuilder = false;

    storedFileInfo.processGlobalBuilder = false;

    buildCount.count = buildCount.count + 1;

    if (node.parameters.length) {

      log.push({

        type: LogType.ERROR,

        message: `The 'build' method can not have arguments.`,

        pos: node.getStart(),

        code: '10905106'

      });

    }

    const buildNode: ts.MethodDeclaration = processComponentBuild(node, log);

    updateItem = processBuildMember(buildNode, context, log);

  } else if (node.body && ts.isBlock(node.body)) {

    if (name === COMPONENT_TRANSITION_FUNCTION) {

      updateItem = ts.factory.updateMethodDeclaration(node, ts.getModifiers(node),

        node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters,

        node.type, processComponentBlock(node.body, false, log, true));

    } else if (isBuilderOrLocalBuilder(node, builderCondition, customBuilder)) {

      storedFileInfo.processBuilder = true;

      storedFileInfo.processGlobalBuilder = false;

      if (builderCondition.isLocalBuilder) {

        storedFileInfo.processLocalBuilder = true;

      }

      CUSTOM_BUILDER_METHOD.add(name);

      INNER_CUSTOM_BUILDER_METHOD.add(name);

      builderTypeParameter.params = getPossibleBuilderTypeParameter(node.parameters);

      const parameters: ts.NodeArray<ts.ParameterDeclaration> = ts.factory.createNodeArray(Array.from(node.parameters));

      if (!builderCondition.isLocalBuilder) {

        parameters.push(createParentParameter());

      }

      if (projectConfig.optLazyForEach) {

        parameters.push(initializeMYIDS());

      }

      const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;

      const componentBlock: ts.Block = processComponentBlock(node.body, false, log, false, true);

      if (partialUpdateConfig.partialUpdateMode && builderCondition.isLocalBuilder &&

        node.body.statements.length) {

        componentBlock.statements.unshift(globalBuilderParamAssignment());

      }

      let builderNode: ts.MethodDeclaration | ts.PropertyDeclaration = ts.factory.updateMethodDeclaration(node,

        ts.concatenateDecoratorsAndModifiers(removeDecorator(customBuilder, 'Builder'), modifiers),

        node.asteriskToken, node.name, node.questionToken, node.typeParameters,

        parameters, node.type, componentBlock);

      builderTypeParameter.params = [];

      updateItem = processBuildMember(builderNode, context, log, true);

      if (builderCondition.isLocalBuilder) {

        checkDecoratorMethod(node, modifiers, log);

        updateItem = localBuilderNode(node, updateItem.body);

      }

      storedFileInfo.processBuilder = false;

      storedFileInfo.processLocalBuilder = false;

    } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) {

      parseStylesNode(node, log);

      if (node.parameters && node.parameters.length === 0) {

        if (ts.isBlock(node.body) && node.body.statements && node.body.statements.length) {

          INNER_STYLE_FUNCTION.set(name, node.body);

          STYLES_ATTRIBUTE.add(name);

          BUILDIN_STYLE_NAMES.add(name);

          decoratorParamSet.add(STYLES);

        }

      } else {

        log.push({

          type: LogType.ERROR,

          message: `'@Styles' decorated functions and methods cannot have arguments.`,

          pos: node.getStart(),

          code: '10905105'

        });

      }

      return undefined;

    }

  }

  return updateItem;

}



function checkDecoratorMethod(node: ts.MethodDeclaration, modifiers: readonly ts.Modifier[], log: LogInfo[]): void {

  if (modifiers && modifiers.length) {

    for (let i = 0; i < modifiers.length; i++) {

      if (modifiers[i].kind && modifiers[i].kind === ts.SyntaxKind.StaticKeyword) {

        log.push({

          type: LogType.ERROR,

          message: `Static methods in custom components cannot be decorated by '@LocalBuilder'.`,

          pos: node.getStart(),

          code: '10905104'

        });

        return;

      }

    }

  }

}



export function isBuilderOrLocalBuilder(node: ts.MethodDeclaration | ts.FunctionDeclaration, builderCondition: builderConditionType,

  customBuilder: ts.Decorator[] = undefined): boolean {

  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);

  if (decorators && decorators.length) {

    for (let i = 0; i < decorators.length; i++) {

      const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim();

      if ([COMPONENT_LOCAL_BUILDER_DECORATOR, COMPONENT_BUILDER_DECORATOR].includes(originalDecortor)) {

        if (originalDecortor === COMPONENT_BUILDER_DECORATOR) {

          builderCondition.isBuilder = true;

          if (customBuilder) {

            customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i));

          }

        } else {

          builderCondition.isLocalBuilder = true;

        }

        return true;

      }

    }

  }

  return false;

}



function localBuilderNode(node: ts.MethodDeclaration, componentBlock: ts.Block): ts.PropertyDeclaration {

  return ts.factory.createPropertyDeclaration(

    undefined, node.name, undefined, undefined,

    ts.factory.createArrowFunction(

      undefined, undefined, node.parameters ? node.parameters : [], undefined,

      ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),

      componentBlock

    )

  );

}



export function createParentParameter(): ts.ParameterDeclaration {

  return ts.factory.createParameterDeclaration(undefined, undefined,

    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT), undefined, undefined,

    ts.factory.createIdentifier(NULL));

}



export function processBuildMember(node: ts.MethodDeclaration | ts.FunctionDeclaration, context: ts.TransformationContext,

  log: LogInfo[], isBuilder = false): ts.MethodDeclaration | ts.FunctionDeclaration {

  return ts.visitNode(node, visitBuild);

  function visitBuild(node: ts.Node): ts.Node {

    if (isGeometryView(node)) {

      node = processGeometryView(node as ts.ExpressionStatement, log);

    }

    if (isProperty(node)) {

      node = createReference(node as ts.PropertyAssignment, log, isBuilder);

    }

    if (isNeedGetRawObject(node)) {

      return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

        ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT),

        ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]);

    }

    return ts.visitEachChild(node, visitBuild, context);

  }

}



function checkStateName(node: ts.PropertyAccessExpression): string {

  if (node.expression && !node.expression.expression && node.name && ts.isIdentifier(node.name)) {

    return node.name.escapedText.toString();

  }

  return null;

}



// EnableV2Compatibility MakeV1Observed Do not generate ObservedObject GetRawObject.

function isNeedGetRawObject(node: ts.Node): boolean {

  return !isInComponentV2Context() && !isEnableV2CompatibilityOrMakeV1Observed(node) && ts.isPropertyAccessExpression(node) &&

    ts.isIdentifier(node.name) && stateObjectCollection.has(checkStateName(node)) && node.parent && ts.isCallExpression(node.parent) &&

    ts.isPropertyAccessExpression(node.parent.expression) && node !== node.parent.expression &&

    node.parent.expression.name.escapedText.toString() !== FOREACH_GET_RAW_OBJECT;

}



function isEnableV2CompatibilityOrMakeV1Observed(node: ts.Node): boolean {

  const notNeedGetRawObjectFunctions: string[] = ['enableV2Compatibility', 'makeV1Observed', 'canBeObserved'];

  if (node.parent && ts.isCallExpression(node.parent) && ts.isPropertyAccessExpression(node.parent.expression) &&

    ts.isIdentifier(node.parent.expression.name) &&

    notNeedGetRawObjectFunctions.includes(node.parent.expression.name.escapedText.toString())) {

    return true;

  } else {

    return false;

  }

}



function isInComponentV2Context(): boolean {

  if (componentCollection.currentClassName) {

    const parentStructInfo: StructInfo =

      processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName);

    return parentStructInfo.isComponentV2;

  }

  return false;

}



function isGeometryView(node: ts.Node): boolean {

  if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) {

    const call: ts.CallExpression = node.expression;

    const exp: ts.Expression = call.expression;

    const args: ts.NodeArray<ts.Expression> = call.arguments;

    if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression) &&

      exp.expression.escapedText.toString() === GEOMETRY_VIEW && ts.isIdentifier(exp.name) &&

      exp.name.escapedText.toString() === COMPONENT_CREATE_FUNCTION && args && args.length === 1 &&

      (ts.isArrowFunction(args[0]) || ts.isFunctionExpression(args[0]))) {

      return true;

    }

  }

  return false;

}



function processGeometryView(node: ts.ExpressionStatement,

  log: LogInfo[]): ts.ExpressionStatement {

  const exp: ts.CallExpression = node.expression as ts.CallExpression;

  const arg: ts.ArrowFunction | ts.FunctionExpression =

    exp.arguments[0] as ts.ArrowFunction | ts.FunctionExpression;

  return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(exp,

    exp.expression, undefined, [ts.factory.createArrowFunction(undefined, undefined, arg.parameters,

      undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),

      getGeometryReaderFunctionBlock(arg, log))]));

}



function getGeometryReaderFunctionBlock(node: ts.ArrowFunction | ts.FunctionExpression,

  log: LogInfo[]): ts.Block {

  let blockNode: ts.Block;

  if (ts.isBlock(node.body)) {

    blockNode = node.body;

  } else if (ts.isArrowFunction(node) && ts.isCallExpression(node.body)) {

    blockNode = ts.factory.createBlock([ts.factory.createExpressionStatement(node.body)]);

  }

  return processComponentBlock(blockNode, false, log);

}



export function updateHeritageClauses(node: ts.StructDeclaration, log: LogInfo[],

  isComponentV2: boolean = false): ts.NodeArray<ts.HeritageClause> {

  if (node.heritageClauses && !checkHeritageClauses(node)) {

    log.push({

      type: LogType.ERROR,

      message: 'Structs are not allowed to inherit from classes or implement interfaces.',

      pos: node.heritageClauses.pos,

      code: '10905212'

    });

  }

  const result: ts.HeritageClause[] = [];

  const heritageClause: ts.HeritageClause = createHeritageClause(isComponentV2);

  result.push(heritageClause);

  return ts.factory.createNodeArray(result);

}



function checkHeritageClauses(node: ts.StructDeclaration): boolean {

  if (node.heritageClauses.length === 1 && node.heritageClauses[0].types &&

    node.heritageClauses[0].types.length === 1) {

    const expressionNode: ts.ExpressionWithTypeArguments = node.heritageClauses[0].types[0];

    if (expressionNode.expression && ts.isIdentifier(expressionNode.expression) &&

      expressionNode.expression.escapedText.toString() === CUSTOM_COMPONENT) {

      return true;

    }

  }

  return false;

}



export function isProperty(node: ts.Node): Boolean {

  if (judgmentParentType(node)) {

    if (node.parent.parent.expression && ts.isIdentifier(node.parent.parent.expression) &&

      !BUILDIN_STYLE_NAMES.has(node.parent.parent.expression.escapedText.toString()) &&

      componentCollection.customComponents.has(

        node.parent.parent.expression.escapedText.toString())) {

      return true;

    } else if (ts.isPropertyAccessExpression(node.parent.parent.expression) &&

      ts.isIdentifier(node.parent.parent.expression.expression) &&

      componentCollection.customComponents.has(

        node.parent.parent.expression.name.escapedText.toString())) {

      return true;

    }

  }

  return false;

}



function judgmentParentType(node: ts.Node): boolean {

  return ts.isPropertyAssignment(node) && node.name && ts.isIdentifier(node.name) &&

    node.parent && ts.isObjectLiteralExpression(node.parent) && node.parent.parent &&

    (ts.isCallExpression(node.parent.parent) || ts.isEtsComponentExpression(node.parent.parent));

}



export function createReference(node: ts.PropertyAssignment, log: LogInfo[], isBuilder = false,

  isParamsLambda: boolean = false, isRecycleComponent: boolean = false): ts.PropertyAssignment {

  const linkParentComponent: string[] = getParentNode(node, linkCollection).slice(1);

  const propertyName: ts.Identifier = node.name as ts.Identifier;

  let initText: string;

  const LINK_REG: RegExp = /^\$/g;

  if (isRecycleComponent && ts.isShorthandPropertyAssignment(node)) {

    return node;

  }

  const initExpression: ts.Expression = node.initializer;

  let is$$: boolean = false;

  if (ts.isIdentifier(initExpression) &&

    initExpression.escapedText.toString().match(LINK_REG)) {

    initText = initExpression.escapedText.toString().replace(LINK_REG, '');

  } else if (ts.isPropertyAccessExpression(initExpression) && initExpression.expression &&

    initExpression.expression.kind === ts.SyntaxKind.ThisKeyword &&

    ts.isIdentifier(initExpression.name) && initExpression.name.escapedText.toString().match(LINK_REG)) {

    initText = initExpression.name.escapedText.toString().replace(LINK_REG, '');

  } else if (isBuilder && ts.isPropertyAccessExpression(initExpression) && initExpression.expression &&

    ts.isIdentifier(initExpression.expression) && initExpression.expression.escapedText.toString() === $$ &&

    ts.isIdentifier(initExpression.name) && linkParentComponent.includes(propertyName.escapedText.toString())) {

    is$$ = true;

    initText = initExpression.name.escapedText.toString();

  } else if (isMatchInitExpression(initExpression) &&

    linkParentComponent.includes(propertyName.escapedText.toString())) {

    initText = initExpression.name.escapedText.toString().replace(LINK_REG, '');

  }

  if (initText) {

    node = addDoubleUnderline(node, propertyName, initText, is$$, isParamsLambda, isRecycleComponent);

  }

  return node;

}



function isMatchInitExpression(initExpression: ts.Expression): boolean {

  return ts.isPropertyAccessExpression(initExpression) &&

    initExpression.expression &&

    initExpression.expression.kind === ts.SyntaxKind.ThisKeyword &&

    ts.isIdentifier(initExpression.name);

}



function addDoubleUnderline(node: ts.PropertyAssignment, propertyName: ts.Identifier,

  initText: string, is$$ = false, isParamsLambda: boolean, isRecycleComponent: boolean): ts.PropertyAssignment {

  return ts.factory.updatePropertyAssignment(node, propertyName,

    ts.factory.createPropertyAccessExpression(

      is$$ && partialUpdateConfig.partialUpdateMode ? ts.factory.createIdentifier($$) : ts.factory.createThis(),

      isParamsLambda || isRecycleComponent ? ts.factory.createIdentifier(initText) : ts.factory.createIdentifier(`__${initText}`)));

}



function getParentNode(node: ts.PropertyAssignment, collection: Map<string, Set<string>>): string[] {

  const grandparentNode: ts.NewExpression = node.parent.parent as ts.NewExpression;

  const grandparentExpression: ts.Identifier | ts.PropertyAccessExpression =

    grandparentNode.expression as ts.Identifier | ts.PropertyAccessExpression;

  let parentComponent: Set<string> = new Set();

  let grandparentName: string;

  if (ts.isIdentifier(grandparentExpression)) {

    grandparentName = grandparentExpression.escapedText.toString();

    parentComponent = collection.get(grandparentName);

  } else if (ts.isPropertyAccessExpression(grandparentExpression)) {

    if (storedFileInfo.isAsPageImport) {

      grandparentName = grandparentExpression.getText();

    } else {

      grandparentName = grandparentExpression.name.escapedText.toString();

    }

    parentComponent = collection.get(grandparentName);

  } else {

    // ignore

  }

  if (!parentComponent) {

    parentComponent = new Set();

  }

  return [grandparentName, ...parentComponent];

}



function addUpdateParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier):

  ts.MethodDeclaration {

  return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements, parentComponentName);

}



function addInitialParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): ts.MethodDeclaration {

  return createParamsInitBlock(COMPONENT_CONSTRUCTOR_INITIAL_PARAMS, statements, parentComponentName);

}



function addUpdateStateVarsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): ts.MethodDeclaration {

  return createParamsInitBlock(COMPONENT_UPDATE_STATE_VARS, statements, parentComponentName);

}



function addPurgeVariableDepFunc(statements: ts.Statement[]): ts.MethodDeclaration {

  return ts.factory.createMethodDeclaration(

    undefined, undefined,

    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PURGE_VARIABLE_DEP),

    undefined, undefined, [ts.factory.createParameterDeclaration(undefined, undefined,

      ts.factory.createIdentifier(RMELMTID), undefined, undefined, undefined)], undefined,

    ts.factory.createBlock(statements, true));

}



function addDeleteParamsFunc(statements: ts.PropertyDeclaration[],

  updateRecyle: boolean = false): ts.MethodDeclaration {

  const deleteStatements: ts.ExpressionStatement[] = [];

  const updateStatements: ts.ExpressionStatement[] = [];

  statements.forEach((statement: ts.PropertyDeclaration) => {

    const name: ts.Identifier = statement.name as ts.Identifier;

    let paramsStatement: ts.ExpressionStatement;

    const decorators: readonly ts.Decorator[] = ts.getAllDecorators(statement);

    const isRegular: boolean = isRegularProperty(decorators);

    if (!partialUpdateConfig.partialUpdateMode || !isRegular) {

      paramsStatement = createParamsWithUnderlineStatement(name);

    }

    if (partialUpdateConfig.partialUpdateMode && !isRegular) {

      updateStatements.push(createElmtIdWithUnderlineStatement(name));

    }

    deleteStatements.push(paramsStatement);

  });

  if (partialUpdateConfig.partialUpdateMode && updateRecyle) {

    return createRecycleElmt(updateStatements);

  }

  const defaultStatement: ts.ExpressionStatement =

    ts.factory.createExpressionStatement(ts.factory.createCallExpression(

      ts.factory.createPropertyAccessExpression(

        ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

          ts.factory.createIdentifier(CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER),

          ts.factory.createIdentifier(CREATE_CONSTRUCTOR_GET_FUNCTION)), undefined, []),

        ts.factory.createIdentifier(CREATE_CONSTRUCTOR_DELETE_FUNCTION)),

      undefined, [ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

        ts.factory.createThis(), ts.factory.createIdentifier(

          !partialUpdateConfig.partialUpdateMode ?

            ABOUT_TO_BE_DELETE_FUNCTION_ID : ABOUT_TO_BE_DELETE_FUNCTION_ID__)),

      undefined, [])]));

  deleteStatements.push(defaultStatement);

  if (partialUpdateConfig.partialUpdateMode) {

    const aboutToBeDeletedInternalStatement: ts.ExpressionStatement = createDeletedInternalStatement();

    deleteStatements.push(aboutToBeDeletedInternalStatement);

  }

  const deleteParamsMethod: ts.MethodDeclaration =

    createParamsInitBlock(COMPONENT_CONSTRUCTOR_DELETE_PARAMS, deleteStatements);

  return deleteParamsMethod;

}



function createRecycleElmt(statements: ts.Statement[]): ts.MethodDeclaration {

  return ts.factory.createMethodDeclaration(undefined, undefined,

    ts.factory.createIdentifier(UPDATE_RECYCLE_ELMT_ID), undefined, undefined, [

      ts.factory.createParameterDeclaration(undefined, undefined,

        ts.factory.createIdentifier(OLD_ELMT_ID)),

      ts.factory.createParameterDeclaration(undefined, undefined,

        ts.factory.createIdentifier(NEW_ELMT_ID))

    ], undefined, ts.factory.createBlock(statements, true));

}



function createParamsWithUnderlineStatement(name: ts.Identifier): ts.ExpressionStatement {

  return ts.factory.createExpressionStatement(

    ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

      ts.factory.createPropertyAccessExpression(ts.factory.createThis(),

        ts.factory.createIdentifier(`__${name.escapedText.toString()}`)),

      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_DELETE_PARAMS)), undefined, []));

}



function createElmtIdWithUnderlineStatement(name: ts.Identifier): ts.ExpressionStatement {

  return ts.factory.createExpressionStatement(

    ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

      ts.factory.createPropertyAccessExpression(ts.factory.createThis(),

        ts.factory.createIdentifier(`__${name.escapedText.toString()}`)),

      ts.factory.createIdentifier(COMPONENT_UPDATE_ELMT_ID)), undefined, [

      ts.factory.createIdentifier(OLD_ELMT_ID), ts.factory.createIdentifier(NEW_ELMT_ID)

    ]));

}



function createDeletedInternalStatement(): ts.ExpressionStatement {

  return ts.factory.createExpressionStatement(ts.factory.createCallExpression(

    ts.factory.createPropertyAccessExpression(ts.factory.createThis(),

      ts.factory.createIdentifier(ABOUTTOBEDELETEDINTERNAL)), undefined, []));

}



export function addRerenderFunc(statements: ts.Statement[]): ts.MethodDeclaration {

  const updateDirtyElementStatement: ts.Statement = ts.factory.createExpressionStatement(

    ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(

      ts.factory.createThis(), ts.factory.createIdentifier(UPDATEDIRTYELEMENTS)), undefined, []));

  statements.push(updateDirtyElementStatement);

  if (storedFileInfo.hasLocalBuilderInFile) {

    statements.unshift(contextStackPushOrPop(ts.factory.createIdentifier(PUSH), [ts.factory.createThis()]));

    statements.push(contextStackPushOrPop(ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), []));

  }

  return ts.factory.createMethodDeclaration(undefined, undefined,

    ts.factory.createIdentifier(COMPONENT_RERENDER_FUNCTION), undefined, undefined, [], undefined,

    ts.factory.createBlock(statements, true));

}



function createParamsInitBlock(express: string, statements: ts.Statement[],

  parentComponentName?: ts.Identifier): ts.MethodDeclaration {

  const methodDeclaration: ts.MethodDeclaration = ts.factory.createMethodDeclaration(

    undefined, undefined, ts.factory.createIdentifier(express), undefined, undefined,

    [ts.factory.createParameterDeclaration(undefined, undefined,

      express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined :

        ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), undefined,

      express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined :

        ts.factory.createTypeReferenceNode(

          ts.factory.createIdentifier(parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined),

      undefined)], undefined, ts.factory.createBlock(statements, true));

  return methodDeclaration;

}



export function validateBuildMethodCount(buildCount: BuildCount, parentComponentName: ts.Identifier,

  log: LogInfo[]): void {

  if (buildCount.count !== 1) {

    log.push({

      type: LogType.ERROR,

      message: `The struct '${parentComponentName.getText()}' must have at least and at most one 'build' method.`,

      pos: parentComponentName.getStart(),

      code: '10905103',

      solutions: [`A structurally modified page must have at least one and no more than one 'build' method.`]

    });

  }

}



function validateHasControllerAndControllerCount(componentName: ts.Identifier, checkController: ControllerType,

  log: LogInfo[]): void {

  if (!checkController.hasController) {

    log.push({

      type: LogType.ERROR,

      message: `The '@CustomDialog' decorated custom component must contain a property of the CustomDialogController type.`,

      pos: componentName.pos,

      code: '10905211'

    });

  }

  if (checkController.unassignedControllerSet.size >= 2) {

    log.push({

      type: LogType.WARN,

      message: 'A @CustomDialog component can only have one uninitialized CustomDialogController.',

      pos: componentName.pos

    });

  }

}



function createHeritageClause(isComponentV2: boolean = false): ts.HeritageClause {

  if (partialUpdateConfig.partialUpdateMode) {

    return ts.factory.createHeritageClause(

      ts.SyntaxKind.ExtendsKeyword,

      [ts.factory.createExpressionWithTypeArguments(

        ts.factory.createIdentifier(isComponentV2 ? constantDefine.STRUCT_PARENT : BASE_COMPONENT_NAME_PU),

        [])]

    );

  }

  return ts.factory.createHeritageClause(

    ts.SyntaxKind.ExtendsKeyword,

    [ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier(BASE_COMPONENT_NAME), [])]

  );

}



function createTypeReference(decoratorName: string, type: ts.TypeNode, log: LogInfo[],

  program: ts.Program): ts.TypeNode {

  let newType: ts.TypeNode;

  switch (decoratorName) {

    case COMPONENT_STATE_DECORATOR:

    case COMPONENT_PROVIDE_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        isSimpleType(type, program, log) ?

          OBSERVED_PROPERTY_SIMPLE :

          OBSERVED_PROPERTY_OBJECT,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_LINK_DECORATOR:

    case COMPONENT_CONSUME_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        isSimpleType(type, program, log) ?

          SYNCHED_PROPERTY_SIMPLE_TWO_WAY :

          SYNCHED_PROPERTY_SIMPLE_ONE_WAY,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_PROP_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        SYNCHED_PROPERTY_SIMPLE_ONE_WAY,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_OBJECT_LINK_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        SYNCHED_PROPERTY_NESED_OBJECT,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_STORAGE_PROP_DECORATOR:

    case COMPONENT_STORAGE_LINK_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [

        type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)

      ]);

      break;

    case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR:

    case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [

        type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)

      ]);

      break;

  }

  return newType;

}



function createTypeReferencePU(decoratorName: string, type: ts.TypeNode, log: LogInfo[],

  program: ts.Program): ts.TypeNode {

  let newType: ts.TypeNode;

  switch (decoratorName) {

    case COMPONENT_STATE_DECORATOR:

    case COMPONENT_PROVIDE_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        isSimpleType(type, program, log) ?

          OBSERVED_PROPERTY_SIMPLE_PU :

          OBSERVED_PROPERTY_OBJECT_PU,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_LINK_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        isSimpleType(type, program, log) ?

          SYNCHED_PROPERTY_SIMPLE_TWO_WAY_PU :

          SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_PROP_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        SYNCHED_PROPERTY_SIMPLE_ONE_WAY_PU,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_OBJECT_LINK_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(

        SYNCHED_PROPERTY_NESED_OBJECT_PU,

        [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]

      );

      break;

    case COMPONENT_CONSUME_DECORATOR:

    case COMPONENT_STORAGE_PROP_DECORATOR:

    case COMPONENT_STORAGE_LINK_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT_PU, [

        type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)

      ]);

      break;

    case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR:

    case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR:

      newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT_PU, [

        type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)

      ]);

      break;

  }

  return newType;

}



export function checkFinalizeConstruction(): ts.Statement {

  return ts.factory.createIfStatement(

    ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken,

      ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(

        ts.factory.createStringLiteral(FINALIZE_CONSTRUCTION),

        ts.factory.createToken(ts.SyntaxKind.InKeyword), ts.factory.createPropertyAccessExpression(

          ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), ts.factory.createIdentifier(PROTOTYPE))))

    ),

    ts.factory.createBlock(

      [

        ts.factory.createExpressionStatement(ts.factory.createCallExpression(

          ts.factory.createPropertyAccessExpression(

            ts.factory.createIdentifier(REFLECT), ts.factory.createIdentifier(CREATE_SET_METHOD)

          ), undefined,

          [

            ts.factory.createPropertyAccessExpression(

              ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), ts.factory.createIdentifier(PROTOTYPE)

            ),

            ts.factory.createStringLiteral(FINALIZE_CONSTRUCTION),

            ts.factory.createArrowFunction(undefined, undefined, [], undefined,

              ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),

              ts.factory.createBlock([], false)

            )

          ]

        ))

      ], true), undefined);

}



export function checkContextStack(): ts.Statement {

  return ts.factory.createIfStatement(

    ts.factory.createBinaryExpression(

      ts.factory.createPropertyAccessExpression(

        ts.factory.createIdentifier(PUV2_VIEW_BASE),

        ts.factory.createIdentifier(CONTEXT_STACK)

      ),

      ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),

      ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)

    ),

    ts.factory.createBlock(

      [ts.factory.createExpressionStatement(ts.factory.createCallExpression(

        ts.factory.createPropertyAccessExpression(

          ts.factory.createIdentifier(REFLECT),

          ts.factory.createIdentifier(CREATE_SET_METHOD)

        ),

        undefined,

        [

          ts.factory.createIdentifier(PUV2_VIEW_BASE),

          ts.factory.createStringLiteral(CONTEXT_STACK),

          ts.factory.createArrayLiteralExpression(

            [],

            false

          )

        ]

      ))],

      true

    ),

    undefined

  );

}



export function contextStackPushOrPop(pushOrPop: ts.Identifier, param: ts.ThisExpression[] | []): ts.ExpressionStatement {

  return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(

    ts.factory.createPropertyAccessExpression(

      ts.factory.createIdentifier(PUV2_VIEW_BASE),

      ts.factory.createIdentifier(CONTEXT_STACK)

    ),

    ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),

    ts.factory.createCallExpression(

      ts.factory.createPropertyAccessExpression(

        ts.factory.createPropertyAccessExpression(

          ts.factory.createIdentifier(PUV2_VIEW_BASE),

          ts.factory.createIdentifier(CONTEXT_STACK)

        ),

        pushOrPop

      ),

      undefined,

      param

    )

  ));

}