/*
 * Copyright (c) 2024 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 {
  CurrentProcessFile,
  LogInfo,
  LogType,
  addLog,
  removeDecorator
} from './utils';
import {
  COMPONENT_CONSTRUCTOR_PARENT,
  COMPONENT_CONSTRUCTOR_PARAMS,
  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU,
  ELMTID,
  COMPONENT_PARAMS_LAMBDA_FUNCTION,
  CUSTOM_COMPONENT_EXTRAINFO,
  COMPONENT_CONSTRUCTOR_UNDEFINED,
  REUSABLE_V2_INNER_DECORATOR,
  REFLECT,
  DEFINE_PROPERTY,
  BASE_CLASS,
  PROTOTYPE,
  IS_REUSABLE_,
  GET_ATTRIBUTE,
  COMPONENT_ENV_DECORATOR,
  COMPONENT_REQUIRE_DECORATOR,
  COMPONENT_CUSTOM_ENV_DECORATOR
} from './pre_define';
import constantDefine from './constant_define';
import createAstNodeUtils from './create_ast_node_utils';
import {
  updateHeritageClauses,
  processComponentMethod,
  BuildCount,
  addRerenderFunc,
  validateBuildMethodCount,
  getEntryNameFunction,
  FreezeParamType,
  decoratorAssignParams,
  processComponentReusePool,
  decoratorAssignParamsForReusable,
  decoratorComponentParamForReusable,
  ComponentParamType
} from './process_component_class';
import { isReuseInV2 } from './process_custom_component';
import { judgeBuilderParamAssignedByBuilder } from './process_component_member';
import {
  componentCollection,
  builderParamObjectCollection,
  validateStmgmtKeywords,
  checkEnvType,
  validateEnvType,
  checkEnvDecoratorExp,
  EnvTypeName,
  ENV_ARG_STATUS,
  preCheckEnvDecoratorArg,
  checkNonSystemEnvDecoratorExp,
  CustomEnvTypeName,
  checkCustomEnvType,
  checkCustomEnvDecoratorExp
} from './validate_ui_syntax';
import logMessageCollection from './log_message_collection';
import { globalProgram, partialUpdateConfig } from '../main';

export class ParamDecoratorInfo {
  initializer: ts.Expression;
  hasRequire: boolean = false;
}

export class StructInfo {
  isComponentV1: boolean = false;
  isComponentV2: boolean = false;
  isCustomDialog: boolean = false;
  isReusable: boolean = false;
  isReusableV2: boolean = false;
  structName: string = '';
  updatePropsDecoratorsV1: string[] = [];
  linkDecoratorsV1: string[] = [];
  paramDecoratorMap: Map<string, ParamDecoratorInfo> = new Map();
  eventDecoratorMap: Map<string, ts.PropertyDeclaration> = new Map();
  localDecoratorSet: Set<string> = new Set();
  providerDecoratorSet: Set<string> = new Set();
  consumerDecoratorSet: Set<string> = new Set();
  builderParamDecoratorSet: Set<string> = new Set();
  regularSet: Set<string> = new Set();
  propertiesMap: Map<string, ts.Expression> = new Map();
  staticPropertySet: Set<string> = new Set();
  computedDecoratorSet: Set<string> = new Set();
  monitorDecoratorSet: Set<string> = new Set();
  envDecoratorSet: Set<string> = new Set();
  customEnvDecoratorSet: Set<string> = new Set();
  syncMonitorDecoratorSet: Set<string> = new Set();
}

const structMapInEts: Map<string, StructInfo> = new Map();

function getOrCreateStructInfo(key: string): StructInfo {
  let structInfo: StructInfo = structMapInEts.get(key);
  if (!structInfo) {
    structInfo = new StructInfo();
    structInfo.structName = key;
    structMapInEts.set(key, structInfo);
  }
  return structInfo;
}

/**
 * import * as a from './common'
 * a.struct()
 */
function getAliasStructInfo(node: ts.CallExpression): StructInfo {
  let aliasStructInfo: StructInfo;
  if (node.expression && structMapInEts.has(node.expression.getText())) {
    aliasStructInfo = structMapInEts.get(node.expression.getText());
  }
  return aliasStructInfo;
}

function processStructComponentV2(node: ts.StructDeclaration, log: LogInfo[],
  context: ts.TransformationContext, StateManagementV2: { hasReusableV2: boolean },
  ReusePool: { hasReusePool: boolean }): ts.ClassDeclaration {
  const isReusableV2: boolean = node.name && ts.isIdentifier(node.name) && isReuseInV2(node.name.getText());
  if (isReusableV2) {
    StateManagementV2.hasReusableV2 = true;
  }
  return ts.factory.createClassDeclaration(isReusableV2 ?
    ts.concatenateDecoratorsAndModifiers(
      [ts.factory.createDecorator(ts.factory.createIdentifier(REUSABLE_V2_INNER_DECORATOR))],
      ts.getModifiers(node)
    ) :
    ts.getModifiers(node),
    node.name,
    node.typeParameters,
    updateHeritageClauses(node, log, true),
    processStructMembersV2(node, context, log, ReusePool)
  );
}

