// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <catch/catch.hpp>

#include <qdoc/inclusionflags.h>
#include <qdoc/inclusionpolicy.h>
#include <qdoc/nodecontext.h>
#include <qdoc/inclusionfilter.h>

TEST_CASE("NodeContext correctly captures internal status", "[NodeContext]")
{
    SECTION("Default NodeContext has isInternal false")
    {
        NodeContext context;
        REQUIRE(context.isInternal == false);
    }

    SECTION("NodeContext can be set to internal")
    {
        NodeContext context;
        context.isInternal = true;
        REQUIRE(context.isInternal == true);
    }

    SECTION("NodeContext preserves all fields when internal is set")
    {
        NodeContext context;
        context.type = NodeType::Class;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = false;

        REQUIRE(context.type == NodeType::Class);
        REQUIRE(context.isPrivate == true);
        REQUIRE(context.isInternal == true);
        REQUIRE(context.isPureVirtual == false);
    }

    SECTION("NodeContext with private but not internal")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;

        REQUIRE(context.isPrivate == true);
        REQUIRE(context.isInternal == false);
    }

    SECTION("NodeContext with internal but not private")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = true;

        REQUIRE(context.isPrivate == false);
        REQUIRE(context.isInternal == true);
    }

    SECTION("NodeContext with both private and internal")
    {
        NodeContext context;
        context.type = NodeType::Class;
        context.isPrivate = true;
        context.isInternal = true;

        REQUIRE(context.isPrivate == true);
        REQUIRE(context.isInternal == true);
    }

    SECTION("Default NodeContext has isInternalAuto false")
    {
        NodeContext context;
        REQUIRE(context.isInternalAuto == false);
    }

    SECTION("NodeContext can be set to isInternalAuto")
    {
        NodeContext context;
        context.isInternalAuto = true;
        REQUIRE(context.isInternalAuto == true);
    }

    SECTION("isInternalAuto is independent from isInternal")
    {
        NodeContext context;
        context.isInternal = true;
        context.isInternalAuto = false;
        REQUIRE(context.isInternal == true);
        REQUIRE(context.isInternalAuto == false);
    }
    SECTION("Default NodeContext has no metaness")
    {
        NodeContext context;
        REQUIRE(context.metaness == std::nullopt);
    }

    SECTION("NodeContext metaness can be set to Signal")
    {
        NodeContext context;
        context.metaness = Metaness::Signal;
        REQUIRE(context.metaness == Metaness::Signal);
    }

    SECTION("NodeContext metaness can be set to Slot")
    {
        NodeContext context;
        context.metaness = Metaness::Slot;
        REQUIRE(context.metaness == Metaness::Slot);
    }
}

TEST_CASE("NodeContext toFlags() behavior", "[NodeContext]")
{
    SECTION("Non-private, non-internal node returns empty flags")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = false;

        auto flags = context.toFlags();
        REQUIRE(flags == InclusionFlags{});
    }

    SECTION("Non-private but internal node returns Internal flag")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
        REQUIRE((flags & InclusionFlag::PrivateFunction) != InclusionFlag::PrivateFunction);
    }

    SECTION("Private function node returns correct flag")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateFunction) == InclusionFlag::PrivateFunction);
    }

    SECTION("Private class node returns correct flag")
    {
        NodeContext context;
        context.type = NodeType::Class;
        context.isPrivate = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateType) == InclusionFlag::PrivateType);
    }

    SECTION("Private variable node returns correct flag")
    {
        NodeContext context;
        context.type = NodeType::Variable;
        context.isPrivate = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateVariable) == InclusionFlag::PrivateVariable);
    }

    SECTION("Internal and private function node returns both flags")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateFunction) == InclusionFlag::PrivateFunction);
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
    }

    SECTION("Internal and private class node returns both flags")
    {
        NodeContext context;
        context.type = NodeType::Class;
        context.isPrivate = true;
        context.isInternal = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateType) == InclusionFlag::PrivateType);
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
    }

    SECTION("Internal-only node returns only Internal flag")
    {
        NodeContext context;
        context.type = NodeType::Class;
        context.isPrivate = false;
        context.isInternal = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
        REQUIRE((flags & InclusionFlag::PrivateType) != InclusionFlag::PrivateType);
    }

    SECTION("isInternalAuto sets InternalAuto flag but not Internal")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isInternalAuto = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::InternalAuto) == InclusionFlag::InternalAuto);
        REQUIRE((flags & InclusionFlag::Internal) != InclusionFlag::Internal);
    }

    SECTION("Both isInternal and isInternalAuto set both flags")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isInternal = true;
        context.isInternalAuto = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
        REQUIRE((flags & InclusionFlag::InternalAuto) == InclusionFlag::InternalAuto);
    }
}

