/**
 * 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 "es2panda_lib.h"
#include <cstddef>
#include <cstring>
#include <cstdint>

#include "util/diagnostic.h"
#include "util/eheap.h"
#include "util/perfMetrics.h"
#include "varbinder/varbinder.h"
#include "varbinder/scope.h"
#include "public/public.h"
#include "generated/signatures.h"
#include "es2panda.h"
#include "varbinder/ETSBinder.h"
#include "checker/ETSAnalyzer.h"
#include "checker/ETSchecker.h"
#include "compiler/core/compileQueue.h"
#include "compiler/core/compilerImpl.h"
#include "compiler/core/ETSCompiler.h"
#include "compiler/core/ETSemitter.h"
#include "compiler/core/ETSGen.h"
#include "compiler/core/regSpiller.h"
#include "compiler/lowering/phase.h"
#include "compiler/lowering/ets/lambdaLowering.h"
#include "ir/astNode.h"
#include "ir/expressions/arrowFunctionExpression.h"
#include "ir/ts/tsAsExpression.h"
#include "ir/expressions/assignmentExpression.h"
#include "ir/expressions/binaryExpression.h"
#include "ir/statements/blockStatement.h"
#include "ir/expressions/callExpression.h"
#include "ir/base/classProperty.h"
#include "ir/ets/etsFunctionType.h"
#include "ir/statements/ifStatement.h"
#include "ir/base/methodDefinition.h"
#include "ir/ets/etsGenericInstantiatedNode.h"
#include "ir/ets/etsNewClassInstanceExpression.h"
#include "ir/ets/etsNewArrayInstanceExpression.h"
#include "ir/ets/etsNewMultiDimArrayInstanceExpression.h"
#include "parser/ETSparser.h"
#include "parser/context/parserContext.h"
#include "parser/program/program.h"
#include "util/generateBin.h"
#include "util/options.h"
#include "compiler/lowering/util.h"
#include "generated/es2panda_lib/es2panda_lib_include.inc"
#include "declgen_ets2ts/declgenEts2Ts.h"

// NOLINTBEGIN

namespace ark::es2panda::public_lib {

static const char *ETSCACHE_SUFFIX = ".etscache";
static const char *DECL_ETS_SUFFIX = ".d.ets";

struct TokenTypeToStr {
    lexer::TokenType token;
    char const *str;
};

__attribute__((unused)) lexer::TokenType StrToToken(TokenTypeToStr const *table, char const *str)
{
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    for (auto *tp = table; tp->str != nullptr; tp++) {
        if (strcmp(str, tp->str) == 0) {
            return tp->token;
        }
    }
    ES2PANDA_UNREACHABLE();
}

__attribute__((unused)) char const *TokenToStr(TokenTypeToStr const *table, lexer::TokenType token)
{
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    for (auto *tp = table; tp->str != nullptr; tp++) {
        if (tp->token == token) {
            return tp->str;
        }
    }
    ES2PANDA_UNREACHABLE();
}

char *StringViewToCString(ArenaAllocator *allocator, std::string_view const utf8)
{
    // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
    if (!utf8.empty() && (utf8.back() == '\0')) {
        // Avoid superfluous allocation.
        return const_cast<char *>(utf8.data());
    }
    char *res = reinterpret_cast<char *>(allocator->Alloc(utf8.size() + 1));
    if (!utf8.empty()) {
        [[maybe_unused]] auto err = memmove_s(res, utf8.size() + 1, utf8.cbegin(), utf8.size());
        ES2PANDA_ASSERT(err == EOK);
    }
    res[utf8.size()] = '\0';
    return res;
    // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
}

__attribute__((unused)) char *StringViewToCString(ArenaAllocator *allocator, util::StringView const sv)
{
    return StringViewToCString(allocator, sv.Utf8());
}

__attribute__((unused)) char *StdStringToCString(ArenaAllocator *allocator, std::string str)
{
    // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
    char *res = reinterpret_cast<char *>(allocator->Alloc(str.length() + 1));
    [[maybe_unused]] auto err = memcpy_s(res, str.length() + 1, str.c_str(), str.length() + 1);
    ES2PANDA_ASSERT(err == EOK);
    return res;
    // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
}

__attribute__((unused)) char *UStringToCString(ArenaAllocator *allocator, util::UString const sv)
{
    return StringViewToCString(allocator, sv.View());
}

__attribute__((unused)) es2panda_variantDoubleCharArrayBool EnumMemberResultToEs2pandaVariant(
    ArenaAllocator *allocator, varbinder::EnumMemberResult variant)
{
    // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
    // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
    es2panda_variantDoubleCharArrayBool es2panda_variant;
    es2panda_variant.index = static_cast<int>(variant.index());
    switch (es2panda_variant.index) {
        case es2panda_variantIndex::CAPI_DOUBLE:
            es2panda_variant.variant.d = std::get<double>(variant);
            break;
        case es2panda_variantIndex::CAPI_CHAR:
            es2panda_variant.variant.c = StringViewToCString(allocator, std::get<util::StringView>(variant));
            break;
        case es2panda_variantIndex::CAPI_BOOL:
            es2panda_variant.variant.b = std::get<bool>(variant);
            break;
        default:
            break;
    }
    return es2panda_variant;
    // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic, readability-simplify-subscript-expr)
    // NOLINTEND(cppcoreguidelines-pro-type-union-access)
}

__attribute__((unused)) es2panda_DynamicImportData *DynamicImportDataToE2pPtr(
    ArenaAllocator *allocator, const varbinder::DynamicImportData *dynamicImportData)
{
    auto import = reinterpret_cast<const es2panda_AstNode *>(dynamicImportData->import);
    auto specifier = reinterpret_cast<const es2panda_AstNode *>(dynamicImportData->specifier);
    auto variable = reinterpret_cast<es2panda_Variable *>(dynamicImportData->variable);
    auto es2pandaDynamicImportData = allocator->New<es2panda_DynamicImportData>();
    es2pandaDynamicImportData->import = import;
    es2pandaDynamicImportData->specifier = specifier;
    es2pandaDynamicImportData->variable = variable;
    return es2pandaDynamicImportData;
}

__attribute__((unused)) es2panda_DynamicImportData DynamicImportDataToE2p(
    const varbinder::DynamicImportData dynamicImportData)
{
    auto import = reinterpret_cast<const es2panda_AstNode *>(dynamicImportData.import);
    auto specifier = reinterpret_cast<const es2panda_AstNode *>(dynamicImportData.specifier);
    auto variable = reinterpret_cast<es2panda_Variable *>(dynamicImportData.variable);
    es2panda_DynamicImportData es2pandaDynamicImportData;
    es2pandaDynamicImportData.import = import;
    es2pandaDynamicImportData.specifier = specifier;
    es2pandaDynamicImportData.variable = variable;
    return es2pandaDynamicImportData;
}

__attribute__((unused)) char const *ArenaStrdup(ArenaAllocator *allocator, char const *src)
{
    size_t len = strlen(src);
    char *res = reinterpret_cast<char *>(allocator->Alloc(len + 1));
    [[maybe_unused]] auto err = memmove_s(res, len + 1, src, len);
    ES2PANDA_ASSERT(err == EOK);

    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    res[len] = '\0';
    return res;
}

extern "C" void MemInitialize()
{
    if (ScopedAllocatorsManager::IsInitialized()) {
        return;
    }
    ScopedAllocatorsManager::Initialize();
}

extern "C" void MemFinalize()
{
    ScopedAllocatorsManager::Finalize();
}

extern "C" es2panda_Config *CreateConfig(int args, char const *const *argv)
{
    MemInitialize();
    auto diagnosticEngine = new util::DiagnosticEngine();
    auto *options = new util::Options(argv[0], *diagnosticEngine);
    if (!options->Parse(Span(argv, args))) {
        return nullptr;
    }
    ark::Logger::ComponentMask mask {};
    mask.set(ark::Logger::Component::ES2PANDA);
    ark::Logger::InitializeStdLogging(options->LogLevel(), mask);

    auto *res = new ConfigImpl;
    res->options = options;
    res->diagnosticEngine = diagnosticEngine;
    return reinterpret_cast<es2panda_Config *>(res);
}

extern "C" __attribute__((unused)) void ResetCounters()
{
    compiler::ResetCalleeCountOutside();
    compiler::ResetGenSymCounter();
}

extern "C" void DestroyConfigControl(es2panda_Config *config, bool logFlag)
{
    auto *cfg = reinterpret_cast<ConfigImpl *>(config);
    if (cfg == nullptr) {
        return;
    }

    if (cfg->options->IsDumpPerfMetrics()) {
        util::DumpPerfMetrics();
    }
    delete cfg->options;
    if (logFlag) {
        cfg->diagnosticEngine->FlushDiagnostic();
    } else {
        cfg->diagnosticEngine->ClearDiagnostics();
    }
    delete cfg->diagnosticEngine;
    delete cfg;
    ark::Logger::Destroy();
    ResetCounters();
}

extern "C" void DestroyConfig(es2panda_Config *config)
{
    DestroyConfigControl(config, true);
}

extern "C" void DestroyConfigWithoutLog(es2panda_Config *config)
{
    DestroyConfigControl(config, false);
}

extern "C" __attribute__((unused)) char const *GetAllErrorMessages(es2panda_Context *context)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    ES2PANDA_ASSERT(ctx != nullptr);
    ES2PANDA_ASSERT(ctx->config != nullptr);
    ES2PANDA_ASSERT(ctx->allocator != nullptr);
    auto *cfg = reinterpret_cast<ConfigImpl *>(ctx->config);
    ES2PANDA_ASSERT(cfg != nullptr);
    ES2PANDA_ASSERT(cfg->diagnosticEngine != nullptr);
    auto allMessages = cfg->diagnosticEngine->PrintAndFlushErrorDiagnostic();
    size_t bufferSize = allMessages.length() + 1;
    char *cStringMessages = reinterpret_cast<char *>(ctx->allocator->Alloc(bufferSize));
    [[maybe_unused]] auto err = memcpy_s(cStringMessages, bufferSize, allMessages.c_str(), bufferSize);
    ES2PANDA_ASSERT(err == EOK);
    return cStringMessages;
}

extern "C" const es2panda_Options *ConfigGetOptions(es2panda_Config *config)
{
    auto options = reinterpret_cast<ConfigImpl *>(config)->options;
    return reinterpret_cast<const es2panda_Options *>(options);
}

static void CompileJob(public_lib::Context *context, varbinder::FunctionScope *scope,
                       compiler::ProgramElement *programElement)
{
    if (!compiler::ETSFunctionEmitter::IsEmissionRequired(scope->Node()->AsScriptFunction(), context->parserProgram)) {
        return;
    }
    compiler::StaticRegSpiller regSpiller;
    auto allocator = ScopedAllocatorsManager::CreateAllocator();
    compiler::ETSCompiler astCompiler {};
    compiler::ETSGen cg {&allocator, &regSpiller, context, std::make_tuple(scope, programElement, &astCompiler)};
    compiler::ETSFunctionEmitter funcEmitter {&cg, programElement};
    funcEmitter.Generate();
}

static void GenerateStdLibCache(es2panda_Config *config, GlobalContext *globalContext, bool LspUsage);

extern "C" __attribute__((unused)) es2panda_GlobalContext *CreateGlobalContext(es2panda_Config *config,
                                                                               const char **externalFileList,
                                                                               size_t fileNum, bool LspUsage)
{
    auto *globalContext = new GlobalContext;
    for (size_t i = 0; i < fileNum; i++) {
        auto fileName = externalFileList[i];
        auto globalAllocator = EHeap::NewAllocator().release();
        globalContext->cachedExternalPrograms.emplace(fileName, nullptr);
        globalContext->externalProgramAllocators.emplace(fileName, globalAllocator);
    }

    GenerateStdLibCache(config, globalContext, LspUsage);

    return reinterpret_cast<es2panda_GlobalContext *>(globalContext);
}

extern "C" __attribute__((unused)) void RemoveFileCache(es2panda_GlobalContext *globalContext, const char *fileName)
{
    auto globalCtx = reinterpret_cast<GlobalContext *>(globalContext);
    ES2PANDA_ASSERT(globalCtx->cachedExternalPrograms.count(fileName) == 1);
    globalCtx->cachedExternalPrograms.erase(fileName);
    delete globalCtx->externalProgramAllocators[fileName];
    globalCtx->externalProgramAllocators.erase(fileName);
}

extern "C" __attribute__((unused)) void AddFileCache(es2panda_GlobalContext *globalContext, const char *fileName)
{
    auto globalCtx = reinterpret_cast<GlobalContext *>(globalContext);
    ES2PANDA_ASSERT(globalCtx->cachedExternalPrograms.count(fileName) == 0);
    auto globalAllocator = EHeap::NewAllocator().release();
    globalCtx->cachedExternalPrograms.emplace(fileName, nullptr);
    globalCtx->externalProgramAllocators.emplace(fileName, globalAllocator);
}

extern "C" __attribute__((unused)) void InvalidateFileCache(es2panda_GlobalContext *globalContext, const char *fileName)
{
    RemoveFileCache(globalContext, fileName);
    AddFileCache(globalContext, fileName);
}

static void ResetContextAndProgram(Context *ctx, parser::Program *program)
{
    ctx->phaseManager->Reset();
    ctx->diagnosticEngine->ClearDiagnostics();
    ctx->state = ES2PANDA_STATE_NEW;
    delete ctx->sourceFile;
    ctx->sourceFile = nullptr;
    program->Clear();
}

static void UpdateProgramTextForIncremental(Context *ctx, parser::Program *program, std::string &&text)
{
    auto *ImportInfo = const_cast<util::ImportInfo *>(&program->GetImportInfo());
    auto sourcePath = std::string {ImportInfo->TextSource()};
    auto resolvedPath = ImportInfo->ResolvedSource();
    ImportInfo->UpdateTextForLSPIncremental(sourcePath, std::move(text));
    auto textView = ImportInfo->DataFor<parser::CacheType::SOURCES>();
    SourceFile updatedSource {sourcePath, textView, resolvedPath, ctx->config->options->IsModule(),
                              program->IsDeclForDynamicStaticInterop()};
    program->SetSource(updatedSource);
}

static void AddDirectDependenciesToExternalSourcesForIncremental(Context *ctx, parser::Program *targetProgram)
{
    auto *importPathManager = ctx->parser->GetImportPathManager();
    auto &externalSources = *targetProgram->GetExternalDecls();
    externalSources.Direct().clear();
    externalSources.Get<util::ModuleKind::MODULE>().clear();
    externalSources.Get<util::ModuleKind::SOURCE_DECL>().clear();

    auto depsIt = importPathManager->GetFileDependencies().find(ArenaString {targetProgram->AbsoluteName().Utf8()});
    if (depsIt == importPathManager->GetFileDependencies().end()) {
        return;
    }

    for (const auto &dep : depsIt->second) {
        auto *depProgram =
            importPathManager->FindOrIntroduceProgramForIncremental(std::string_view {dep.data(), dep.size()});
        if (depProgram == nullptr || depProgram == targetProgram) {
            continue;
        }
        externalSources.Add(depProgram);
    }
}

extern "C" __attribute__((unused)) int IncrementalPrepareProgram(es2panda_Context *context, const char *fileName,
                                                                 const char *sourceText, bool isChanged)
{
    ES2PANDA_ASSERT(context != nullptr);
    auto *ctx = reinterpret_cast<Context *>(context);
    parser::Program *targetProgram =
        ctx->parser->GetImportPathManager()->FindOrIntroduceProgramForIncremental(fileName);
    // If source is unchanged, reuse existing AST and skip recompilation.
    if (!isChanged && targetProgram->Ast() != nullptr) {
        ctx->parserProgram = targetProgram;
        ctx->sourceFileName = fileName;
        ctx->input = std::string(sourceText);
        delete ctx->sourceFile;
        ctx->sourceFile = new SourceFile(ctx->sourceFileName, ctx->input, ctx->config->options->IsModule());
        AddDirectDependenciesToExternalSourcesForIncremental(ctx, targetProgram);
        return 1;
    }

    ResetContextAndProgram(ctx, targetProgram);
    ctx->parserProgram = targetProgram;
    ctx->sourceFileName = fileName;
    ctx->input = std::string(sourceText);
    ctx->sourceFile = new SourceFile(ctx->sourceFileName, ctx->input, ctx->config->options->IsModule());
    UpdateProgramTextForIncremental(ctx, targetProgram, std::string(sourceText));
    ctx->parser->GetImportPathManager()->PrepareParseQueueForProgram(targetProgram);
    return 0;
}

static void InitializeContext(Context *res)
{
    parser::ImportCache<parser::CacheType::SOURCES>::ActivateCache();
    res->phaseManager = new compiler::PhaseManager(res, ScriptExtension::ETS, res->allocator);
    res->queue = new compiler::CompileQueue(res->config->options->GetThread());

    res->parser = new parser::ETSParser(res, parser::ParserStatus::NO_OPTS);

    res->PushChecker(res->allocator->New<checker::ETSChecker>(res->allocator, *res->diagnosticEngine));
    res->PushAnalyzer(res->allocator->New<checker::ETSAnalyzer>(res->GetChecker()));
    res->GetChecker()->SetAnalyzer(res->GetAnalyzer());

    res->codeGenCb = CompileJob;
    res->emitter = new compiler::ETSEmitter(res);
    res->state = ES2PANDA_STATE_NEW;
}

static Context *InitContext(es2panda_Config *config)
{
    auto *cfg = reinterpret_cast<ConfigImpl *>(config);
    auto *res = new Context;
    if (cfg == nullptr) {
        res->errorMessage = "Config is nullptr.";
        res->state = ES2PANDA_STATE_ERROR;
        return res;
    }

    if (cfg->options->GetExtension() != ScriptExtension::ETS) {
        res->errorMessage = "Invalid extension. Plugin API supports only ETS.";
        res->state = ES2PANDA_STATE_ERROR;
        res->diagnosticEngine = cfg->diagnosticEngine;
        return res;
    }

    res->config = cfg;
    res->diagnosticEngine = cfg->diagnosticEngine;
    return res;
}

__attribute__((unused)) static es2panda_Context *CreateContext(es2panda_Config *config, std::string &&source,
                                                               const char *fileName,
                                                               es2panda_GlobalContext *globalContext, bool isExternal,
                                                               bool genStdLib, bool isLspUsage = false)
{
    auto res = InitContext(config);
    if (res->state == ES2PANDA_STATE_ERROR) {
        return reinterpret_cast<es2panda_Context *>(res);
    }
    auto *cfg = reinterpret_cast<ConfigImpl *>(config);

    res->isExternal = isExternal;
    res->isLspUsage = isLspUsage;
    res->globalContext = reinterpret_cast<GlobalContext *>(globalContext);

    res->input = std::move(source);
    res->sourceFileName = fileName;
    res->sourceFile = new SourceFile(res->sourceFileName, res->input, cfg->options->IsModule());
    if (isExternal) {
        ir::EnableContextHistory();
        ES2PANDA_ASSERT(res->globalContext != nullptr);
        if (genStdLib) {
            ES2PANDA_ASSERT(res->globalContext->stdLibAllocator != nullptr);
            res->allocator = res->globalContext->stdLibAllocator;
        } else {
            ES2PANDA_ASSERT(res->globalContext->externalProgramAllocators.count(fileName) != 0);
            res->allocator =
                reinterpret_cast<ArenaAllocator *>(res->globalContext->externalProgramAllocators[fileName]);
        }
    } else {
        ir::DisableContextHistory();
        res->eheapScope = new EHeap::Scope();
        res->allocator = EHeap::NewAllocator().release();
    }

    InitializeContext(res);
    return reinterpret_cast<es2panda_Context *>(res);
}

__attribute__((unused)) static std::stringstream ReadFile(char const *sourceFileName, Context *&res)
{
    std::ifstream inputStream;
    inputStream.open(sourceFileName);
    if (inputStream.fail()) {
        res = new Context;
        res->errorMessage = "Failed to open file: ";
        res->errorMessage.append(sourceFileName);
    }
    std::stringstream ss;
    ss << inputStream.rdbuf();
    if (inputStream.fail()) {
        res = new Context;
        res->errorMessage = "Failed to read file: ";
        res->errorMessage.append(sourceFileName);
    }
    return ss;
}

extern "C" __attribute__((unused)) es2panda_Context *CreateCacheContextFromFile(es2panda_Config *config,
                                                                                char const *sourceFileName,
                                                                                es2panda_GlobalContext *globalContext,
                                                                                bool isExternal)
{
    Context *res = nullptr;
    auto ss = ReadFile(sourceFileName, res);
    if (res != nullptr) {
        return reinterpret_cast<es2panda_Context *>(res);
    }
    return CreateContext(config, ss.str(), sourceFileName, globalContext, isExternal, false);
}

extern "C" __attribute__((unused)) es2panda_Context *CreateContextFromFile(es2panda_Config *config,
                                                                           char const *sourceFileName)
{
    Context *res = nullptr;
    auto ss = ReadFile(sourceFileName, res);
    if (res != nullptr) {
        return reinterpret_cast<es2panda_Context *>(res);
    }

    return CreateContext(config, ss.str(), sourceFileName, nullptr, false, false);
}

extern "C" __attribute__((unused)) es2panda_Context *CreateCacheContextFromString(es2panda_Config *config,
                                                                                  const char *source,
                                                                                  char const *fileName,
                                                                                  es2panda_GlobalContext *globalContext,
                                                                                  bool isExternal, bool isLspUsage)
{
    return CreateContext(config, std::string(source), fileName, globalContext, isExternal, false, isLspUsage);
}

extern "C" __attribute__((unused)) es2panda_Context *CreateContextFromMultiFile(es2panda_Config *config,
                                                                                char const *sourceFileNames)
{
    return CreateContext(config, "", sourceFileNames, nullptr, false, false);
}

extern "C" __attribute__((unused)) es2panda_Context *CreateContextFromString(es2panda_Config *config,
                                                                             const char *source, char const *fileName)
{
    // NOTE: gogabr. avoid copying source.
    return CreateContext(config, std::string(source), fileName, nullptr, false, false);
}

extern __attribute__((unused)) es2panda_Context *CreateContextSimultaneousMode(es2panda_Config *config,
                                                                               int fileNamesCount,
                                                                               char const *const *fileNames)
{
    auto res = InitContext(config);
    if (res->state == ES2PANDA_STATE_ERROR) {
        return reinterpret_cast<es2panda_Context *>(res);
    }
    auto *cfg = reinterpret_cast<ConfigImpl *>(config);

    ES2PANDA_ASSERT(cfg->options->IsSimultaneous());

    Span<const char *const> files(fileNames, fileNamesCount);
    for (const char *file : files) {
        res->sourceFileNames.emplace_back(std::string {file});
    }

    res->input = "";
    res->sourceFileName = "";
    res->sourceFile = new SourceFile(res->sourceFileName, res->input, cfg->options->IsModule());
    ir::DisableContextHistory();
    res->eheapScope = new EHeap::Scope();
    res->allocator = EHeap::NewAllocator().release();

    InitializeContext(res);
    return reinterpret_cast<es2panda_Context *>(res);
}

extern __attribute__((unused)) es2panda_Context *CreateContextSimultaneousModeForLsp(es2panda_Config *config,
                                                                                     int fileNamesCount,
                                                                                     char const *const *fileNames,
                                                                                     bool isLspUsage)
{
    auto *ctx = reinterpret_cast<Context *>(CreateContextSimultaneousMode(config, fileNamesCount, fileNames));
    ctx->isLspUsage = isLspUsage;
    return reinterpret_cast<es2panda_Context *>(ctx);
}

extern __attribute__((unused)) es2panda_Context *CreateContextGenerateAbcForExternalSourceFiles(
    es2panda_Config *config, int fileNamesCount, char const *const *fileNames)
{
    reinterpret_cast<ConfigImpl *>(config)->diagnosticEngine->LogDiagnostic(
        diagnostic::DEPRECATED_PUBLIC_API, util::DiagnosticMessageParams {__FUNCTION__});
    return CreateContextSimultaneousMode(config, fileNamesCount, fileNames);
}

extern "C" __attribute__((unused)) es2panda_Context *CreateContextFromStringWithHistory(es2panda_Config *config,
                                                                                        const char *source,
                                                                                        char const *fileName)
{
    // NOTE: gogabr. avoid copying source.
    es2panda_Context *context = CreateContextFromString(config, source, fileName);
    ir::EnableContextHistory();
    return context;
}

__attribute__((unused)) static Context *Parse(Context *ctx)
{
    if (ctx->state != ES2PANDA_STATE_NEW) {
        ctx->state = ES2PANDA_STATE_ERROR;
        ctx->errorMessage = "Bad state at entry to Parse, needed NEW";
        return ctx;
    }

    ctx->phaseManager->Reset();
    ES2PANDA_PERF_SCOPE("@Parser");

    if (ctx->parserProgram != nullptr && ctx->isLspUsage) {
        ctx->parser->AsETSParser()->IncrementalParse();
    } else if (ctx->config->options->IsSimultaneous()) {
        ES2PANDA_ASSERT(ctx->parserProgram == nullptr);
        ctx->parser->AsETSParser()->ParseInSimultMode();
    } else {
        ES2PANDA_ASSERT(ctx->parserProgram == nullptr);
        ctx->parser->ParseGlobal();
    }
    ES2PANDA_ASSERT(ctx->parserProgram != nullptr);

    ctx->state = ES2PANDA_STATE_PARSED;
    ctx->phaseManager->SetCurrentPhaseIdToAfterParse();
    return ctx;
}

__attribute__((unused)) static Context *Bind(Context *ctx)
{
    if (ctx->state < ES2PANDA_STATE_PARSED) {
        ctx = Parse(ctx);
    }
    if (ctx->state == ES2PANDA_STATE_ERROR) {
        return ctx;
    }

    ES2PANDA_ASSERT(ctx->state == ES2PANDA_STATE_PARSED);
    while (auto phase = ctx->phaseManager->NextPhase()) {
        if (phase->Name() == "plugins-after-bind") {
            break;
        }
        phase->Apply(ctx);
    }
    ctx->state = ES2PANDA_STATE_BOUND;
    return ctx;
}

__attribute__((unused)) static void SaveCache(Context *ctx)
{
    if (ctx->allocator == ctx->globalContext->stdLibAllocator) {
        return;
    }

    ES2PANDA_ASSERT(ctx->globalContext != nullptr);
    auto &cacheMap = ctx->globalContext->cachedExternalPrograms;
    ES2PANDA_ASSERT(cacheMap.count(ctx->sourceFileName) != 0);
    cacheMap[ctx->sourceFileName] = ctx->parserProgram;

    // cycle dependencies
    ctx->parserProgram->GetExternalDecls()->Visit([&cacheMap](auto *extProgram) {
        auto absPath = std::string {extProgram->AbsoluteName()};
        if (cacheMap.count(absPath) == 1 && cacheMap[absPath] == nullptr) {
            cacheMap[absPath] = extProgram;
        }
    });
}

__attribute__((unused)) static void MarkAsLowered(Context *ctx)
{
    auto markAsLowered = [](parser::Program *program) {
        if (!program->IsASTLowered()) {
            program->MarkASTAsLowered();
        }
        if (!program->IsProgramModified()) {
            program->SetProgramModified(true);
        }
    };

    markAsLowered(ctx->parserProgram);
    ctx->parserProgram->SetProgramModified(true);
    ctx->parserProgram->GetExternalDecls()->Visit<false>([&markAsLowered](auto *extProgram) {
        markAsLowered(extProgram);
        extProgram->MaybeIteratePackage(
            [&markAsLowered](parser::Program *fractionOrSelf) { markAsLowered(fractionOrSelf); });
    });
}

__attribute__((unused)) static Context *Check(Context *ctx)
{
    if (ctx->state < ES2PANDA_STATE_PARSED) {
        ctx = Parse(ctx);
    }

    if (ctx->state == ES2PANDA_STATE_ERROR) {
        return ctx;
    }

    ES2PANDA_ASSERT(ctx->state >= ES2PANDA_STATE_PARSED && ctx->state < ES2PANDA_STATE_CHECKED);
    while (auto phase = ctx->phaseManager->NextPhase()) {
        if (phase->Name() == "plugins-after-check") {
            break;
        }
        ES2PANDA_PERF_EVENT_SCOPE("@phases/" + std::string(phase->Name()));
        phase->Apply(ctx);
    }
    ctx->phaseManager->SetCurrentPhaseIdToAfterCheck();
    ctx->state = !ctx->diagnosticEngine->IsAnyError() ? ES2PANDA_STATE_CHECKED : ES2PANDA_STATE_ERROR;
    if (ctx->isLspUsage) {
        MarkAsLowered(ctx);
    }
    return ctx;
}

extern "C" void FreeCompilerPartMemory(es2panda_Context *context)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    delete ctx->emitter;
    ctx->emitter = nullptr;
    delete ctx->parser;
    ctx->parser = nullptr;
    delete ctx->queue;
    ctx->queue = nullptr;
    delete ctx->sourceFile;
    ctx->sourceFile = nullptr;
    delete ctx->phaseManager;
    if (!ctx->isExternal) {
        delete ctx->allocator;
        ctx->allocator = nullptr;
        delete ctx->eheapScope;
        ctx->eheapScope = nullptr;
    } else {
        ES2PANDA_ASSERT(ctx->globalContext != nullptr);
        for (auto [_, varbinder] : ctx->parserProgram->VarBinders()) {
            ctx->globalContext->allocatedVarbinders.insert(varbinder->AsETSBinder());
        }
    }
}

__attribute__((unused)) static Context *Lower(Context *ctx)
{
    if (ctx->state < ES2PANDA_STATE_CHECKED) {
        ctx = Check(ctx);
    }

    if (ctx->state == ES2PANDA_STATE_ERROR) {
        return ctx;
    }

    ES2PANDA_ASSERT(ctx->state == ES2PANDA_STATE_CHECKED);
    while (auto phase = ctx->phaseManager->NextPhase()) {
        ES2PANDA_PERF_EVENT_SCOPE("@phases/" + std::string(phase->Name()));
        phase->Apply(ctx);
        if (ctx->diagnosticEngine->IsAnyError()) {
            ctx->state = ES2PANDA_STATE_ERROR;
            return ctx;
        }
    }

    ctx->parserProgram->GetExternalDecls()->Visit([](auto *extProgram) {
        if (!extProgram->IsASTLowered()) {
            extProgram->MarkASTAsLowered();
        }
    });
    if (ctx->isExternal) {
        SaveCache(ctx);
    }
    ctx->state = ES2PANDA_STATE_LOWERED;

    return ctx;
}

__attribute__((unused)) static Context *GenerateAsm(Context *ctx)
{
    if (ctx->state < ES2PANDA_STATE_LOWERED) {
        ctx = Lower(ctx);
    }

    if (ctx->state == ES2PANDA_STATE_ERROR) {
        return ctx;
    }

    ES2PANDA_ASSERT(ctx->state == ES2PANDA_STATE_LOWERED);

    ES2PANDA_PERF_SCOPE("@EmitProgram");
    auto *emitter = ctx->emitter;

    // Handle context literals.
    uint32_t index = 0;
    for (const auto &buff : ctx->contextLiterals) {
        emitter->AddLiteralBuffer(buff, index++);
    }

    emitter->LiteralBufferIndex() += ctx->contextLiterals.size();

    /* Main thread can also be used instead of idling */
    ctx->queue->Schedule(ctx);
    emitter->AsETSEmitter()->SetupDependenciesForTheProgram(ctx->parserProgram);
    ctx->queue->Consume();
    ctx->queue->Wait([emitter](compiler::CompileJob *job) { emitter->AddProgramElement(job->GetProgramElement()); });

    if (ctx->config->options->GetCompilationMode() == CompilationMode::SIMULTANEOUS_INCREMENTAL) {
        ctx->output = emitter->AsETSEmitter()->EmitRecordsSimultIncMode();
    } else {
        emitter->EmitRecords();
        std::unordered_map<std::string, std::unique_ptr<pandasm::Program>> res;
        auto &imd = ctx->parserProgram->GetImportInfo();
        res.emplace(ctx->parser->GetImportPathManager()->FormAbcFilePath(imd),
                    std::unique_ptr<pandasm::Program>(emitter->DumpDebugInfo()));
        ctx->output = std::move(res);
    }
    ctx->state = !ctx->diagnosticEngine->IsAnyError() ? ES2PANDA_STATE_ASM_GENERATED : ES2PANDA_STATE_ERROR;
    return ctx;
}