function createReusableV2ReflectFunction(): ts.FunctionDeclaration {
  const reflectStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement(
    ts.factory.createCallExpression(
      ts.factory.createPropertyAccessExpression(
        ts.factory.createIdentifier(REFLECT), ts.factory.createIdentifier(DEFINE_PROPERTY)
      ), undefined,
      [
        ts.factory.createPropertyAccessExpression(
          ts.factory.createIdentifier(BASE_CLASS), ts.factory.createIdentifier(PROTOTYPE)
        ),
        ts.factory.createStringLiteral(IS_REUSABLE_),
        ts.factory.createObjectLiteralExpression([
          ts.factory.createPropertyAssignment(
            ts.factory.createIdentifier(GET_ATTRIBUTE),
            ts.factory.createArrowFunction(
              undefined,
              undefined,
              [],
              undefined,
              ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
              ts.factory.createTrue()
            )
          )], 
          false
        )
      ]
    )
  );
  const parameter: ts.ParameterDeclaration = ts.factory.createParameterDeclaration(
    undefined,
    undefined,
    ts.factory.createIdentifier(BASE_CLASS),
    undefined,
    undefined,
    undefined
  );
  return ts.factory.createFunctionDeclaration(
    undefined,
    undefined,
    ts.factory.createIdentifier(REUSABLE_V2_INNER_DECORATOR),
    undefined,
    [parameter],
    undefined,
    ts.factory.createBlock([reflectStatement])
  );
}

function processStructMembersV2(node: ts.StructDeclaration, context: ts.TransformationContext,
  log: LogInfo[], ReusePool: { hasReusePool: boolean }): ts.ClassElement[] {
  const structName: string = node.name.getText();
  const newMembers: ts.ClassElement[] = [];
  const buildCount: BuildCount = { count: 0 };
  const structInfo: StructInfo = getOrCreateStructInfo(structName);
  const addStatementsInConstructor: ts.Statement[] = [];
  const addStatementsInResetOnReuse: ts.Statement[] = [];
  const paramStatementsInStateVarsMethod: ts.Statement[] = [];
  const structDecorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
  const componentParamType: ComponentParamType = { componentFreezeParam: undefined, memOptParam: undefined };
  decoratorAssignParams(structDecorators, context, componentParamType);
  traverseStructInfo(structInfo, addStatementsInConstructor, paramStatementsInStateVarsMethod, addStatementsInResetOnReuse);
  if (partialUpdateConfig.partialUpdateMode && decoratorAssignParamsForReusable(structDecorators, context, componentParamType)) {
    addStatementsInConstructor.push(...decoratorComponentParamForReusable(componentParamType));
  }
  node.members.forEach((member: ts.ClassElement) => {
    if (ts.isGetAccessor(member) && member.modifiers?.some(isComputedDecorator) && member.name &&
      ts.isIdentifier(member.name)) {
      const checker: ts.TypeChecker | undefined = CurrentProcessFile.getChecker();
      const symbol: ts.Symbol = checker?.getSymbolAtLocation(member.name);
      validateComputedGetter(symbol, log);
    }
    if (ts.isConstructorDeclaration(member)) {
      processStructConstructorV2(node.members, newMembers, addStatementsInConstructor, componentParamType);
      createResetStateVarsOnReuse(structInfo, newMembers, addStatementsInResetOnReuse);
      return;
    } else if (ts.isPropertyDeclaration(member)) {
      newMembers.push(processComponentProperty(member, structInfo, log));
      return;
    } else if (ts.isMethodDeclaration(member) && member.name) {
      const newMethodNode: ts.MethodDeclaration = processComponentMethod(member, context, log, buildCount);
      if (newMethodNode) {
        newMembers.push(newMethodNode);
      }
      return;
    }
    newMembers.push(member);
  });
  validateBuildMethodCount(buildCount, node.name, log);
  updateStateVarsMethodNode(paramStatementsInStateVarsMethod, newMembers);
  if (structDecorators && Array.isArray(structDecorators) && structDecorators.length) {
    processComponentReusePool(newMembers, structDecorators, true, ReusePool, structName);
  }
  newMembers.push(addRerenderFunc([]));
  if (componentCollection.entryComponent === structName) {
    newMembers.push(getEntryNameFunction(componentCollection.entryComponent));
  }
  return newMembers;
}

