/*
 * 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 {
    createGetter,
    generateToRecord,
    generateThisBacking,
    createSetter2,
    isCustomDialogController,
    findCachedMemoMetadata,
    canCastTypeFromValue,
    checkIsPropertyCanBeNonNull,
} from './utils';
import {
    BasePropertyTranslator,
    InnerClassPropertyCachedTranslator,
    InnerClassPropertyTranslator,
    InnerClassPropertyTypes,
    PropertyCachedTranslator,
    PropertyCachedTranslatorOptions,
    PropertyTranslator,
    PropertyTranslatorOptions,
} from './base';
import { backingField, expectName, flatVisitMethodWithOverloads } from '../../common/arkts-utils';
import { CustomComponentNames, NodeCacheNames, StateManagementTypes } from '../../common/predefines';
import { factory } from './factory';
import { PropertyCache } from './cache/propertyCache';
import { factory as UIFactory } from '../ui-factory';
import { optionsHasField } from '../utils';
import { CustomComponentInnerClassPropertyInfo } from '../../collectors/ui-collectors/records';
import { PropertyFactoryCallTypeCache, PropertyValueCache } from '../memo-collect-cache';
import { AstNodeCacheValueMetadata, NodeCacheFactory } from '../../common/node-cache';
import { GenSymGenerator } from '../../common/gensym-generator';

function initializeStructWithRegularProperty(
    this: BasePropertyTranslator,
    newName: string,
    originalName: string,
    metadata?: arkts.AstNodeCacheValueMetadata
): arkts.Statement {
    const propertyValue = this.property.value;
    const propertyType = this.property.typeAnnotation;
    let right: arkts.Expression;
    if (this.initializeOptions?.shouldCheckNonNull) {
        const id: string = GenSymGenerator.getInstance().id(originalName);
        const optionsType: arkts.TypeNode | undefined = propertyType?.clone();
        const optionsValue: arkts.Expression = factory.generateDefiniteInitializers(optionsType, originalName);
        const canCastType: boolean = canCastTypeFromValue(propertyValue);
        const defaultType: arkts.TypeNode | undefined = canCastType && !!propertyType ? propertyType.clone() : undefined;
        const defaultRawValue: arkts.Expression = !!propertyValue ? propertyValue : arkts.factory.createUndefinedLiteral();
        const defaultValue: arkts.Expression = !!defaultType
            ? arkts.factory.createTSAsExpression(defaultRawValue, defaultType, false)
            : defaultRawValue;
        if (this.isMemoCached) {
            if (!!optionsType) {
                PropertyValueCache.getInstance().collect({ value: optionsType, shouldCache: this.isMemoCached, metadata });
            }
            if (!!defaultType) {
                PropertyValueCache.getInstance().collect({ value: defaultType, shouldCache: this.isMemoCached, metadata });
            }
        }
        right = arkts.factory.createConditionalExpression(
            factory.createBlockStatementForOptionsHasMemberExpression(
                arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME),
                id,
                originalName
            ),
            optionsValue,
            defaultValue
        );
    } else if (this.initializeOptions?.isRequired || !propertyType) {
        right = factory.generateDefiniteInitializers(propertyType, originalName);
    } else {
        right = arkts.factory.createBinaryExpression(
            factory.createBlockStatementForOptionalExpression(
                arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME),
                originalName,
                { isNonNull: this.initializeOptions?.isRequired }
            ),
            propertyValue ?? arkts.factory.createUndefinedLiteral(),
            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING
        )
    }
    const assign: arkts.AssignmentExpression = arkts.factory.createAssignmentExpression(
        generateThisBacking(newName),
        right,
        arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION
    );
    if (this.isMemoShouldUpdate) {
        if (!!propertyValue) {
            PropertyValueCache.getInstance().collect({ value: propertyValue });
        }
    }
    return arkts.factory.createExpressionStatement(assign);
}

function initializeStructWithCustomDialogControllerInit(
    this: BasePropertyTranslator,
    newName: string,
    originalName: string
): arkts.Statement {
    const propertyValue = this.property.value;
    const value = propertyValue ?? arkts.factory.createUndefinedLiteral();
    const thisValue: arkts.Expression = generateThisBacking(newName, false, false);
    if (this.isMemoShouldUpdate) {
        if (!!propertyValue) {
            PropertyValueCache.getInstance().collect({ value: propertyValue });
        }
    }
    return arkts.factory.createIfStatement(
        factory.createBlockStatementForOptionalExpression(
            arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME),
            optionsHasField(originalName),
            { isNonNull: this.initializeOptions?.isRequired }
        ),
        arkts.factory.createBlockStatement([
            arkts.factory.createExpressionStatement(
                arkts.factory.createAssignmentExpression(
                    thisValue,
                    UIFactory.generateMemberExpression(
                        arkts.factory.createTSNonNullExpression(
                            arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME)
                        ),
                        originalName
                    ),
                    arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION
                ),
            ),
        ]),
        arkts.factory.createBlockStatement([
            arkts.factory.createIfStatement(
                arkts.factory.createUnaryExpression(
                    thisValue,
                    arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EXCLAMATION_MARK
                ),
                arkts.factory.createExpressionStatement(
                    arkts.factory.createAssignmentExpression(
                        thisValue,
                        value,
                        arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION
                    )
                )
            ),
        ])
    );
}

function getterWithRegularProperty(
    this: BasePropertyTranslator,
    newName: string,
    originalName: string,
    metadata?: AstNodeCacheValueMetadata
): arkts.MethodDefinition {
    let thisValue: arkts.Expression = generateThisBacking(newName, false, false);
    if (this.initializeOptions?.isRequired) {
        thisValue = arkts.factory.createTSNonNullExpression(thisValue);
    }
    const getter: arkts.MethodDefinition = createGetter(
        originalName,
        this.propertyType,
        getGetterReturnValue.bind(this)(thisValue, metadata),
        this.isMemoCached,
        false,
        metadata
    );
    if (this.isMemoCached) {
        NodeCacheFactory.getInstance().getCache(NodeCacheNames.MEMO).collect(getter, metadata);
    }
    return getter;
}

function setterWithRegularProperty(
    this: BasePropertyTranslator,
    newName: string,
    originalName: string,
    metadata?: AstNodeCacheValueMetadata
): arkts.MethodDefinition {
    const thisValue: arkts.Expression = generateThisBacking(newName, false, false);
    const thisSet: arkts.ExpressionStatement = arkts.factory.createExpressionStatement(
        arkts.factory.createAssignmentExpression(
            thisValue,
            arkts.factory.createIdentifier('value'),
            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION,
        )
    );
    const setter: arkts.MethodDefinition = createSetter2(originalName, this.propertyType, thisSet);
    if (this.isMemoCached) {
        NodeCacheFactory.getInstance().getCache(NodeCacheNames.MEMO).collect(setter, metadata);
    }
    return setter;
}

function getGetterReturnValue(
    this: BasePropertyTranslator,
    thisValue: arkts.Expression,
    metadata?: AstNodeCacheValueMetadata
): arkts.Expression {
    if (!this.propertyType) {
        return thisValue;
    }
    const newType = this.propertyType.clone();
    const returnValue = arkts.factory.createTSAsExpression(thisValue, newType, false);
    if (this.isMemoCached) {
        PropertyFactoryCallTypeCache.getInstance().collect({ node: newType, metadata });
    }
    return returnValue;
}

/**
 * @deprecated
 */
