/*
 * 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 { NormalClassMethodInfo, StructMethodInfo } from '../../collectors/ui-collectors/records';
import { addMemoAnnotation } from '../../collectors/memo-collectors/utils';
import { ArkTsDefaultNames, DecoratorNames, GetSetTypes, ObservedNames, StateManagementTypes } from '../../common/predefines';
import { backingField } from '../../common/arkts-utils';
import { factory as UIFactory } from '../ui-factory';
import { generateThisBacking, removeDecorator } from './utils';
import { factory } from './factory';
import {
    getterBodyWithObservedTrackProperty,
    IObservedTrackTranslator,
    setterIfEqualsNewValueWithObservedTrackProperty,
} from './observedTrack';
import {
    getterBodyWithObservedV2TraceProperty,
    IObservedV2TraceTranslator,
    setterIfEqualsNewValueWithObservedV2TraceProperty,
} from './observedV2Trace';
import { MonitorCacheTranslator } from './monitor';
import { SyncMonitorCacheTranslator } from './syncMonitor';
import { getPropertyRewriteKey, PropertyRewriteCache } from './cache/propertyRewriteCache';
import { ComputedCacheTranslator } from './computed';
import { ComponentLifecycleCacheTranslator, ComponentLifecycleTranslator } from './componentLifecycle';
import { MetaDataCollector } from '../../common/metadata-collector';

export class CacheFactory {
    /**
     * add `@memo` to the `@Builder` methods in class.
     */
    static addMemoToBuilderClassMethodFromInfo(
        method: arkts.MethodDefinition,
        metadata: StructMethodInfo
    ): arkts.MethodDefinition {
        if (metadata.annotationInfo?.hasBuilder) {
            removeDecorator(method, DecoratorNames.BUILDER);
            addMemoAnnotation(method.function);
        }
        return method;
    }

    static transformObservedImplementsMethodFromInfo(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        getterSetterType?: GetSetTypes
    ): arkts.MethodDefinition {
        if (!metadata.name || !getterSetterType) {
            return node;
        }
        if (MetaDataCollector.getInstance().isDeclaration || metadata.isDecl) {
            return node;
        }
        const rewriteOptions: IObservedTrackTranslator = {
            className: metadata.name,
            traceDecorator: DecoratorNames.TRACK,
            isTracked: !!metadata.inheritPorpertyInfo?.annotationInfo?.hasTrack,
            isDefinitelyNonNull: !!node.function.returnTypeAnnotation?.tsType?.definitelyNotETSNullish
        };
        if (getterSetterType === GetSetTypes.GET) {
            return this.createTrackGet(node, metadata, rewriteOptions);
        }
        if (getterSetterType === GetSetTypes.SET) {
            return this.createTrackSet(node, metadata, rewriteOptions);
        }
        return node;
    }

    static createTrackGet(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        rewriteOptions: IObservedTrackTranslator
    ): arkts.MethodDefinition {
        const originalName: string = metadata.name!;
        const newName: string = backingField(originalName);
        const body = getterBodyWithObservedTrackProperty.bind(rewriteOptions)(originalName, newName);
        node.function.setBody(body);
        return node;
    }

    static createTrackSet(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        rewriteOptions: IObservedTrackTranslator
    ): arkts.MethodDefinition {
        const originalName: string = metadata.name!;
        const newName: string = backingField(originalName);
        const scriptFunction = node.function;
        const params = scriptFunction.params;
        if (params.length <= 0 || !arkts.isETSParameterExpression(params.at(0)!)) {
            return node;
        }
        const originParam: arkts.ETSParameterExpression = params.at(0)! as arkts.ETSParameterExpression;
        const type = originParam.typeAnnotation;
        if (!type || !arkts.isTypeNode(type)) {
            return node;
        }
        const ifEqualsNewValue = setterIfEqualsNewValueWithObservedTrackProperty.bind(rewriteOptions)(
            originalName,
            newName
        );
        const body = arkts.factory.createBlockStatement([ifEqualsNewValue]);
        const param = arkts.factory.createETSParameterExpression(
            arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, type),
            false,
            undefined
        );
        scriptFunction.setParams([param]).setBody(body);
        return node;
    }

    static transformObservedV2ImplementsMethodFromInfo(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        getterSetterType?: GetSetTypes
    ): arkts.MethodDefinition {
        if (!metadata.name || !metadata.classInfo?.name || !getterSetterType) {
            return node;
        }
        if (node.isStatic || MetaDataCollector.getInstance().isDeclaration || metadata.isDecl) {
            return node;
        }
        const rewriteOptions: IObservedV2TraceTranslator = {
            className: metadata.classInfo.name,
            traceDecorator: DecoratorNames.TRACE,
            isTraced: !!metadata.inheritPorpertyInfo?.annotationInfo?.hasTrace,
            isDefinitelyNonNull: !!node.function.returnTypeAnnotation?.tsType?.definitelyNotETSNullish
        };
        if (getterSetterType === GetSetTypes.GET) {
            return this.createTraceGet(node, metadata, rewriteOptions);
        }
        if (getterSetterType === GetSetTypes.SET) {
            return this.createTraceSet(node, metadata, rewriteOptions);
        }
        return node;
    }

    static createTraceGet(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        rewriteOptions: IObservedV2TraceTranslator
    ): arkts.MethodDefinition {
        const originalName: string = metadata.name!;
        const newName: string = backingField(originalName);
        const body = getterBodyWithObservedV2TraceProperty.bind(rewriteOptions)(originalName, newName);
        node.function.setBody(body);
        return node;
    }

    static createTraceSet(
        node: arkts.MethodDefinition,
        metadata: NormalClassMethodInfo,
        rewriteOptions: IObservedV2TraceTranslator
    ): arkts.MethodDefinition {
        const originalName: string = metadata.name!;
        const newName: string = backingField(originalName);
        const scriptFunction = node.function;
        const params = scriptFunction.params;
        if (params.length <= 0 || !arkts.isETSParameterExpression(params.at(0)!)) {
            return node;
        }
        const originParam: arkts.ETSParameterExpression = params.at(0)! as arkts.ETSParameterExpression;
        const type = originParam.typeAnnotation;
        if (!type || !arkts.isTypeNode(type)) {
            return node;
        }
        const ifEqualsNewValue = setterIfEqualsNewValueWithObservedV2TraceProperty.bind(rewriteOptions)(
            originalName,
            newName
        );
        const body = arkts.factory.createBlockStatement([ifEqualsNewValue]);
        const param = arkts.factory.createETSParameterExpression(
            arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, type),
            false,
            undefined
        );
        scriptFunction.setParams([param]).setBody(body);
        return node;
    }

    static rewriteMonitorMethodFromInfo(
        method: arkts.MethodDefinition,
        metadata: StructMethodInfo | NormalClassMethodInfo
    ): arkts.MethodDefinition {
        if (!metadata.name) {
            return method;
        }
        const monitorTranslator = new MonitorCacheTranslator({ method, methodInfo: metadata });
        const newNodes: arkts.AstNode[] = monitorTranslator.translateMember();
        PropertyRewriteCache.getInstance().collectRewriteNodes(getPropertyRewriteKey(method, metadata.name), newNodes);
        return method;
    }

    static rewriteSyncMonitorMethodFromInfo(
        method: arkts.MethodDefinition,
        metadata: StructMethodInfo | NormalClassMethodInfo
    ): arkts.MethodDefinition {
        if (!metadata.name) {
            return method;
        }
        const syncMonitorTranslator = new SyncMonitorCacheTranslator({ method, methodInfo: metadata });
        const newNodes: arkts.AstNode[] = syncMonitorTranslator.translateMember();
        PropertyRewriteCache.getInstance().collectRewriteNodes(getPropertyRewriteKey(method, metadata.name), newNodes);
        return method;
    }

    static rewriteComputedMethodFromInfo(
        method: arkts.MethodDefinition,
        metadata: StructMethodInfo | NormalClassMethodInfo
    ): arkts.MethodDefinition {
        if (!metadata.name) {
            return method;
        }
        const computedTranslator = new ComputedCacheTranslator({ method, methodInfo: metadata });
        const newNodes: arkts.AstNode[] = computedTranslator.translateMember();
        PropertyRewriteCache.getInstance().collectRewriteNodes(getPropertyRewriteKey(method, metadata.name), newNodes);
        return method;
    }

    static rewriteLiveCycleMethodFromInfo(
        method: arkts.MethodDefinition,
        metadata: StructMethodInfo
    ): arkts.MethodDefinition {
        if (!metadata.name) {
            return method;
        }
        const computedTranslator = new ComponentLifecycleCacheTranslator({ method, methodInfo: metadata });
        const newNodes: arkts.AstNode[] = computedTranslator.translateMember();
        PropertyRewriteCache.getInstance().collectRewriteNodes(getPropertyRewriteKey(method, metadata.name), newNodes);
        return method;
    }
}