function isComputedDecorator(decorator: ts.Decorator): boolean {
  return ts.isDecorator(decorator) && ts.isIdentifier(decorator.expression) &&
    decorator.expression.escapedText.toString() === constantDefine.COMPUTED;
}

function validateComputedGetter(symbol: ts.Symbol, log: LogInfo[]): void {
  if (symbol && symbol.declarations) {
    symbol.declarations.forEach((declaration: ts.Declaration) => {
      logMessageCollection.checkComputedGetter(symbol, declaration, log);
    });
  }
}

function traverseStructInfo(structInfo: StructInfo,
  addStatementsInConstructor: ts.Statement[], paramStatementsInStateVarsMethod: ts.Statement[],
  addStatementsInResetOnReuse: ts.Statement[]): void {
  const needInitFromParams: string[] = [...structInfo.builderParamDecoratorSet,
    ...structInfo.eventDecoratorMap.keys()];
  for (const property of structInfo.propertiesMap) {
    if (!structInfo.staticPropertySet.has(property[0]) &&
      !structInfo.envDecoratorSet.has(property[0]) &&
      !structInfo.customEnvDecoratorSet.has(property[0])) {
      setPropertyStatement(structInfo, addStatementsInConstructor, property[0], property[1],
        needInitFromParams, addStatementsInResetOnReuse);
    }
  }
  for (const param of structInfo.paramDecoratorMap) {
    if (!structInfo.staticPropertySet.has(param[0]) &&
      !structInfo.builderParamDecoratorSet.has(param[0])) {
      paramStatementsInStateVarsMethod.push(updateParamNode(param[0]));
    }
  }
}

function setPropertyStatement(structInfo: StructInfo, addStatementsInConstructor: ts.Statement[],
  propName: string, initializer: ts.Expression, needInitFromParams: string[], 
  addStatementsInResetOnReuse: ts.Statement[]): void {
  if (needInitFromParams.includes(propName)) {
    if (structInfo.eventDecoratorMap.has(propName)) {
      const eventDeclaration: ts.PropertyDeclaration = structInfo.eventDecoratorMap.get(propName);
      addStatementsInConstructor.push(
        createPropertyAssignNode(propName, initializer || getDefaultValueForEvent(eventDeclaration, false), true));
      addStatementsInResetOnReuse.push(
        createPropertyAssignNode(propName, initializer || getDefaultValueForEvent(eventDeclaration, true), true));
    } else {
      const builderParamExpression: ts.ExpressionStatement = createPropertyAssignNode(propName, initializer, true);
      addStatementsInConstructor.push(builderParamExpression);
      addStatementsInResetOnReuse.push(builderParamExpression);
    }
  } else if (structInfo.paramDecoratorMap.has(propName)) {
    const paramProperty: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(propName);
    addStatementsInConstructor.push(createInitOrUpdateParam(propName, paramProperty.initializer, true));
    addStatementsInResetOnReuse.push(createInitOrUpdateParam(propName, paramProperty.initializer, false));
  } else if (structInfo.consumerDecoratorSet.has(propName)) {
    addStatementsInConstructor.push(createPropertyAssignNode(propName, initializer, false));
    addStatementsInResetOnReuse.push(createResetNode(propName, constantDefine.RESET_CONSUMER, initializer));
  } else if (structInfo.regularSet.has(propName)) {
    addStatementsInConstructor.push(createPropertyAssignNode(propName, initializer, false));
  } else if (structInfo.computedDecoratorSet.has(propName)) {
    addStatementsInResetOnReuse.push(createResetNode(propName, constantDefine.RESET_COMPUTED));
  } else {
    const propertyAssignNode: ts.ExpressionStatement = createPropertyAssignNode(propName, initializer, false);
    addStatementsInConstructor.push(propertyAssignNode);
    addStatementsInResetOnReuse.push(propertyAssignNode);
  }
}

function getDefaultValueForEvent(node: ts.PropertyDeclaration, isResetOnReuse: boolean = false): ts.Expression {
  let param: ts.NodeArray<ts.ParameterDeclaration>;
  if (node.type && ts.isFunctionTypeNode(node.type) && node.type.parameters && node.type.parameters.length) {
    param = node.type.parameters;
  }
  return ts.factory.createArrowFunction(undefined, undefined, isResetOnReuse && param ? param : [], undefined,
    ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
    ts.factory.createBlock([], false));
}

function createPropertyAssignNode(propName: string, initializer: ts.Expression,
  initFromParams: boolean): ts.ExpressionStatement {
  return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(
    ts.factory.createPropertyAccessExpression(
      ts.factory.createThis(),
      ts.factory.createIdentifier(propName)
    ),
    ts.factory.createToken(ts.SyntaxKind.EqualsToken),
    setInitValue(propName, initializer, initFromParams)
  ));
}

