/*
 * 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 { ARKUI_INTEROP_SOURCE_NAME, NodeCacheNames } from '../../common/predefines';
import { ESValueMethodNames, BuilderMethodNames, InteroperAbilityNames, BuilderParams, InteropInternalNames } from './predefines';
import { 
    createELMTID,
    createEmptyESValue,
    createGlobal,
    createInitReturn,
    getPropertyESValue,
    getWrapValue,
    setPropertyESValue,
} from './utils';
import { ImportCollector } from '../../common/import-collector';
import { FileManager } from '../../common/file-manager';
import { LANGUAGE_VERSION } from '../../common/predefines';
import { NodeCacheFactory } from '../../common/node-cache';

interface builderParam {
    args: arkts.Expression[],
    paramsInfo: arkts.Statement[]
}

function invokeFunctionWithParam(
    functionName: string,
    result: string,
    className: string,
    args: arkts.Expression[],
    node: arkts.AstNode
): arkts.Statement {
    let builderNode = getWrapValue(arkts.factory.createIdentifier(className));
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                arkts.factory.createIdentifier(result),
                arkts.factory.createCallExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier(functionName),
                        arkts.factory.createIdentifier(ESValueMethodNames.INVOKE),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    [
                        builderNode,
                        arkts.factory.createIdentifier(InteropInternalNames.ELMTID),
                        ...args
                    ],
                    undefined,
                    false,
                    false
                )
            )
        ]
    );
}

function invokeRunPendingJobs(): arkts.Statement {
    return arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            arkts.factory.createMemberExpression(
                arkts.factory.createIdentifier(BuilderMethodNames.RUNPENDINGJOBS),
                arkts.factory.createIdentifier(ESValueMethodNames.INVOKE),
                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                false,
                false
            ),
            [],
            undefined,
            false,
            false
        )
    );
}

function invokeComponent(): arkts.Statement[] {
    const viewPU = getPropertyESValue('viewPUCreate', InteropInternalNames.GLOBAL, 'viewPUCreate');
    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('viewPUCreate')],
                    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 [viewPU, create];
}

function createBuilderInitializer(
    className: string,
    functionName: string,
    param: builderParam,
    node: arkts.AstNode
): arkts.ArrowFunctionExpression {
    const block = arkts.factory.createBlockStatement([
        createGlobal(),
        createELMTID(),
        getPropertyESValue(BuilderMethodNames.CREATECOMPATIBLENODE, InteropInternalNames.GLOBAL, functionName),
        ...param.paramsInfo,
        invokeFunctionWithParam(
            BuilderMethodNames.CREATECOMPATIBLENODE,
            InteropInternalNames.COMPONENT,
            className,
            param.args,
            node
        ),
        ...invokeComponent(),
        createInitReturn(className)
    ]);
    const newNode = arkts.factory.createArrowFunctionExpression(
        arkts.factory.createScriptFunction(
            block,
            undefined,
            [],
            undefined,
            false,
            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
            undefined,
            undefined
        )
    );
    newNode.setNoDebugLineFlag();
    return newNode;
}

/**
 * getInstanceParam
 * @returns  let instanceParam = (instance.getProperty("arg1") as ESValue);
 */
function getInstanceParam(): arkts.Statement {
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                arkts.factory.createIdentifier(BuilderParams.INSTANCEPARAM),
                arkts.factory.createTSAsExpression(
                    arkts.factory.createCallExpression(
                        arkts.factory.createMemberExpression(
                            arkts.factory.createIdentifier(InteropInternalNames.INSTANCE),
                            arkts.factory.createIdentifier(ESValueMethodNames.GETPROPERTY),
                            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                            false,
                            false
                        ),
                        [arkts.factory.createStringLiteral('arg1')],
                        undefined,
                        false,
                        false,
                    ),
                    arkts.factory.createETSTypeReference(
                        arkts.factory.createETSTypeReferencePart(
                            arkts.factory.createIdentifier(ESValueMethodNames.ESVALUE)
                        )
                    ),
                    false
                )
            ),
        ]
    );
}

/**
 * instanceParamTypeOf
 * @returns
 *  if (((instanceParam.typeOf()) != ("object"))) {
          return;
    }
 */
