// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { beforeNextRender, dedupingMixin, microTask } from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
export const ScrollableMixin = dedupingMixin((superClass) => {
    class ScrollableMixin extends superClass {
        resizeObserver_;
        constructor(...args) {
            super(...args);
            this.resizeObserver_ = new ResizeObserver((entries) => {
                requestAnimationFrame(() => {
                    for (const entry of entries) {
                        this.onScrollableContainerResize_(entry.target);
                    }
                });
            });
        }
        ready() {
            super.ready();
            beforeNextRender(this, () => {
                this.requestUpdateScroll();
                // Listen to the 'scroll' event for each scrollable container.
                const scrollableElements = this.shadowRoot.querySelectorAll('[scrollable]');
                for (const scrollableElement of scrollableElements) {
                    scrollableElement.addEventListener('scroll', this.updateScrollEvent_.bind(this));
                }
            });
        }
        disconnectedCallback() {
            super.disconnectedCallback();
            this.resizeObserver_.disconnect();
        }
        /**
         * Called any time the contents of a scrollable container may have
         * changed. This ensures that the <iron-list> contents of dynamically
         * sized containers are resized correctly.
         */
        updateScrollableContents() {
            this.requestUpdateScroll();
            const ironLists = this.shadowRoot.querySelectorAll('[scrollable] iron-list');
            for (const ironList of ironLists) {
                // When the scroll-container of an iron-list has scrollHeight of 1,
                // the iron-list will default to showing a minimum of 3 items.
                // After an iron-resize is fired, it will resize to have the correct
                // scrollHeight, but another iron-resize is required to render all
                // the items correctly.
                // If the scrollHeight of the scroll-container is 0, the element is
                // not yet rendered, and we must wait until its scrollHeight becomes
                // 1, then fire the first iron-resize event.
                const scrollContainer = ironList.parentElement;
                const scrollHeight = scrollContainer.scrollHeight;
                if (scrollHeight <= 1 && ironList.items.length > 0 &&
                    window.getComputedStyle(scrollContainer).display !== 'none') {
                    // The scroll-container does not have a proper scrollHeight yet.
                    // An additional iron-resize is needed, which will be triggered by
                    // the observer after scrollHeight changes.
                    // Do not observe for resize if there are no items, or if the
                    // scroll-container is explicitly hidden, as in those cases there
                    // will not be any future resizes.
                    this.resizeObserver_.observe(scrollContainer);
                }
                if (scrollHeight !== 0) {
                    // If the iron-list is already rendered, fire an initial
                    // iron-resize event. Otherwise, the resizeObserver_ will handle
                    // firing the iron-resize event, upon its scrollHeight becoming 1.
                    ironList.notifyResize();
                }
            }
        }
        /**
         * Setup the initial scrolling related classes for each scrollable
         * container. Called from ready() and updateScrollableContents(). May
         * also be called directly when the contents change (e.g. when not using
         * iron-list).
         */
        requestUpdateScroll() {
            requestAnimationFrame(() => {
                const scrollableElements = this.shadowRoot.querySelectorAll('[scrollable]');
                for (const scrollableElement of scrollableElements) {
                    this.updateScroll_(scrollableElement);
                }
            });
        }
        saveScroll(list) {
            // Store a FIFO of saved scroll positions so that multiple updates in
            // a frame are applied correctly. Specifically we need to track when
            // '0' is saved (but not apply it), and still handle patterns like
            // [30, 0, 32].
            list.savedScrollTops = list.savedScrollTops || [];
            list.savedScrollTops.push(list.scrollTarget.scrollTop);
        }
        restoreScroll(list) {
            microTask.run(() => {
                const scrollTop = list.savedScrollTops.shift();
                // Ignore scrollTop of 0 in case it was intermittent (we do not need
                // to explicitly scroll to 0).
                if (scrollTop !== 0) {
                    list.scroll(0, scrollTop);
                }
            });
        }
        /**
         * Event wrapper for updateScroll_.
         */
        updateScrollEvent_(event) {
            const scrollable = event.target;
            this.updateScroll_(scrollable);
        }
        /**
         * This gets called once initially and any time a scrollable container
         * scrolls.
         */
        updateScroll_(scrollable) {
            scrollable.classList.toggle('can-scroll', scrollable.clientHeight < scrollable.scrollHeight);
            scrollable.classList.toggle('is-scrolled', scrollable.scrollTop > 0);
            scrollable.classList.toggle('scrolled-to-bottom', scrollable.scrollTop + scrollable.clientHeight >=
                scrollable.scrollHeight);
        }
        /**
         * This gets called upon a resize event on the scrollable element
         */
        onScrollableContainerResize_(scrollable) {
            const nodeList = scrollable.querySelectorAll('iron-list');
            if (nodeList.length === 0 || scrollable.scrollHeight > 1) {
                // Stop observing after the scrollHeight has its correct value, or
                // if somehow there are no more iron-lists in the scrollable.
                this.resizeObserver_.unobserve(scrollable);
            }
            if (scrollable.scrollHeight !== 0) {
                // Fire iron-resize event only if scrollHeight has changed from 0 to
                // 1 or from 1 to the correct size. ResizeObserver doesn't exactly
                // observe scrollHeight and may fire despite it staying at 0, so
                // we can ignore those events.
                for (const node of nodeList) {
                    node.notifyResize();
                }
            }
        }
    }
    return ScrollableMixin;
});