function createResetNode(propName: string, type: string, initializer: ts.Expression = null): ts.ExpressionStatement {
  return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
    ts.factory.createPropertyAccessExpression(
      ts.factory.createThis(),
      ts.factory.createIdentifier(type)
    ),
    undefined,
    type === constantDefine.RESET_CONSUMER ? 
    [
      ts.factory.createStringLiteral(propName),
      initializer ? initializer : ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
    ] : [
      ts.factory.createStringLiteral(propName)
    ]
  ));
}

function processComponentProperty(member: ts.PropertyDeclaration, structInfo: StructInfo,
  log: LogInfo[]): ts.PropertyDeclaration {
  const propName: string = member.name.getText();
  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member);
  let initializer: ts.Expression;
  if (!structInfo.regularSet.has(propName) && !member.type) {
    checkV2ComponentMemberType(member.name, propName, log);
  }
  if (structInfo.staticPropertySet.has(propName)) {
    initializer = member.initializer;
  }
  if (structInfo.envDecoratorSet.has(propName)) {
    checkEnvInitializerV2(member, log);
    const envDecorator: ts.Decorator | undefined = decorators.find(decorator => getDecoratorName(decorator) === COMPONENT_ENV_DECORATOR);
    const propertyType: ts.Type | undefined = CurrentProcessFile.getChecker()?.getTypeAtLocation?.(member);
    const envTypeName: EnvTypeName = { currentTypeName: '', envArgStatus: ENV_ARG_STATUS.SYSTEM_PROPERTIES };
    envDecorator && preCheckEnvDecoratorArg(envDecorator, envTypeName);
    if (envTypeName.envArgStatus === ENV_ARG_STATUS.WRITABLE_SYSTEM_ENV ||
      envTypeName.envArgStatus === ENV_ARG_STATUS.READONLY_SYSTEM_ENV) {
      checkNonSystemEnvDecoratorExp(envDecorator, member.type);
    } else if (propertyType && !checkEnvType(propertyType, envTypeName)) {
      validateEnvType(member);
    } else if (propertyType && envDecorator) {
      checkEnvDecoratorExp(envDecorator, envTypeName.currentTypeName);
    }
  }
  if (structInfo.customEnvDecoratorSet.has(propName)) {
    const customEnvDecorator: ts.Decorator | undefined =
      decorators.find(decorator => getDecoratorNameFromAst(decorator) === COMPONENT_CUSTOM_ENV_DECORATOR);
    const customEnvTypeNames: CustomEnvTypeName = { currentTypeNames: [] };
    checkCustomEnvType(member.type, customEnvTypeNames);
    if (customEnvDecorator) {
      checkCustomEnvDecoratorExp(customEnvDecorator, customEnvTypeNames.currentTypeNames);
    }
    return ts.factory.updatePropertyDeclaration(member,
      ts.concatenateDecoratorsAndModifiers(decorators, ts.getModifiers(member)),
      member.name, member.questionToken, member.type, member.initializer);
  }
  if (structInfo.paramDecoratorMap.has(propName) && structInfo.builderParamDecoratorSet.has(propName)) {
    return processRequireBuilderParamProperty(member, decorators, initializer);
  }
  if (structInfo.paramDecoratorMap.has(propName)) {
    return processParamProperty(member, decorators, initializer);
  }
  if (structInfo.builderParamDecoratorSet.has(propName)) {
    return processBuilderParamProperty(member, log, decorators, initializer);
  }
  return ts.factory.updatePropertyDeclaration(member,
    ts.concatenateDecoratorsAndModifiers(decorators, ts.getModifiers(member)),
    member.name, member.questionToken, member.type, initializer);
}

function checkV2ComponentMemberType(node: ts.Node, propName: string, log: LogInfo[]): void {
  log.push({
    type: LogType.ERROR,
    message: `The property '${propName}' must specify a type.`,
    pos: node.getStart(),
    code: '10905328'
  });
}

function checkEnvInitializerV2(member: ts.PropertyDeclaration, log: LogInfo[]): void {
  if (member.initializer) {
    log.push({
      type: LogType.ERROR,
      message: `The '@Env' property cannot be specified a default value.`,
      pos: member.getStart(),
      code: '10905362'
    })
  }
}

function processParamProperty(member: ts.PropertyDeclaration,
  decorators: readonly ts.Decorator[], initializer: ts.Expression): ts.PropertyDeclaration {
  const newDecorators: readonly ts.Decorator[] = removeDecorator(decorators, constantDefine.REQUIRE);
  return ts.factory.updatePropertyDeclaration(member,
    ts.concatenateDecoratorsAndModifiers(newDecorators, ts.getModifiers(member)),
    member.name, member.questionToken, member.type, initializer);
}

