import { once } from 'alpinejs/src/utils/once'

export default (Alpine) => {
    Alpine.data('notificationComponent', ({ notification }) => ({
        isShown: false,

        computedStyle: null,

        transitionDuration: null,

        transitionEasing: null,

        init() {
            this.computedStyle = window.getComputedStyle(this.$el)

            this.transitionDuration =
                parseFloat(this.computedStyle.transitionDuration) * 1000

            this.transitionEasing = this.computedStyle.transitionTimingFunction

            this.configureTransitions()
            this.configureAnimations()

            if (
                notification.duration &&
                notification.duration !== 'persistent'
            ) {
                setTimeout(() => {
                    if (!this.$el.matches(':hover')) {
                        this.close()

                        return
                    }

                    this.$el.addEventListener('mouseleave', () => this.close())
                }, notification.duration)
            }

            this.isShown = true
        },

        configureTransitions() {
            const display = this.computedStyle.display

            const show = () => {
                Alpine.mutateDom(() => {
                    this.$el.style.setProperty('display', display)
                    this.$el.style.setProperty('visibility', 'visible')
                })
                this.$el._x_isShown = true
            }

            const hide = () => {
                Alpine.mutateDom(() => {
                    this.$el._x_isShown
                        ? this.$el.style.setProperty('visibility', 'hidden')
                        : this.$el.style.setProperty('display', 'none')
                })
            }

            const toggle = once(
                (value) => (value ? show() : hide()),
                (value) => {
                    this.$el._x_toggleAndCascadeWithTransitions(
                        this.$el,
                        value,
                        show,
                        hide,
                    )
                },
            )

            Alpine.effect(() => toggle(this.isShown))
        },

        configureAnimations() {
            let animation

            Livewire.hook(
                'commit',
                ({ component, commit, succeed, fail, respond }) => {
                    if (
                        !component.snapshot.data
                            .isFilamentNotificationsComponent
                    ) {
                        return
                    }

                    // Calling `el.getBoundingClientRect()` from outside `requestAnimationFrame()` can
                    // occasionally cause the page to scroll to the top.
                    requestAnimationFrame(() => {
                        const getTop = () =>
                            this.$el.getBoundingClientRect().top
                        const oldTop = getTop()

                        respond(() => {
                            animation = () => {
                                if (!this.isShown) {
                                    return
                                }

                                this.$el.animate(
                                    [
                                        {
                                            transform: `translateY(${
                                                oldTop - getTop()
                                            }px)`,
                                        },
                                        { transform: 'translateY(0px)' },
                                    ],
                                    {
                                        duration: this.transitionDuration,
                                        easing: this.transitionEasing,
                                    },
                                )
                            }

                            this.$el
                                .getAnimations()
                                .forEach((animation) => animation.finish())
                        })

                        succeed(({ snapshot, effect }) => {
                            animation()
                        })
                    })
                },
            )
        },

        close() {
            this.isShown = false

            setTimeout(
                () =>
                    window.dispatchEvent(
                        new CustomEvent('notificationClosed', {
                            detail: {
                                id: notification.id,
                            },
                        }),
                    ),
                this.transitionDuration,
            )
        },

        markAsRead() {
            window.dispatchEvent(
                new CustomEvent('markedNotificationAsRead', {
                    detail: {
                        id: notification.id,
                    },
                }),
            )
        },

        markAsUnread() {
            window.dispatchEvent(
                new CustomEvent('markedNotificationAsUnread', {
                    detail: {
                        id: notification.id,
                    },
                }),
            )
        },
    }))
}
