/*
 * Copyright (C) 2013-2025 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "AnchorPositionEvaluator.h"
#include "PropertyCascade.h"
#include "ResolvedStyle.h"
#include "SelectorChecker.h"
#include "SelectorMatchingState.h"
#include "StyleChange.h"
#include "StyleComputedStyle.h"
#include "StyleDisplay.h"
#include "StylePositionTryFallback.h"
#include "StyleUpdate.h"
#include "Styleable.h"
#include "TreeResolutionState.h"
#include <wtf/Function.h>
#include <wtf/Ref.h>

namespace WebCore {

class Document;
class Element;
class Node;
class ShadowRoot;

namespace Style {

class ComputedStyle;
class Resolver;
struct BuilderPositionTryFallback;
struct MatchResult;
struct PositionTryFallback;
struct PseudoElementIdentifier;
struct ResolutionContext;
struct ResolvedStyle;

enum class IsInDisplayNoneTree : bool { No, Yes };

DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(TreeResolverScope);
class TreeResolver {
public:
    TreeResolver(Document&, std::unique_ptr<Update> = { });
    ~TreeResolver();

    std::unique_ptr<Update> resolve();

    bool needsInterleavedLayout() const { return m_needsInterleavedLayout; }

private:
    enum class ResolutionType : uint8_t { RebuildUsingExisting, AnimationOnly, FastPathInherit, FullWithMatchResultCache, Full };
    ResolvedStyle styleForStyleable(const Styleable&, ResolutionType, const ResolutionContext&, const Style::ComputedStyle* existingStyle);

    void resolveComposedTree();

    const Style::ComputedStyle* existingStyle(const Element&);

    enum class LayoutInterleavingAction : uint8_t { None, SkipDescendants };
    enum class DescendantsToResolve : uint8_t { None, RebuildAllUsingExisting, ChildrenWithExplicitInherit, Children, All };

    LayoutInterleavingAction updateStateForQueryContainer(Element&, const Style::ComputedStyle*, DescendantsToResolve&);

    // For elements requiring style/layout interleaving (anchor-positioned and query
    // containers), descendant resolution is deferred on the first style resolution
    // pass and resumed in subsequent passes.
    // When deferral is needed, deferDescendantResolution saves the internal state
    // relevant to which descendants should be resolved.
    void deferDescendantResolution(Element&, OptionSet<Change>, DescendantsToResolve);
    // When descendant resolution can be resumed, resumeDescendantResolutionIfNeeded
    // restores the previously saved state (if there is)
    void resumeDescendantResolutionIfNeeded(Element&, OptionSet<Change>&, DescendantsToResolve&);

    std::pair<ElementUpdate, DescendantsToResolve> resolveElement(Element&, const Style::ComputedStyle* existingStyle, ResolutionType);

    ElementUpdate createAnimatedElementUpdate(ResolvedStyle&&, const Styleable&, OptionSet<Change>, const ResolutionContext&, IsInDisplayNoneTree = IsInDisplayNoneTree::No);
    std::unique_ptr<Style::ComputedStyle> resolveStartingStyle(const ResolvedStyle&, const Styleable&, const ResolutionContext&);
    std::unique_ptr<Style::ComputedStyle> resolveAfterChangeStyleForNonAnimated(const ResolvedStyle&, const Styleable&, const ResolutionContext&);
    std::unique_ptr<Style::ComputedStyle> resolveAgainInDifferentContext(const ResolvedStyle&, const Styleable&, const Style::ComputedStyle& parentStyle,  OptionSet<PropertyCascade::PropertyType>, std::optional<BuilderPositionTryFallback>&&, const ResolutionContext&);
    const Style::ComputedStyle& parentAfterChangeStyle(const Styleable&, const ResolutionContext&) const;

    HashSet<AnimatableCSSProperty> applyCascadeAfterAnimation(Style::ComputedStyle&, const HashMap<AnimatableCSSProperty, EnumSet<PropertyCascade::AnimationSource>>&, const MatchResult&, const Element&, const ResolutionContext&);

    std::optional<ElementUpdate> resolvePseudoElement(Element&, const PseudoElementIdentifier&, const ElementUpdate&, IsInDisplayNoneTree, const Style::ComputedStyle*);
    std::optional<ElementUpdate> resolveAncestorPseudoElement(Element&, const PseudoElementIdentifier&, const ElementUpdate&);
    std::optional<ResolvedStyle> resolveAncestorFirstLinePseudoElement(Element&, const ElementUpdate&);
    std::optional<ResolvedStyle> resolveAncestorFirstLetterPseudoElement(Element&, const ElementUpdate&, ResolutionContext&);

    void resetStyleForNonRenderedDescendants(Element&);

    struct Scope : RefCounted<Scope> {
        WTF_DEPRECATED_MAKE_STRUCT_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(Scope, TreeResolverScope);
        Ref<Resolver> resolver;
        SelectorMatchingState selectorMatchingState;
        RefPtr<ShadowRoot> shadowRoot;
        RefPtr<Scope> enclosingScope;

        Scope(Document&, Update&);
        Scope(ShadowRoot&, Scope& enclosingScope);
        ~Scope();
    };

    struct Parent {
        Element* element;
        const Style::ComputedStyle& style;
        OptionSet<Change> changes;
        DescendantsToResolve descendantsToResolve { DescendantsToResolve::None };
        bool didPushScope { false };
        bool resolvedFirstLineAndLetterChild { false };
        bool needsUpdateQueryContainerDependentStyle { false };
        IsInDisplayNoneTree isInDisplayNoneTree { IsInDisplayNoneTree::No };

#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
        // Used to determine whether the AXObjectCache has already propagated down font and color updates for the current subtree.
        bool didAXUpdateFontSubtree { false };
        bool didAXUpdateTextColorSubtree { false };
#endif

        Parent(Document&);
        Parent(Element&, const Style::ComputedStyle&, OptionSet<Change>, DescendantsToResolve, IsInDisplayNoneTree);
    };

    Scope& scope() { return m_scopeStack.last(); }
    const Scope& scope() const { return m_scopeStack.last(); }

    Parent& parent() LIFETIME_BOUND { return m_parentStack.last(); }
    const Parent& parent() const LIFETIME_BOUND { return m_parentStack.last(); }

    void pushScope(ShadowRoot&);
    void pushEnclosingScope();
    void popScope();

#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
    void pushParent(Element&, const Style::ComputedStyle&, OptionSet<Change>, DescendantsToResolve, IsInDisplayNoneTree, bool, bool);
#else
    void pushParent(Element&, const Style::ComputedStyle&, OptionSet<Change>, DescendantsToResolve, IsInDisplayNoneTree);
#endif
    void popParent();
    void popParentsToDepth(unsigned depth);

    DescendantsToResolve computeDescendantsToResolve(const ElementUpdate&, const Style::ComputedStyle* existingStyle, Validity) const;
    static std::optional<ResolutionType> determineResolutionType(const Element&, const Style::ComputedStyle*, DescendantsToResolve, OptionSet<Change> parentChange);
    void resetDescendantStyleRelations(Element&, DescendantsToResolve);

    ResolutionContext makeResolutionContext();
    ResolutionContext makeResolutionContextForPseudoElement(const ElementUpdate&, const PseudoElementIdentifier&);
    std::optional<ResolutionContext> makeResolutionContextForInheritedFirstLine(const ElementUpdate&, const Style::ComputedStyle& inheritStyle);
    const Parent* boxGeneratingParent() const LIFETIME_BOUND;
    const Style::ComputedStyle* parentBoxStyle() const LIFETIME_BOUND;
    const Style::ComputedStyle* parentBoxStyleForPseudoElement(const ElementUpdate&) const LIFETIME_BOUND;
    const Style::ComputedStyle* NODELETE documentElementStyle() const;

    LayoutInterleavingAction updateAnchorPositioningState(Element&, const Style::ComputedStyle*);

    void generatePositionOptionsIfNeeded(const ResolvedStyle&, const Styleable&, const ResolutionContext&);
    std::unique_ptr<Style::ComputedStyle> generatePositionOption(const PositionTryFallback&, const ResolvedStyle&, const Styleable&, const ResolutionContext&);
    struct PositionOptions;
    void sortPositionOptionsIfNeeded(PositionOptions&, const Styleable&);
    std::optional<ResolvedStyle> tryChoosePositionOption(const Styleable&, const ResolutionContext&);

    void updateForPositionVisibility(Style::ComputedStyle&, const Styleable&);

    // This returns the style that was in effect (applied to the render tree) before we started the style resolution.
    // Layout interleaving may cause different styles to be applied during the style resolution.
    const Style::ComputedStyle* beforeResolutionStyle(const Element&, std::optional<PseudoElementIdentifier>);
    void saveBeforeResolutionStyleForInterleaving(const Element&, const Style::ComputedStyle*);

    bool hasUnresolvedAnchorPosition(const Styleable&) const;
    bool hasResolvedAnchorPosition(const Styleable&) const;
    // Returns true if (1) the styleable specifies position fallbacks and
    // (2) we're in the middle of trying position options.
    bool isTryingPositionOption(const Styleable&) const;

    void collectChangedAnchorNames(const Style::ComputedStyle&, const Style::ComputedStyle* currentStyle);

    static unsigned maximumRenderTreeDepth();

    const CheckedRef<Document> m_document;
    std::unique_ptr<Style::ComputedStyle> m_computedDocumentElementStyle;

    Vector<Ref<Scope>, 4> m_scopeStack;
    Vector<Parent, 32> m_parentStack;
    bool m_didSeePendingStylesheet { false };
    bool m_isFullDocumentStyleRebuild { false };

    // States relevant to deferring and resuming descendant resolution.
    // Also see deferDescendantResolution and resumeDescendantResolutionIfNeeded.
    struct DeferredDescendantResolutionState {
        OptionSet<Change> changes;
        DescendantsToResolve descendantsToResolve { DescendantsToResolve::None };
    };
    HashMap<Ref<Element>, DeferredDescendantResolutionState> m_deferredDescendantResolutionStates;
    bool m_needsInterleavedLayout { false };
    bool m_didFirstInterleavedLayout { false };

    struct QueryContainerState {
        bool invalidated { false };
    };
    HashMap<Ref<Element>, QueryContainerState> m_queryContainerStates;

    // This state gets passes to the style builder and holds state for a single tree resolution, including over any interleaving.
    TreeResolutionState m_treeResolutionState;
    HashMap<Ref<const Element>, std::unique_ptr<Style::ComputedStyle>> m_savedBeforeResolutionStylesForInterleaving;

    struct PositionOption {
        // New style after applying the option.
        std::unique_ptr<Style::ComputedStyle> style;

        // The position option used to generate the style. If option is nullopt, no position option is used.
        std::optional<PositionTryFallback> option;

        // The size of the content box of the element when the style is generated.
        // Non-overlay scrollbars appearing or disappearing may affect the content box size that anchor functions are resolved against.
        std::optional<LayoutSize> scrollContainerSizeOnGeneration;
    };

    struct PositionOptions {
        // Array of option styles. By convention, the original style is at index 0.
        Vector<PositionOption> optionStyles { };
        ResolvedStyle originalResolvedStyle;
        size_t index { 0 };
        bool sorted { false };
        bool chosen { false };
        bool isFirstTry { true };

        const Style::ComputedStyle& NODELETE originalStyle() const;
        std::unique_ptr<Style::ComputedStyle> currentOption() const;
    };
    HashMap<WeakStyleable, PositionOptions> m_positionOptions;

    HashSet<AtomString> m_changedAnchorNames;
    bool m_allAnchorNamesInvalid { false };

    std::unique_ptr<Update> m_update;
};

// Integrate with the HTML5 event loop instead, see EventLoop.cpp and consumers.
void deprecatedQueuePostResolutionCallback(Function<void()>&&);
bool NODELETE postResolutionCallbacksAreSuspended();

class PostResolutionCallbackDisabler {
public:
    enum class DrainCallbacks : bool { No, Yes };
    explicit PostResolutionCallbackDisabler(Document&, DrainCallbacks = DrainCallbacks::Yes);
    ~PostResolutionCallbackDisabler();

private:
    DrainCallbacks m_drainCallbacks;
};

} // namespace Style
} // namespace WebCore