function processRequireBuilderParamProperty(member: ts.PropertyDeclaration,
  decorators: readonly ts.Decorator[], initializer: ts.Expression): ts.PropertyDeclaration {
  const tempDecorators: readonly ts.Decorator[] = removeDecorator(decorators, constantDefine.REQUIRE);
  const newDecorators: readonly ts.Decorator[] = removeDecorator(tempDecorators, constantDefine.BUILDER_PARAM);
  return ts.factory.updatePropertyDeclaration(member,
    ts.concatenateDecoratorsAndModifiers(newDecorators, ts.getModifiers(member)),
    member.name, member.questionToken, member.type, initializer);
}

function processBuilderParamProperty(member: ts.PropertyDeclaration, log: LogInfo[],
  decorators: readonly ts.Decorator[], initializer: ts.Expression): ts.PropertyDeclaration {
  if (judgeBuilderParamAssignedByBuilder(member)) {
    log.push({
      type: LogType.ERROR,
      message: `'@BuilderParam' property can only initialized by '@Builder' function.`,
      pos: member.getStart(),
      code: '10905107'
    });
  }
  const newDecorators: readonly ts.Decorator[] = removeDecorator(decorators, constantDefine.BUILDER_PARAM);
  return ts.factory.updatePropertyDeclaration(member,
    ts.concatenateDecoratorsAndModifiers(newDecorators, ts.getModifiers(member)),
    member.name, member.questionToken, member.type, initializer);
}

function setInitValue(propName: string, initializer: ts.Expression,
  initFromParams: boolean): ts.Expression {
  let initNode: ts.Expression = initializer ||
    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED);
  if (initFromParams) {
    initNode = createInitNode(propName, initNode);
  }
  return initNode;
}

function createInitNode(propName: string, defaultValue: ts.Expression): ts.Expression {
  return ts.factory.createConditionalExpression(
    ts.factory.createBinaryExpression(
      ts.factory.createStringLiteral(propName),
      ts.factory.createToken(ts.SyntaxKind.InKeyword),
      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)
    ),
    ts.factory.createToken(ts.SyntaxKind.QuestionToken),
    ts.factory.createPropertyAccessExpression(
      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS),
      ts.factory.createIdentifier(propName)
    ),
    ts.factory.createToken(ts.SyntaxKind.ColonToken),
    defaultValue
  );
}

function parseComponentProperty(node: ts.StructDeclaration, structInfo: StructInfo, log: LogInfo[],
  sourceFileNode: ts.SourceFile): void {
  node.members.forEach((member: ts.ClassElement) => {
    if (sourceFileNode && member && member.name && ts.isIdentifier(member.name)) {
      validateStmgmtKeywords(member.name.getText(), member.name);
    }
    if (ts.isPropertyDeclaration(member)) {
      const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member);
      const modifiers: readonly ts.Modifier[] = ts.getModifiers(member);
      structInfo.propertiesMap.set(member.name.getText(), member.initializer);
      parsePropertyDecorator(member, decorators, structInfo, log, sourceFileNode);
      parsePropertyModifiers(member.name.getText(), structInfo, modifiers);
    } else if (ts.isGetAccessor(member)) {
      const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member);
      parseGetAccessor(member, decorators, structInfo);
    } else if (ts.isMethodDeclaration(member)) {
      const decorators: readonly ts.Decorator[] = ts.getAllDecorators(member);
      parseMethodDeclaration(member, decorators, structInfo, sourceFileNode, log);
    }
  });
}

function parseGetAccessor(member: ts.GetAccessorDeclaration, decorators: readonly ts.Decorator[],
  structInfo: StructInfo): void {
  if (decorators.length && decorators.some((value: ts.Decorator) => getDecoratorName(value) === constantDefine.COMPUTED_DECORATOR)) {
    structInfo.propertiesMap.set(member.name.getText(), undefined);
    structInfo.computedDecoratorSet.add(member.name.getText());
  }
}

function parseMethodDeclaration(member: ts.MethodDeclaration, decorators: readonly ts.Decorator[],
  structInfo: StructInfo, sourceFileNode: ts.sourceFile, log: LogInfo[]): void {
  if (!decorators.length) {
    return;
  }
  let hasRequire: boolean = false;
  let hasSyncMonitor: boolean = false;
  decorators.forEach(decorator => {
    const decoratorName: string = getDecoratorName(decorator);
    if(decoratorName === constantDefine.MONITOR_DECORATOR) {
      structInfo.monitorDecoratorSet.add(member.name.getText());
    } else if (decoratorName === COMPONENT_REQUIRE_DECORATOR) {
      hasRequire = true;
    } else if (decoratorName === constantDefine.SYNC_MONITOR_DECORATOR) {
      structInfo.syncMonitorDecoratorSet.add(member.name.getText());
      hasSyncMonitor = true;
    }
  });
  hasRequire && hasSyncMonitor && checkRequireDecoratorV2(member, log, sourceFileNode);
}

