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

#include "sections.h"

#include "aggregate.h"
#include "classnode.h"
#include "config.h"
#include "enumnode.h"
#include "functionnode.h"
#include "generator.h"
#include "utilities.h"
#include "namespacenode.h"
#include "qmlpropertynode.h"
#include "qmltypenode.h"
#include "sharedcommentnode.h"
#include "typedefnode.h"
#include "variablenode.h"

#include <QtCore/qobjectdefs.h>

QT_BEGIN_NAMESPACE

QList<Section> Sections::s_stdSummarySections {
    { "Namespaces",       "namespace",       "namespaces",       "", Section::Summary },
    { "Classes",          "class",           "classes",          "", Section::Summary },
    { "Types",            "type",            "types",            "", Section::Summary },
    { "Variables",        "variable",        "variables",        "", Section::Summary },
    { "Static Variables", "static variable", "static variables", "", Section::Summary },
    { "Functions",        "function",        "functions",        "", Section::Summary },
    { "Macros",           "macro",           "macros",           "", Section::Summary },
};

QList<Section> Sections::s_stdDetailsSections {
    { "Namespaces",             "namespace",       "namespaces",       "nmspace", Section::Details },
    { "Classes",                "class",           "classes",          "classes", Section::Details },
    { "Type Documentation",     "type",            "types",            "types",   Section::Details },
    { "Variable Documentation", "variable",        "variables",        "vars",    Section::Details },
    { "Static Variables",       "static variable", "static variables", QString(), Section::Details },
    { "Function Documentation", "function",        "functions",        "func",    Section::Details },
    { "Macro Documentation",    "macro",           "macros",           "macros",  Section::Details },
};

QList<Section> Sections::s_stdCppClassSummarySections {
    { "Public Types",             "public type",             "public types",             "", Section::Summary },
    { "Properties",               "property",                "properties",               "", Section::Summary },
    { "Public Functions",         "public function",         "public functions",         "", Section::Summary },
    { "Public Slots",             "public slot",             "public slots",             "", Section::Summary },
    { "Signals",                  "signal",                  "signals",                  "", Section::Summary },
    { "Public Variables",         "public variable",         "public variables",         "", Section::Summary },
    { "Static Public Members",    "static public member",    "static public members",    "", Section::Summary },
    { "Protected Types",          "protected type",          "protected types",          "", Section::Summary },
    { "Protected Functions",      "protected function",      "protected functions",      "", Section::Summary },
    { "Protected Slots",          "protected slot",          "protected slots",          "", Section::Summary },
    { "Protected Variables",      "protected type",          "protected variables",      "", Section::Summary },
    { "Static Protected Members", "static protected member", "static protected members", "", Section::Summary },
    { "Private Types",            "private type",            "private types",            "", Section::Summary },
    { "Private Functions",        "private function",        "private functions",        "", Section::Summary },
    { "Private Slots",            "private slot",            "private slots",            "", Section::Summary },
    { "Static Private Members",   "static private member",   "static private members",   "", Section::Summary },
    { "Related Non-Members",      "related non-member",      "related non-members",      "", Section::Summary },
    { "Macros",                   "macro",                   "macros",                   "", Section::Summary },
};

QList<Section> Sections::s_stdCppClassDetailsSections {
    { "Member Type Documentation",     "member", "members", "types",     Section::Details },
    { "Property Documentation",        "member", "members", "prop",      Section::Details },
    { "Member Function Documentation", "member", "members", "func",      Section::Details },
    { "Member Variable Documentation", "member", "members", "vars",      Section::Details },
    { "Related Non-Members",           "member", "members", "relnonmem", Section::Details },
    { "Macro Documentation",           "member", "members", "macros",    Section::Details },
};

QList<Section> Sections::s_stdQmlTypeSummarySections {
    { "Properties",          "property",          "properties",          "", Section::Summary },
    { "Attached Properties", "attached property", "attached properties", "", Section::Summary },
    { "Signals",             "signal",            "signals",             "", Section::Summary },
    { "Signal Handlers",     "signal handler",    "signal handlers",     "", Section::Summary },
    { "Attached Signals",    "attached signal",   "attached signals",    "", Section::Summary },
    { "Methods",             "method",            "methods",             "", Section::Summary },
    { "Attached Methods",    "attached method",   "attached methods",    "", Section::Summary },
};

