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

#ifndef ES2PANDA_PARSER_INCLUDE_AST_SCRIPT_FUNCTION_H
#define ES2PANDA_PARSER_INCLUDE_AST_SCRIPT_FUNCTION_H

#include <ir/astNode.h>
#include <ir/expressions/identifier.h>
#include <util/enumbitops.h>

namespace panda::es2panda::compiler {
class PandaGen;
}  // namespace panda::es2panda::compiler

namespace panda::es2panda::binder {
class FunctionScope;
}  // namespace panda::es2panda::binder

namespace panda::es2panda::ir {

class TSTypeParameterDeclaration;

class ScriptFunction : public AstNode {
public:
    explicit ScriptFunction(binder::FunctionScope *scope, ArenaVector<Expression *> &&params,
                            TSTypeParameterDeclaration *typeParams, AstNode *body, Expression *returnTypeAnnotation,
                            ir::ScriptFunctionFlags flags, bool declare, bool isTsFunction)
        : AstNode(AstNodeType::SCRIPT_FUNCTION),
          scope_(scope),
          id_(nullptr),
          params_(std::move(params)),
          typeParams_(typeParams),
          body_(body),
          returnTypeAnnotation_(returnTypeAnnotation),
          flags_(flags),
          declare_(declare),
          exportDefault_(false)
    {
        thisParam_ = nullptr;
        if (isTsFunction && !params_.empty()) {
            auto *firstParam = params_.front();
            if (firstParam->IsIdentifier() && firstParam->AsIdentifier()->Name().Is(THIS_PARAM)) {
                thisParam_ = firstParam;
                params_.erase(params_.begin());
                scope_->ParamScope()->RemoveThisParam(THIS_PARAM);
                scope_->Bindings().erase(THIS_PARAM);
            }
        }
    }

    const Identifier *Id() const
    {
        return id_;
    }

    Identifier *Id()
    {
        return id_;
    }

    const ArenaVector<Expression *> &Params() const
    {
        return params_;
    }

    ArenaVector<Expression *> &Params()
    {
        return params_;
    }

    const TSTypeParameterDeclaration *TypeParams() const
    {
        return typeParams_;
    }

    TSTypeParameterDeclaration *TypeParams()
    {
        return typeParams_;
    }

    const Expression *ThisParams() const
    {
        return thisParam_;
    }

    Expression *ThisParams()
    {
        return thisParam_;
    }

    const AstNode *Body() const
    {
        return body_;
    }

    AstNode *Body()
    {
        return body_;
    }

    const Expression *ReturnTypeAnnotation() const
    {
        return returnTypeAnnotation_;
    }

    Expression *ReturnTypeAnnotation()
    {
        return returnTypeAnnotation_;
    }

    bool IsGenerator() const
    {
        return (flags_ & ir::ScriptFunctionFlags::GENERATOR) != 0;
    }

    bool IsAsync() const
    {
        return (flags_ & ir::ScriptFunctionFlags::ASYNC) != 0;
    }

    bool IsArrow() const
    {
        return (flags_ & ir::ScriptFunctionFlags::ARROW) != 0;
    }

    bool IsOverload() const
    {
        return (flags_ & ir::ScriptFunctionFlags::OVERLOAD) != 0;
    }

    bool IsConstructor() const
    {
        return (flags_ & ir::ScriptFunctionFlags::CONSTRUCTOR) != 0 ||
               (flags_ & ir::ScriptFunctionFlags::GENERATED_CONSTRUCTOR) != 0;
    }

    bool IsStaticInitializer() const
    {
        return (flags_ & ir::ScriptFunctionFlags::STATIC_INITIALIZER) != 0;
    }

    bool IsInstanceInitializer() const
    {
        return (flags_ & ir::ScriptFunctionFlags::INSTANCE_INITIALIZER) != 0;
    }

    bool IsMethod() const
    {
        return (flags_ & ir::ScriptFunctionFlags::METHOD) != 0 || IsInstanceInitializer() || IsStaticInitializer();
    }

    bool FunctionBodyIsExpression() const
    {
        return (flags_ & ir::ScriptFunctionFlags::EXPRESSION) != 0;
    }

    bool Declare() const
    {
        return declare_;
    }

    void SetIdent(Identifier *id)
    {
        id_ = id;
    }

    void SetAsExportDefault()
    {
        exportDefault_ = true;
    }

    void AddFlag(ir::ScriptFunctionFlags flags)
    {
        flags_ |= flags;
    }

    bool HasFlag(ir::ScriptFunctionFlags flag) const
    {
        return (flags_ & flag) != 0;
    }

    void SetInSendable(bool inSendable)
    {
        inSendable_ = inSendable;
    }

    void IncreasePropertyCount()
    {
        expectedPropertyCount_++;
    }

    size_t ExpectedPropertyCount() const
    {
        return expectedPropertyCount_;
    }

    size_t FormalParamsLength() const;
    util::StringView GetName() const;

    binder::FunctionScope *Scope() const
    {
        return scope_;
    }

    bool IsConcurrent() const
    {
        return (flags_ & ir::ScriptFunctionFlags::CONCURRENT) != 0;
    }

    bool IsSendable() const
    {
        return (flags_ & ir::ScriptFunctionFlags::SENDABLE) != 0;
    }

    bool CanBeConcurrent() const
    {
        return !(IsGenerator() || IsArrow());
    }

    void AddConcurrentModuleRequest(int moduleRequestId)
    {
        if (!IsConcurrent()) {
            return;
        }

        concurrentModuleRequests_.emplace_back(moduleRequestId);
    }

    std::vector<int> GetConcurrentModuleRequests() const
    {
        return concurrentModuleRequests_;
    }

    bool InSendable() const
    {
        return inSendable_;
    }

    void CalculateFunctionExpectedPropertyCount();
    void ExtractThisPropertyFromStatement(const ir::Statement *stmt,
                          const std::function<void(const util::StringView&)>& addPropertyName);

    void Iterate(const NodeTraverser &cb) const override;
    void Dump(ir::AstDumper *dumper) const override;
    void Compile([[maybe_unused]] compiler::PandaGen *pg) const override;
    void UpdateSelf(const NodeUpdater &cb, binder::Binder *binder) override;
    util::StringView SourceCode(binder::Binder *binder) const;

private:
    static constexpr std::string_view THIS_PARAM = "this";

    binder::FunctionScope *scope_;
    Identifier *id_;
    Expression *thisParam_;
    ArenaVector<Expression *> params_;
    TSTypeParameterDeclaration *typeParams_;
    AstNode *body_;
    Expression *returnTypeAnnotation_;
    ir::ScriptFunctionFlags flags_;
    bool declare_;
    bool exportDefault_;
    std::vector<int> concurrentModuleRequests_;
    bool inSendable_ {false};
    size_t expectedPropertyCount_ {0};
};

}  // namespace panda::es2panda::ir

#endif