__attribute__((unused)) Context *GenerateBin(Context *ctx)
{
    if (ctx->state < ES2PANDA_STATE_ASM_GENERATED) {
        ctx = GenerateAsm(ctx);
    }

    if (ctx->state == ES2PANDA_STATE_ERROR) {
        return ctx;
    }

    ES2PANDA_ASSERT(ctx->state == ES2PANDA_STATE_ASM_GENERATED);

    auto reporter = [ctx](const diagnostic::DiagnosticKind &kind, const util::DiagnosticMessageParams &params) {
        ctx->diagnosticEngine->LogDiagnostic(kind, params);
    };
    util::GenerateBinaryFiles(ctx->output, *ctx->config->options, reporter);
    ctx->state = !ctx->diagnosticEngine->IsAnyError() ? ES2PANDA_STATE_BIN_GENERATED : ES2PANDA_STATE_ERROR;
    return ctx;
}

extern "C" __attribute__((unused)) es2panda_Context *ProceedToState(es2panda_Context *context,
                                                                    es2panda_ContextState state)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    switch (state) {
        case ES2PANDA_STATE_NEW:
            break;
        case ES2PANDA_STATE_PARSED:
            ctx = Parse(ctx);
            break;
        case ES2PANDA_STATE_BOUND:
            ctx = Bind(ctx);
            break;
        case ES2PANDA_STATE_CHECKED:
            ctx = Check(ctx);
            break;
        case ES2PANDA_STATE_LOWERED:
            ctx = Lower(ctx);
            break;
        case ES2PANDA_STATE_ASM_GENERATED:
            ctx = GenerateAsm(ctx);
            break;
        case ES2PANDA_STATE_BIN_GENERATED:
            ctx = GenerateBin(ctx);
            break;
        default:
            ctx->errorMessage = "It does not make sense to request stage";
            ctx->state = ES2PANDA_STATE_ERROR;
            break;
    }

    if (ctx->config != nullptr) {
        ctx->config->diagnosticEngine->EnsureLocations();
    }

    return reinterpret_cast<es2panda_Context *>(ctx);
}