QList<Section> Sections::s_stdQmlTypeDetailsSections {
    { "Property Documentation",          "member",         "members",         "qmlprop",    Section::Details },
    { "Attached Property Documentation", "member",         "members",         "qmlattprop", Section::Details },
    { "Signal Documentation",            "signal",         "signals",         "qmlsig",     Section::Details },
    { "Signal Handler Documentation",    "signal handler", "signal handlers", "qmlsighan",  Section::Details },
    { "Attached Signal Documentation",   "signal",         "signals",         "qmlattsig",  Section::Details },
    { "Method Documentation",            "member",         "members",         "qmlmeth",    Section::Details },
    { "Attached Method Documentation",   "member",         "members",         "qmlattmeth", Section::Details },
};

QList<Section> Sections::s_sinceSections {
    { "New Namespaces",              "", "", "", Section::Details },
    { "New Classes",                 "", "", "", Section::Details },
    { "New Member Functions",        "", "", "", Section::Details },
    { "New Functions in Namespaces", "", "", "", Section::Details },
    { "New Global Functions",        "", "", "", Section::Details },
    { "New Macros",                  "", "", "", Section::Details },
    { "New Enum Types",              "", "", "", Section::Details },
    { "New Enum Values",             "", "", "", Section::Details },
    { "New Type Aliases",            "", "", "", Section::Details },
    { "New Properties",              "", "", "", Section::Details },
    { "New Variables",               "", "", "", Section::Details },
    { "New QML Types",               "", "", "", Section::Details },
    { "New QML Properties",          "", "", "", Section::Details },
    { "New QML Signals",             "", "", "", Section::Details },
    { "New QML Signal Handlers",     "", "", "", Section::Details },
    { "New QML Methods",             "", "", "", Section::Details },
};

QList<Section> Sections::s_allMembers{ { "", "member", "members", "", Section::AllMembers } };

/*!
  \class Section
  \brief A class for containing the elements of one documentation section
 */

/*!
  The destructor must delete the members of collections
  when the members are allocated on the heap.
 */
Section::~Section()
{
    clear();
}

/*!
  A Section is now an element in a static vector, so we
  don't have to repeatedly construct and destroy them. But
  we do need to clear them before each call to build the
  sections for a C++ or QML entity.
 */
void Section::clear()
{
    m_reimplementedMemberMap.clear();
    m_members.clear();
    m_obsoleteMembers.clear();
    m_reimplementedMembers.clear();
    m_inheritedMembers.clear();
    m_classNodesList.clear();
    m_aggregate = nullptr;
}

/*!
  Construct a name for the \a node that can be used for sorting
  a set of nodes into equivalence classes.
 */
QString sortName(const Node *node)
{
    QString nodeName{node->name()};

    int numDigits = 0;
    for (qsizetype i = nodeName.size() - 1; i > 0; --i) {
        if (nodeName.at(i).digitValue() == -1)
            break;
        ++numDigits;
    }

    // we want 'qint8' to appear before 'qint16'
    if (numDigits > 0) {
        for (int i = 0; i < 4 - numDigits; ++i)
            nodeName.insert(nodeName.size() - numDigits - 1, QLatin1Char('0'));
    }

    if (node->isClassNode())
        return QLatin1Char('A') + nodeName;

    if (node->isFunction(Node::CPP)) {
        const auto *fn = static_cast<const FunctionNode *>(node);

        QString sortNo;
        if (fn->isCtor())
            sortNo = QLatin1String("C");
        else if (fn->isCCtor())
            sortNo = QLatin1String("D");
        else if (fn->isMCtor())
            sortNo = QLatin1String("E");
        else if (fn->isDtor())
            sortNo = QLatin1String("F");
        else if (nodeName.startsWith(QLatin1String("operator")) && nodeName.size() > 8
                    && !nodeName[8].isLetterOrNumber())
            sortNo = QLatin1String("H");
        else
            sortNo = QLatin1String("G");

        return sortNo + nodeName + QLatin1Char(' ') + QString::number(fn->overloadNumber(), 36);
    }

    if (node->isFunction(Node::QML))
        return QLatin1Char('E') + nodeName + QLatin1Char(' ') +
            QString::number(static_cast<const FunctionNode*>(node)->overloadNumber(), 36);

    if (node->isProperty() || node->isVariable())
        return QLatin1Char('G') + nodeName;

    return QLatin1Char('B') + nodeName;
}