function instanceParamTypeOf(): arkts.Statement {
    return arkts.factory.createIfStatement(
        arkts.factory.createBinaryExpression(
            arkts.factory.createCallExpression(
                arkts.factory.createMemberExpression(
                    arkts.factory.createIdentifier(BuilderParams.INSTANCEPARAM),
                    arkts.factory.createIdentifier('typeOf'),
                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                    false,
                    false
                ),
                [],
                undefined,
                false,
                false
            ),
            arkts.factory.createStringLiteral('object'),
            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_EQUAL
        ),
        arkts.factory.createBlockStatement([arkts.factory.createReturnStatement()])
    );
}

/**
 * getParamWrapped
 * @param argument param
 * @returns let param_wrapped = ESValue.wrap(param);
 */
function getParamWrapped(argument: arkts.Identifier): arkts.Statement {
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED),
                arkts.factory.createCallExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier('ESValue'),
                        arkts.factory.createIdentifier('wrap'),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    [argument],
                    undefined,
                    false,
                    false
                )
            ),
        ]
    );
}

/**
 * getItem
 * @returns let param_wrapped_it = param_wrapped.keys();
 */
function getItem(): arkts.Statement {
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_IT),
                arkts.factory.createCallExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED),
                        arkts.factory.createIdentifier('keys'),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    [],
                    undefined,
                    false,
                    false
                )
            ),
        ]
    );
}

function getResult(): arkts.Statement {
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_CONST,
                arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_KEY),
                arkts.factory.createCallExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_IT),
                        arkts.factory.createIdentifier('next'),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    [],
                    undefined,
                    false,
                    false
                )
            ),
        ]
    );
}

function getResultDone(): arkts.Statement {
    return arkts.factory.createIfStatement(
        arkts.factory.createMemberExpression(
            arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_KEY),
            arkts.factory.createIdentifier('done'),
            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
            false,
            false
        ),
        arkts.factory.createBlockStatement([arkts.BreakStatement.create1BreakStatement()])
    );
}

function getParamWrappedProperty(): arkts.Expression {
    return arkts.factory.createCallExpression(
        arkts.factory.createMemberExpression(
            arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED),
            arkts.factory.createIdentifier(ESValueMethodNames.GETPROPERTY),
            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
            false,
            false
        ),
        [
            arkts.factory.createTSAsExpression(
                arkts.factory.createMemberExpression(
                    arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_KEY),
                    arkts.factory.createIdentifier('value'),
                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                    false,
                    false
                ),

                arkts.factory.createETSTypeReference(
                    arkts.factory.createETSTypeReferencePart(arkts.factory.createIdentifier(ESValueMethodNames.ESVALUE))
                ),
                false
            ),
        ],
        undefined,
        false,
        false
    );
}

function setInstanceParam(): arkts.Statement {
    return arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            arkts.factory.createMemberExpression(
                arkts.factory.createIdentifier(BuilderParams.INSTANCEPARAM),
                arkts.factory.createIdentifier(ESValueMethodNames.SETPROPERTY),
                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                false,
                false
            ),
            [
                arkts.factory.createTSAsExpression(
                    arkts.factory.createMemberExpression(
                        arkts.factory.createIdentifier(BuilderParams.PARAM_WRAPPED_KEY),
                        arkts.factory.createIdentifier('value'),
                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                        false,
                        false
                    ),
                    arkts.factory.createETSTypeReference(
                        arkts.factory.createETSTypeReferencePart(
                            arkts.factory.createIdentifier(ESValueMethodNames.ESVALUE)
                        )
                    ),
                    false
                ),
                getParamWrappedProperty(),
            ],
            undefined,
            false,
            false
        )
    );
}

/**
 * getWhile
 * @returns
 *  while (true) {
        let param_wrapped_key = param_wrapped_it.next();
        if (param_wrapped_key.done) {
            break;
        }
        instanceParam.setProperty((param_wrapped_key.value as ESValue),
            param_wrapped.getProperty((param_wrapped_key.value as ESValue)));
    }
 */
function getWhile(): arkts.Statement {
    return arkts.WhileStatement.createWhileStatement(
        arkts.factory.createBooleanLiteral(true),
        arkts.factory.createBlockStatement([getResult(), getResultDone(), setInstanceParam()])
    );
}