extern "C" __attribute__((unused)) void DestroyContext(es2panda_Context *context)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    if (ctx->config != nullptr) {
        ctx->config->diagnosticEngine->EnsureLocations();
    }
    if (ctx->emitter != nullptr) {
        FreeCompilerPartMemory(context);
    }
    delete ctx;
}

extern "C" __attribute__((unused)) void DestroyGlobalContext(es2panda_GlobalContext *globalContext)
{
    auto *globalCtx = reinterpret_cast<GlobalContext *>(globalContext);
    for (auto [_, alloctor] : globalCtx->externalProgramAllocators) {
        delete alloctor;
    }

    delete globalCtx->stdLibAllocator;
    delete globalCtx;
}

extern "C" __attribute__((unused)) es2panda_ContextState ContextState(es2panda_Context *context)
{
    auto *s = reinterpret_cast<Context *>(context);
    return s->state;
}

extern "C" __attribute__((unused)) char const *ContextErrorMessage(es2panda_Context *context)
{
    auto *s = reinterpret_cast<Context *>(context);
    return s->errorMessage.c_str();
}

extern "C" __attribute__((unused)) es2panda_Program *ContextProgram(es2panda_Context *context)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    return reinterpret_cast<es2panda_Program *>(ctx->parserProgram);
}