export class RegularPropertyTranslator extends PropertyTranslator {
    protected hasInitializeStruct: boolean = true;
    protected hasUpdateStruct: boolean = false;
    protected hasToRecord: boolean = true;
    protected hasField: boolean = true;
    protected hasGetter: boolean = true;
    protected hasSetter: boolean = true;

    constructor(options: PropertyTranslatorOptions) {
        super(options);
        this.initializeOptions = {
            shouldCheckNonNull: true
        }
    }

    initializeStruct(
        newName: string,
        originalName: string,
        metadata?: AstNodeCacheValueMetadata
    ): arkts.Statement | undefined {
        let initializeStruct: arkts.Statement | undefined;
        if (
            !!this.propertyType &&
            !!this.structInfo.annotations.customDialog &&
            isCustomDialogController(this.propertyType)
        ) {
            initializeStruct = initializeStructWithCustomDialogControllerInit.bind(this)(newName, originalName);
        } else {
            initializeStruct = initializeStructWithRegularProperty.bind(this)(newName, originalName, metadata);
        }
        return initializeStruct;
    }

    getter(newName: string, originalName: string, metadata?: AstNodeCacheValueMetadata): arkts.MethodDefinition {
        return getterWithRegularProperty.bind(this)(newName, originalName, metadata);
    }

    setter(newName: string, originalName: string, metadata?: AstNodeCacheValueMetadata): arkts.MethodDefinition {
        return setterWithRegularProperty.bind(this)(newName, originalName, metadata);
    }
}

export class RegularPropertyCachedTranslator extends PropertyCachedTranslator {
    protected hasInitializeStruct: boolean = true;
    protected hasUpdateStruct: boolean = false;
    protected hasToRecord: boolean = true;
    protected hasField: boolean = true;
    protected hasGetter: boolean = true;
    protected hasSetter: boolean = true;

    constructor(options: PropertyCachedTranslatorOptions) {
        super(options);
        this.initializeOptions = {
            shouldCheckNonNull: !this.propertyInfo.structInfo?.annotationInfo?.hasComponentV2
        }
        this.initializeOptions.canDefinitelyBeNonNull = checkIsPropertyCanBeNonNull(
            this.property,
            this.initializeOptions
        );
    }

    initializeStruct(
        newName: string,
        originalName: string,
        metadata?: AstNodeCacheValueMetadata
    ): arkts.Statement | undefined {
        let initializeStruct: arkts.Statement | undefined;
        if (
            !!this.propertyType &&
            !!this.propertyInfo.structInfo?.annotationInfo?.hasCustomDialog &&
            isCustomDialogController(this.propertyType)
        ) {
            initializeStruct = initializeStructWithCustomDialogControllerInit.bind(this)(newName, originalName);
        } else {
            initializeStruct = initializeStructWithRegularProperty.bind(this)(newName, originalName, metadata);
        }
        return initializeStruct;
    }

    getter(newName: string, originalName: string, metadata?: AstNodeCacheValueMetadata): arkts.MethodDefinition {
        return getterWithRegularProperty.bind(this)(newName, originalName, metadata);
    }

    setter(newName: string, originalName: string, metadata?: AstNodeCacheValueMetadata): arkts.MethodDefinition {
        return setterWithRegularProperty.bind(this)(newName, originalName, metadata);
    }
}

/**
 * @deprecated
 */
export class RegularInnerClassTranslator<T extends InnerClassPropertyTypes> extends InnerClassPropertyTranslator<T> {
    translateProperty(): T {
        return this.property;
    }

    static canBeTranslated(node: arkts.AstNode): node is InnerClassPropertyTypes {
        return true;
    }
}

export class RegularCachedInnerClassTranslator<
    T extends InnerClassPropertyTypes,
> extends InnerClassPropertyCachedTranslator<T> {
    translateProperty(): T {
        return this.property;
    }

    static canBeTranslated(
        node: arkts.AstNode,
        metadata?: CustomComponentInnerClassPropertyInfo
    ): node is InnerClassPropertyTypes {
        return true;
    }
}