function checkRequireDecoratorV2(member: ts.MethodDeclaration | ts.PropertyDeclaration, log: LogInfo[],
  sourceFileNode: ts.sourceFile): void {
  const message: string = 'In a struct decorated with \'@ComponentV2\', \'@Require\' can only be used with \'@Param\'' +
    ' and \'@BuilderParam\'.';
  addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905325' });
}

export function getDecoratorName(decorator: ts.Decorator): string {
  return decorator.getText().replace(/\([^\(\)]*\)/, '').trim();
}

export function getDecoratorNameFromAst(
  decorator: ts.Decorator
): string {
  function getIdentifierName(node: ts.Node): string {
    if (ts.isIdentifier(node)) {
      return `@` + node.text;
    }
    if (ts.isPropertyAccessExpression(node)) {
      return getIdentifierName(node.name);
    }
    return '';
  }

  try {
    const expr: ts.Exprssion = decorator.expression;

    if (ts.isCallExpression(expr)) {
      return getIdentifierName(expr.expression);
    }

    return getIdentifierName(expr);
  } catch (err) {
    return '';
  }
}

function parsePropertyModifiers(propName: string, structInfo: StructInfo,
  modifiers: readonly ts.Modifier[]): void {
  if (modifiers && modifiers.length) {
    const isStatic: boolean = modifiers.some((item: ts.Modifier) => {
      return item.kind === ts.SyntaxKind.StaticKeyword;
    });
    if (isStatic) {
      structInfo.staticPropertySet.add(propName);
    }
  }
}

class PropertyDecorator {
  hasParam: boolean = false;
  hasRequire: boolean = false;
  hasOnce: boolean = false;
  hasEvent: boolean = false;
  hasEnv: boolean = false;
  hasCustomEnv: boolean = false;
}

const decoratorsFunc: Record<string, Function> = {
  'Param': parseParamDecorator,
  'Event': parseEventDecorator,
  'Require': parseRequireDecorator,
  'Once': parseOnceDecorator,
  'Local': parseLocalDecorator,
  'BuilderParam': parseBuilderParamDecorator,
  'Provider': parseProviderDecorator,
  'Consumer': parseConsumerDecorator,
  'Env': parseEnvDecorator,
};

const astDecoratorsFunc: Record<string, Function> = {
  'CustomEnv': parseCustomEnvDecorator,
};

function parsePropertyDecorator(member: ts.PropertyDeclaration, decorators: readonly ts.Decorator[],
  structInfo: StructInfo, log: LogInfo[], sourceFileNode: ts.SourceFile): void {
  const propertyDecorator: PropertyDecorator = new PropertyDecorator();
  let isRegular: boolean = true;
  for (let i = 0; i < decorators.length; i++) {
    const originalName: string = getDecoratorName(decorators[i]);
    const name: string = originalName.replace('@', '').trim();
    const originAstName: string = getDecoratorNameFromAst(decorators[i]);
    const astName: string = originAstName.replace('@', '').trim();
    if (decoratorsFunc[name]) {
      decoratorsFunc[name](propertyDecorator, member, structInfo);
    }
    if (astDecoratorsFunc[astName]) {
      astDecoratorsFunc[astName](propertyDecorator, member, structInfo);
    }
    if (constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(originalName) ||
      originalName === constantDefine.DECORATOR_BUILDER_PARAM ||
      originalName === constantDefine.DECORATOR_ENV) {
      isRegular = false;
    }
  }
  if (isRegular) {
    structInfo.regularSet.add(member.name.getText());
  }
  checkPropertyDecorator(propertyDecorator, member, log, sourceFileNode, structInfo);
}

function checkPropertyDecorator(propertyDecorator: PropertyDecorator,
  member: ts.PropertyDeclaration, log: LogInfo[], sourceFileNode: ts.SourceFile,
  structInfo: StructInfo): void {
  if (log && sourceFileNode) {
    checkParamDecorator(propertyDecorator, member, log, sourceFileNode, structInfo);
  }
}

function parseParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  propertyDecorator.hasParam = true;
  let paramDecoratorInfo: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(member.name.getText());
  if (!paramDecoratorInfo) {
    paramDecoratorInfo = new ParamDecoratorInfo();
  }
  paramDecoratorInfo.initializer = member.initializer;
  structInfo.paramDecoratorMap.set(member.name.getText(), paramDecoratorInfo);
}

function parseEventDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  propertyDecorator.hasEvent = true;
  structInfo.eventDecoratorMap.set(member.name.getText(), member);
}

function parseRequireDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  propertyDecorator.hasRequire = true;
  let paramDecoratorInfo: ParamDecoratorInfo = structInfo.paramDecoratorMap.get(member.name.getText());
  if (!paramDecoratorInfo) {
    paramDecoratorInfo = new ParamDecoratorInfo();
  }
  paramDecoratorInfo.hasRequire = true;
  structInfo.paramDecoratorMap.set(member.name.getText(), paramDecoratorInfo);
}

function parseEnvDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  propertyDecorator.hasEnv = true;
  structInfo.envDecoratorSet.add(member.name.getText());
}

function parseCustomEnvDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  propertyDecorator.hasCustomEnv = true;
  structInfo.customEnvDecoratorSet.add(member.name.getText());
}

function parseOnceDecorator(propertyDecorator: PropertyDecorator): void {
  propertyDecorator.hasOnce = true;
}

function parseLocalDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  structInfo.localDecoratorSet.add(member.name.getText());
}

function parseProviderDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  structInfo.providerDecoratorSet.add(member.name.getText());
}

function parseConsumerDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  structInfo.consumerDecoratorSet.add(member.name.getText());
}

function parseBuilderParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  structInfo: StructInfo): void {
  let builderParamSet: Set<string> = builderParamObjectCollection.get(structInfo.structName);
  if (!builderParamSet) {
    builderParamSet = new Set();
  }
  builderParamSet.add(member.name.getText());
  builderParamObjectCollection.set(structInfo.structName, builderParamSet);
  structInfo.builderParamDecoratorSet.add(member.name.getText());
}

function checkHasBuilderParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration,
  sourceFileNode: ts.SourceFile, structInfo: StructInfo): boolean {
  let checkResult: boolean = false;
  if (StructInfo) {
    checkResult = structInfo.builderParamDecoratorSet.has(member.name.getText());
  }
  return checkResult;
}

function checkParamDecorator(propertyDecorator: PropertyDecorator, member: ts.PropertyDeclaration, log: LogInfo[],
  sourceFileNode: ts.SourceFile, structInfo: StructInfo): void {
  if (propertyDecorator.hasParam && !member.initializer && !propertyDecorator.hasRequire) {
    const message: string = 'When a variable decorated with \'@Param\' is not assigned a default value, ' +
      'it must also be decorated with \'@Require\'.';
    addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905327' });
  }
  if (propertyDecorator.hasOnce && !propertyDecorator.hasParam) {
    const message: string = 'When a variable decorated with \'@Once\', it must also be decorated with \'@Param\'.';
    addLog(LogType.ERROR, message, member.getStart(), log, sourceFileNode, { code: '10905326' });
  }
  if (propertyDecorator.hasRequire && !propertyDecorator.hasParam && !checkHasBuilderParamDecorator(propertyDecorator,
    member, sourceFileNode, structInfo)) {
    checkRequireDecoratorV2(member, log, sourceFileNode);
  }
}

function processStructConstructorV2(members: ts.NodeArray<ts.ClassElement>, newMembers: ts.ClassElement[],
  paramStatements: ts.Statement[], freezeParam: FreezeParamType): void {
  const freezeParamNode: ts.Expression = freezeParam.componentFreezeParam ?
    freezeParam.componentFreezeParam : undefined;
  const constructorIndex: number = members.findIndex((item: ts.ClassElement) => {
    return ts.isConstructorDeclaration(item);
  });
  if (constructorIndex !== -1) {
    const constructorNode: ts.ConstructorDeclaration = members[constructorIndex] as ts.ConstructorDeclaration;
    newMembers.splice(constructorIndex, 0, ts.factory.updateConstructorDeclaration(constructorNode, ts.getModifiers(constructorNode),
      createConstructorParams(), updateConstructorBody(constructorNode.body, paramStatements, freezeParamNode)));
  }
}

function createConstructorParams(): ts.ParameterDeclaration[] {
  const paramNames: string[] = [COMPONENT_CONSTRUCTOR_PARENT, COMPONENT_CONSTRUCTOR_PARAMS,
    COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU, ELMTID, COMPONENT_PARAMS_LAMBDA_FUNCTION,
    CUSTOM_COMPONENT_EXTRAINFO];
  return paramNames.map((name: string) => {
    return createAstNodeUtils.createParameterDeclaration(name);
  });
}

function updateConstructorBody(node: ts.Block, paramStatements: ts.Statement[],
  freezeParamNode: ts.Expression): ts.Block {
  const body: ts.Statement[] = [createSuperV2()];
  if (node.statements) {
    body.push(...node.statements);
  }
  body.push(...paramStatements, createAstNodeUtils.createFinalizeConstruction(freezeParamNode));
  return ts.factory.createBlock(body, true);
}

