/**
 * Copyright (c) 2024-2026 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.
 */

#include "lowering_test.h"
#include "compiler/lowering/ets/stringConstantsLowering.h"

namespace ark::es2panda {

static bool CheckConstReorderLeft(const ir::AstNode *node)
{
    if (node->IsBinaryExpression()) {
        auto const binOp = node->AsBinaryExpression();
        return (binOp->OperatorType() == lexer::TokenType::PUNCTUATOR_PLUS && binOp->Left()->IsIdentifier() &&
                binOp->Right()->IsStringLiteral());
    }
    return false;
}

static bool CheckConstReorderRight(const ir::AstNode *node)
{
    if (node->IsBinaryExpression()) {
        auto const binOp = node->AsBinaryExpression();
        return (binOp->OperatorType() == lexer::TokenType::PUNCTUATOR_PLUS && binOp->Left()->IsStringLiteral() &&
                binOp->Right()->IsIdentifier());
    }
    return false;
}

TEST_F(LoweringTest, TestStringConstansConcat)
{
    char const *text = R"(
        function main() {
            let v = "a" + ("bb" + "1") + (("ccc" + "123") + "dddd");
        }
    )";
    char const *expect = "abb1ccc123dddd";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return node->IsBinaryExpression(); }));
        ASSERT_TRUE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder1)
{
    char const *text = R"(
        const constPad = '=';
        function main() {
            let resultStr = "aa";
            resultStr = constPad + constPad + constPad;
        }
    )";
    char const *expect = "===";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_TRUE(ast->IsAnyChild([](ir::AstNode *const node) { return node->IsBinaryExpression(); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder2)
{
    char const *text = R"(
        const constPad = '=';
        function main() {
            let resultStr = "aa";
            resultStr = (constPad + constPad + constPad);
        }
    )";
    char const *expect = "===";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_TRUE(ast->IsAnyChild([](ir::AstNode *const node) { return node->IsBinaryExpression(); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder3)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';
        function main() {
            let resultStr = "aa";
            resultStr = (constPad1 + "%" + constPad2) + "=" + constPad3;
        }
    )";
    char const *expect = "=%%=&";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_TRUE(ast->IsAnyChild([](ir::AstNode *const node) { return node->IsBinaryExpression(); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder4)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';

        function main() {
            let resultStr = "aa";
            resultStr = resultStr + constPad1 + constPad2 + constPad3;
        }
    )";
    char const *expect = "=%&";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderLeft(node); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder5)
{
    char const *text = R"(
        function main() {
            let resultStr = "aa";
            resultStr = "^" + "%" + "=" + resultStr;
        }
    )";
    char const *expect = "^%=";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_TRUE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderRight(node); }));
        ASSERT_TRUE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder6)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';
        function main() {
            let resultStr = "aa";
            resultStr = resultStr + constPad1 + "%" + constPad2 + "=" + constPad3 + resultStr;
        }
    )";
    char const *expect = "=%%=&";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderLeft(node); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder7)
{
    char const *text = R"(
        const constPad = '=';
        function main() {
            let resultStr = "aa";
            let identifierStr = "bb";
            resultStr = (identifierStr + constPad) + (constPad + identifierStr);
        }
    )";
    char const *expect = "==";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderLeft(node); }));
        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderRight(node); }));
        ASSERT_FALSE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder8)
{
    char const *text = R"(
        function getString(str: string) {
            return "the " + str + " string";
        }

        function main(): void {
            const con = " constant";
            let str = "";
            str = str + getString("empty") + con;
        }
    )";
    char const *expect = " constant";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderLeft(node); }));
        ASSERT_TRUE(ast->IsAnyChild([expect](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder9)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';

        function main() {
            let str = "aa";
            str = (constPad1 + constPad2 + str + constPad3 + constPad1) + (constPad3 + str);
        }
    )";
    char const *expect1 = "=%";
    char const *expect2 = "&=&";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderRight(node); }));
        ASSERT_FALSE(ast->IsAnyChild([expect1](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect1);
        }));
        ASSERT_FALSE(ast->IsAnyChild([expect2](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect2);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder10)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';

        function main() {
            let str = "aa";
            str = (str + (str + constPad1) + (constPad2 + str) + constPad1 + constPad3) + str;
        }
    )";
    char const *expect1 = "=%";
    char const *expect2 = "=&";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderRight(node); }));
        ASSERT_FALSE(ast->IsAnyChild([expect1](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect1);
        }));
        ASSERT_FALSE(ast->IsAnyChild([expect2](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect2);
        }));
    }
}

TEST_F(LoweringTest, TestStringConstansReorder11)
{
    char const *text = R"(
        const constPad1 = '=';
        const constPad2 = '%';
        const constPad3 = '&';

        function main() {
            let str = "aa";
            let strNew = "bb";
            str = (str + (constPad1 + strNew)) + (constPad3 + constPad2 + constPad1) + strNew;
        }
    )";
    char const *expect1 = "=";
    char const *expect2 = "&%=";

    CONTEXT(ES2PANDA_STATE_CHECKED, text)
    {
        const auto *const ast = GetAst();

        ASSERT_FALSE(ast->IsAnyChild([](ir::AstNode *const node) { return CheckConstReorderRight(node); }));
        ASSERT_TRUE(ast->IsAnyChild([expect1](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect1);
        }));
        ASSERT_FALSE(ast->IsAnyChild([expect2](ir::AstNode *const node) {
            return node->IsStringLiteral() && node->AsStringLiteral()->Str().Is(expect2);
        }));
    }
}
}  // namespace ark::es2panda