/*
 * 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 { BuilderMethodNames, ESValueMethodNames, InteroperAbilityNames, InteropInternalNames } from './predefines';
import { getCustomComponentOptionsName } from '../utils';
import { InteropContext } from '../component-transformer';
import { createVariableLet, initialArgs } from './initstatevar';
import {
    getPropertyESValue, 
    getWrapValue, 
    setPropertyESValue, 
    createEmptyESValue, 
    createGlobal, 
    createELMTID, 
    createInitReturn,
} from './utils';
import { DecoratorNames, LANGUAGE_VERSION, NodeCacheNames } from '../../common/predefines';
import { hasDecoratorInterop } from './utils';
import { FileManager } from '../../common/file-manager';
import { NodeCacheFactory } from '../../common/node-cache';

function paramsLambdaDeclaration(name: string, args?: arkts.ObjectExpression): arkts.Statement[] {
    const result: arkts.Statement[] = [];
    result.push(
        arkts.factory.createVariableDeclaration(
            arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
            [
                arkts.factory.createVariableDeclarator(
                    arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                    arkts.factory.createIdentifier(InteropInternalNames.PARAMSLAMBDA),
                    arkts.factory.createArrowFunctionExpression(
                        arkts.factory.createScriptFunction(
                            arkts.factory.createBlockStatement([
                                arkts.factory.createReturnStatement(
                                    args ? args : arkts.ObjectExpression.createObjectExpression(
                                        arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION,
                                        [],
                                        false
                                    ),
                                )
                            ]),
                            undefined,
                            [],
                            arkts.factory.createETSTypeReference(
                                arkts.factory.createETSTypeReferencePart(
                                    arkts.factory.createIdentifier(getCustomComponentOptionsName(name))
                                )
                            ),
                            false,
                            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
                            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
                            undefined,
                            undefined
                        )
                    )
                ),
            ]
        )
    );
    return result;
}

function createExtraInfo(properties: string[], value: string[]): arkts.Statement[] {
    const body: arkts.Statement[] = [];
    body.push(createEmptyESValue(InteropInternalNames.EXTRAINFO));
    properties.forEach((prop, index) => {
        const val = value[index];
        body.push(setPropertyESValue(InteropInternalNames.EXTRAINFO, prop, arkts.factory.createStringLiteral(val)));
    });
    return body;
}

function generateUnwrapCallExpression(expression: arkts.Expression): arkts.Expression {
    return arkts.factory.createCallExpression(
        arkts.factory.createMemberExpression(
            expression,
            arkts.factory.createIdentifier('unwrap'),
            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
            false,
            false
        ),
        [],
        undefined,
        false,
        false
    );
}

function newComponent(context: InteropContext, className: string, node: arkts.CallExpression): arkts.Statement {
    const newNode = arkts.factory.createArrowFunctionExpression(
        arkts.factory.createScriptFunction(
            arkts.factory.createBlockStatement([]),
            undefined,
            [],
            undefined,
            false,
            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
            undefined,
            undefined
        )
    );
    newNode.setNoDebugLineFlag();

    let componentNode = arkts.factory.createETSNewClassInstanceExpression(
        arkts.factory.createIdentifier(className),
        [
            generateUnwrapCallExpression(arkts.factory.createIdentifier(InteropInternalNames.PARENT)),
            generateUnwrapCallExpression(arkts.factory.createIdentifier(InteropInternalNames.PARAM)),
            context.storage ?? arkts.factory.createUndefinedLiteral(),
            generateUnwrapCallExpression(arkts.factory.createIdentifier(InteropInternalNames.ELMTID)),
            arkts.factory.createTSAsExpression(
                newNode,
                arkts.factory.createETSTypeReference(
                    arkts.factory.createETSTypeReferencePart(arkts.factory.createIdentifier('Object'))
                ),
                false
            ),
            generateUnwrapCallExpression(arkts.factory.createIdentifier(InteropInternalNames.EXTRAINFO))
        ]
    );
    componentNode.startPosition = node.startPosition;
    componentNode.endPosition = node.endPosition;
    return createVariableLet(
        InteropInternalNames.COMPONENT,
        getWrapValue(
            componentNode
        )
    );
}

function createComponent(context: InteropContext, className: string, isV2: boolean, node: arkts.CallExpression): arkts.Statement[] {
    let viewCreateMethod: string = 'viewPUCreate';
    if (isV2) {
        viewCreateMethod = 'viewV2Create';
    }
    const component = newComponent(context, className, node);
    const create = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            arkts.factory.createMemberExpression(
                arkts.factory.createCallExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier(InteropInternalNames.GLOBAL),
                        arkts.factory.createIdentifier(ESValueMethodNames.GETPROPERTY),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    [arkts.factory.createStringLiteral(viewCreateMethod)],
                    undefined,
                    false,
                    false
                ),
                arkts.factory.createIdentifier('invoke'),
                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                false,
                false
            ),
            [arkts.factory.createIdentifier(InteropInternalNames.COMPONENT)],
            undefined,
            false,
            false
        )
    );
    return [component, create];
}


function createWrapperBlock(
    context: InteropContext,
    varMap: Map<string, arkts.ClassProperty>,
    updateProp: arkts.Property[],
    node: arkts.CallExpression,
    isV2: boolean
): arkts.BlockStatement {
    const className: string = context.className;
    const path: string = context.path ?? '';
    const args: arkts.ObjectExpression | undefined = context.arguments;
    const initial = [createGlobal(), createEmptyESValue(InteropInternalNames.PARAM)];
    const initialArgsStatement = args ? initialArgs(args, varMap, updateProp, node) : [];
    return arkts.factory.createBlockStatement([
        ...initial,
        ...initialArgsStatement,
        ...createExtraInfo(['page'], [path]),
        createELMTID(),
        ...createComponent(context, className, isV2, node),
        createInitReturn(className),
    ]);
}

function createInitializer(
    context: InteropContext,
    varMap: Map<string, arkts.ClassProperty>,
    updateProp: arkts.Property[],
    node: arkts.CallExpression,
    isV2: boolean
): arkts.ArrowFunctionExpression {
    const block = createWrapperBlock(context, varMap, updateProp, node, isV2);
    const newNode = arkts.factory.createArrowFunctionExpression(
        arkts.factory.createScriptFunction(
            block,
            undefined,
            [
                arkts.factory.createETSParameterExpression(
                    arkts.factory.createIdentifier(InteropInternalNames.PARENT,
                        arkts.factory.createETSTypeReference(
                            arkts.factory.createETSTypeReferencePart(
                                arkts.factory.createIdentifier(ESValueMethodNames.ESVALUE)
                            )
                        )
                    ),
                    false,
                    undefined
                ),
            ],
            undefined,
            false,
            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
            undefined,
            undefined
        )
    );
    newNode.setNoDebugLineFlag();
    return newNode;
}

function createUpdateProp(updateProp: arkts.Property[]): arkts.Statement[] {
    const result: arkts.Statement[] = [];
    const updateParam = createEmptyESValue('updateParam');
    result.push(updateParam);
    updateProp.forEach((prop) => {
        const key = prop.key as arkts.Identifier;
        const value = prop.value;
        const insertProperty = setPropertyESValue('updateParam', key.name, getWrapValue(value!));
        result.push(insertProperty);
    });
    return result;
}

function updateStateVars(updateProp: arkts.Property[]): arkts.Statement[] {
    const insertProp = createUpdateProp(updateProp);
    return [
        ...insertProp,
        arkts.factory.createExpressionStatement(
            arkts.factory.createCallExpression(
                arkts.factory.createMemberExpression(
                    arkts.factory.createIdentifier(InteropInternalNames.INSTANCE),
                    arkts.factory.createIdentifier(ESValueMethodNames.INVOKEMETHOD),
                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                    false,
                    false
                ),
                [
                    arkts.factory.createStringLiteral('updateStateVars'),
                    arkts.factory.createIdentifier('updateParam')
                ],
                undefined,
                false,
                false
            )
        )
    ];
}


/**
 * 
 * @param updateProp
 * @returns (instance: ESValue) => { instance.invokeMethod('updateStateVars', updateParam) }
 */
