/*
 * Copyright (c) 2022-2025, Andreas Kling <andreas@ladybird.org>
 * Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/AllOf.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Painting/PaintableFragment.h>

namespace Web::Painting {

class HitTestDisplayList;

class PaintableWithLines : public Paintable {
public:
    static NonnullRefPtr<PaintableWithLines> create(Layout::BlockContainer const&);
    static NonnullRefPtr<PaintableWithLines> create(Layout::InlineNode const&, size_t line_index);
    virtual ~PaintableWithLines() override;
    virtual StringView class_name() const override { return "PaintableWithLines"sv; }

    virtual void reset_for_relayout() override;

    Vector<PaintableFragment> const& fragments() const { return m_fragments; }
    Vector<PaintableFragment>& fragments() { return m_fragments; }

    bool has_only_block_level_fragments() const
    {
        return !m_fragments.is_empty() && all_of(m_fragments, [](auto& fragment) { return fragment.is_block_level_box(); });
    }

    void add_fragment(Layout::LineBoxFragment const& fragment, LineBoxData line_box_data)
    {
        m_fragments.empend(*this, fragment, line_box_data);
    }
    void reset_fragment_selection_states()
    {
        for (auto& fragment : m_fragments)
            fragment.set_selection_state(SelectionState::None);
    }

    virtual void paint(DisplayListRecordingContext&, PaintPhase) const override;
    virtual void record_hit_test_items(DisplayListRecordingContext&, PaintPhase) const override;
    static void paint_text_fragment_debug_highlight(DisplayListRecordingContext&, PaintableFragment const&);

    size_t line_index() const { return m_line_index; }

protected:
    PaintableWithLines(Layout::BlockContainer const&);
    PaintableWithLines(Layout::InlineNode const&, size_t line_index);

private:
    [[nodiscard]] virtual bool is_paintable_with_lines() const final { return true; }

    Optional<PaintableFragment const&> fragment_at_position(DOM::Position const&) const;
    void paint_cursor(DisplayListRecordingContext&) const;
    Optional<CSSPixelRect> empty_line_caret_rect(DOM::Position const&) const;

    // A caret target for a line box with no fragments (e.g. a blank line in a textarea).
    struct EmptyLineCaretTarget {
        size_t offset { 0 };
        size_t line_box_index { 0 };
        CSSPixelRect rect;
    };
    Vector<EmptyLineCaretTarget> empty_line_caret_targets() const;
    void record_empty_line_caret_items(HitTestDisplayList&, VisualContextIndex) const;

    Vector<PaintableFragment> m_fragments;

    size_t m_line_index { 0 };
};

}
