/*
 * 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, InteroperAbilityNames, InteropInternalNames } from './predefines';
import { annotation, backingField, isAnnotation } from '../../common/arkts-utils';
import { stateProxy, getWrapValue, setPropertyESValue, createEmptyESValue } from './utils';
import { hasDecoratorInterop } from './utils';
import { DecoratorNames, DeprecatedDecoratorNames, LANGUAGE_VERSION } from '../../common/predefines';
import { FileManager } from '../../common/file-manager';
import { factory as UIFactory } from '../ui-factory';

export function initialArgs(args: arkts.ObjectExpression, varMap: Map<string, arkts.ClassProperty>,
    updateProp: arkts.Property[], node: arkts.CallExpression): arkts.Statement[] {
    const result: arkts.Statement[] = [];
    const proxySet = new Set<string>();
    for (const property of args.properties) {
        if (!(property instanceof arkts.Property)) {
            continue;
        }
        const key = property.key;
        const value = property.value!;
        if (!key || !arkts.isIdentifier(key)) {
            const errorMessage = 'Error arguments in Legacy Component.';
            logDiagnostic(errorMessage, node);
            continue;
        }
        const keyName = key.name;
        const keyProperty = varMap.get(keyName);
        if (keyProperty === undefined) {
            continue;
        }
        const keyType = keyProperty.typeAnnotation!;
        const annotations = keyProperty.annotations;
        if (annotations.length === 0) {
            const valueProperty = arkts.getDecl(value);
            if (
                valueProperty instanceof arkts.ClassProperty &&
                (hasDecoratorInterop(valueProperty, DecoratorNames.PROVIDE) ||
                    hasDecoratorInterop(valueProperty, DecoratorNames.CONSUME))
            ) {
                const errorMessage = 'Cannot assign @Provide or @Consume decorated data to regular property.';
                logDiagnostic(errorMessage, node);
            }
            const initParam = processNormal(keyName, value);
            result.push(...initParam);
        } else if (hasDecoratorInterop(keyProperty, DecoratorNames.LINK)) {
            const initParam = processLink(keyName, value, keyType, proxySet);
            result.push(...initParam);
        } else if (hasDecoratorInterop(keyProperty, DecoratorNames.CONSUME)) {
            const errorMessage = 'The @Consume property cannot be assigned.';
            logDiagnostic(errorMessage, node);
        } else if (
            hasDecoratorInterop(keyProperty, DeprecatedDecoratorNames.PROP) ||
            hasDecoratorInterop(keyProperty, DecoratorNames.OBJECT_LINK) ||
            hasDecoratorInterop(keyProperty, DecoratorNames.PARAM)
        ) {
            updateProp.push(property);
            const initParam = processNormal(keyName, value);
            result.push(...initParam);
        } else if (
            hasDecoratorInterop(keyProperty, DecoratorNames.STATE) ||
            hasDecoratorInterop(keyProperty, DecoratorNames.PROVIDE)
        ) {
            const initParam = processNormal(keyName, value);
            result.push(...initParam);
        } else if (hasDecoratorInterop(keyProperty, DecoratorNames.CONSUME)) {
            const errorMessage = 'The @Consume property cannot be assigned.';
            logDiagnostic(errorMessage, node);
        } else if (hasDecoratorInterop(keyProperty, DecoratorNames.BUILDER_PARAM)) {
            const initParam = processBuilderParam(keyName, value);
            result.push(...initParam);
        } else {
            const initParam = processNormal(keyName, value);
            result.push(...initParam);
        }
    }
    return result;
}

export function logDiagnostic(errorMessage: string, node: arkts.AstNode): void {
    const diagnosticKind = arkts.createDiagnosticKind(errorMessage, arkts.Es2pandaPluginDiagnosticType.ES2PANDA_PLUGIN_ERROR);
    arkts.logDiagnostic(diagnosticKind, node.startPosition);
}

export function createVariableLet(varName: string, expression: arkts.Expression): arkts.VariableDeclaration {
    return arkts.factory.createVariableDeclaration(
        arkts.Es2pandaVariableDeclarationKind.VARIABLE_DECLARATION_KIND_LET,
        [
            arkts.factory.createVariableDeclarator(
                arkts.Es2pandaVariableDeclaratorFlag.VARIABLE_DECLARATOR_FLAG_LET,
                arkts.factory.createIdentifier(varName),
                expression
            ),
        ]
    );
}

function createBackingFieldExpression(varName: string): arkts.TSNonNullExpression {
    return arkts.factory.createTSNonNullExpression(
        arkts.factory.createMemberExpression(
            arkts.factory.createThisExpression(),
            arkts.factory.createIdentifier(backingField(varName)),
            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
            false,
            false
        )
    );
}


function getStateProxy(proxyName: string, stateVar: () => arkts.Expression): arkts.Statement {
    return createVariableLet(
        proxyName,
        arkts.factory.createCallExpression(
            arkts.factory.createIdentifier(InteroperAbilityNames.GETCOMPATIBLESTATE),
            [stateVar()],
            undefined,
            false,
            false
        )
    );
}

/**
 * Processes a nested object literal and generates code to instantiate and populate it using ESValue methods.
 * 
 * Converts a nested object structure into a sequence of statements that:
 * 1. Instantiate empty objects via `ESValue.instantiateEmptyObject()`
 * 2. Sets properties on these objects using `setProperty()`
 * 3. Nests objects by assigning child objects as properties of parent objects
 * 
 * @param target - A nested object literal (e.g., { b: { c: { d: '1' }, cc: { d: '1' } } })
 * @returns Generated code statements that reconstruct the input object using ESValue APIs
 * @example
 * Input: { b: { c: { d: '1' }, cc: { d: '1' } } }
 * Output:
 * let param0 = ESValue.instantiateEmptyObject();
 * let param00 = ESValue.instantiateEmptyObject();
 * param00.setProperty("d", ESValue.wrap("1"));
 * param0.setProperty("c", param00);
 * let param01 = ESValue.instantiateEmptyObject();
 * param01.setProperty("d", ESValue.wrap("1"));
 * param0.setProperty("cc", param01);
 * param.setProperty("b", param0);
 */