function staticBuilderUpdateArgs(): arkts.Statement[] {
     const invoke = arkts.factory.createExpressionStatement(
        arkts.factory.createCallExpression(
            arkts.factory.createMemberExpression(
                arkts.factory.createIdentifier('updateFunc'),
                arkts.factory.createIdentifier('invoke'),
                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
                false,
                false
            ),
            [arkts.factory.createIdentifier(InteropInternalNames.INSTANCE)],
            undefined,
            false,
            false
        )
    );
    return [
        createGlobal(),
        getPropertyESValue('updateFunc', InteropInternalNames.GLOBAL, 'staticBuilderUpdate'),
        invoke
    ];
}

function getUpdateArgs(node: arkts.CallExpression | arkts.Property): arkts.Statement[] {
    if (arkts.isProperty(node)) {
        return [];
    }
    if (node.arguments.length !== 1) {
        return staticBuilderUpdateArgs();
    }
    let body: arkts.Statement[] = [];
    let argument = node.arguments[0];
    if (arkts.isObjectExpression(argument)) {
        body?.push(getPropertyESValue(InteropInternalNames.PARAM, InteropInternalNames.INSTANCE, 'arg1'));
        for (const property of argument.properties) {
            if (!(property instanceof arkts.Property)) {
                continue;
            }
            const key = property.key;
            const value = property.value;
            if (!(key instanceof arkts.Identifier) || value === undefined) {
                throw Error('Error arguments in Legacy Builder Function');
            }
            body?.push(setPropertyESValue(InteropInternalNames.PARAM, key.name, getWrapValue(value)));
        }
        const endBody = [
            createGlobal(),
            getPropertyESValue(
                BuilderMethodNames.RUNPENDINGJOBS,
                InteropInternalNames.GLOBAL,
                BuilderMethodNames.RUNPENDINGJOBS
            ),
            invokeRunPendingJobs()
        ];
        body?.push(...endBody);
    } else if (arkts.isIdentifier(argument)) {
        const functionBody = [
            getInstanceParam(),
            instanceParamTypeOf(),
            getParamWrapped(argument),
            getItem(),
            getWhile(),
            createGlobal(),
            getPropertyESValue(
                BuilderMethodNames.RUNPENDINGJOBS,
                InteropInternalNames.GLOBAL,
                BuilderMethodNames.RUNPENDINGJOBS
            ),
            invokeRunPendingJobs()
        ];
        body?.push(...functionBody);
    } else {
        return staticBuilderUpdateArgs();
    }
    return body;
}

