/*
 * Copyright (c) 2025 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 * as arkts from '@koalaui/libarkts';
import { annotation, backingField, expectName } from '../../common/arkts-utils';
import { DecoratorNames, ObservedNames, StateManagementTypes, LANGUAGE_VERSION } from '../../common/predefines';
import {
    BaseObservedPropertyTranslator,
    IBaseObservedPropertyTranslator,
    ObservedPropertyCachedTranslator,
    ObservedPropertyCachedTranslatorOptions,
    ObservedPropertyTranslator,
    ObservedPropertyTranslatorOptions,
} from './base';
import {
    collectStateManagementTypeImport,
    generateThisBacking,
    hasDecorator,
    hasDecoratorName,
    removeDecorator,
    findPropertyAccessModifierFlags
} from './utils';
import { ClassScopeInfo } from '../struct-translators/utils';
import { factory } from './factory';
import { factory as uiFactory } from '../ui-factory';
import { ImportCollector } from '../../common/import-collector';
import { MetaDataCollector } from '../../common/metadata-collector';
import { FileManager } from '../../common/file-manager';

function isObservedTypeDynamic(typeNode: arkts.TypeNode | undefined): boolean {
    if (!typeNode) {
        return false;
    }
    let ident: arkts.Identifier | undefined;
    if (arkts.isETSTypeReference(typeNode) &&
        typeNode.part &&
        typeNode.part.name &&
        arkts.isIdentifier(typeNode.part.name)) {
        ident = typeNode.part.name;
    }
    if (!ident) {
        return false;
    }
    const typeDecl = arkts.getDecl(ident);
    if (!typeDecl) {
        return false;
    }
    const program = arkts.getProgramFromAstNode(typeDecl);
    if (!program) {
        return false;
    }
    const fileManager = FileManager.getInstance();
    return fileManager.getLanguageVersionByFilePath(program.absoluteName) === LANGUAGE_VERSION.ARKTS_1_1;
}

export function getterBodyWithObservedV2TraceProperty(
    this: IObservedV2TraceTranslator,
    originalName: string,
    newName: string
): arkts.BlockStatement {
    const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.className);
    ImportCollector.getInstance().collectImport(StateManagementTypes.UI_UTILS);
    const observedMember: arkts.Expression = this.isStatic
        ? uiFactory.generateMemberExpression(classIdent.clone(), newName)
        : generateThisBacking(newName);
    const isTypeDynamic: boolean = isObservedTypeDynamic(this.propertyType);
    const hasPropertyValue: boolean = !!this.property?.value;
    let makeObservedArg: arkts.Expression;
    if (isTypeDynamic) {
        makeObservedArg = arkts.factory.createTSAsExpression(
            arkts.factory.createTSAsExpression(observedMember, uiFactory.createTypeReferenceFromString('Any'), false),
            uiFactory.createTypeReferenceFromString('object'),
            false
        );
    } else if (this.property && !hasPropertyValue) {
        makeObservedArg = arkts.factory.createTSAsExpression(observedMember, this.propertyType, false);
    } else {
        const isDefinitelyNonNull: boolean = this.isDefinitelyNonNull ?? !!this.property?.tsType?.definitelyNotETSNullish;
        makeObservedArg = isDefinitelyNonNull && !this.isStatic && !hasPropertyValue 
            ? arkts.factory.createTSNonNullExpression(observedMember)
            : observedMember;
    }
    const makeObservedCall: arkts.CallExpression = arkts.factory.createCallExpression(
        uiFactory.generateMemberExpression(
            arkts.factory.createIdentifier(StateManagementTypes.UI_UTILS),
            StateManagementTypes.MAKE_OBSERVED
        ),
        [makeObservedArg],
        undefined,
        false,
        false
    );
    const returnMember: arkts.ReturnStatement = isTypeDynamic
        ? arkts.factory.createReturnStatement(arkts.factory.createTSAsExpression(makeObservedCall, this.propertyType, false))
        : arkts.factory.createReturnStatement(makeObservedCall);
    return arkts.factory.createBlockStatement([createAddRef.bind(this)(originalName, classIdent), returnMember]);
}

function getterWithObservedV2TraceProperty(
    this: IObservedV2TraceTranslator,
    originalName: string,
    newName: string
): arkts.MethodDefinition {
    let methodModifier = this.isStatic
        ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC
        : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC;
    let body: arkts.BlockStatement | undefined;
    if (!this.isDecl) {
        body = getterBodyWithObservedV2TraceProperty.bind(this)(originalName, newName);
    } else {
        methodModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
    }
    return uiFactory.createMethodDefinition({
        kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET,
        key: arkts.factory.createIdentifier(originalName),
        function: {
            body: body,
            returnTypeAnnotation: this.propertyType,
            modifiers: methodModifier,
            flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER,
        },
        modifiers: methodModifier,
    });
}

function setterWithObservedV2TraceProperty(
    this: IObservedV2TraceTranslator,
    originalName: string,
    newName: string
): arkts.MethodDefinition {
    let methodModifier = this.isStatic
        ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC
        : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC;
    let body: arkts.BlockStatement | undefined;
    if (!this.isDecl) {
        const ifEqualsNewValue: arkts.IfStatement = setterIfEqualsNewValueWithObservedV2TraceProperty.bind(this)(
            originalName,
            newName
        );
        body = arkts.factory.createBlockStatement([ifEqualsNewValue]);
    } else {
        methodModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
    }
    const param = arkts.factory.createETSParameterExpression(
        arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, this.propertyType),
        false,
        undefined
    );
    return uiFactory.createMethodDefinition({
        kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET,
        key: arkts.factory.createIdentifier(originalName),
        function: {
            body: body,
            params: [param],
            modifiers: methodModifier,
            flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER,
        },
        modifiers: methodModifier,
    });
}

function createAddRef(
    this: IObservedV2TraceTranslator,
    originalName: string,
    classIdent: arkts.Identifier
): arkts.ExpressionStatement {
    const metaName: string = `${StateManagementTypes.META}_${originalName}`;
    const conditionalAddRef = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(generateThisBacking(ObservedNames.CONDITIONAL_ADD_REF), [
                generateThisBacking(metaName),
            ],
            undefined,
            false,
            false
        )
    );
    const metaAddRef = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            uiFactory.generateMemberExpression(
                uiFactory.generateMemberExpression(classIdent.clone(), metaName),
                ObservedNames.ADD_REF
            ),
            [],
            undefined,
            false,
            false
        )
    );
    return this.isStatic ? metaAddRef : conditionalAddRef;
}

export function setterIfEqualsNewValueWithObservedV2TraceProperty(
    this: IObservedV2TraceTranslator,
    originalName: string,
    newName: string
): arkts.IfStatement {
    const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.className);
    const metaName: string = `${StateManagementTypes.META}_${originalName}`;
    const backingValue: arkts.Expression = generateThisBacking(newName);
    const metaValue: arkts.Expression = generateThisBacking(metaName);
    const staticMetaValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), metaName);
    const staticBackingValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), newName);
    const setNewValue = arkts.factory.createExpressionStatement(
        arkts.factory.createAssignmentExpression(
            this.isStatic ? staticBackingValue : backingValue,
            arkts.factory.createIdentifier(ObservedNames.NEW_VALUE),
            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION
        )
    );

    const fireChange = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            arkts.factory.createMemberExpression(
                this.isStatic ? staticMetaValue.clone() : metaValue.clone(),
                arkts.factory.createIdentifier(ObservedNames.FIRE_CHANGE),
                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                false,
                false
            ),
            [],
            undefined,
            false,
            false
        )
    );

    const subscribingWatches = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(generateThisBacking(ObservedNames.EXECUATE_WATCHES), [
                arkts.factory.createStringLiteral(originalName),
            ],
            undefined,
            false,
            false
        )
    );

    const consequentArr = this.isStatic ? [setNewValue, fireChange] : [setNewValue, fireChange, subscribingWatches];
    return arkts.factory.createIfStatement(
        arkts.factory.createBinaryExpression(
            this.isStatic ? staticBackingValue.clone() : backingValue.clone(),
            arkts.factory.createIdentifier(ObservedNames.NEW_VALUE),
            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL
        ),
        arkts.factory.createBlockStatement(consequentArr)
    );
}

export interface IObservedV2TraceTranslator extends IBaseObservedPropertyTranslator {
    className: string;
    traceDecorator: DecoratorNames.TRACE;
    isTraced?: boolean;
    isStatic?: boolean;
    isDecl?: boolean;
    isDefinitelyNonNull?: boolean;
}

/**
 * @deprecated
 */