TEST_CASE("InclusionPolicy basic functionality", "[InclusionPolicy]")
{
    SECTION("Default InclusionPolicy")
    {
        InclusionPolicy policy;
        REQUIRE(policy.includePrivateFunction == false);
        REQUIRE(policy.includePrivateType == false);
        REQUIRE(policy.includePrivateVariable == false);
        REQUIRE(policy.showInternal == false);
    }

    SECTION("InclusionPolicy with custom values")
    {
        InclusionPolicy policy;
        policy.includePrivateFunction = true;
        policy.includePrivateType = true;
        policy.includePrivateVariable = true;
        policy.showInternal = true;

        REQUIRE(policy.includePrivateFunction == true);
        REQUIRE(policy.includePrivateType == true);
        REQUIRE(policy.includePrivateVariable == true);
        REQUIRE(policy.showInternal == true);
    }

    SECTION("InclusionPolicy toFlags() with showInternal")
    {
        InclusionPolicy policy;
        policy.showInternal = true;

        auto flags = policy.toFlags();
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
        REQUIRE((flags & InclusionFlag::InternalAuto) == InclusionFlag::InternalAuto);
    }

    SECTION("InclusionPolicy toFlags() without showInternal")
    {
        InclusionPolicy policy;
        policy.showInternal = false;

        auto flags = policy.toFlags();
        REQUIRE((flags & InclusionFlag::Internal) != InclusionFlag::Internal);
        REQUIRE((flags & InclusionFlag::InternalAuto) != InclusionFlag::InternalAuto);
    }

    SECTION("InclusionPolicy toFlags() with both private and internal")
    {
        InclusionPolicy policy;
        policy.includePrivateFunction = true;
        policy.showInternal = true;

        auto flags = policy.toFlags();
        REQUIRE((flags & InclusionFlag::PrivateFunction) == InclusionFlag::PrivateFunction);
        REQUIRE((flags & InclusionFlag::Internal) == InclusionFlag::Internal);
    }
}

TEST_CASE("InclusionFilter basic functionality", "[InclusionFilter]")
{
    SECTION("Public node is included by default")
    {
        NodeContext context;
        context.isPrivate = false;
        context.isInternal = false;

        InclusionPolicy policy;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Private node is excluded by default")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;

        InclusionPolicy policy;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == false);
    }

    SECTION("Private node is included when policy allows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = true;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Pure virtual functions are always included")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isPureVirtual = true;
        context.isInternal = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Internal node is excluded by default")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = true;

        InclusionPolicy policy;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == false);
    }

    SECTION("Internal node is included when policy allows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = true;

        InclusionPolicy policy;
        policy.showInternal = true;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Internal and private node follows internal policy when internal disabled")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;

        InclusionPolicy policy;
        policy.includePrivateFunction = true;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == false);
    }

    SECTION("Internal and private node is included when both policies allow")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;

        InclusionPolicy policy;
        policy.includePrivateFunction = true;
        policy.showInternal = true;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Pure virtual internal function is still excluded when showInternal is false")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = true;

        InclusionPolicy policy;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == false);
    }

    SECTION("Pure virtual internal function is included when showInternal is true")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = true;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;
        policy.showInternal = true;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }
}

TEST_CASE("InclusionFilter::isReimplementedMemberVisible functionality", "[InclusionFilter]")
{
    SECTION("Public reimplemented member is always visible")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = false;

        InclusionPolicy policy;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Public internal reimplemented member is visible despite internal status")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternal = true;

        InclusionPolicy policy;
        policy.showInternal = false;

        // This is the key test - public reimplemented members should be visible
        // even when marked internal and showInternal is false
        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Public auto-internal reimplemented member is always visible")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false;
        context.isInternalAuto = true;

        InclusionPolicy policy;
        policy.showInternal = false;

        // Context is a node with isInternalAuto = true (undocumented node)
        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Protected reimplemented member is always visible")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false; // protected is considered non-private
        context.isInternal = false;

        InclusionPolicy policy;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Protected internal reimplemented member is visible despite internal status")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = false; // protected is considered non-private
        context.isInternal = true;

        InclusionPolicy policy;
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Pure virtual private reimplemented member is always visible")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;
        context.isPureVirtual = true;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Pure virtual private internal reimplemented member is visible")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = true;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;
        policy.showInternal = false;

        // Pure virtual should override internal status for reimplemented members
        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Private reimplemented member follows private inclusion policy")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;
        context.isPureVirtual = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == false);
    }

    SECTION("Private reimplemented member included when private policy allows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = false;
        context.isPureVirtual = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = true;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Private internal reimplemented member ignores internal status")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = true;
        policy.showInternal = false; // This should be ignored

        // Should follow private policy, not internal policy
        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }

    SECTION("Private internal reimplemented member excluded when private policy disallows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = false;
        policy.showInternal = true; // This should be ignored for reimplemented members

        // Should follow private policy, not internal policy
        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == false);
    }

    SECTION("Private variable reimplemented member follows private variable policy")
    {
        NodeContext context;
        context.type = NodeType::Variable;
        context.isPrivate = true;
        context.isInternal = true;
        context.isPureVirtual = false;

        InclusionPolicy policy;
        policy.includePrivateFunction = true; // This doesn't apply to variables
        policy.includePrivateVariable = true; // This does
        policy.showInternal = false;

        REQUIRE(InclusionFilter::isReimplementedMemberVisible(policy, context) == true);
    }
}

