import { ContextProvider } from "@videojs/element/context";
import { isNull } from "@videojs/utils/predicate";

//#region src/store/provider-mixin.ts
/**
* Create a mixin that provides player context to descendant elements and
* owns the `store.attach()` lifecycle.
*
* Media and container elements register themselves via media/container
* contexts that carry both the current value and a setter. When a media
* element is available, the provider calls `store.attach({ media, container })`.
*
* As a fallback for plain `<video>`/`<audio>` that can't consume context,
* the provider queries its subtree after a microtask.
*
* @param config - Provider configuration with contexts and store factory.
*/
function createProviderMixin(config) {
	return (BaseClass) => {
		class PlayerProviderElement extends BaseClass {
			#store = config.factory();
			#detach = null;
			#media = null;
			#container = null;
			#fallbackQueued = false;
			#setMedia = (media) => {
				if (this.#media === media) return;
				this.#media = media;
				this.#mediaProvider.setValue({
					media,
					setMedia: this.#setMedia
				});
				this.#tryAttach();
			};
			#setContainer = (container) => {
				if (this.#container === container) return;
				this.#container = container;
				this.#containerProvider.setValue({
					container,
					setContainer: this.#setContainer
				});
				this.#tryAttach();
			};
			#playerProvider = new ContextProvider(this, {
				context: config.playerContext,
				initialValue: this.store
			});
			#mediaProvider = new ContextProvider(this, {
				context: config.mediaContext,
				initialValue: {
					media: this.#media,
					setMedia: this.#setMedia
				}
			});
			#containerProvider = new ContextProvider(this, {
				context: config.containerContext,
				initialValue: {
					container: this.#container,
					setContainer: this.#setContainer
				}
			});
			get store() {
				if (isNull(this.#store)) this.#store = config.factory();
				return this.#store;
			}
			connectedCallback() {
				super.connectedCallback();
				this.#playerProvider.setValue(this.store);
				this.#mediaProvider.setValue({
					media: this.#media,
					setMedia: this.#setMedia
				});
				this.#containerProvider.setValue({
					container: this.#container,
					setContainer: this.#setContainer
				});
				this.#tryAttach();
				this.#queueFallbackDiscovery();
			}
			disconnectedCallback() {
				super.disconnectedCallback();
				this.#detachStore();
			}
			destroyCallback() {
				this.#detachStore();
				this.#store?.destroy();
				this.#store = null;
				super.destroyCallback();
			}
			#tryAttach() {
				const store = this.#store;
				if (!store) return;
				if (!this.#media) {
					this.#detachStore();
					return;
				}
				const target = {
					media: this.#media,
					container: this.#container
				};
				const hasMediaChanged = store.target?.media !== target.media;
				const hasContainerChanged = store.target?.container !== target.container;
				if (hasMediaChanged || hasContainerChanged) {
					this.#detachStore();
					this.#detach = store.attach(target);
				}
			}
			#detachStore() {
				this.#detach?.();
				this.#detach = null;
			}
			#queueFallbackDiscovery() {
				if (this.#media || this.#fallbackQueued) return;
				this.#fallbackQueued = true;
				queueMicrotask(() => {
					this.#fallbackQueued = false;
					if (this.#media) return;
					const media = this.querySelector("video, audio");
					if (media) this.#setMedia(media);
				});
			}
		}
		return PlayerProviderElement;
	};
}

//#endregion
export { createProviderMixin };
//# sourceMappingURL=provider-mixin.js.map