export class ObservedV2TraceTranslator extends ObservedPropertyTranslator implements IObservedV2TraceTranslator {
    traceDecorator: DecoratorNames.TRACE = DecoratorNames.TRACE;
    className: string;
    isTraced?: boolean;
    isStatic?: boolean;
    isDecl?: boolean;
    propertyModifier: arkts.Es2pandaModifierFlags = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE;
    protected hasBackingField: boolean = true;
    protected hasMetaField: boolean = true;
    protected hasGetterSetter: boolean = true;
    protected isTracked: boolean = false;

    constructor(options: ObservedPropertyTranslatorOptions) {
        super(options);
        this.className = this.classScopeInfo.className;
        this.hasImplement = arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_SETTER);
        this.isTraced = hasDecorator(this.property, this.traceDecorator);
        this.isStatic = arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC);
        this.isDecl = MetaDataCollector.getInstance().isDeclaration ||
 	        arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE);
        if (this.isStatic) {
            this.propertyModifier = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
        }
    }

    translateMember(): arkts.AstNode[] {
        if (!this.isTraced) {
            return [this.property];
        }
        return super.translateMember();
    }

    metaField(originalName: string, newName: string): arkts.ClassProperty {
        const field = super.metaField(originalName, newName);
        field.setAnnotations([
            annotation(DecoratorNames.JSONSTRINGIFYIGNORE),
            annotation(DecoratorNames.JSONPARSEIGNORE),
        ]);
        return field;
    }

    getter(originalName: string, newName: string): arkts.MethodDefinition {
        return getterWithObservedV2TraceProperty.bind(this)(originalName, newName);
    }

    setter(originalName: string, newName: string): arkts.MethodDefinition {
        return setterWithObservedV2TraceProperty.bind(this)(originalName, newName);
    }
}