TEST_CASE("NodeContext auto-generated doc handling", "[NodeContext]")
{
    SECTION("Default NodeContext has hasAutoGeneratedDoc false")
    {
        NodeContext context;
        REQUIRE(context.hasAutoGeneratedDoc == false);
    }

    SECTION("NodeContext can be set to hasAutoGeneratedDoc")
    {
        NodeContext context;
        context.hasAutoGeneratedDoc = true;
        REQUIRE(context.hasAutoGeneratedDoc == true);
    }

    SECTION("NodeContext toFlags() returns AutoGeneratedDoc flag when set")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::AutoGeneratedDoc) == InclusionFlag::AutoGeneratedDoc);
    }

    SECTION("NodeContext toFlags() does not return AutoGeneratedDoc when not set")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = false;

        auto flags = context.toFlags();
        REQUIRE((flags & InclusionFlag::AutoGeneratedDoc) != InclusionFlag::AutoGeneratedDoc);
    }
}

TEST_CASE("InclusionPolicy auto-generated docs functionality", "[InclusionPolicy]")
{
    SECTION("Default InclusionPolicy has showAutoGeneratedDocs true")
    {
        InclusionPolicy policy;
        REQUIRE(policy.showAutoGeneratedDocs == true);
    }

    SECTION("InclusionPolicy toFlags() includes AutoGeneratedDoc when enabled")
    {
        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = true;

        auto flags = policy.toFlags();
        REQUIRE((flags & InclusionFlag::AutoGeneratedDoc) == InclusionFlag::AutoGeneratedDoc);
    }

    SECTION("InclusionPolicy toFlags() excludes AutoGeneratedDoc when disabled")
    {
        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = false;

        auto flags = policy.toFlags();
        REQUIRE((flags & InclusionFlag::AutoGeneratedDoc) != InclusionFlag::AutoGeneratedDoc);
    }
}

TEST_CASE("InclusionFilter auto-generated docs filtering", "[InclusionFilter]")
{
    SECTION("Auto-generated doc is included by default")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;

        InclusionPolicy policy;
        // Default: showAutoGeneratedDocs = true

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Auto-generated doc is excluded when policy disallows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;

        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == false);
    }

    SECTION("Non-auto-generated doc is not affected by policy")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = false;

        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = false;

        REQUIRE(InclusionFilter::isIncluded(policy, context) == true);
    }

    SECTION("Internal auto-generated doc is excluded when either policy disallows")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;
        context.isInternal = true;

        // Case 1: Both disabled
        InclusionPolicy policy1;
        policy1.showAutoGeneratedDocs = false;
        policy1.showInternal = false;
        REQUIRE(InclusionFilter::isIncluded(policy1, context) == false);

        // Case 2: Auto-generated disabled, internal enabled
        InclusionPolicy policy2;
        policy2.showAutoGeneratedDocs = false;
        policy2.showInternal = true;
        REQUIRE(InclusionFilter::isIncluded(policy2, context) == false);

        // Case 3: Auto-generated enabled, internal disabled
        InclusionPolicy policy3;
        policy3.showAutoGeneratedDocs = true;
        policy3.showInternal = false;
        REQUIRE(InclusionFilter::isIncluded(policy3, context) == false);

        // Case 4: Both enabled
        InclusionPolicy policy4;
        policy4.showAutoGeneratedDocs = true;
        policy4.showInternal = true;
        REQUIRE(InclusionFilter::isIncluded(policy4, context) == true);
    }

    SECTION("Auto-generated doc does not require documentation warning when disabled")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;

        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = false;

        REQUIRE(InclusionFilter::requiresDocumentation(policy, context) == false);
    }

    SECTION("Auto-generated doc requires documentation warning when enabled")
    {
        NodeContext context;
        context.type = NodeType::Function;
        context.hasAutoGeneratedDoc = true;

        InclusionPolicy policy;
        policy.showAutoGeneratedDocs = true;

        REQUIRE(InclusionFilter::requiresDocumentation(policy, context) == true);
    }
}