function createUpdater(updateProp: arkts.Property[]): arkts.ArrowFunctionExpression {
    const updateState = updateProp.length !== 0 ? updateStateVars(updateProp) : [];
    const newNode = arkts.factory.createArrowFunctionExpression(
        arkts.factory.createScriptFunction(
            arkts.factory.createBlockStatement([...updateState]),
            undefined,
            [
                arkts.factory.createETSParameterExpression(
                    arkts.factory.createIdentifier(
                        InteropInternalNames.INSTANCE,
                        arkts.factory.createETSTypeReference(
                            arkts.factory.createETSTypeReferencePart(
                                arkts.factory.createIdentifier(ESValueMethodNames.ESVALUE)
                            )
                        )
                    ),
                    false,
                    undefined,
                ),
            ],
            undefined,
            false,
            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
            undefined,
            undefined
        )
    );
    newNode.setNoDebugLineFlag();
    return newNode;
}

function updateArguments(context: InteropContext, name: string): arkts.ObjectExpression {
    const property = arkts.factory.createProperty(
        arkts.Es2pandaPropertyKind.PROPERTY_KIND_INIT,
        arkts.factory.createIdentifier(name),
        arkts.factory.createTSAsExpression(
            context.content,
            arkts.factory.createETSTypeReference(
                arkts.factory.createETSTypeReferencePart(arkts.factory.createIdentifier('Function'))
            ),
            false
        ),
        false,
        false
    );
    return context.arguments
        ? arkts.factory.updateObjectExpression(
              context.arguments,
              [...(context.arguments?.properties as arkts.Property[]), property]
          )
        : arkts.factory.createObjectExpression(
              [property]
          );
}