/*!
  Inserts the \a node into this section if it is appropriate
  for this section.
 */
void Section::insert(Node *node)
{
    bool irrelevant = false;
    bool inherited = false;
    if (!node->isRelatedNonmember()) {
        Aggregate *p = node->parent();
        if (!p->isNamespace() && p != m_aggregate) {
            if (!p->isQmlType() || !p->isAbstract())
                inherited = true;
        }
    }

    if (node->isPrivate() || node->isInternal()) {
        irrelevant = true;
    } else if (node->isFunction()) {
        auto *func = static_cast<FunctionNode *>(node);
        irrelevant = (inherited && (func->isSomeCtor() || func->isDtor()));
    } else if (node->isClassNode() || node->isEnumType() || node->isTypedef()
               || node->isVariable()) {
        irrelevant = (inherited && m_style != AllMembers);
        if (!irrelevant && m_style == Details && node->isTypedef()) {
            const auto *tdn = static_cast<const TypedefNode *>(node);
            if (tdn->associatedEnum())
                irrelevant = true;
        }
    }

    if (!irrelevant) {
        QString key = sortName(node);
        if (node->isDeprecated()) {
            m_obsoleteMembers.push_back(node);
        } else {
            if (!inherited || m_style == AllMembers)
                m_members.push_back(node);

            if (inherited && (node->parent()->isClassNode() || node->parent()->isNamespace())) {
                if (m_inheritedMembers.isEmpty()
                    || m_inheritedMembers.last().first != node->parent()) {
                    std::pair<Aggregate *, int> p(node->parent(), 0);
                    m_inheritedMembers.append(p);
                }
                m_inheritedMembers.last().second++;
            }
        }
    }
}

/*!
  Returns \c true if the \a node is a reimplemented member
  function of the current class. If true, the \a node is
  inserted into the reimplemented member map. True
  is returned only if \a node is inserted into the map.
  That is, false is returned if the \a node is already in
  the map.
 */
bool Section::insertReimplementedMember(Node *node)
{
    if (!node->isPrivate() && !node->isRelatedNonmember()) {
        const auto *fn = static_cast<const FunctionNode *>(node);
        if (!fn->overridesThis().isEmpty()) {
            if (fn->parent() == m_aggregate) {
                QString key = sortName(fn);
                if (!m_reimplementedMemberMap.contains(key)) {
                    m_reimplementedMemberMap.insert(key, node);
                    return true;
                }
            }
        }
    }
    return false;
}

/*!
  If this section is not empty, convert its maps to sequential
  structures for better traversal during doc generation.
 */
void Section::reduce()
{
    // TODO:TEMPORARY:INTERMEDITATE: Section uses a series of maps
    // to internally manage the categorization of the various members
    // of an aggregate. It further uses a secondary "flattened"
    // (usually vector) version that is later used by consumers of a
    // Section content.
    //
    // One of the uses of those maps is that of ordering, by using
    // keys generated with `sortName`.
    // Nonetheless, this is the only usage that comes from the keys,
    // as they are neither necessary nor used outside of the internal
    // code for Section.
    //
    // Hence, the codebase is moving towards removing the maps in
    // favor of building a flattened, consumer ready, version of the
    // categorization directly, cutting the intermediate conversion
    // step.
    //
    // To do so while keeping as much of the old behavior as possible,
    // we provide a sorting for the flattened version that is based on
    // `sortName`, as the previous ordering was.
    //
    // This acts as a relatively heavy pessimization, as `sortName`,
    // used as a comparator, can be called multiple times for each
    // Node, while before it would have been called almost-once.
    //
    // Instead of fixing this issue, by for example caching the
    // sortName of each Node instance, we temporarily keep the
    // pessimization while the various maps are removed.
    //
    // When all the maps are removed, we can remove `sortName`, which
    // produces strings to use as key requiring a few allocations and
    // expensive operations, with an actual comparator function, which
    // should be more lightweight and more than offset the
    // multiple-calls.
    static auto node_less_than = [](const Node* left, const Node* right) {
      return sortName(left) < sortName(right);
    };

    std::stable_sort(m_members.begin(), m_members.end(), node_less_than);
    std::stable_sort(m_obsoleteMembers.begin(), m_obsoleteMembers.end(), node_less_than);

    m_reimplementedMembers = m_reimplementedMemberMap.values().toVector();

    for (auto &cn : m_classNodesList) {
        std::stable_sort(cn.second.begin(), cn.second.end(), node_less_than);
    }
}