// NOTE(dkofanov): 'Program **' should be 'Program *'.
using ExternalSourceEntry = std::pair<char *, parser::Program *>;

extern "C" __attribute__((unused)) es2panda_ExternalSource **ProgramExternalSources(es2panda_Context *context,
                                                                                    es2panda_Program *program,
                                                                                    size_t *lenP)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    auto *allocator = ctx->allocator;
    auto *vec = allocator->New<ArenaVector<ExternalSourceEntry *>>(allocator->Adapter());

    // NOTE(dkofanov): only ctx->parserProgram has non-empty externalSources.
    auto programE2p = reinterpret_cast<parser::Program *>(program);
    programE2p->GetExternalDecls()->Visit([vec, allocator](auto *extProgram) {
        auto key = StringViewToCString(allocator, extProgram->GetImportInfo().ModuleName());
        vec->emplace_back(allocator->New<ExternalSourceEntry>(key, extProgram));
    });

    *lenP = vec->size();
    return reinterpret_cast<es2panda_ExternalSource **>(vec->data());
}

extern "C" __attribute__((unused)) es2panda_ExternalSource **ProgramDirectExternalSources(es2panda_Context *context,
                                                                                          es2panda_Program *program,
                                                                                          size_t *lenP)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    auto *allocator = ctx->allocator;
    auto *vec = allocator->New<ArenaVector<ExternalSourceEntry *>>(allocator->Adapter());

    auto programE2p = reinterpret_cast<parser::Program *>(program);
    // NOTE(dkofanov): only ctx->parserProgram has non-empty externalSources.
    for (auto &[name, extProgram] : programE2p->GetExternalDecls()->Direct()) {
        auto key = StringViewToCString(allocator, name);
        vec->emplace_back(allocator->New<ExternalSourceEntry>(key, extProgram));
    }

    *lenP = vec->size();
    return reinterpret_cast<es2panda_ExternalSource **>(vec->data());
}