function createResetStateVarsOnReuse(structInfo: StructInfo, newMembers: ts.ClassElement[], 
  addStatementsInResetOnReuse: ts.Statement[]): void {
  if (structInfo.monitorDecoratorSet.size) {
    addStatementsInResetOnReuse.push(generateResetMonitor());
  }
  const resetOnReuseNode: ts.MethodDeclaration = ts.factory.createMethodDeclaration(
    [ts.factory.createToken(ts.SyntaxKind.PublicKeyword)],
    undefined,
    ts.factory.createIdentifier(constantDefine.RESET_STATE_VARS_METHOD),
    undefined,
    undefined,
    [ts.factory.createParameterDeclaration(
      undefined,
      undefined,
      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS),
      undefined,
      ts.factory.createTypeReferenceNode(
        ts.factory.createIdentifier(constantDefine.OBJECT_TYPE),
        undefined
      ),
      undefined
    )],
    ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword),
    ts.factory.createBlock(
      addStatementsInResetOnReuse,
      true
    )
  );
  newMembers.push(resetOnReuseNode);
}

function generateResetMonitor(): ts.ExpressionStatement {
  return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
    ts.factory.createPropertyAccessExpression(
      ts.factory.createThis(),
      ts.factory.createIdentifier(constantDefine.RESET_MONITORS_ON_REUSE)
    ),
    undefined,
    []
  ));
}

function createSuperV2(): ts.Statement {
  const paramNames: string[] = [COMPONENT_CONSTRUCTOR_PARENT, ELMTID, CUSTOM_COMPONENT_EXTRAINFO];
  return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
    ts.factory.createSuper(), undefined, paramNames.map((name: string) => {
      return ts.factory.createIdentifier(name);
    })));
}

function createInitOrUpdateParam(propName: string, initializer: ts.Expression, isInit: boolean): ts.ExpressionStatement {
  return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
    ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
      ts.factory.createIdentifier(isInit ? constantDefine.INIT_PARAM : constantDefine.RESET_PARAM)),
    undefined,
    [
      ts.factory.createStringLiteral(propName),
      ts.factory.createConditionalExpression(ts.factory.createParenthesizedExpression(
        ts.factory.createBinaryExpression(ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS),
          ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
          createParamBinaryNode(propName)
        )),
      ts.factory.createToken(ts.SyntaxKind.QuestionToken),
      ts.factory.createPropertyAccessExpression(
        ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), ts.factory.createIdentifier(propName)
      ),
      ts.factory.createToken(ts.SyntaxKind.ColonToken),
      initializer || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
      )
    ]));
}

function updateParamNode(propName: string): ts.IfStatement {
  return ts.factory.createIfStatement(createParamBinaryNode(propName),
    ts.factory.createBlock([
      ts.factory.createExpressionStatement(ts.factory.createCallExpression(
        ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
          ts.factory.createIdentifier(constantDefine.UPDATE_PARAM)),
        undefined,
        [
          ts.factory.createStringLiteral(propName),
          ts.factory.createPropertyAccessExpression(
            ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS), ts.factory.createIdentifier(propName)
          )
        ]))], true));
}

function createParamBinaryNode(propName: string): ts.BinaryExpression {
  return ts.factory.createBinaryExpression(
    ts.factory.createStringLiteral(propName),
    ts.factory.createToken(ts.SyntaxKind.InKeyword),
    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)
  );
}

function updateStateVarsMethodNode(paramStatements: ts.Statement[], newMembers: ts.ClassElement[]): void {
  if (paramStatements.length) {
    newMembers.push(ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.PublicKeyword)],
      undefined, ts.factory.createIdentifier(constantDefine.UPDATE_STATE_VARS), undefined, undefined,
      [createAstNodeUtils.createParameterDeclaration(COMPONENT_CONSTRUCTOR_PARAMS)], undefined,
      ts.factory.createBlock([emptyJudgeForParamsNode(), ...paramStatements], true)));
  }
}

function emptyJudgeForParamsNode(): ts.IfStatement {
  return ts.factory.createIfStatement(ts.factory.createBinaryExpression(
    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS),
    ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
    ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
  ), ts.factory.createBlock([ts.factory.createReturnStatement(undefined)], true), undefined);
}

function resetStructMapInEts(): void {
  structMapInEts.clear();
}

export default {
  getOrCreateStructInfo: getOrCreateStructInfo,
  processStructComponentV2: processStructComponentV2,
  parseComponentProperty: parseComponentProperty,
  resetStructMapInEts: resetStructMapInEts,
  getAliasStructInfo: getAliasStructInfo,
  createReusableV2ReflectFunction: createReusableV2ReflectFunction
};