/*!
  \class Sections
  \brief A class for creating vectors of collections for documentation

  Each element in a vector is an instance of Section, which
  contains all the elements that will be documented in one
  section of a reference documentation page.
 */

/*!
  This constructor builds the vectors of sections based on the
  type of the \a aggregate node.
 */
Sections::Sections(Aggregate *aggregate) : m_aggregate(aggregate)
{
    initAggregate(s_allMembers, m_aggregate);
    switch (m_aggregate->nodeType()) {
    case Node::Class:
    case Node::Struct:
    case Node::Union:
        initAggregate(s_stdCppClassSummarySections, m_aggregate);
        initAggregate(s_stdCppClassDetailsSections, m_aggregate);
        buildStdCppClassRefPageSections();
        break;
    case Node::QmlType:
    case Node::QmlValueType:
        initAggregate(s_stdQmlTypeSummarySections, m_aggregate);
        initAggregate(s_stdQmlTypeDetailsSections, m_aggregate);
        buildStdQmlTypeRefPageSections();
        break;
    case Node::Namespace:
    case Node::HeaderFile:
    case Node::Proxy:
    default:
        initAggregate(s_stdSummarySections, m_aggregate);
        initAggregate(s_stdDetailsSections, m_aggregate);
        buildStdRefPageSections();
        break;
    }
}

/*!
  This constructor builds a vector of sections from the \e since
  node map, \a nsmap
 */
Sections::Sections(const NodeMultiMap &nsmap) : m_aggregate(nullptr)
{
    if (nsmap.isEmpty())
        return;
    SectionVector &sections = sinceSections();
    for (auto it = nsmap.constBegin(); it != nsmap.constEnd(); ++it) {
        Node *node = it.value();
        switch (node->nodeType()) {
        case Node::QmlType:
            sections[SinceQmlTypes].appendMember(node);
            break;
        case Node::Namespace:
            sections[SinceNamespaces].appendMember(node);
            break;
        case Node::Class:
        case Node::Struct:
        case Node::Union:
            sections[SinceClasses].appendMember(node);
            break;
        case Node::Enum: {
            // The map can contain an enum node with \since, or an enum node
            // with \value containing a since-clause. In the latter case,
            // key() is an empty string.
            if (!it.key().isEmpty())
                sections[SinceEnumTypes].appendMember(node);
            else
                sections[SinceEnumValues].appendMember(node);
            break;
        }
        case Node::Typedef:
        case Node::TypeAlias:
            sections[SinceTypeAliases].appendMember(node);
            break;
        case Node::Function: {
            const auto *fn = static_cast<const FunctionNode *>(node);
            switch (fn->metaness()) {
            case FunctionNode::QmlSignal:
                sections[SinceQmlSignals].appendMember(node);
                break;
            case FunctionNode::QmlSignalHandler:
                sections[SinceQmlSignalHandlers].appendMember(node);
                break;
            case FunctionNode::QmlMethod:
                sections[SinceQmlMethods].appendMember(node);
                break;
            default:
                if (fn->isMacro())
                    sections[SinceMacros].appendMember(node);
                else {
                    Node *p = fn->parent();
                    if (p) {
                        if (p->isClassNode())
                            sections[SinceMemberFunctions].appendMember(node);
                        else if (p->isNamespace()) {
                            if (p->name().isEmpty())
                                sections[SinceGlobalFunctions].appendMember(node);
                            else
                                sections[SinceNamespaceFunctions].appendMember(node);
                        } else
                            sections[SinceGlobalFunctions].appendMember(node);
                    } else
                        sections[SinceGlobalFunctions].appendMember(node);
                }
                break;
            }
            break;
        }
        case Node::Property:
            sections[SinceProperties].appendMember(node);
            break;
        case Node::Variable:
            sections[SinceVariables].appendMember(node);
            break;
        case Node::QmlProperty:
            sections[SinceQmlProperties].appendMember(node);
            break;
        default:
            break;
        }
    }
}

