/*
 * Copyright (c) 2023-2024, Matthew Olsson <mattco@serenityos.org>.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Animations/TimeValue.h>
#include <LibWeb/Bindings/Animation.h>
#include <LibWeb/DOM/AbstractElement.h>
#include <LibWeb/DOM/EventTarget.h>

namespace Web::Animations {

// Sorted by composite order:
// https://www.w3.org/TR/css-animations-2/#animation-composite-order
enum class AnimationClass {
    CSSAnimationWithOwningElement,
    CSSTransition,
    CSSAnimationWithoutOwningElement,
    None,
};

// https://www.w3.org/TR/web-animations-1/#the-animation-interface
class Animation : public DOM::EventTarget {
    WEB_PLATFORM_OBJECT(Animation, DOM::EventTarget);
    GC_DECLARE_ALLOCATOR(Animation);

public:
    static constexpr bool OVERRIDES_FINALIZE = true;

    static GC::Ref<Animation> create(JS::Realm&, GC::Ptr<AnimationEffect>, Optional<GC::Ptr<AnimationTimeline>>);
    static GC::Ref<Animation> construct_impl(JS::Realm&, GC::Ptr<AnimationEffect>, Optional<GC::Ptr<AnimationTimeline>>);

    FlyString const& id() const { return m_id; }
    void set_id(FlyString value) { m_id = move(value); }

    GC::Ptr<AnimationEffect> effect() const { return m_effect; }
    void set_effect(GC::Ptr<AnimationEffect>);

    GC::Ptr<AnimationTimeline> timeline() const { return m_timeline; }
    void set_timeline(GC::Ptr<AnimationTimeline>);

    virtual GC::Ptr<AnimationTimeline> timeline_for_bindings() const { return m_timeline; }
    virtual void set_timeline_for_bindings(GC::Ptr<AnimationTimeline> timeline) { set_timeline(timeline); }

    // https://drafts.csswg.org/web-animations-2/#dom-animation-starttime
    NullableCSSNumberish start_time_for_bindings() const
    {
        return NullableCSSNumberish::from_optional_css_numberish_time(realm(), start_time());
    }
    Optional<TimeValue> start_time() const { return m_start_time; }
    WebIDL::ExceptionOr<void> set_start_time_for_bindings(NullableCSSNumberish const&);

    void calculate_auto_aligned_start_time();

    // https://drafts.csswg.org/web-animations-2/#dom-animation-currenttime
    NullableCSSNumberish current_time_for_bindings() const
    {
        return NullableCSSNumberish::from_optional_css_numberish_time(realm(), current_time());
    }
    Optional<TimeValue> current_time() const;
    WebIDL::ExceptionOr<void> set_current_time_for_bindings(NullableCSSNumberish const&);

    double playback_rate() const { return m_playback_rate; }
    WebIDL::ExceptionOr<void> set_playback_rate(double value);

    Bindings::AnimationPlayState play_state_for_bindings() const;
    Bindings::AnimationPlayState play_state() const;

    bool is_relevant() const;

    bool is_replaceable() const;
    Bindings::AnimationReplaceState replace_state() const { return m_replace_state; }
    void set_replace_state(Bindings::AnimationReplaceState value);

    // https://www.w3.org/TR/web-animations-1/#dom-animation-pending
    bool pending() const { return m_pending_play_task == TaskState::Scheduled || m_pending_pause_task == TaskState::Scheduled; }

    // https://www.w3.org/TR/web-animations-1/#dom-animation-ready
    GC::Ref<WebIDL::Promise> ready() const { return current_ready_promise(); }

    // https://www.w3.org/TR/web-animations-1/#dom-animation-finished
    GC::Ref<WebIDL::Promise> finished() const { return current_finished_promise(); }
    bool is_finished() const { return m_is_finished; }

    bool is_idle() const { return play_state() == Bindings::AnimationPlayState::Idle; }

    GC::Ptr<WebIDL::CallbackType> onfinish();
    void set_onfinish(GC::Ptr<WebIDL::CallbackType>);
    GC::Ptr<WebIDL::CallbackType> oncancel();
    void set_oncancel(GC::Ptr<WebIDL::CallbackType>);
    GC::Ptr<WebIDL::CallbackType> onremove();
    void set_onremove(GC::Ptr<WebIDL::CallbackType>);

    enum class AutoRewind {
        Yes,
        No,
    };
    enum class ShouldInvalidate {
        Yes,
        No,
    };
    void cancel(ShouldInvalidate = ShouldInvalidate::Yes);
    WebIDL::ExceptionOr<void> finish();
    WebIDL::ExceptionOr<void> play();
    WebIDL::ExceptionOr<void> play_an_animation(AutoRewind);
    WebIDL::ExceptionOr<void> pause();
    WebIDL::ExceptionOr<void> update_playback_rate(double);
    WebIDL::ExceptionOr<void> reverse();
    void persist();

    Optional<TimeValue> convert_an_animation_time_to_timeline_time(Optional<TimeValue>) const;
    Optional<double> convert_a_timeline_time_to_an_origin_relative_time(Optional<TimeValue>) const;

    GC::Ptr<DOM::Document> document_for_timing() const;
    void update();

    void effect_timing_changed(Badge<AnimationEffect>);

    virtual bool is_css_animation() const { return false; }
    virtual bool is_css_transition() const { return false; }

    Optional<DOM::AbstractElement> owning_element() const { return m_owning_element; }
    void set_owning_element(Optional<DOM::AbstractElement>&& value) { m_owning_element = move(value); }

    virtual AnimationClass animation_class() const { return AnimationClass::None; }
    virtual int class_specific_composite_order(GC::Ref<Animation>) const { return 0; }

    unsigned int global_animation_list_order() const { return m_global_animation_list_order; }

    auto release_saved_cancel_time() { return move(m_saved_cancel_time); }

    TimeValue associated_effect_end() const;

    Optional<CSS::AnimationPlayState> last_css_animation_play_state() const { return m_last_css_animation_play_state; }
    void set_last_css_animation_play_state(CSS::AnimationPlayState state) { m_last_css_animation_play_state = state; }

protected:
    Animation(JS::Realm&);

    virtual void initialize(JS::Realm&) override;
    virtual void visit_edges(Cell::Visitor&) override;
    virtual void finalize() override;

private:
    enum class TaskState {
        None,
        Scheduled,
    };

    enum class DidSeek {
        Yes,
        No,
    };

    enum class SynchronouslyNotify {
        Yes,
        No,
    };

    double effective_playback_rate() const;

    WebIDL::ExceptionOr<Optional<TimeValue>> validate_a_css_numberish_time(NullableCSSNumberish const&) const;

    void apply_any_pending_playback_rate();
    WebIDL::ExceptionOr<void> silently_set_current_time(Optional<TimeValue>);
    void update_finished_state(DidSeek, SynchronouslyNotify);
    void reset_an_animations_pending_tasks();

    bool is_ready() const;
    void run_pending_play_task();

    bool is_ready_to_run_pending_pause_task() const;
    void run_pending_pause_task();

    GC::Ref<WebIDL::Promise> current_ready_promise() const;
    GC::Ref<WebIDL::Promise> current_finished_promise() const;

    void invalidate_effect();

    // https://www.w3.org/TR/web-animations-1/#dom-animation-id
    FlyString m_id;

    // https://www.w3.org/TR/web-animations-1/#global-animation-list
    unsigned int m_global_animation_list_order { 0 };

    // https://www.w3.org/TR/web-animations-1/#dom-animation-effect
    GC::Ptr<AnimationEffect> m_effect;

    // https://www.w3.org/TR/web-animations-1/#dom-animation-timeline
    GC::Ptr<AnimationTimeline> m_timeline;

    // https://www.w3.org/TR/web-animations-1/#animation-start-time
    Optional<TimeValue> m_start_time {};

    bool m_auto_align_start_time { false };

    // https://www.w3.org/TR/web-animations-1/#animation-hold-time
    Optional<TimeValue> m_hold_time {};

    // https://www.w3.org/TR/web-animations-1/#previous-current-time
    Optional<TimeValue> m_previous_current_time {};

    // https://www.w3.org/TR/web-animations-1/#playback-rate
    double m_playback_rate { 1.0 };

    // https://www.w3.org/TR/web-animations-1/#pending-playback-rate
    Optional<double> m_pending_playback_rate {};

    // https://www.w3.org/TR/web-animations-1/#dom-animation-replacestate
    Bindings::AnimationReplaceState m_replace_state { Bindings::AnimationReplaceState::Active };

    // Note: The following promises are initialized lazily to avoid constructing them outside of an execution context
    // https://www.w3.org/TR/web-animations-1/#current-ready-promise
    mutable GC::Ptr<WebIDL::Promise> m_current_ready_promise;

    // https://www.w3.org/TR/web-animations-1/#current-finished-promise
    mutable GC::Ptr<WebIDL::Promise> m_current_finished_promise;
    bool m_is_finished { false };

    // https://www.w3.org/TR/web-animations-1/#pending-play-task
    TaskState m_pending_play_task { TaskState::None };

    // https://www.w3.org/TR/web-animations-1/#pending-pause-task
    TaskState m_pending_pause_task { TaskState::None };

    // https://www.w3.org/TR/css-animations-2/#owning-element-section
    Optional<DOM::AbstractElement> m_owning_element;

    Optional<HTML::TaskID> m_pending_finish_microtask_id;

    Optional<TimeValue> m_saved_cancel_time;

    Optional<CSS::AnimationPlayState> m_last_css_animation_play_state;
};

}