extern "C" __attribute__((unused)) char const *ExternalSourceName(es2panda_ExternalSource *eSource)
{
    auto *entry = reinterpret_cast<ExternalSourceEntry *>(eSource);
    return entry->first;
}

extern "C" __attribute__((unused)) es2panda_Program **ExternalSourcePrograms(es2panda_ExternalSource *eSource,
                                                                             size_t *lenP)
{
    auto *entry = reinterpret_cast<ExternalSourceEntry *>(eSource);
    *lenP = 1;
    return reinterpret_cast<es2panda_Program **>(&entry->second);
}

extern "C" void AstNodeForEach(es2panda_AstNode *ast, void (*func)(es2panda_AstNode *, void *), void *arg)
{
    auto *node = reinterpret_cast<ir::AstNode *>(ast);
    func(ast, arg);
    node->IterateRecursively([=](ir::AstNode *child) { func(reinterpret_cast<es2panda_AstNode *>(child), arg); });
}

#define SET_NUMBER_LITERAL_IMPL(name, type)                                        \
    extern "C" bool NumberLiteralSet##name(es2panda_AstNode *node, type new_value) \
    {                                                                              \
        auto &n = reinterpret_cast<ir::NumberLiteral *>(node)->Number();           \
        if (!n.Is##name()) {                                                       \
            /* CC-OFFNXT(G.PRE.05) function gen */                                 \
            return false;                                                          \
        }                                                                          \
        n.SetValue<type>(std::move(new_value));                                    \
        /* CC-OFFNXT(G.PRE.05) The macro is used to generate a function. */        \
        return true;                                                               \
    }

SET_NUMBER_LITERAL_IMPL(Int, int32_t)
SET_NUMBER_LITERAL_IMPL(Long, int64_t)
SET_NUMBER_LITERAL_IMPL(Double, double)
SET_NUMBER_LITERAL_IMPL(Float, float)

#undef SET_NUMBER_LITERAL_IMPL

template <typename T>
es2panda_AstNode *CreateNumberLiteral(es2panda_Context *ctx, T value)
{
    auto number = ark::es2panda::lexer::Number(value);
    auto allocator = reinterpret_cast<Context *>(ctx)->allocator;
    auto node = allocator->New<ir::NumberLiteral>(number);
    return reinterpret_cast<es2panda_AstNode *>(node);
}

template <typename T>
es2panda_AstNode *UpdateNumberLiteral(es2panda_Context *ctx, es2panda_AstNode *original, T value)
{
    auto number = ark::es2panda::lexer::Number(value);
    auto allocator = reinterpret_cast<Context *>(ctx)->allocator;
    auto node = allocator->New<ir::NumberLiteral>(number);
    auto *e2pOriginal = reinterpret_cast<ir::AstNode *>(original);
    node->SetOriginalNode(e2pOriginal);
    node->SetParent(e2pOriginal->Parent());
    node->SetRange(e2pOriginal->Range());
    return reinterpret_cast<es2panda_AstNode *>(node);
}

extern "C" const char *NumberLiteralStrConst(es2panda_Context *context, es2panda_AstNode *classInstance)
{
    auto str = reinterpret_cast<const ir::NumberLiteral *>(classInstance)->Str();
    return StringViewToCString(reinterpret_cast<Context *>(context)->allocator, str);
}

extern "C" void *AllocMemory(es2panda_Context *context, size_t numberOfElements, size_t sizeOfElement)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    void *ptr = allocator->Alloc(numberOfElements * sizeOfElement);
    return ptr;
}

extern "C" es2panda_SourcePosition *CreateSourcePosition(es2panda_Context *context, size_t index, size_t line)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    return reinterpret_cast<es2panda_SourcePosition *>(
        allocator->New<lexer::SourcePosition>(index, line, reinterpret_cast<Context *>(context)->parserProgram));
}

extern "C" es2panda_SourceRange *CreateSourceRange(es2panda_Context *context, es2panda_SourcePosition *start,
                                                   es2panda_SourcePosition *end)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    auto startE2p = *(reinterpret_cast<lexer::SourcePosition *>(start));
    auto endE2p = *(reinterpret_cast<lexer::SourcePosition *>(end));
    return reinterpret_cast<es2panda_SourceRange *>(allocator->New<lexer::SourceRange>(startE2p, endE2p));
}

extern "C" const es2panda_DiagnosticKind *CreateDiagnosticKind(es2panda_Context *context, const char *dmessage,
                                                               es2panda_PluginDiagnosticType etype)
{
    auto ctx = reinterpret_cast<Context *>(context);
    auto id = ctx->config->diagnosticKindStorage.size() + 1;
    auto type = util::DiagnosticType::SUGGESTION;
    if (etype == ES2PANDA_PLUGIN_WARNING) {
        type = util::DiagnosticType::PLUGIN_WARNING;
    } else if (etype == ES2PANDA_PLUGIN_ERROR) {
        type = util::DiagnosticType::PLUGIN_ERROR;
    }
    ctx->config->diagnosticKindStorage.emplace_back(type, id, dmessage, false);
    return reinterpret_cast<const es2panda_DiagnosticKind *>(&ctx->config->diagnosticKindStorage.back());
}

extern "C" es2panda_DiagnosticInfo *CreateDiagnosticInfo(es2panda_Context *context, const es2panda_DiagnosticKind *kind,
                                                         const char **args, size_t argc, es2panda_SourcePosition *pos)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    auto diagnosticInfo = allocator->New<es2panda_DiagnosticInfo>();
    diagnosticInfo->kind = kind;
    diagnosticInfo->args = args;
    diagnosticInfo->argc = argc;
    diagnosticInfo->pos = pos;
    return diagnosticInfo;
}

extern "C" void AstNodeSetNoDebugLineFlag([[maybe_unused]] es2panda_Context *context, es2panda_AstNode *node)
{
    auto *e2pNode = reinterpret_cast<ir::AstNode *>(node);
    ES2PANDA_ASSERT(e2pNode != nullptr);
    e2pNode->AddAstNodeFlags(ir::AstNodeFlags::NO_DEBUG_LINE_INFO);
}

extern "C" es2panda_SuggestionInfo *CreateSuggestionInfo(es2panda_Context *context, const es2panda_DiagnosticKind *kind,
                                                         const char **args, size_t argc, const char *substitutionCode,
                                                         const char *title, es2panda_SourceRange *range)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    auto suggestionInfo = allocator->New<es2panda_SuggestionInfo>();
    suggestionInfo->kind = kind;
    suggestionInfo->args = args;
    suggestionInfo->argc = argc;
    suggestionInfo->substitutionCode = substitutionCode;
    suggestionInfo->title = title;
    suggestionInfo->range = range;
    return suggestionInfo;
}

extern "C" void LogDiagnosticWithSuggestions(es2panda_Context *context, const es2panda_DiagnosticInfo *diagnosticInfo,
                                             es2panda_SuggestionInfo **suggestionInfos, size_t suggestionCount)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    auto *diagnosticKind = reinterpret_cast<const diagnostic::DiagnosticKind *>(diagnosticInfo->kind);
    util::DiagnosticMessageParams diagnosticParams;
    diagnosticParams.reserve(diagnosticInfo->argc);
    for (size_t i = 0; i < diagnosticInfo->argc; ++i) {
        diagnosticParams.push_back(diagnosticInfo->args[i]);
    }
    auto *pos = reinterpret_cast<lexer::SourcePosition *>(diagnosticInfo->pos);

    std::vector<util::Suggestion *> suggestions;
    suggestions.reserve(suggestionCount);
    for (size_t i = 0; i < suggestionCount; ++i) {
        const es2panda_SuggestionInfo *info = suggestionInfos[i];
        if (info == nullptr) {
            continue;
        }

        auto *suggestionKind = reinterpret_cast<const diagnostic::DiagnosticKind *>(info->kind);
        std::vector<std::string> suggestionParams;
        suggestionParams.reserve(info->argc);
        for (size_t j = 0; j < info->argc; ++j) {
            suggestionParams.push_back(info->args[j]);
        }
        auto *range = reinterpret_cast<lexer::SourceRange *>(info->range);
        auto suggestion = ctx->diagnosticEngine->CreateSuggestion(suggestionKind, suggestionParams,
                                                                  info->substitutionCode, info->title, range);
        suggestions.push_back(std::move(suggestion));
    }
    ctx->diagnosticEngine->LogDiagnostic(*diagnosticKind, diagnosticParams, *pos, std::move(suggestions));
}

extern "C" void LogDiagnosticWithSuggestion(es2panda_Context *context, const es2panda_DiagnosticInfo *diagnosticInfo,
                                            const es2panda_SuggestionInfo *suggestionInfo)
{
    auto ctx = reinterpret_cast<Context *>(context);
    auto diagnostickind = reinterpret_cast<const diagnostic::DiagnosticKind *>(diagnosticInfo->kind);
    auto suggestionkind = reinterpret_cast<const diagnostic::DiagnosticKind *>(suggestionInfo->kind);
    util::DiagnosticMessageParams diagnosticParams;
    for (size_t i = 0; i < diagnosticInfo->argc; ++i) {
        diagnosticParams.push_back(diagnosticInfo->args[i]);
    }

    std::vector<std::string> suggestionParams;

    for (size_t i = 0; i < suggestionInfo->argc; ++i) {
        suggestionParams.push_back(suggestionInfo->args[i]);
    }

    auto E2pRange = reinterpret_cast<lexer::SourceRange *>(suggestionInfo->range);
    auto posE2p = reinterpret_cast<lexer::SourcePosition *>(diagnosticInfo->pos);
    auto suggestion = ctx->diagnosticEngine->CreateSuggestion(
        suggestionkind, suggestionParams, suggestionInfo->substitutionCode, suggestionInfo->title, E2pRange);
    auto *diag = ctx->diagnosticEngine->LogDiagnostic(*diagnostickind, diagnosticParams, *posE2p, suggestion);
    diag->EnsureLocation();
}