/*!
  The behavior of the destructor depends on the type of the
  Aggregate node that was passed to the constructor. If the
  constructor was passed a multimap, the destruction is a
  bit different because there was no Aggregate node.
 */
Sections::~Sections()
{
    if (m_aggregate) {
        switch (m_aggregate->nodeType()) {
        case Node::Class:
        case Node::Struct:
        case Node::Union:
            clear(stdCppClassSummarySections());
            clear(stdCppClassDetailsSections());
            allMembersSection().clear();
            break;
        case Node::QmlType:
        case Node::QmlValueType:
            clear(stdQmlTypeSummarySections());
            clear(stdQmlTypeDetailsSections());
            allMembersSection().clear();
            break;
        default:
            clear(stdSummarySections());
            clear(stdDetailsSections());
            allMembersSection().clear();
            break;
        }
        m_aggregate = nullptr;
    } else {
        clear(sinceSections());
    }
}

/*!
  Initialize the Aggregate in each Section of vector \a v with \a aggregate.
 */
void Sections::initAggregate(SectionVector &v, Aggregate *aggregate)
{
    for (Section &section : v)
        section.setAggregate(aggregate);
}

/*!
  Reset each Section in vector \a v to its initialized state.
 */
void Sections::clear(QList<Section> &v)
{
    for (Section &section : v)
        section.clear();
}

/*!
  Linearize the maps in each Section in \a v.
 */
void Sections::reduce(QList<Section> &v)
{
    for (Section &section : v)
        section.reduce();
}

/*!
  This is a private helper function for buildStdRefPageSections().
 */
void Sections::stdRefPageSwitch(SectionVector &v, Node *n, Node *t)
{
    // t is the reference node to be tested, n is the node to be distributed.
    // t differs from n only for shared comment nodes.
    if (!t)
        t = n;

    switch (t->nodeType()) {
    case Node::Namespace:
        v[StdNamespaces].insert(n);
        return;
    case Node::Class:
    case Node::Struct:
    case Node::Union:
        v[StdClasses].insert(n);
        return;
    case Node::Enum:
    case Node::Typedef:
    case Node::TypeAlias:
        v[StdTypes].insert(n);
        return;
    case Node::Function: {
        auto *func = static_cast<FunctionNode *>(t);
        if (func->isMacro())
            v[StdMacros].insert(n);
        else
            v[StdFunctions].insert(n);
    }
        return;
    case Node::Variable: {
        const auto *var = static_cast<const VariableNode *>(t);
        if (!var->doc().isEmpty()) {
            if (var->isStatic())
                v[StdStaticVariables].insert(n);
            else
                v[StdVariables].insert(n);
        }
    }
        return;
    case Node::SharedComment: {
        auto *scn = static_cast<SharedCommentNode *>(t);
        if (!scn->doc().isEmpty() && scn->collective().size())
            stdRefPageSwitch(
                    v, scn,
                    scn->collective().first()); // TODO: warn about mixed node types in collective?
    }
        return;
    default:
        return;
    }
}

/*!
  Build the section vectors for a standard reference page,
  when the aggregate node is not a C++ class or a QML type.

  If this is for a namespace page then if the namespace node
  itself does not have documentation, only its children that
  have documentation should be documented. In other words,
  there are cases where a namespace is declared but does not
  have documentation, but some of the elements declared in
  that namespace do have documentation.

  This special processing of namespaces that do not have a
  documentation comment is meant to allow documenting its
  members that do have documentation while avoiding posting
  error messages for its members that are not documented.
 */
void Sections::buildStdRefPageSections()
{
    const NamespaceNode *ns = nullptr;
    bool documentAll = true; // document all the children
    if (m_aggregate->isNamespace()) {
        ns = static_cast<const NamespaceNode *>(m_aggregate);
        if (!ns->hasDoc())
            documentAll = false; // only document children that have documentation
    }
    for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
        Node *n = *it;
        if (documentAll || n->hasDoc()) {
            stdRefPageSwitch(stdSummarySections(), n);
            stdRefPageSwitch(stdDetailsSections(), n);
        }
    }
    if (!m_aggregate->relatedByProxy().isEmpty()) {
        const QList<Node *> &relatedBy = m_aggregate->relatedByProxy();
        for (const auto &node : relatedBy)
            stdRefPageSwitch(stdSummarySections(), node);
    }
    /*
      If we are building the sections for the reference page
      for a namespace node, include all the namespace node's
      included children in the sections.
     */
    if (ns && !ns->includedChildren().isEmpty()) {
        const QList<Node *> &children = ns->includedChildren();
        for (const auto &child : children) {
            if (documentAll || child->hasDoc())
                stdRefPageSwitch(stdSummarySections(), child);
        }
    }
    reduce(stdSummarySections());
    reduce(stdDetailsSections());
    allMembersSection().reduce();
}