function processObjectLiteral(
    target: arkts.ObjectExpression,
    curParam: string,
    result: arkts.Statement[],
    keyName: string
): void {
    if (curParam !== InteropInternalNames.PARAM) {
        const createParam = createEmptyESValue(curParam);
        result.push(createParam);
    }
    (target.properties as arkts.Property[]).forEach((property: arkts.Property) => {
        const paramName = curParam + keyName;
        const key = property.key;
        const value = property.value;
        if (!key || !value || !arkts.isIdentifier(key)) {
            return;
        }
        if (arkts.isObjectExpression(value)) {
            processObjectLiteral(value, paramName, result, keyName);
            const setProperty = setPropertyESValue(curParam, key.name, arkts.factory.createIdentifier(paramName));
            result.push(setProperty);
        } else {
            const setProperty = setPropertyESValue(curParam, key.name, getWrapValue(value));
            result.push(setProperty);
        }
    });
}


/**
 * 
 * @param keyName - The name of the state variable (e.g., state)
 * @returns generate code to process @Link data interoperability
 * @example
 * Input: {link: this.state}
 * Output:
 * let __Proxy_state = getCompatibleState(this.state);
 * param.setProperty("link", __Proxy_state);
 */
export function processLink(
    keyName: string,
    value: arkts.Expression,
    type: arkts.TypeNode,
    proxySet: Set<string>
): arkts.Statement[] {
    const valueDecl = arkts.getDecl(value);
    const result: arkts.Statement[] = [];
    if (valueDecl instanceof arkts.ClassProperty) {
        let varName = ((value as arkts.MemberExpression).property as arkts.Identifier).name;
        let proxyName = stateProxy(varName);
        let stateVar = (): arkts.TSNonNullExpression => createBackingFieldExpression(varName);
        if (!proxySet.has(varName)) {
            proxySet.add(varName);
            const getProxy = getStateProxy(proxyName, stateVar);
            result.push(getProxy);
        }
        const setParam = setPropertyESValue(
            InteropInternalNames.PARAM,
            keyName,
            arkts.factory.createIdentifier(proxyName)
        );
        result.push(setParam);
    } else {
        throw Error('unsupported data for Link');
    }
    return result;
}