extern "C" void LogDiagnostic(es2panda_Context *context, const es2panda_DiagnosticKind *ekind, const char **args,
                              size_t argc, es2panda_SourcePosition *pos)
{
    auto ctx = reinterpret_cast<Context *>(context);
    auto kind = reinterpret_cast<const diagnostic::DiagnosticKind *>(ekind);
    util::DiagnosticMessageParams params;
    for (size_t i = 0; i < argc; ++i) {
        params.push_back(args[i]);
    }
    auto posE2p = reinterpret_cast<lexer::SourcePosition *>(pos);
    auto *diag = ctx->diagnosticEngine->LogDiagnostic(*kind, params, *posE2p);
    diag->EnsureLocation();
}

const es2panda_DiagnosticStorage *GetDiagnostics(es2panda_Context *context, size_t etype)
{
    auto ctx = reinterpret_cast<Context *>(context);
    auto type = static_cast<util::DiagnosticType>(etype);
    return reinterpret_cast<const es2panda_DiagnosticStorage *>(&ctx->diagnosticEngine->GetDiagnosticStorage(type));
}

extern "C" const es2panda_DiagnosticStorage *GetSemanticErrors(es2panda_Context *context)
{
    return GetDiagnostics(context, util::DiagnosticType::SEMANTIC);
}

extern "C" const es2panda_DiagnosticStorage *GetSyntaxErrors(es2panda_Context *context)
{
    return GetDiagnostics(context, util::DiagnosticType::SYNTAX);
}

extern "C" const es2panda_DiagnosticStorage *GetPluginErrors(es2panda_Context *context)
{
    return GetDiagnostics(context, util::DiagnosticType::PLUGIN_ERROR);
}

extern "C" const es2panda_DiagnosticStorage *GetPluginWarnings(es2panda_Context *context)
{
    return GetDiagnostics(context, util::DiagnosticType::PLUGIN_WARNING);
}

extern "C" const es2panda_DiagnosticStorage *GetWarnings(es2panda_Context *context)
{
    return GetDiagnostics(context, util::DiagnosticType::WARNING);
}

extern "C" bool IsAnyError(es2panda_Context *context)
{
    return reinterpret_cast<Context *>(context)->diagnosticEngine->IsAnyError();
}

extern "C" size_t SourcePositionCol([[maybe_unused]] es2panda_Context *context, es2panda_SourcePosition *position)
{
    static const size_t EMPTY = 1;
    auto es2pandaPosition = reinterpret_cast<lexer::SourcePosition *>(position);
    if (es2pandaPosition->Program() == nullptr) {
        return EMPTY;
    }
    return es2pandaPosition->ToLocation().col;
}

extern "C" size_t SourcePositionIndex([[maybe_unused]] es2panda_Context *context, es2panda_SourcePosition *position)
{
    return reinterpret_cast<lexer::SourcePosition *>(position)->index;
}

extern "C" size_t SourcePositionLine([[maybe_unused]] es2panda_Context *context, es2panda_SourcePosition *position)
{
    return reinterpret_cast<lexer::SourcePosition *>(position)->line;
}

extern "C" es2panda_SourcePosition *SourceRangeStart([[maybe_unused]] es2panda_Context *context,
                                                     es2panda_SourceRange *range)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    auto E2pRange = reinterpret_cast<lexer::SourceRange *>(range);
    return reinterpret_cast<es2panda_SourcePosition *>(allocator->New<lexer::SourcePosition>(E2pRange->start));
}

extern "C" es2panda_SourcePosition *SourceRangeEnd([[maybe_unused]] es2panda_Context *context,
                                                   es2panda_SourceRange *range)
{
    auto *allocator = reinterpret_cast<Context *>(context)->allocator;
    auto E2pRange = reinterpret_cast<lexer::SourceRange *>(range);
    return reinterpret_cast<es2panda_SourcePosition *>(allocator->New<lexer::SourcePosition>(E2pRange->end));
}

extern "C" es2panda_Scope *AstNodeFindNearestScope([[maybe_unused]] es2panda_Context *ctx, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<ir::AstNode *>(node);
    return reinterpret_cast<es2panda_Scope *>(compiler::NearestScope(E2pNode));
}

extern "C" es2panda_Scope *AstNodeRebind(es2panda_Context *ctx, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<ir::AstNode *>(node);
    auto context = reinterpret_cast<Context *>(ctx);
    auto varbinder = context->parserProgram->VarBinder()->AsETSBinder();
    auto phaseManager = context->phaseManager;
    if (E2pNode->IsScriptFunction() ||
        E2pNode->FindChild([](ir::AstNode *n) { return n->IsScriptFunction(); }) != nullptr) {
        while (!E2pNode->IsProgram()) {
            E2pNode = E2pNode->Parent();
        }
    }
    return reinterpret_cast<es2panda_Scope *>(compiler::Rebind(phaseManager, varbinder, E2pNode));
}

extern "C" void AstNodeRecheck(es2panda_Context *ctx, es2panda_AstNode *node)
{
    ES2PANDA_PERF_SCOPE("@Recheck");
    auto E2pNode = reinterpret_cast<ir::AstNode *>(node);
    auto context = reinterpret_cast<Context *>(ctx);
    auto varbinder = context->parserProgram->VarBinder()->AsETSBinder();
    auto checker = context->GetChecker()->AsETSChecker();
    auto phaseManager = context->phaseManager;
    if (E2pNode->IsScriptFunction() ||
        E2pNode->FindChild([](ir::AstNode *n) { return n->IsScriptFunction(); }) != nullptr) {
        while (!E2pNode->IsProgram()) {
            E2pNode = E2pNode->Parent();
        }
    }
    compiler::Recheck(phaseManager, varbinder, checker, E2pNode);
    context->state = !context->diagnosticEngine->IsAnyError() ? ES2PANDA_STATE_CHECKED : ES2PANDA_STATE_ERROR;
    return;
}

#include "generated/es2panda_lib/es2panda_lib_impl.inc"

extern "C" Es2pandaEnum Es2pandaEnumFromString([[maybe_unused]] es2panda_Context *ctx, const char *str)
{
    return IrToE2pEnum(es2panda::util::gen::ast_verifier::FromString(str));
}

extern "C" char *Es2pandaEnumToString(es2panda_Context *ctx, Es2pandaEnum id)
{
    auto *allocator = reinterpret_cast<Context *>(ctx)->allocator;
    return StringViewToCString(allocator, es2panda::util::gen::ast_verifier::ToString(E2pToIrEnum(id)));
}

extern "C" es2panda_AstNode *DeclarationFromIdentifier([[maybe_unused]] es2panda_Context *ctx, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<ir::Identifier *>(node);
    return reinterpret_cast<es2panda_AstNode *>(compiler::DeclarationFromIdentifier(E2pNode));
}

extern "C" bool IsImportTypeKind([[maybe_unused]] es2panda_Context *context, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<const ir::AstNode *>(node);
    if (E2pNode->IsETSImportDeclaration()) {
        return E2pNode->AsETSImportDeclaration()->IsTypeKind();
    }

    if (E2pNode->IsImportDeclaration()) {
        return E2pNode->AsETSImportDeclaration()->IsTypeKind();
    }

    auto ctx = reinterpret_cast<Context *>(context);
    auto id = ctx->config->diagnosticKindStorage.size() + 1;
    auto type = util::DiagnosticType::PLUGIN_WARNING;
    util::DiagnosticMessageParams params {};
    diagnostic::DiagnosticKind *kind =
        &ctx->config->diagnosticKindStorage.emplace_back(type, id, "Insert wrong node!", false);
    ctx->diagnosticEngine->LogDiagnostic(*kind, params, E2pNode->Start());
    return false;
}

extern "C" char *GetLicenseFromRootNode(es2panda_Context *ctx, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<const ir::AstNode *>(node);
    auto *allocator = reinterpret_cast<Context *>(ctx)->allocator;
    return StringViewToCString(allocator, compiler::GetLicenseFromRootNode(E2pNode));
}

extern "C" char *JsdocStringFromDeclaration([[maybe_unused]] es2panda_Context *ctx, es2panda_AstNode *node)
{
    auto E2pNode = reinterpret_cast<const ir::AstNode *>(node);
    auto *allocator = reinterpret_cast<Context *>(ctx)->allocator;
    return StringViewToCString(allocator, compiler::JsdocStringFromDeclaration(E2pNode));
}

extern "C" es2panda_AstNode *FirstDeclarationByNameFromNode([[maybe_unused]] es2panda_Context *ctx,
                                                            const es2panda_AstNode *node, const char *name)
{
    if (node == nullptr) {
        return nullptr;
    }

    util::StringView nameE2p {name};
    ir::AstNode *res = reinterpret_cast<const ir::AstNode *>(node)->FindChild([&nameE2p](const ir::AstNode *ast) {
        if (ast != nullptr && ast->IsMethodDefinition() && ast->AsMethodDefinition()->Key() != nullptr &&
            ast->AsMethodDefinition()->Key()->IsIdentifier() &&
            ast->AsMethodDefinition()->Key()->AsIdentifier()->Name() == nameE2p) {
            return true;
        }

        return false;
    });

    return reinterpret_cast<es2panda_AstNode *>(res);
}