/*!
  Inserts the node \a n in one of the entries in the vector \a v
  depending on the node's type, access attribute, and a few other
  attributes if the node is a signal, slot, or function.
 */
void Sections::distributeNodeInSummaryVector(SectionVector &sv, Node *n)
{
    if (n->isSharedCommentNode())
        return;
    if (n->isFunction()) {
        auto *fn = static_cast<FunctionNode *>(n);
        if (fn->isRelatedNonmember()) {
            if (fn->isMacro())
                sv[Macros].insert(n);
            else
                sv[RelatedNonmembers].insert(n);
            return;
        }
        if (fn->isIgnored())
            return;
        if (fn->isSlot()) {
            if (fn->isPublic())
                sv[PublicSlots].insert(fn);
            else if (fn->isPrivate())
                sv[PrivateSlots].insert(fn);
            else
                sv[ProtectedSlots].insert(fn);
        } else if (fn->isSignal()) {
            if (fn->isPublic())
                sv[Signals].insert(fn);
        } else if (fn->isPublic()) {
            if (fn->isStatic())
                sv[StaticPublicMembers].insert(fn);
            else if (!sv[PublicFunctions].insertReimplementedMember(fn))
                sv[PublicFunctions].insert(fn);
        } else if (fn->isPrivate()) {
            if (fn->isStatic())
                sv[StaticPrivateMembers].insert(fn);
            else if (!sv[PrivateFunctions].insertReimplementedMember(fn))
                sv[PrivateFunctions].insert(fn);
        } else { // protected
            if (fn->isStatic())
                sv[StaticProtectedMembers].insert(fn);
            else if (!sv[ProtectedFunctions].insertReimplementedMember(fn))
                sv[ProtectedFunctions].insert(fn);
        }
        return;
    }
    if (n->isRelatedNonmember()) {
        sv[RelatedNonmembers].insert(n);
        return;
    }
    if (n->isVariable()) {
        if (n->isStatic()) {
            if (n->isPublic())
                sv[StaticPublicMembers].insert(n);
            else if (n->isPrivate())
                sv[StaticPrivateMembers].insert(n);
            else
                sv[StaticProtectedMembers].insert(n);
        } else {
            if (n->isPublic())
                sv[PublicVariables].insert(n);
            else if (!n->isPrivate())
                sv[ProtectedVariables].insert(n);
        }
        return;
    }
    /*
      Getting this far means the node is either a property
      or some kind of type, like an enum or a typedef.
    */
    if (n->isTypedef() && (n->name() == QLatin1String("QtGadgetHelper")))
        return;
    if (n->isProperty())
        sv[Properties].insert(n);
    else if (n->isPublic())
        sv[PublicTypes].insert(n);
    else if (n->isPrivate())
        sv[PrivateTypes].insert(n);
    else
        sv[ProtectedTypes].insert(n);
}

/*!
  Inserts the node \a n in one of the entries in the vector \a v
  depending on the node's type, access attribute, and a few other
  attributes if the node is a signal, slot, or function.
 */