function createBuilderUpdate(node: arkts.CallExpression | arkts.Property): arkts.ArrowFunctionExpression {
    const newNode = arkts.factory.createArrowFunctionExpression(
        arkts.factory.createScriptFunction(
            arkts.factory.createBlockStatement([...getUpdateArgs(node)]),
            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 getInitArgs(node: arkts.CallExpression | arkts.Property): builderParam {
    const args: arkts.Expression[] = [];
    let ObjectExpressionNum: number = 0;
    const body: arkts.Statement[] = [];
    if (arkts.isCallExpression(node)) {
        node.arguments.forEach((argument) => {
            if (arkts.isObjectExpression(argument)) {
                processArgument(argument, ObjectExpressionNum, body, args);
                ObjectExpressionNum++;
            } else {
                args.push(getWrapValue(argument));
            }
        });
    }
    return { args: args, paramsInfo: body };
}

function processArgument(
    argument: arkts.ObjectExpression,
    ObjectExpressionNum: number,
    body: arkts.Statement[],
    args: arkts.AstNode[]
): void {
    const paramName: string = 'paramObject' + ObjectExpressionNum;
    body.push(createEmptyESValue(paramName));
    for (const property of argument.properties) {
        if (!(property instanceof arkts.Property)) {
            continue;
        }
        const key = property.key;
        const value = property.value;
        if (!(key instanceof arkts.Identifier) || value === undefined) {
            throw Error('Error arguments in Legacy Builder Function');
        }
        body.push(setPropertyESValue(paramName, key.name, getWrapValue(value)));
    }
    args.push(arkts.factory.createIdentifier(paramName));
}

export function insertCompatibleImport(): void {
    ImportCollector.getInstance().collectSource(InteroperAbilityNames.ARKUICOMPATIBLE, ARKUI_INTEROP_SOURCE_NAME);
    ImportCollector.getInstance().collectImport(InteroperAbilityNames.ARKUICOMPATIBLE);
}

/**
 * 
 * @param node node
 * @param callName `@Builder` callName
 * @returns After Checked, transform builder/WrappedBuilder -> compatibleComponent
 */
export function generateBuilderCompatible<T extends arkts.CallExpression | arkts.Property>(
    node: T,
    callName: string
): T {
    let functionName = getFunctionName(node);
    let param: builderParam = getInitArgs(node);
    const initializer = createBuilderInitializer(callName, functionName, param, node);
    const updater: arkts.ArrowFunctionExpression = createBuilderUpdate(node);
    let result: T;
    const callExprMetaData = { callName: InteroperAbilityNames.ARKUICOMPATIBLE, hasReceiver: false };
    const memoCache = NodeCacheFactory.getInstance().getCache(NodeCacheNames.MEMO);
    if (arkts.isProperty(node)) {
        const callExpr = arkts.factory.createCallExpression(
            arkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE),
            [initializer, updater],
            undefined,
            false,
            false
        );
        const newValue = arkts.factory.createArrowFunctionExpression(
            arkts.factory.createScriptFunction(
                arkts.factory.createBlockStatement([arkts.factory.createExpressionStatement(callExpr)]),
                undefined, [], undefined, false,
                arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW,
                arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE,
                undefined, undefined
            )
        );
        newValue.setNoDebugLineFlag();
        result = arkts.factory.updateProperty(node, node.kind, node.key, newValue, node.isMethod, node.isComputed) as T;
        memoCache.collect(callExpr, callExprMetaData);
        memoCache.collect(result);
    } else {
        result = arkts.factory.updateCallExpression(
            node,
            arkts.factory.createIdentifier(InteroperAbilityNames.ARKUICOMPATIBLE),
            [initializer, updater],
            undefined,
            node.isOptional,
            node.hasTrailingComma,
            node.trailingBlock
        ) as T;
        memoCache.collect(result, callExprMetaData);
    }
    result.startPosition = node.startPosition;
    result.endPosition = node.endPosition;
    return result;
}

function getFunctionName(node: arkts.CallExpression | arkts.Property): string {
    if (arkts.isProperty(node)) {
        return 'createCompatibleNodeWithFuncVoid';
    }
    switch (node.arguments.length) {
        case 0:
            return 'createCompatibleNodeWithFuncVoid';
        case 1:
            return 'createCompatibleNodeWithFunc';
        case 2:
            return 'createCompatibleNodeWithFunc2';
        case 3:
            return 'createCompatibleNodeWithFunc3';
        case 4:
            return 'createCompatibleNodeWithFunc4';
        case 5:
            return 'createCompatibleNodeWithFunc5';
        case 6:
            return 'createCompatibleNodeWithFunc6';
        case 7:
            return 'createCompatibleNodeWithFunc7';
        case 8:
            return 'createCompatibleNodeWithFunc8';
        case 9:
            return 'createCompatibleNodeWithFunc9';
        case 10:
            return 'createCompatibleNodeWithFunc10';
        default:
            throw Error('Error arguments in Legacy Builder Function');
    }
}

export function isFromBuilder1_1(decl: arkts.AstNode | undefined): boolean {
    if (!decl || !arkts.isMethodDefinition(decl)) {
        return false;
    }
    const path = arkts.getProgramFromAstNode(decl)?.absoluteName;
    const fileManager = FileManager.getInstance();
    if (!path || fileManager.getLanguageVersionByFilePath(path) !== LANGUAGE_VERSION.ARKTS_1_1) {
        return false;
    }

    const annotations = decl.function?.annotations;
    const decorators: string[] = annotations.map((annotation) => {
        return (annotation.expr as arkts.Identifier).name;
    });
    for (const decorator of decorators) {
        if (decorator === 'memo' || decorator === 'Builder' || decorator === 'Memo') {
            return true;
        }
    }
    return false;
}

export function addcompatibleComponentImport(): void {
    ImportCollector.getInstance().collectSource('compatibleComponent', 'arkui.component.interop');
    ImportCollector.getInstance().collectImport('compatibleComponent');
}