extern "C" es2panda_AstNode *FirstDeclarationByNameFromProgram([[maybe_unused]] es2panda_Context *ctx,
                                                               const es2panda_Program *program, const char *name)
{
    if (program == nullptr) {
        return nullptr;
    }

    auto programE2p = reinterpret_cast<const parser::Program *>(program);
    es2panda_AstNode *res =
        FirstDeclarationByNameFromNode(ctx, reinterpret_cast<const es2panda_AstNode *>(programE2p->Ast()), name);
    if (res != nullptr) {
        return res;
    }

    // NOTE(dkofanov): broken logic.
    // "direct" external sources are filled only for main program.
    // packages are not supported since order of their fractions is not determined.
    for (const auto &ext_source : programE2p->GetExternalDecls()->Direct()) {
        const auto *ext_program = ext_source.second;
        if (ext_program != nullptr) {
            ES2PANDA_ASSERT(!ext_program->Is<util::ModuleKind::PACKAGE>());
            res = FirstDeclarationByNameFromNode(ctx, reinterpret_cast<const es2panda_AstNode *>(ext_program->Ast()),
                                                 name);
        }
        if (res != nullptr) {
            return res;
        }
    }

    return nullptr;
}

static ArenaSet<ir::AstNode *> AllDeclarationsByNameFromNodeHelper(ArenaAllocator *const allocator,
                                                                   const ir::AstNode *node,
                                                                   const util::StringView &name)
{
    auto result = ArenaSet<ir::AstNode *> {allocator->Adapter()};

    if (node == nullptr) {
        return result;
    }

    node->IterateRecursively([&result, &name](ir::AstNode *ast) {
        if (ast != nullptr && ast->IsMethodDefinition() && ast->AsMethodDefinition()->Key() != nullptr &&
            ast->AsMethodDefinition()->Key()->IsIdentifier() &&
            ast->AsMethodDefinition()->Key()->AsIdentifier()->Name() == name) {
            result.insert(ast);
        }
    });

    return result;
}

extern "C" es2panda_AstNode **AllDeclarationsByNameFromNode([[maybe_unused]] es2panda_Context *ctx,
                                                            const es2panda_AstNode *node, const char *name,
                                                            size_t *declsLen)
{
    util::StringView nameE2p {name};
    auto nodeE2p = reinterpret_cast<const ir::AstNode *>(node);
    auto allocator = reinterpret_cast<Context *>(ctx)->allocator;
    auto result = AllDeclarationsByNameFromNodeHelper(allocator, nodeE2p, nameE2p);
    *declsLen = result.size();
    auto apiRes = allocator->New<es2panda_AstNode *[]>(*declsLen);
    size_t i = 0;
    for (auto elem : result) {
        auto toPush = reinterpret_cast<es2panda_AstNode *>(elem);
        apiRes[i++] = toPush;
    };
    return apiRes;
}

extern "C" es2panda_AstNode **AllDeclarationsByNameFromProgram([[maybe_unused]] es2panda_Context *ctx,
                                                               const es2panda_Program *program, const char *name,
                                                               size_t *declsLen)
{
    auto allocator = reinterpret_cast<Context *>(ctx)->allocator;
    if (program == nullptr) {
        *declsLen = 0;
        return allocator->New<es2panda_AstNode *[]>(0);
    }

    util::StringView nameE2p {name};
    auto programE2p = reinterpret_cast<const parser::Program *>(program);
    auto result = ArenaSet<ir::AstNode *> {allocator->Adapter()};

    ArenaSet<ir::AstNode *> res = AllDeclarationsByNameFromNodeHelper(allocator, programE2p->Ast(), nameE2p);
    result.insert(res.begin(), res.end());

    for (auto &[_, ext_program] : programE2p->GetExternalDecls()->Direct()) {
        if (ext_program != nullptr) {
            res = AllDeclarationsByNameFromNodeHelper(allocator, ext_program->Ast(), nameE2p);
            result.insert(res.begin(), res.end());
        }
    }

    *declsLen = result.size();
    auto apiRes = allocator->New<es2panda_AstNode *[]>(*declsLen);
    size_t i = 0;
    for (auto &elem : result) {
        auto toPush = reinterpret_cast<es2panda_AstNode *>(elem);
        apiRes[i++] = toPush;
    };

    return apiRes;
}

extern "C" __attribute__((unused)) es2panda_TsDeclgen *CreateTsDeclgen(
    es2panda_Context *ctx, size_t fileNamesCount, const char *const *inputFiles, const char *const *outputDeclEts,
    const char *const *outputEts, bool exportAll, bool isolated, const char *recordFile, bool genAnnotations)
{
    auto *ctxImpl = reinterpret_cast<Context *>(ctx);
    if (fileNamesCount == 0) {
        return nullptr;
    }
    if (!ctxImpl->config->options->IsSimultaneous()) {
        ES2PANDA_ASSERT(fileNamesCount == 1);
    }
    ark::es2panda::declgen_ets2ts::DeclgenOptions declgenOptions;
    declgenOptions.exportAll = exportAll;
    declgenOptions.isolated = isolated;
    declgenOptions.genAnnotations = genAnnotations;
    declgenOptions.recordFile = recordFile ? recordFile : "";
    declgenOptions.outputDeclEts = outputDeclEts[0] ? outputDeclEts[0] : "";
    declgenOptions.outputEts = outputEts[0] ? outputEts[0] : "";

    std::unordered_map<std::string, size_t> inputFileIndexMap;
    for (size_t i = 0; i < fileNamesCount; ++i) {
        inputFileIndexMap.emplace(std::string(inputFiles[i]), i);
    }

    auto *declgen = new ark::es2panda::declgen_ets2ts::TSDeclGenerator(ctxImpl, inputFileIndexMap, outputDeclEts,
                                                                       outputEts, fileNamesCount);
    if (!declgen->SetDeclgenOptions(declgenOptions)) {
        delete declgen;
        return nullptr;
    }
    return reinterpret_cast<es2panda_TsDeclgen *>(declgen);
}

extern "C" __attribute__((unused)) void DestroyTsDeclgen(es2panda_TsDeclgen *declgen)
{
    auto *declgenImpl = reinterpret_cast<ark::es2panda::declgen_ets2ts::TSDeclGenerator *>(declgen);
    delete declgenImpl;
}

extern "C" __attribute__((unused)) int GenerateTsDeclarationsAfterParsed(es2panda_TsDeclgen *declgen)
{
    auto *declgenImpl = reinterpret_cast<ark::es2panda::declgen_ets2ts::TSDeclGenerator *>(declgen);
    if (declgenImpl->GenerateTsDeclarationsAfterParsedPhase()) {
        return 0;
    }
    return 1;
}

extern "C" __attribute__((unused)) int GenerateTsDeclarationsAfterCheck(es2panda_TsDeclgen *declgen)
{
    auto *declgenImpl = reinterpret_cast<ark::es2panda::declgen_ets2ts::TSDeclGenerator *>(declgen);
    if (declgenImpl->GenerateTsDeclarationsAfterCheckPhase()) {
        return 0;
    }
    return 1;
}

extern "C" __attribute__((unused)) int WriteTsDeclarations(es2panda_TsDeclgen *declgen)
{
    auto *declgenImpl = reinterpret_cast<ark::es2panda::declgen_ets2ts::TSDeclGenerator *>(declgen);
    if (declgenImpl->Write()) {
        return 0;
    }
    return 1;
}

inline static parser::Program *FindProgramInContextByPath(Context *ctxImpl, const std::string &inputPathStr)
{
    if (!ctxImpl->config->options->IsSimultaneous()) {
        return ctxImpl->parserProgram;
    }

    for (const auto &[_, prog] : ctxImpl->parserProgram->GetExternalDecls()->Direct()) {
        if (prog && prog->IsBuiltSimultaneously() && prog->AbsoluteName().Mutf8() == inputPathStr) {
            return prog;
        }
    }

    return nullptr;
}

extern "C" __attribute__((unused)) char *FormOutputPathForFile(es2panda_Context *ctx, const char *inputPath)
{
    auto *ctxImpl = reinterpret_cast<Context *>(ctx);
    if (ctxImpl->state != ES2PANDA_STATE_PARSED) {
        return StdStringToCString(ctxImpl->Allocator(), "");
    }

    auto *prog = FindProgramInContextByPath(ctxImpl, inputPath);
    if (prog == nullptr) {
        return StdStringToCString(ctxImpl->Allocator(), "");
    }

    return StdStringToCString(ctxImpl->Allocator(),
                              ctxImpl->parser->GetImportPathManager()->FormAbcFilePath(prog->GetImportInfo()));
}

static bool HandleMultiFileModeTemplate(
    Context *ctxImpl, const std::string &outputPath,
    const std::function<std::pair<bool, const std::string>(const std::string &)> &findPath,
    const std::function<bool(const std::string &, const std::string &)> &compare)
{
    std::pair<bool, const std::string> pair = findPath(outputPath);
    if (!pair.first) {
        return false;
    }

    for (auto [_, prog] : ctxImpl->parserProgram->GetExternalDecls()->Direct()) {
        if (prog == nullptr || !prog->IsBuiltSimultaneously()) {
            continue;
        }

        // 'ImportPathManager::FormEtscacheFilePath' should be used instead:
        std::string inputRelativeDir = ark::os::RemoveExtension(prog->AbsoluteName().Mutf8());
        if (compare(inputRelativeDir, pair.second)) {
            compiler::HandleGenerateDecl(ctxImpl, prog, outputPath);
            return !ctxImpl->diagnosticEngine->IsAnyError();
        }
    }
    return false;
}