/**
 *
 * @param keyName - The name of the state variable (e.g., state)
 * @returns generate code to process regular data interoperability
 */
export function processNormal(keyName: string, value: arkts.Expression): arkts.Statement[] {
    const result: arkts.Statement[] = [];
    if (arkts.isObjectExpression(value)) {
        processObjectLiteral(value, InteropInternalNames.PARAM, result, keyName);
    } else {
        const setProperty = setPropertyESValue(InteropInternalNames.PARAM, keyName, getWrapValue(value));
        result.push(setProperty);
    }
    return result;
}

function isDynamicBuilder(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;
    }
    return true;
}

/**
 * 
 * @param keyName
 * @param value
 * @returns generate code to process @BuilderParam interoperability
 * @example
 * Input: {builderParam: this.builder}
 * Output: param.setProperty("builderParam", transferCompatibleBuilder(this.builder));
 */
export function processBuilderParam(keyName: string, value: arkts.Expression): arkts.Statement[] {
    const result: arkts.Statement[] = [];
    const decl = arkts.getDecl(value);
    const isDynamic = isDynamicBuilder(decl);

    let newValue = value;

    if (!isDynamic) {
        if (checkUpdatable(value)) {
            const funcType = arkts.factory.createFunctionType(
                arkts.factory.createFunctionSignature(
                    undefined,
                    [
                        UIFactory.createParameterDeclaration('__memo_context', 'Any'),
                        UIFactory.createParameterDeclaration('__memo_id', 'Any'),
                        UIFactory.createParameterDeclaration('arg', 'Object'),
                    ],
                    arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID),
                    false
                ),
                arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW
            );

            const innerAsExpr = arkts.factory.createTSAsExpression(value, UIFactory.createTypeReferenceFromString('Any'), false);
            const outerAsExpr = arkts.factory.createTSAsExpression(innerAsExpr, funcType, false);
    
            newValue = arkts.factory.createCallExpression(
                arkts.factory.createIdentifier(BuilderMethodNames.TRANSFERCOMPATIBLEUPDATABLEBUILDER),    
                [UIFactory.createTypeReferenceFromString('Object')],
                [outerAsExpr]
            );
        } else {
            newValue = arkts.factory.createCallExpression(
                arkts.factory.createIdentifier(BuilderMethodNames.TRANSFERCOMPATIBLEBUILDER),
                undefined,
                [value]
            );
        }
    }
    
    const setProperty = setPropertyESValue(InteropInternalNames.PARAM, keyName, newValue);
    result.push(setProperty);
    return result;
}

function getIdentifier(value: arkts.AstNode): arkts.Identifier | undefined {
    if (arkts.isIdentifier(value)) {
        return value;
    } else if (
        arkts.isMemberExpression(value) &&
        arkts.isThisExpression(value.object) &&
        arkts.isIdentifier(value.property)
    ) {
        return value.property;
    } else {
        return undefined;
    }
}

function checkUpdatable(value: arkts.AstNode): boolean {
    const ident = getIdentifier(value);
    if (ident === undefined) {
        return false;
    }
    const decl = arkts.getDecl(ident) as arkts.MethodDefinition;
    const script = decl.function;
    const params = script.params;
    if (params.length === 1 && arkts.isETSParameterExpression(params[0])) {
        const type = params[0].typeAnnotation;
        if (type === undefined) {
            return false;
        }
        if (arkts.isETSUnionType(type)) {
            for (const element of type.types) {
                if (isNonBuiltinType(element)) {
                    return true;
                }
            }
        } else if (isNonBuiltinType(type)) {
            return true;
        }
    }
    return false;
}

function isNonBuiltinType(type: arkts.AstNode): boolean {
    if (!arkts.isETSTypeReference(type)) {
        return false;
    }
    const ident = type.part?.name;
    if (ident && arkts.isIdentifier(ident)) {
        const decl = arkts.getDecl(ident);
        if (!decl) {
            return false;
        }
        const moduleName = arkts.getProgramFromAstNode(decl)?.moduleName;
        if (moduleName === 'escompat') {
            return false;
        }
        return true;
    }
    return false;
}