import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.60.1_tslib@2.8.1_typescript@6.0.2/node_modules/tslib/tslib.es6.js';
import { css, LitElement, nothing, html } from 'lit';
import { property, state, query, customElement } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { sharedStyles } from '../styles/shared-styles.js';
import { utilityStyles } from '../styles/utility-styles.js';
import { pauseIcon, playIcon, skipBackIcon, skipForwardIcon, seekToLiveIcon, statsIcon, settingsIcon, fullscreenExitIcon, fullscreenIcon } from '../icons/index.js';
import { isLiveContent, isMediaStreamSource, calculateSeekableRange, canSeekStream, calculateLiveThresholds, calculateIsNearLive, formatTimeDisplay } from '@livepeer-frameworks/player-core';

let FwPlayerControls = class FwPlayerControls extends LitElement {
    constructor() {
        super(...arguments);
        this.playbackMode = "auto";
        this.isContentLive = false;
        this.devMode = false;
        this.showStatsButton = false;
        this.isStatsOpen = false;
        this._settingsOpen = false;
        this._isNearLiveState = true;
        this._buffered = null;
        this._boundVideo = null;
        this._onBufferedUpdate = null;
        this._onWindowClick = (event) => {
            const path = event.composedPath();
            const anchor = this._settingsAnchorEl;
            const insideControls = anchor !== null &&
                path.some((entry) => {
                    if (!(entry instanceof Node)) {
                        return false;
                    }
                    return anchor.contains(entry);
                });
            if (!insideControls) {
                this._settingsOpen = false;
            }
        };
    }
    connectedCallback() {
        super.connectedCallback();
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this._unbindVideoEvents();
        this._detachWindowClickListener();
    }
    updated(changed) {
        this._bindVideoEvents();
        this._reconcileNearLiveState();
        if (changed.has("_settingsOpen")) {
            if (this._settingsOpen) {
                this._attachWindowClickListener();
            }
            else {
                this._detachWindowClickListener();
            }
        }
    }
    _bindVideoEvents() {
        const video = this.pc?.s.videoElement ?? null;
        if (video === this._boundVideo) {
            return;
        }
        this._unbindVideoEvents();
        this._boundVideo = video;
        if (!video) {
            this._buffered = null;
            return;
        }
        const updateBuffered = () => {
            this._buffered = this.pc.getBufferedRanges() ?? video.buffered;
        };
        updateBuffered();
        video.addEventListener("progress", updateBuffered);
        video.addEventListener("loadeddata", updateBuffered);
        this._onBufferedUpdate = updateBuffered;
    }
    _unbindVideoEvents() {
        if (!this._boundVideo) {
            return;
        }
        const updateBuffered = this._onBufferedUpdate;
        if (updateBuffered) {
            this._boundVideo.removeEventListener("progress", updateBuffered);
            this._boundVideo.removeEventListener("loadeddata", updateBuffered);
        }
        this._boundVideo = null;
        this._onBufferedUpdate = null;
    }
    _attachWindowClickListener() {
        window.setTimeout(() => {
            if (!this._settingsOpen) {
                return;
            }
            window.addEventListener("click", this._onWindowClick);
        }, 0);
    }
    _detachWindowClickListener() {
        window.removeEventListener("click", this._onWindowClick);
    }
    _deriveBufferWindowMs(tracks) {
        if (!tracks) {
            return undefined;
        }
        // Filter out meta tracks and tracks with lastms <= 0 (same as PlayerController)
        const trackValues = Object.values(tracks).filter((t) => t.type !== "meta" && (t.lastms === undefined || t.lastms > 0));
        if (trackValues.length === 0) {
            return undefined;
        }
        const firstmsValues = trackValues
            .map((track) => track.firstms)
            .filter((value) => typeof value === "number");
        const lastmsValues = trackValues
            .map((track) => track.lastms)
            .filter((value) => typeof value === "number");
        if (firstmsValues.length === 0 || lastmsValues.length === 0) {
            return undefined;
        }
        const firstms = Math.min(...firstmsValues);
        const lastms = Math.max(...lastmsValues);
        const window = lastms - firstms;
        if (!Number.isFinite(window) || window <= 0) {
            return undefined;
        }
        return window;
    }
    _getSeekingContext() {
        const state = this.pc.s;
        const controller = this.pc.getController();
        const sourceType = state.currentSourceInfo?.type;
        const mistStreamInfo = state.streamState?.streamInfo;
        const isLive = isLiveContent(this.isContentLive, mistStreamInfo, state.duration);
        const bufferWindowMs = mistStreamInfo?.meta?.buffer_window ??
            this._deriveBufferWindowMs(mistStreamInfo?.meta?.tracks);
        const isWebRTC = isMediaStreamSource(state.videoElement);
        const allowMediaStreamDvr = isMediaStreamSource(state.videoElement) && bufferWindowMs !== undefined && bufferWindowMs > 0;
        const calculatedRange = calculateSeekableRange({
            isLive,
            video: state.videoElement,
            mistStreamInfo,
            currentTime: state.currentTime,
            duration: state.duration,
            allowMediaStreamDvr,
        });
        const controllerSeekableStart = this.pc.getSeekableStart();
        const controllerLiveEdge = this.pc.getLiveEdge();
        const useControllerRange = Number.isFinite(controllerSeekableStart) &&
            Number.isFinite(controllerLiveEdge) &&
            controllerLiveEdge >= controllerSeekableStart &&
            (controllerLiveEdge > 0 || controllerSeekableStart > 0);
        const seekableStart = useControllerRange
            ? controllerSeekableStart
            : calculatedRange.seekableStart;
        const liveEdge = useControllerRange ? controllerLiveEdge : calculatedRange.liveEdge;
        const hasDvrWindow = isLive &&
            Number.isFinite(liveEdge) &&
            Number.isFinite(seekableStart) &&
            liveEdge > seekableStart;
        const baseCanSeek = controller?.canSeekStream?.() ??
            canSeekStream({
                video: state.videoElement,
                isLive,
                duration: state.duration,
                bufferWindowMs,
            });
        const liveThresholds = calculateLiveThresholds(sourceType, isWebRTC, bufferWindowMs);
        return {
            mistStreamInfo,
            isLive,
            sourceType,
            seekableStart,
            liveEdge,
            hasDvrWindow,
            canSeek: baseCanSeek && (!isLive || hasDvrWindow),
            commitOnRelease: isLive,
            liveThresholds,
        };
    }
    _reconcileNearLiveState() {
        const context = this._getSeekingContext();
        if (!context.isLive) {
            if (!this._isNearLiveState) {
                this._isNearLiveState = true;
            }
            return;
        }
        const next = calculateIsNearLive(this.pc.s.currentTime, context.liveEdge, context.liveThresholds, this._isNearLiveState);
        if (next !== this._isNearLiveState) {
            this._isNearLiveState = next;
        }
    }
    _handleModeChange(event) {
        const { mode } = event.detail;
        this.dispatchEvent(new CustomEvent("fw-mode-change", {
            detail: { mode },
            bubbles: true,
            composed: true,
        }));
    }
    render() {
        const state = this.pc.s;
        const disabled = !state.videoElement;
        const context = this._getSeekingContext();
        const shouldShowControls = state.shouldShowControls ||
            state.isPaused ||
            !state.hasPlaybackStarted ||
            state.shouldShowIdleScreen ||
            !!state.error ||
            this._settingsOpen;
        const timeDisplay = formatTimeDisplay({
            isLive: context.isLive,
            currentTime: state.currentTime,
            duration: state.duration,
            liveEdge: context.liveEdge,
            seekableStart: context.seekableStart,
            unixoffset: context.mistStreamInfo?.unixoffset,
        });
        const showTimeDisplay = !(context.isLive && timeDisplay === "LIVE");
        const liveButtonDisabled = !context.hasDvrWindow || this._isNearLiveState;
        return html `
      <div
        class=${classMap({
            "fw-controls-wrapper": true,
            "fw-controls-wrapper--visible": shouldShowControls,
            "fw-controls-wrapper--hidden": !shouldShowControls,
        })}
      >
        <div class="fw-control-bar" @click=${(event) => event.stopPropagation()}>
          ${context.canSeek
            ? html `
                <div class="fw-seek-wrapper">
                  <fw-seek-bar
                    .currentTime=${state.currentTime}
                    .duration=${state.duration}
                    .buffered=${this._buffered}
                    .disabled=${disabled}
                    .isLive=${context.isLive}
                    .seekableStart=${context.seekableStart}
                    .liveEdge=${context.liveEdge}
                    .commitOnRelease=${context.commitOnRelease}
                    .isPlaying=${state.isPlaying}
                    .thumbnailCues=${state.thumbnailCues}
                    @fw-seek=${(event) => this.pc.seek(event.detail.time)}
                  ></fw-seek-bar>
                </div>
              `
            : nothing}

          <div class="fw-controls-row">
            <div class="fw-controls-left">
              <div class="fw-control-group">
                <button
                  type="button"
                  class="fw-btn-flush"
                  ?disabled=${disabled}
                  aria-label=${state.isPlaying ? this.pc.t("pause") : this.pc.t("play")}
                  @click=${() => this.pc.togglePlay()}
                >
                  ${state.isPlaying ? pauseIcon(18) : playIcon(18)}
                </button>

                ${context.canSeek
            ? html `
                      <button
                        type="button"
                        class="fw-btn-flush hidden sm:flex"
                        ?disabled=${disabled}
                        aria-label=${this.pc.t("skipBackward")}
                        @click=${() => this.pc.seekBy(-1e4)}
                      >
                        ${skipBackIcon(16)}
                      </button>
                      <button
                        type="button"
                        class="fw-btn-flush hidden sm:flex"
                        ?disabled=${disabled}
                        aria-label=${this.pc.t("skipForward")}
                        @click=${() => this.pc.seekBy(10000)}
                      >
                        ${skipForwardIcon(16)}
                      </button>
                    `
            : nothing}
              </div>

              <div class="fw-control-group">
                <fw-volume-control .pc=${this.pc}></fw-volume-control>
              </div>

              ${showTimeDisplay
            ? html `
                    <div class="fw-control-group">
                      <span class="fw-time-display">${timeDisplay}</span>
                    </div>
                  `
            : nothing}
              ${context.isLive
            ? html `
                    <div class="fw-control-group">
                      <button
                        type="button"
                        @click=${() => this.pc.jumpToLive()}
                        ?disabled=${liveButtonDisabled}
                        class=${classMap({
                "fw-live-badge": true,
                "fw-live-badge--active": liveButtonDisabled,
                "fw-live-badge--behind": !liveButtonDisabled,
            })}
                        title=${!context.hasDvrWindow
                ? this.pc.t("live")
                : this._isNearLiveState
                    ? this.pc.t("live")
                    : this.pc.t("live")}
                      >
                        ${this.pc.t("live").toUpperCase()}
                        ${!this._isNearLiveState && context.hasDvrWindow
                ? seekToLiveIcon(10)
                : nothing}
                      </button>
                    </div>
                  `
            : nothing}
            </div>

            <div class="fw-controls-right">
              ${this.showStatsButton
            ? html `
                    <div class="fw-control-group">
                      <button
                        type="button"
                        class=${classMap({
                "fw-btn-flush": true,
                "fw-btn-flush--active": this.isStatsOpen,
            })}
                        aria-label=${this.pc.t("showStats")}
                        title=${this.pc.t("showStats")}
                        @click=${() => this.dispatchEvent(new CustomEvent("fw-stats-toggle", {
                bubbles: true,
                composed: true,
            }))}
                      >
                        ${statsIcon(16)}
                      </button>
                    </div>
                  `
            : nothing}
              <div class="fw-control-group fw-settings-anchor">
                <button
                  type="button"
                  class=${classMap({
            "fw-btn-flush": true,
            group: true,
            "fw-btn-flush--active": this._settingsOpen,
        })}
                  aria-label=${this.pc.t("settings")}
                  title=${this.pc.t("settings")}
                  ?disabled=${disabled}
                  @click=${(event) => {
            event.stopPropagation();
            if (disabled) {
                return;
            }
            this._settingsOpen = !this._settingsOpen;
        }}
                >
                  <span class="transition-transform group-hover:rotate-90"
                    >${settingsIcon(16)}</span
                  >
                </button>

                <fw-settings-menu
                  .pc=${this.pc}
                  .open=${this._settingsOpen}
                  .playbackMode=${this.playbackMode}
                  .isContentLive=${this.isContentLive}
                  .activeLocale=${this.activeLocale}
                  @click=${(event) => event.stopPropagation()}
                  @fw-close=${() => {
            this._settingsOpen = false;
        }}
                  @fw-mode-change=${this._handleModeChange}
                ></fw-settings-menu>
              </div>

              <div class="fw-control-group">
                <button
                  type="button"
                  class="fw-btn-flush"
                  ?disabled=${disabled}
                  aria-label=${state.isFullscreen
            ? this.pc.t("exitFullscreen")
            : this.pc.t("fullscreen")}
                  @click=${() => this.pc.toggleFullscreen()}
                >
                  ${state.isFullscreen ? fullscreenExitIcon(16) : fullscreenIcon(16)}
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;
    }
};
FwPlayerControls.styles = [
    sharedStyles,
    utilityStyles,
    css `
      :host {
        display: contents;
      }

      .fw-settings-anchor {
        position: relative;
      }
    `,
];
__decorate([
    property({ attribute: false })
], FwPlayerControls.prototype, "pc", void 0);
__decorate([
    property({ type: String })
], FwPlayerControls.prototype, "playbackMode", void 0);
__decorate([
    property({ type: Boolean, attribute: "is-content-live" })
], FwPlayerControls.prototype, "isContentLive", void 0);
__decorate([
    property({ type: Boolean, attribute: "dev-mode" })
], FwPlayerControls.prototype, "devMode", void 0);
__decorate([
    property({ type: Boolean, attribute: "show-stats-button" })
], FwPlayerControls.prototype, "showStatsButton", void 0);
__decorate([
    property({ type: Boolean, attribute: "is-stats-open" })
], FwPlayerControls.prototype, "isStatsOpen", void 0);
__decorate([
    property({ attribute: "active-locale" })
], FwPlayerControls.prototype, "activeLocale", void 0);
__decorate([
    state()
], FwPlayerControls.prototype, "_settingsOpen", void 0);
__decorate([
    state()
], FwPlayerControls.prototype, "_isNearLiveState", void 0);
__decorate([
    state()
], FwPlayerControls.prototype, "_buffered", void 0);
__decorate([
    query(".fw-settings-anchor")
], FwPlayerControls.prototype, "_settingsAnchorEl", void 0);
FwPlayerControls = __decorate([
    customElement("fw-player-controls")
], FwPlayerControls);

export { FwPlayerControls };
//# sourceMappingURL=fw-player-controls.js.map
