/*
 * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <LibGfx/Font/Typeface.h>
#include <LibGfx/FontCascadeList.h>
#include <LibURL/URL.h>
#include <LibWeb/Bindings/FontFace.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/CSS/ParsedFontFace.h>
#include <LibWeb/CSS/StyleValues/ComputationContext.h>

namespace Web::CSS {

class FontLoader;

class FontFace final : public Bindings::PlatformObject {
    WEB_PLATFORM_OBJECT(FontFace, Bindings::PlatformObject);
    GC_DECLARE_ALLOCATOR(FontFace);

public:
    using FontFaceSource = Variant<String, GC::Ref<WebIDL::BufferSource>>;

    [[nodiscard]] static GC::Ref<FontFace> construct_impl(JS::Realm&, String family, FontFaceSource source, Bindings::FontFaceDescriptors const& descriptors);
    [[nodiscard]] static GC::Ref<FontFace> create_css_connected(JS::Realm&, CSSFontFaceRule&);
    virtual ~FontFace() override;

    String family() const { return m_family; }
    WebIDL::ExceptionOr<void> set_family(String const&);
    void set_family_impl(NonnullRefPtr<StyleValue const> const& value);

    String style() const { return m_style; }
    WebIDL::ExceptionOr<void> set_style(String const&);
    void set_style_impl(NonnullRefPtr<StyleValue const> const& value);

    String weight() const { return m_weight; }
    WebIDL::ExceptionOr<void> set_weight(String const&);
    void set_weight_impl(NonnullRefPtr<StyleValue const> const& value);

    String stretch() const { return m_stretch; }
    WebIDL::ExceptionOr<void> set_stretch(String const&);
    void set_stretch_impl(NonnullRefPtr<StyleValue const> const& value);

    String unicode_range() const { return m_unicode_range; }
    WebIDL::ExceptionOr<void> set_unicode_range(String const&);
    void set_unicode_range_impl(NonnullRefPtr<StyleValue const> const& value);

    String feature_settings() const { return m_feature_settings; }
    WebIDL::ExceptionOr<void> set_feature_settings(String const&);
    void set_feature_settings_impl(NonnullRefPtr<StyleValue const> const& value);

    String variation_settings() const { return m_variation_settings; }
    WebIDL::ExceptionOr<void> set_variation_settings(String const&);
    void set_variation_settings_impl(NonnullRefPtr<StyleValue const> const& value);

    String display() const { return m_display; }
    WebIDL::ExceptionOr<void> set_display(String const&);
    void set_display_impl(NonnullRefPtr<StyleValue const> const& value);

    String ascent_override() const { return m_ascent_override; }
    WebIDL::ExceptionOr<void> set_ascent_override(String const&);
    void set_ascent_override_impl(NonnullRefPtr<StyleValue const> const& value);

    String descent_override() const { return m_descent_override; }
    WebIDL::ExceptionOr<void> set_descent_override(String const&);
    void set_descent_override_impl(NonnullRefPtr<StyleValue const> const& value);

    String line_gap_override() const { return m_line_gap_override; }
    WebIDL::ExceptionOr<void> set_line_gap_override(String const&);
    void set_line_gap_override_impl(NonnullRefPtr<StyleValue const> const& value);

    bool is_css_connected() const { return m_css_font_face_rule != nullptr; }
    void disconnect_from_css_rule();
    void reparse_connected_css_font_face_rule_descriptors();

    ParsedFontFace parsed_font_face() const;

    RefPtr<Gfx::Typeface const> typeface() const { return m_parsed_font; }

    FontWeightRange declared_weight_range() const { return m_cached_weight_range; }
    int declared_slope() const { return m_cached_slope; }
    int declared_width() const { return m_cached_width; }
    bool should_be_registered_with_font_computer() const { return is_css_connected() || status() == Bindings::FontFaceLoadStatus::Loaded; }

    RefPtr<Gfx::FontCascadeList const> font_with_point_size(float point_size, Gfx::FontVariationSettings const&, Gfx::ShapeFeatures const&) const;

    Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
    bool has_urls() const { return !m_urls.is_empty(); }

    bool has_non_default_unicode_range() const
    {
        if (m_unicode_ranges.size() != 1)
            return true;
        auto const& range = m_unicode_ranges.first();
        return range.min_code_point() != 0 || range.max_code_point() != 0x10FFFF;
    }

    Bindings::FontFaceLoadStatus status() const { return m_status; }

    GC::Ref<WebIDL::Promise> load();
    GC::Ref<WebIDL::Promise> loaded() const;

    GC::Ref<WebIDL::Promise> font_status_promise() { return m_font_status_promise; }

    void add_to_set(FontFaceSet&);
    void remove_from_set(FontFaceSet&);

private:
    FontFace(JS::Realm&, GC::Ref<WebIDL::Promise> font_status_promise);

    virtual void initialize(JS::Realm&) override;
    virtual void visit_edges(Visitor&) override;
    void reject_status_promise(JS::Value reason);

    Optional<FontComputer&> font_computer() const;

    [[nodiscard]] Optional<ComputationContext> computation_context() const;

    // FIXME: Should we be storing StyleValues instead?
    String m_family;
    String m_style;
    String m_weight;
    String m_stretch;
    String m_unicode_range;
    Vector<Gfx::UnicodeRange> m_unicode_ranges;
    String m_feature_settings;
    String m_variation_settings;
    String m_display;
    String m_ascent_override;
    String m_descent_override;
    String m_line_gap_override;

    FontWeightRange m_cached_weight_range { 400, 400 };
    int m_cached_slope { 0 };
    int m_cached_width { 100 };
    GC::Ptr<FontLoader> m_font_loader;

    // https://drafts.csswg.org/css-font-loading/#dom-fontface-status
    Bindings::FontFaceLoadStatus m_status { Bindings::FontFaceLoadStatus::Unloaded };

    GC::Ref<WebIDL::Promise> m_font_status_promise; // [[FontStatusPromise]]
    Vector<ParsedFontFace::Source> m_urls;          // [[Urls]]
    ByteBuffer m_binary_data {};                    // [[Data]]

    RefPtr<Gfx::Typeface const> m_parsed_font;
    RefPtr<Core::Promise<NonnullRefPtr<Gfx::Typeface const>>> m_font_load_promise;

    GC::Ptr<CSSFontFaceRule> m_css_font_face_rule;
    HashTable<GC::Ref<FontFaceSet>> m_containing_sets;
};

bool font_format_is_supported(FlyString const& name);

bool font_tech_is_supported(FontTech);
bool font_tech_is_supported(FlyString const& name);

}