// #28937 Will be removed after binary import support is fully implemented.
__attribute__((unused)) static bool HandleMultiFileMode(Context *ctxImpl, const std::string &outputPath)
{
    if (util::StringView(outputPath).EndsWith(ETSCACHE_SUFFIX)) {
        auto findPathFunc = [](const std::string &path) -> std::pair<bool, const std::string> {
            return std::pair<bool, const std::string>(true, ark::os::RemoveExtension(path));
        };

        auto compareFunc = [](const std::string &path1, const std::string &path2) -> bool {
            return util::StringView(path1).EndsWith(path2);
        };

        return HandleMultiFileModeTemplate(ctxImpl, outputPath, findPathFunc, compareFunc);
    }

    if (util::StringView(outputPath).EndsWith(DECL_ETS_SUFFIX)) {
        auto findPathFunc = [ctxImpl](const std::string &path) -> std::pair<bool, const std::string> {
            /**
             * path = declgenV2OutPath + /direct1/direct2/file.d.ets
             * relativePath = /direct1/direct2/file.d.ets
             * relativePath = package/direct1/direct2/file.d.ets
             * relativePath = package/direct1/direct2/file
             */
            const util::Options &options = *ctxImpl->config->options;
            const auto &arktsConfig = options.ArkTSConfig();
            const std::string &declgenV2OutPath = arktsConfig.DeclgenV2OutPath();
            if (!util::StringView(path).StartsWith(declgenV2OutPath)) {
                return std::pair<bool, const std::string>(false, path);
            }

            std::string relativePath = path.substr(declgenV2OutPath.length());
            relativePath = arktsConfig.Package() + relativePath;
            relativePath = ark::os::RemoveExtension(relativePath);
            relativePath = ark::os::RemoveExtension(relativePath);

            return std::pair<bool, const std::string>(true, relativePath);
        };

        auto compareFunc = [](const std::string &path1, const std::string &path2) -> bool {
            return util::StringView(path1).EndsWith(path2);
        };
        return HandleMultiFileModeTemplate(ctxImpl, outputPath, findPathFunc, compareFunc);
    }

    return false;
}

// #28937 Will be removed after binary import support is fully implemented.
extern "C" __attribute__((unused)) int GenerateStaticDeclarationsFromContext(es2panda_Context *ctx,
                                                                             const char *outputPath)
{
    auto *ctxImpl = reinterpret_cast<Context *>(ctx);
    if (ctxImpl->state != ES2PANDA_STATE_CHECKED) {
        return 1;
    }

    // Multiple file mode
    if (ctxImpl->config->options->IsSimultaneous()) {
        bool success = HandleMultiFileMode(ctxImpl, outputPath);
        return success ? 0 : 1;
    }

    // Single file mode
    compiler::HandleGenerateDecl(ctxImpl, ctxImpl->parserProgram, outputPath);
    return ctxImpl->diagnosticEngine->IsAnyError() ? 1 : 0;
}

extern "C" void InsertETSImportDeclarationAndParse(es2panda_Context *context, es2panda_Program *program,
                                                   es2panda_AstNode *importDeclaration)
{
    auto *ctx = reinterpret_cast<Context *>(context);
    auto *parserProgram = reinterpret_cast<parser::Program *>(program);
    auto *importDeclE2p = reinterpret_cast<ir::ETSImportDeclaration *>(importDeclaration);
    importDeclE2p->AddAstNodeFlags(ir::AstNodeFlags::NOCLEANUP);

    auto &stmt = parserProgram->Ast()->StatementsForUpdates();
    bool hasUseStatic = !stmt.empty() && stmt.front()->IsExpressionStatement();
    if (hasUseStatic) {
        auto *expansion = stmt.front()->AsExpressionStatement()->GetExpression();
        hasUseStatic = hasUseStatic && expansion->IsStringLiteral() &&
                       expansion->AsStringLiteral()->Str() == compiler::Signatures::STATIC_PROGRAM_FLAG;
    }
    size_t insertIndex = hasUseStatic ? 1 : 0;

    stmt.insert(stmt.begin() + insertIndex, importDeclE2p);
    importDeclE2p->SetParent(parserProgram->Ast());

    ctx->parser->AsETSParser()->ParseSources();

    for ([[maybe_unused]] auto *specific : importDeclE2p->Specifiers()) {
        ES2PANDA_ASSERT(specific->Parent() != nullptr);
    }
}

__attribute__((unused)) static void GenerateStdLibCache(es2panda_Config *config, GlobalContext *globalContext,
                                                        bool LspUsage)
{
    auto cfg = reinterpret_cast<ConfigImpl *>(config);
    globalContext->stdLibAllocator = EHeap::NewAllocator().release();
    auto ctx = CreateContext(config, std::move(""), cfg->options->SourceFileName().c_str(),
                             reinterpret_cast<es2panda_GlobalContext *>(globalContext), true, true, LspUsage);
    ProceedToState(ctx, es2panda_ContextState::ES2PANDA_STATE_CHECKED);
    if (!LspUsage) {
        AstNodeRecheck(ctx,
                       reinterpret_cast<es2panda_AstNode *>(reinterpret_cast<Context *>(ctx)->parserProgram->Ast()));
        AstNodeRecheck(ctx,
                       reinterpret_cast<es2panda_AstNode *>(reinterpret_cast<Context *>(ctx)->parserProgram->Ast()));
        ProceedToState(ctx, es2panda_ContextState::ES2PANDA_STATE_LOWERED);
    }
    globalContext->stdLibAstCache = reinterpret_cast<Context *>(ctx)->parserProgram->GetExternalDecls();
    DestroyContext(ctx);
}

extern "C" int ExtractDeclarationsFromAbcFile(const char *abcFile, const char *cacheDir)
{
    const auto pf = panda_file::OpenPandaFile(abcFile);
    if (pf == nullptr) {
        return 1;
    }
    util::ImportPathManager::ExtractEtscacheToFile(*pf, abcFile, cacheDir);
    return 0;
}

es2panda_Impl g_impl = {
    ES2PANDA_LIB_VERSION,

    MemInitialize,
    MemFinalize,
    CreateConfig,
    DestroyConfig,
    DestroyConfigWithoutLog,
    GetAllErrorMessages,
    ConfigGetOptions,
    CreateContextFromFile,
    CreateCacheContextFromFile,
    CreateContextFromString,
    CreateContextFromStringWithHistory,
    CreateCacheContextFromString,
    CreateContextGenerateAbcForExternalSourceFiles,
    CreateContextSimultaneousMode,
    CreateContextSimultaneousModeForLsp,
    ProceedToState,
    DestroyContext,
    CreateGlobalContext,
    DestroyGlobalContext,
    ContextState,
    ContextErrorMessage,
    ContextProgram,
    ProgramExternalSources,
    ProgramDirectExternalSources,
    ExternalSourceName,
    ExternalSourcePrograms,
    AstNodeForEach,
    NumberLiteralSetInt,
    NumberLiteralSetLong,
    NumberLiteralSetDouble,
    NumberLiteralSetFloat,
    CreateNumberLiteral<int32_t>,
    UpdateNumberLiteral<int32_t>,
    CreateNumberLiteral<int64_t>,
    UpdateNumberLiteral<int64_t>,
    CreateNumberLiteral<double>,
    UpdateNumberLiteral<double>,
    CreateNumberLiteral<float>,
    UpdateNumberLiteral<float>,
    NumberLiteralStrConst,
    AllocMemory,
    CreateSourcePosition,
    CreateSourceRange,
    SourcePositionCol,
    SourcePositionIndex,
    SourcePositionLine,
    SourceRangeStart,
    SourceRangeEnd,
    AstNodeSetNoDebugLineFlag,
    CreateDiagnosticKind,
    CreateDiagnosticInfo,
    CreateSuggestionInfo,
    LogDiagnosticWithSuggestions,
    LogDiagnosticWithSuggestion,
    LogDiagnostic,
    GetSemanticErrors,
    GetSyntaxErrors,
    GetPluginErrors,
    GetWarnings,
    IsAnyError,
    AstNodeFindNearestScope,
    AstNodeRebind,
    AstNodeRecheck,
    Es2pandaEnumFromString,
    Es2pandaEnumToString,
    DeclarationFromIdentifier,
    IsImportTypeKind,
    JsdocStringFromDeclaration,
    GetLicenseFromRootNode,
    FirstDeclarationByNameFromNode,
    FirstDeclarationByNameFromProgram,
    AllDeclarationsByNameFromNode,
    AllDeclarationsByNameFromProgram,
    CreateTsDeclgen,
    GenerateTsDeclarationsAfterParsed,
    GenerateTsDeclarationsAfterCheck,
    WriteTsDeclarations,
    DestroyTsDeclgen,
    FormOutputPathForFile,
    InsertETSImportDeclarationAndParse,
    GenerateStaticDeclarationsFromContext,
    InvalidateFileCache,
    RemoveFileCache,
    AddFileCache,
    IncrementalPrepareProgram,
    FreeCompilerPartMemory,
    ResetCounters,
    ExtractDeclarationsFromAbcFile,

#include "generated/es2panda_lib/es2panda_lib_list.inc"

};

}  // namespace ark::es2panda::public_lib

extern "C" es2panda_Impl const *es2panda_GetImpl(int version)
{
    if (version != ES2PANDA_LIB_VERSION) {
        return nullptr;
    }
    return &ark::es2panda::public_lib::g_impl;
}

// NOLINTEND