export class ObservedV2TraceCachedTranslator
    extends ObservedPropertyCachedTranslator
    implements IObservedV2TraceTranslator
{
    traceDecorator: DecoratorNames.TRACE = DecoratorNames.TRACE;
    className: string;
    isTraced?: boolean;
    isStatic: boolean = false;
    isDecl: boolean = false;
    propertyModifier: arkts.Es2pandaModifierFlags = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE;
    protected hasBackingField: boolean = true;
    protected hasMetaField: boolean = true;
    protected hasGetterSetter: boolean = true;
    protected isTracked: boolean = false;

    constructor(options: ObservedPropertyCachedTranslatorOptions) {
        super(options);
        this.className = this.propertyInfo.classInfo?.name!;
        this.isTraced = !!this.propertyInfo.annotationInfo?.hasTrace;
        this.isDecl = !!MetaDataCollector.getInstance().isDeclaration;
        if (!!this.propertyInfo.modifiers) {
            this.isStatic = (this.propertyInfo.modifiers & arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC) === arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
            this.isDecl ||= (this.propertyInfo.modifiers & arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE) === arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
        }
        if (this.isStatic) {
            this.propertyModifier = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
        }
        if (this.isDecl) {
            this.propertyModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
        }
    }

    translateMember(): arkts.AstNode[] {
        if (!this.isTraced) {
            return [this.property];
        }
        return super.translateMember();
    }

    metaField(originalName: string, newName: string): arkts.ClassProperty {
        const field = super.metaField(originalName, newName);
        field.setAnnotations([
            annotation(DecoratorNames.JSONSTRINGIFYIGNORE),
            annotation(DecoratorNames.JSONPARSEIGNORE),
        ]);
        return field;
    }

    getter(originalName: string, newName: string): arkts.MethodDefinition {
        return getterWithObservedV2TraceProperty.bind(this)(originalName, newName);
    }

    setter(originalName: string, newName: string): arkts.MethodDefinition {
        return setterWithObservedV2TraceProperty.bind(this)(originalName, newName);
    }
}