void Sections::distributeNodeInDetailsVector(SectionVector &dv, Node *n)
{
    if (n->isSharingComment())
        return;

    // t is the reference node to be tested - typically it's this node (n), but for
    // shared comment nodes we need to distribute based on the nodes in its collective.
    Node *t = n;

    if (n->isSharedCommentNode() && n->hasDoc()) {
        auto *scn = static_cast<SharedCommentNode *>(n);
        if (scn->collective().size())
            t = scn->collective().first(); // TODO: warn about mixed node types in collective?
    }

    if (t->isFunction()) {
        auto *fn = static_cast<FunctionNode *>(t);
        if (fn->isRelatedNonmember()) {
            if (fn->isMacro())
                dv[DetailsMacros].insert(n);
            else
                dv[DetailsRelatedNonmembers].insert(n);
            return;
        }
        if (fn->isIgnored())
            return;
        if (!fn->hasAssociatedProperties() || !fn->doc().isEmpty())
            dv[DetailsMemberFunctions].insert(n);
        return;
    }
    if (t->isRelatedNonmember()) {
        dv[DetailsRelatedNonmembers].insert(n);
        return;
    }
    if (t->isEnumType() || t->isTypedef()) {
        if (t->name() != QLatin1String("QtGadgetHelper"))
            dv[DetailsMemberTypes].insert(n);
        return;
    }
    if (t->isProperty())
        dv[DetailsProperties].insert(n);
    else if (t->isVariable() && !t->doc().isEmpty())
        dv[DetailsMemberVariables].insert(n);
}

void Sections::distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n)
{
    if (n->isSharingComment())
        return;

    // t is the reference node to be tested - typically it's this node (n), but for
    // shared comment nodes we need to distribute based on the nodes in its collective.
    Node *t = n;

    if (n->isSharedCommentNode() && n->hasDoc()) {
        if (n->isPropertyGroup()) {
            dv[QmlProperties].insert(n);
            return;
        }
        auto *scn = static_cast<SharedCommentNode *>(n);
        if (scn->collective().size())
            t = scn->collective().first(); // TODO: warn about mixed node types in collective?
    }

    if (t->isQmlProperty()) {
        auto *pn = static_cast<QmlPropertyNode *>(t);
        if (pn->isAttached())
            dv[QmlAttachedProperties].insert(n);
        else
            dv[QmlProperties].insert(n);
    } else if (t->isFunction()) {
        auto *fn = static_cast<FunctionNode *>(t);
        if (fn->isQmlSignal()) {
            if (fn->isAttached())
                dv[QmlAttachedSignals].insert(n);
            else
                dv[QmlSignals].insert(n);
        } else if (fn->isQmlSignalHandler()) {
            dv[QmlSignalHandlers].insert(n);
        } else if (fn->isQmlMethod()) {
            if (fn->isAttached())
                dv[QmlAttachedMethods].insert(n);
            else
                dv[QmlMethods].insert(n);
        }
    }
}

/*!
  Distributes a node \a n into the correct place in the summary section vector \a sv.
  Nodes that are sharing a comment are handled recursively - for recursion, the \a
  sharing parameter is set to \c true.
 */
void Sections::distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing)
{
    if (n->isSharingComment() && !sharing)
        return;
    if (n->isQmlProperty()) {
        auto *pn = static_cast<QmlPropertyNode *>(n);
        if (pn->isAttached())
            sv[QmlAttachedProperties].insert(pn);
        else
            sv[QmlProperties].insert(pn);
    } else if (n->isFunction()) {
        auto *fn = static_cast<FunctionNode *>(n);
        if (fn->isQmlSignal()) {
            if (fn->isAttached())
                sv[QmlAttachedSignals].insert(fn);
            else
                sv[QmlSignals].insert(fn);
        } else if (fn->isQmlSignalHandler()) {
            sv[QmlSignalHandlers].insert(fn);
        } else if (fn->isQmlMethod()) {
            if (fn->isAttached())
                sv[QmlAttachedMethods].insert(fn);
            else
                sv[QmlMethods].insert(fn);
        }
    } else if (n->isSharedCommentNode()) {
        auto *scn = static_cast<SharedCommentNode *>(n);
        if (scn->isPropertyGroup()) {
            sv[QmlProperties].insert(scn);
        } else {
            for (const auto &child : scn->collective())
                distributeQmlNodeInSummaryVector(sv, child, true);
        }
    }
}

static void pushBaseClasses(QStack<ClassNode *> &stack, ClassNode *cn)
{
    const QList<RelatedClass> baseClasses = cn->baseClasses();
    for (const auto &cls : baseClasses) {
        if (cls.m_node)
            stack.prepend(cls.m_node);
    }
}

/*!
  Build the section vectors for a standard reference page,
  when the aggregate node is a C++.
 */
