/*
 * Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/RefCounted.h>
#include <AK/WeakPtr.h>
#include <AK/Weakable.h>
#include <LibGC/Ptr.h>
#include <LibGC/Weak.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/CSS/Display.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/InvalidateDisplayList.h>
#include <LibWeb/Painting/ChromeWidget.h>
#include <LibWeb/Painting/ShadowData.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/RefCountedTreeNode.h>
#include <LibWeb/TraversalDecision.h>

namespace Web::Painting {

enum class PaintPhase {
    Background,
    Border,
    TableCollapsedBorder,
    Foreground,
    Outline,
    Overlay,
};

struct HitTestResult {
    NonnullRefPtr<Paintable> paintable;
    RefPtr<ChromeWidget> chrome_widget {};
    size_t index_in_node { 0 };
    Optional<CSSPixels> vertical_distance {};
    Optional<CSSPixels> horizontal_distance {};
    enum InternalPosition {
        None,
        Before,
        Inside,
        After,
    };
    InternalPosition internal_position { None };

    DOM::Node* dom_node();
    DOM::Node const* dom_node() const;
};

enum class HitTestType {
    Exact,      // Exact matches only
    TextCursor, // Clicking past the right/bottom edge of text will still hit the text
};

class WEB_API Paintable
    : public RefCounted<Paintable>
    , public Weakable<Paintable>
    , public RefCountedTreeNode<Paintable> {

public:
    virtual ~Paintable();

    virtual StringView class_name() const { return "Paintable"sv; }

    void detach_from_layout_node();

    [[nodiscard]] bool is_visible() const
    {
        auto const& cv = computed_values();
        return cv.visibility() == CSS::Visibility::Visible && cv.opacity() != 0;
    }
    [[nodiscard]] bool is_positioned() const { return m_positioned; }
    [[nodiscard]] bool is_fixed_position() const { return m_fixed_position; }
    [[nodiscard]] bool is_sticky_position() const { return m_sticky_position; }
    [[nodiscard]] bool is_absolutely_positioned() const { return m_absolutely_positioned; }
    [[nodiscard]] bool is_floating() const { return m_floating; }
    [[nodiscard]] bool is_inline() const { return m_inline; }
    [[nodiscard]] CSS::Display display() const { return m_display; }

    bool has_stacking_context() const;
    RefPtr<StackingContext> enclosing_stacking_context();

    virtual void paint(DisplayListRecordingContext&, PaintPhase) const { }
    void paint_inspector_overlay(DisplayListRecordingContext&) const;

    [[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;

    virtual bool forms_unconnected_subtree() const { return false; }

    virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y);

    Layout::Node const& layout_node() const
    {
        VERIFY(m_layout_node);
        return *m_layout_node;
    }
    Layout::Node& layout_node() { return const_cast<Layout::Node&>(const_cast<Paintable const&>(*this).layout_node()); }

    [[nodiscard]] GC::Ptr<DOM::Node> dom_node();
    [[nodiscard]] GC::Ptr<DOM::Node const> dom_node() const;
    void set_dom_node(GC::Ptr<DOM::Node>);

    CSS::ImmutableComputedValues const& computed_values() const;

    bool visible_for_hit_testing() const;

    GC::Ptr<HTML::Navigable> navigable() const;

    virtual void set_needs_repaint(InvalidateDisplayList = InvalidateDisplayList::Yes);

    RefPtr<PaintableBox> containing_block() const;

    template<typename T>
    bool fast_is() const = delete;

    [[nodiscard]] virtual bool is_navigable_container_viewport_paintable() const { return false; }
    [[nodiscard]] virtual bool is_viewport_paintable() const { return false; }
    [[nodiscard]] virtual bool is_paintable_box() const { return false; }
    [[nodiscard]] virtual bool is_paintable_with_lines() const { return false; }
    [[nodiscard]] virtual bool is_svg_paintable() const { return false; }
    [[nodiscard]] virtual bool is_svg_svg_paintable() const { return false; }
    [[nodiscard]] virtual bool is_svg_path_paintable() const { return false; }
    [[nodiscard]] virtual bool is_svg_graphics_paintable() const { return false; }
    [[nodiscard]] virtual bool is_text_paintable() const { return false; }

    DOM::Document const& document() const;
    DOM::Document& document();

    CSSPixelPoint box_type_agnostic_position() const;

    void scroll_ancestor_to_offset_into_view(size_t offset);

    enum class SelectionState : u8 {
        None,        // No selection
        Start,       // Selection starts in this Node
        End,         // Selection ends in this Node
        StartAndEnd, // Selection starts and ends in this Node
        Full,        // Selection starts before and ends after this Node
    };

    SelectionState selection_state() const { return m_selection_state; }
    void set_selection_state(SelectionState state);

    // https://drafts.csswg.org/css-pseudo-4/#highlight-styling
    struct TextDecorationStyle {
        Vector<CSS::TextDecorationLine> line;
        CSS::TextDecorationStyle style;
        Color color;
    };
    struct SelectionStyle {
        Color background_color;
        Optional<Color> text_color;
        Optional<Vector<ShadowData>> text_shadow;
        Optional<TextDecorationStyle> text_decoration;

        bool has_styling() const
        {
            return background_color.alpha() > 0 || text_color.has_value() || text_shadow.has_value() || text_decoration.has_value();
        }
    };
    [[nodiscard]] SelectionStyle selection_style() const;

    [[nodiscard]] String debug_description() const;

    friend class Layout::Node;

protected:
    explicit Paintable(Layout::Node const&);

    virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const { }
    Optional<WeakPtr<PaintableBox>> mutable m_containing_block;

private:
    GC::Weak<DOM::Node> m_dom_node;
    GC::Weak<Layout::Node const> m_layout_node;

    SelectionState m_selection_state { SelectionState::None };

    bool m_positioned : 1 { false };
    bool m_fixed_position : 1 { false };
    bool m_sticky_position : 1 { false };
    bool m_absolutely_positioned : 1 { false };
    bool m_floating : 1 { false };
    bool m_inline : 1 { false };
    CSS::Display m_display;
};

inline DOM::Node* HitTestResult::dom_node()
{
    return paintable->dom_node();
}

inline DOM::Node const* HitTestResult::dom_node() const
{
    return paintable->dom_node();
}

template<>
inline bool Paintable::fast_is<PaintableBox>() const { return is_paintable_box(); }

template<>
inline bool Paintable::fast_is<PaintableWithLines>() const { return is_paintable_with_lines(); }

template<>
inline bool Paintable::fast_is<TextPaintable>() const { return is_text_paintable(); }

WEB_API Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& border_rect, CSSPixelRect const& reference_rect, CSS::BorderRadiusData const& top_left_radius, CSS::BorderRadiusData const& top_right_radius, CSS::BorderRadiusData const& bottom_right_radius, CSS::BorderRadiusData const& bottom_left_radius);

}
