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

#pragma once

#include <AK/Forward.h>
#include <LibWeb/Layout/FormattingContext.h>
#include <LibWeb/Layout/TableGrid.h>
#include <LibWeb/Layout/TableWrapper.h>

namespace Web::Layout {

enum class TableDimension {
    Row,
    Column
};

class TableFormattingContext final : public FormattingContext {
public:
    explicit TableFormattingContext(LayoutState&, LayoutMode, Box const&, FormattingContext* parent);
    ~TableFormattingContext();

    void run_until_width_calculation(AvailableSpace const& available_space);

    virtual void run(AvailableSpace const&) override;
    virtual CSSPixels automatic_content_width() const override;
    virtual CSSPixels automatic_content_height() const override;
    StaticPositionRect calculate_static_position_rect(Box const&) const;

    Box const& table_box() const { return context_box(); }
    TableWrapper const& table_wrapper() const
    {
        return as<TableWrapper>(*table_box().containing_block());
    }

    static bool border_is_less_specific(CSS::BorderData const& a, CSS::BorderData const& b);

    virtual void parent_context_did_dimension_child_root_box() override;

private:
    CSSPixels run_caption_layout(CSS::CaptionSide);
    CSSPixels compute_capmin();
    void compute_constrainedness();
    void compute_cell_measures();
    void compute_outer_content_sizes();
    template<class RowOrColumn>
    void initialize_table_measures();
    template<class RowOrColumn>
    void compute_table_measures();
    template<class RowOrColumn>
    void compute_intrinsic_percentage(size_t max_cell_span);
    void compute_table_width();
    void distribute_width_to_columns();
    void distribute_excess_width_to_columns(CSSPixels available_width);
    void distribute_excess_width_to_columns_fixed_mode(CSSPixels excess_width);
    void compute_table_height();
    void distribute_height_to_rows();
    void position_row_boxes();
    void position_cell_boxes();
    void border_conflict_resolution();
    CSSPixels border_spacing_horizontal() const;
    CSSPixels border_spacing_vertical() const;
    void finish_grid_initialization(TableGrid const&);

    CSSPixels compute_columns_total_used_width() const;
    void commit_candidate_column_widths(Vector<CSSPixels> const& candidate_widths);
    void assign_columns_width_linear_combination(Vector<CSSPixels> const& candidate_widths, CSSPixels available_width);

    template<class ColumnFilter, class BaseWidthGetter>
    bool distribute_excess_width_proportionally_to_base_width(CSSPixels excess_width, ColumnFilter column_filter, BaseWidthGetter base_width_getter);
    template<class ColumnFilter>
    bool distribute_excess_width_equally(CSSPixels excess_width, ColumnFilter column_filter);
    template<class ColumnFilter>
    bool distribute_excess_width_by_intrinsic_percentage(CSSPixels excess_width, ColumnFilter column_filter);

    bool use_fixed_mode_layout() const;

    CSSPixels m_table_height { 0 };
    CSSPixels m_automatic_content_height { 0 };

    Optional<AvailableSpace> m_available_space;

    struct Column {
        CSSPixels left_offset { 0 };
        CSSPixels min_size { 0 };
        CSSPixels max_size { 0 };
        CSSPixels used_width { 0 };
        bool has_intrinsic_percentage { false };
        double intrinsic_percentage { 0 };
        // Store whether the column is constrained: https://www.w3.org/TR/css-tables-3/#constrainedness
        bool is_constrained { false };
        // Store whether the column has originating cells, defined in https://www.w3.org/TR/css-tables-3/#terminology.
        bool has_originating_cells { false };
    };

    using Cell = TableGrid::Cell;
    using Row = TableGrid::Row;

    // Accessors to enable direction-agnostic table measurement.

    template<class RowOrColumn>
    static size_t cell_span(Cell const& cell);

    template<class RowOrColumn>
    static size_t cell_index(Cell const& cell);

    template<class RowOrColumn>
    static CSSPixels cell_min_size(Cell const& cell);

    template<class RowOrColumn>
    static CSSPixels cell_max_size(Cell const& cell);

    template<class RowOrColumn>
    static double cell_percentage_contribution(Cell const& cell);

    template<class RowOrColumn>
    static bool cell_has_intrinsic_percentage(Cell const& cell);

    template<class RowOrColumn>
    void initialize_intrinsic_percentages_from_rows_or_columns();

    template<class RowOrColumn>
    void initialize_intrinsic_percentages_from_cells();

    template<class RowOrColumn>
    CSSPixels border_spacing();

    template<class RowOrColumn>
    Vector<RowOrColumn>& table_rows_or_columns();

    CSSPixels compute_row_content_height(Cell const& cell) const;

    enum class ConflictingSide {
        Top,
        Bottom,
        Left,
        Right,
    };

    struct ConflictingEdge {
        GC::Ptr<Node const> element;
        Painting::PaintableBox::ConflictingElementKind element_kind;
        ConflictingSide side;
        Optional<size_t> row;
        Optional<size_t> column;
    };

    static TableFormattingContext::ConflictingEdge const& winning_conflicting_edge(TableFormattingContext::ConflictingEdge const& a, TableFormattingContext::ConflictingEdge const& b);

    static CSS::BorderData const& border_data_conflicting_edge(ConflictingEdge const& conflicting_edge);
    static Painting::PaintableBox::BorderDataWithElementKind const border_data_with_element_kind_from_conflicting_edge(ConflictingEdge const& conflicting_edge);

    class BorderConflictFinder {
    public:
        BorderConflictFinder(TableFormattingContext const* context);
        Vector<ConflictingEdge> conflicting_edges(Cell const&, ConflictingSide) const;

    private:
        void collect_conflicting_col_elements();
        void collect_conflicting_row_group_elements();

        void collect_cell_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
        void collect_row_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
        void collect_row_group_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
        void collect_column_group_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;
        void collect_table_box_conflicting_edges(Vector<ConflictingEdge>&, Cell const&, ConflictingSide) const;

        GC::Ptr<Node const> get_col_element(size_t index) const
        {
            if (index >= m_col_elements_by_index.size())
                return {};
            return m_col_elements_by_index[index];
        }

        struct RowGroupInfo {
            GC::Ptr<Node const> row_group;
            size_t start_index;
            size_t row_count;
        };

        Vector<GC::Ptr<Node const>> m_col_elements_by_index;
        Vector<Optional<RowGroupInfo>> m_row_group_elements_by_index;
        TableFormattingContext const* m_context;
    };

    Vector<Cell> m_cells;
    Vector<Vector<Optional<Cell const&>>> m_cells_by_coordinate;
    Vector<Column> m_columns;
    Vector<Row> m_rows;
};

}