void Sections::buildStdCppClassRefPageSections()
{
    SectionVector &summarySections = stdCppClassSummarySections();
    SectionVector &detailsSections = stdCppClassDetailsSections();
    Section &allMembers = allMembersSection();

    for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
        Node *n = *it;
        if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
            && !n->isSharedCommentNode())
            allMembers.insert(n);

        distributeNodeInSummaryVector(summarySections, n);
        distributeNodeInDetailsVector(detailsSections, n);
    }
    if (!m_aggregate->relatedByProxy().isEmpty()) {
        const QList<Node *> relatedBy = m_aggregate->relatedByProxy();
        for (const auto &node : relatedBy)
            distributeNodeInSummaryVector(summarySections, node);
    }

    QStack<ClassNode *> stack;
    auto *cn = static_cast<ClassNode *>(m_aggregate);
    pushBaseClasses(stack, cn);
    while (!stack.isEmpty()) {
        ClassNode *cn = stack.pop();
        for (auto it = cn->constBegin(); it != cn->constEnd(); ++it) {
            Node *n = *it;
            if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
                && !n->isSharedCommentNode())
                allMembers.insert(n);
        }
        pushBaseClasses(stack, cn);
    }
    reduce(summarySections);
    reduce(detailsSections);
    allMembers.reduce();
}

/*!
  Build the section vectors for a standard reference page,
  when the aggregate node is a QML type.
 */
void Sections::buildStdQmlTypeRefPageSections()
{
    ClassNodes *classNodes = nullptr;
    SectionVector &summarySections = stdQmlTypeSummarySections();
    SectionVector &detailsSections = stdQmlTypeDetailsSections();
    Section &allMembers = allMembersSection();

    const Aggregate *qtn = m_aggregate;
    while (qtn) {
        if (!qtn->isAbstract() || !classNodes)
            classNodes = &allMembers.classNodesList().emplace_back(static_cast<const QmlTypeNode*>(qtn), NodeVector{});
        for (const auto n : qtn->childNodes()) {
            if (n->isInternal())
                continue;

            // Skip overridden property/function documentation from abstract base type
            if (qtn != m_aggregate && qtn->isAbstract()) {
                NodeList candidates;
                m_aggregate->findChildren(n->name(), candidates);
                if (std::any_of(candidates.cbegin(), candidates.cend(), [&n](const Node *c) {
                    if (c->nodeType() == n->nodeType()) {
                        if (!n->isFunction() || static_cast<const FunctionNode*>(n)->compare(c, false))
                            return true;
                    }
                    return false;
                })) {
                    continue;
                }
            }

            if (!n->isSharedCommentNode() || n->isPropertyGroup()) {
                allMembers.insert(n);
                classNodes->second.push_back(n);
            }


            if (qtn == m_aggregate || qtn->isAbstract()) {
                distributeQmlNodeInSummaryVector(summarySections, n);
                distributeQmlNodeInDetailsVector(detailsSections, n);
            }
        }
        if (qtn->qmlBaseNode() == qtn) {
            qCDebug(lcQdoc, "error: circular type definition: '%s' inherits itself",
                    qPrintable(qtn->name()));
            break;
        }
        qtn = static_cast<QmlTypeNode *>(qtn->qmlBaseNode());
    }

    reduce(summarySections);
    reduce(detailsSections);
    allMembers.reduce();
}

/*!
  Returns true if any sections in this object contain obsolete
  members. If it returns false, then \a summary_spv and \a details_spv
  have not been modified. Otherwise, both vectors will contain pointers
  to the sections that contain obsolete members.
 */
bool Sections::hasObsoleteMembers(SectionPtrVector *summary_spv,
                                  SectionPtrVector *details_spv) const
{
    const SectionVector *sections = nullptr;
    if (m_aggregate->isClassNode())
        sections = &stdCppClassSummarySections();
    else if (m_aggregate->isQmlType())
        sections = &stdQmlTypeSummarySections();
    else
        sections = &stdSummarySections();
    for (const auto &section : *sections) {
        if (!section.obsoleteMembers().isEmpty())
            summary_spv->append(&section);
    }
    if (m_aggregate->isClassNode())
        sections = &stdCppClassDetailsSections();
    else if (m_aggregate->isQmlType())
        sections = &stdQmlTypeDetailsSections();
    else
        sections = &stdDetailsSections();
    for (const auto &it : *sections) {
        if (!it.obsoleteMembers().isEmpty())
            details_spv->append(&it);
    }
    return !summary_spv->isEmpty();
}

QT_END_NAMESPACE