function generateVarMap(context: InteropContext, decl: arkts.ClassDefinition): Map<string, arkts.ClassProperty> {
    let needBuilderParam = !!context.content;
    const result = new Map<string, arkts.ClassProperty>();
    const definition = decl;
    const body = definition.body;
    body.forEach((node) => {
        if (node instanceof arkts.ClassProperty && node.key instanceof arkts.Identifier) {
            const key = node.key.name;
            result.set(key, node);
            if (needBuilderParam && hasDecoratorInterop(node, DecoratorNames.BUILDER_PARAM)) {
                context.arguments = updateArguments(context, key);
                needBuilderParam = false;
            }
        }
    });
    return result;
}

export function processArguments(arg: arkts.Expression): arkts.ObjectExpression {
    if (!arkts.isObjectExpression(arg)) {
        throw new Error('Cannot find arguments for InteropComponent');
    }
    const properties = (arg.properties as arkts.Property[]).map((property: arkts.Property) => {
        const key = property.key;
        if (!!key && arkts.isIdentifier(key) && key.name.startsWith('__backing_')) {
            return arkts.factory.updateProperty(
                property,
                property.kind,
                arkts.factory.updateIdentifier(key, key.name.slice('__backing_'.length)),
                property.value,
                property.isMethod,
                property.isComputed
            );
        }
        return property;
    });
    return arkts.factory.updateObjectExpression(
        arg,
        properties
    );
}


function getProperParams(
    args: readonly arkts.Expression[]
): [arkts.ObjectExpression | undefined, arkts.Identifier | undefined, arkts.ArrowFunctionExpression | undefined] {
    let initializers: arkts.AstNode | undefined = undefined;
    let storage: arkts.AstNode | undefined = undefined;
    let content: arkts.AstNode | undefined = undefined;
    args.forEach((arg) => {
        if (arkts.isObjectExpression(arg)) {
            initializers = processArguments(arg);
        } else if (arkts.isIdentifier(arg)) {
            storage = arg;
        } else if (arkts.isArrowFunctionExpression(arg)) {
            content = arg;
        }
    });
    return [initializers, storage, content];
}

/**
 *
 * @param node
 * @returns After Checked, transform instantiate_Interop -> ArkUICompatible
 */
export function generateArkUICompatible(node: arkts.CallExpression, globalBuilder: boolean): arkts.CallExpression {
    const classInterop = (node.callee as arkts.MemberExpression).object as arkts.Identifier;
    const className = classInterop.name;
    const decl = arkts.getDecl(classInterop);
    if (!(decl instanceof arkts.ClassDefinition)) {
        throw Error("can't find legacy class declaration");
    }
    const filePath = arkts.getProgramFromAstNode(decl)?.moduleName;
    const args = node.arguments;
    const [options, storage, content] = getProperParams(args);

    if (!!content) {
        NodeCacheFactory.getInstance().getCache(NodeCacheNames.MEMO).collect(content);
    }
    const context: InteropContext = {
        className: className,
        path: filePath,
        arguments: options,
        storage: storage,
        content: content as arkts.ArrowFunctionExpression | undefined,
    };

    const varMap: Map<string, arkts.ClassProperty> = generateVarMap(context, decl);
    const updateProp: arkts.Property[] = [];
    const isComponentV2 = decl.annotations.some(
        (annotation) => annotation.expr instanceof arkts.Identifier && annotation.expr.name === 'ComponentV2'
    );
    const initializer = createInitializer(context, varMap, updateProp, node, isComponentV2);
    const updater = createUpdater(updateProp);
    const result = arkts.factory.updateCallExpression(
        node,
        arkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE),
        [
            initializer,
            updater,
            globalBuilder ? arkts.factory.createUndefinedLiteral() : arkts.factory.createThisExpression(),
        ],
        undefined,
        node.isOptional,
        node.hasTrailingComma,
        node.trailingBlock
    );
    NodeCacheFactory.getInstance().getCache(NodeCacheNames.MEMO).collect(result);
    return result;
}