import { PRIORITY_CAPTIONS } from "../preview/FrameController.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { EFTemporal, flushStartTimeMsCache } from "./EFTemporal.js";
import { EFSourceMixin } from "./EFSourceMixin.js";
import { FetchMixin } from "./FetchMixin.js";
import { AsyncValue } from "./EFMedia.js";
import { EFAudio } from "./EFAudio.js";
import { CrossUpdateController } from "./CrossUpdateController.js";
import { EFVideo } from "./EFVideo.js";
import { flushSequenceDurationCache } from "./EFTimegroup.js";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";

//#region src/elements/EFCaptions.ts
const stopWords = new Set([
	"",
	".",
	"!",
	"?",
	","
]);
let EFCaptionsActiveWord = class EFCaptionsActiveWord$1 extends LitElement {
	#wordText = "";
	#wordIndex = 0;
	set wordText(text) {
		this.#wordText = text;
		if (!text || stopWords.has(text)) {
			this.hidden = true;
			this.textContent = "";
		} else {
			this.hidden = false;
			this.textContent = text + " ";
		}
	}
	get wordText() {
		return this.#wordText;
	}
	set wordIndex(index) {
		this.#wordIndex = index;
		const seedValue = index * 9007 % 233 / 233;
		this.style.setProperty("--ef-word-seed", seedValue.toString());
	}
	get wordIndex() {
		return this.#wordIndex;
	}
};
EFCaptionsActiveWord = __decorate([customElement("ef-captions-active-word")], EFCaptionsActiveWord);
let EFCaptionsSegment = class EFCaptionsSegment$1 extends LitElement {
	#segmentText = "";
	set segmentText(text) {
		this.#segmentText = text;
		if (!text || stopWords.has(text)) {
			this.hidden = true;
			this.textContent = "";
		} else {
			this.hidden = false;
			this.textContent = text;
		}
	}
	get segmentText() {
		return this.#segmentText;
	}
};
EFCaptionsSegment = __decorate([customElement("ef-captions-segment")], EFCaptionsSegment);
let EFCaptionsBeforeActiveWord = class EFCaptionsBeforeActiveWord$1 extends EFCaptionsSegment {
	set segmentText(text) {
		const hasActiveWord = (this.closest("ef-captions")?.querySelector("ef-captions-active-word"))?.wordText;
		const finalText = text && hasActiveWord ? text + " " : text;
		if (!finalText || stopWords.has(finalText)) {
			this.hidden = true;
			this.textContent = "";
		} else {
			this.hidden = false;
			this.textContent = finalText;
		}
	}
};
EFCaptionsBeforeActiveWord = __decorate([customElement("ef-captions-before-active-word")], EFCaptionsBeforeActiveWord);
let EFCaptionsAfterActiveWord = class EFCaptionsAfterActiveWord$1 extends EFCaptionsSegment {
	set segmentText(text) {
		const finalText = text;
		if (!finalText || stopWords.has(finalText)) {
			this.hidden = true;
			this.textContent = "";
		} else {
			this.hidden = false;
			this.textContent = finalText;
		}
	}
};
EFCaptionsAfterActiveWord = __decorate([customElement("ef-captions-after-active-word")], EFCaptionsAfterActiveWord);
let EFCaptions = class EFCaptions$1 extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), { assetType: "caption_files" }) {
	constructor(..._args) {
		super(..._args);
		this.targetSelector = "";
		this.wordStyle = "";
		this.captionsSrc = "";
		this.captionsData = null;
		this.captionsScript = "";
		this.activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
		this.segmentContainers = this.getElementsByTagName("ef-captions-segment");
		this.beforeActiveWordContainers = this.getElementsByTagName("ef-captions-before-active-word");
		this.afterActiveWordContainers = this.getElementsByTagName("ef-captions-after-active-word");
		this.unifiedCaptionsDataTask = new AsyncValue();
	}
	static {
		this.styles = [css`
      :host {
        display: block;
        white-space: normal;
        line-height: 1;
        gap: 0;
      }
      ::slotted(*) {
        display: inline;
        margin: 0;
        padding: 0;
      }
    `];
	}
	set target(value) {
		this.targetSelector = value;
	}
	#cachedIntrinsicDurationMs = null;
	render() {
		return html`<slot></slot>`;
	}
	transcriptionsPath() {
		if (!this.targetElement) return null;
		const fileId = this.targetElement.fileId ?? this.targetElement.assetId;
		if (fileId) return `${this.apiHost}/api/v1/files/${fileId}/transcription`;
		return null;
	}
	captionsPath() {
		if (!this.targetElement) return null;
		const fileId = this.targetElement.fileId ?? this.targetElement.assetId;
		if (fileId) return `${this.apiHost}/api/v1/files/${fileId}`;
		const targetSrc = this.targetElement.src;
		let normalizedSrc = targetSrc.startsWith("/") ? targetSrc.slice(1) : targetSrc;
		normalizedSrc = normalizedSrc.replace(/^\/+/, "");
		return `/api/v1/assets/captions?src=${encodeURIComponent(normalizedSrc)}`;
	}
	#captionsDataLoaded = false;
	#captionsDataPromise = null;
	#captionsDataValue = null;
	#transcriptionData = null;
	shouldAutoReady() {
		return false;
	}
	/**
	* Load captions data from all possible sources
	*/
	async loadCaptionsData(signal) {
		if (this.#captionsDataLoaded && this.#captionsDataValue) {
			this.setContentReadyState("ready");
			return this.#captionsDataValue;
		}
		if (this.#captionsDataPromise) return this.#captionsDataPromise;
		this.unifiedCaptionsDataTask.startPending();
		this.setContentReadyState("loading");
		this.#captionsDataPromise = this.#doLoadCaptionsData(signal);
		try {
			this.#captionsDataValue = await this.#captionsDataPromise;
			this.#captionsDataLoaded = true;
			if (this.#captionsDataValue) this.unifiedCaptionsDataTask.setValue(this.#captionsDataValue);
			this.setContentReadyState("ready");
			return this.#captionsDataValue;
		} catch (error) {
			if (error instanceof DOMException && error.name === "AbortError") throw error;
			console.error("Failed to load captions data:", error);
			this.setContentReadyState("error");
			return null;
		} finally {
			this.#captionsDataPromise = null;
		}
	}
	async #doLoadCaptionsData(signal) {
		if (this.captionsData) return this.captionsData;
		if (this.captionsScript) {
			const scriptElement = this.#findElementById(this.captionsScript);
			if (scriptElement?.textContent) try {
				return JSON.parse(scriptElement.textContent);
			} catch (error) {
				console.error(`Failed to parse captions from script #${this.captionsScript}:`, error);
			}
		}
		if (this.captionsSrc) try {
			return await (await this.fetch(this.captionsSrc, { signal })).json();
		} catch (error) {
			if (error instanceof DOMException && error.name === "AbortError") throw error;
			console.error(`Failed to load captions from ${this.captionsSrc}:`, error);
		}
		if (this.targetElement && !this.hasCustomCaptionsData) {
			const transcriptionPath = this.transcriptionsPath();
			if (transcriptionPath) try {
				this.#transcriptionData = await (await this.fetch(transcriptionPath, { signal })).json();
				signal?.throwIfAborted();
				if (this.#transcriptionData) return this.#loadTranscriptionFragment(signal);
			} catch (error) {
				if (error instanceof DOMException && error.name === "AbortError") throw error;
			}
		}
		return null;
	}
	async #loadTranscriptionFragment(signal) {
		if (!this.#transcriptionData) return null;
		const fragmentIndex = Math.floor(this.ownCurrentTimeMs / this.#transcriptionData.work_slice_ms);
		const fragmentPath = `${this.apiHost}/api/v1/transcriptions/${this.#transcriptionData.id}/fragments/${fragmentIndex}`;
		try {
			return await (await this.fetch(fragmentPath, { signal })).json();
		} catch (error) {
			if (error instanceof DOMException && error.name === "AbortError") throw error;
			console.error("Failed to load transcription fragment:", error);
			return null;
		}
	}
	/**
	* Query readiness state for a given time.
	* @implements FrameRenderable
	*/
	getFrameState(_timeMs) {
		const hasData = this.#captionsDataLoaded && this.#captionsDataValue !== null;
		return {
			needsPreparation: !hasData,
			isReady: hasData,
			priority: PRIORITY_CAPTIONS
		};
	}
	/**
	* Async preparation - waits for captions data to load.
	* @implements FrameRenderable
	*/
	async prepareFrame(_timeMs, signal) {
		await this.loadCaptionsData(signal);
		signal.throwIfAborted();
	}
	/**
	* Synchronous render - updates caption text containers.
	* Sets textContent directly on child elements (light DOM).
	* @implements FrameRenderable
	*/
	renderFrame(_timeMs) {
		this.updateTextContainers();
	}
	#rootTimegroupUpdateController;
	connectedCallback() {
		super.connectedCallback();
		this.loadCaptionsData().catch(() => {});
		const target = this.targetSelector ? this.#findElementById(this.targetSelector) : null;
		if (target && (target instanceof EFAudio || target instanceof EFVideo)) new CrossUpdateController(target, this);
		else if (this.hasCustomCaptionsData && this.rootTimegroup) new CrossUpdateController(this.rootTimegroup, this);
		if (this.rootTimegroup) {
			this.#rootTimegroupUpdateController = {
				hostUpdated: () => {
					Promise.resolve().then(() => {
						this.updateTextContainers();
					});
				},
				hostDisconnected: () => {
					this.#rootTimegroupUpdateController = void 0;
				}
			};
			this.rootTimegroup.addController(this.#rootTimegroupUpdateController);
		}
		new MutationObserver(() => {
			if (this.style.display === "none") {
				this.style.removeProperty("display");
				this.style.opacity = "0";
				this.style.pointerEvents = "none";
			} else if (!this.style.display || this.style.display === "") {
				this.style.removeProperty("opacity");
				this.style.removeProperty("pointer-events");
			}
		}).observe(this, {
			attributes: true,
			attributeFilter: ["style"]
		});
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		if (this.#rootTimegroupUpdateController && this.rootTimegroup) {
			this.rootTimegroup.removeController(this.#rootTimegroupUpdateController);
			this.#rootTimegroupUpdateController = void 0;
		}
	}
	updated(changedProperties) {
		if (this.rootTimegroup && !this.#rootTimegroupUpdateController) {
			this.#rootTimegroupUpdateController = {
				hostUpdated: () => {
					Promise.resolve().then(() => {
						this.updateTextContainers();
					});
				},
				hostDisconnected: () => {
					this.#rootTimegroupUpdateController = void 0;
				}
			};
			this.rootTimegroup.addController(this.#rootTimegroupUpdateController);
		}
		if (changedProperties.has("rootTimegroup") && this.#rootTimegroupUpdateController) {
			const oldRootTimegroup = changedProperties.get("rootTimegroup");
			if (oldRootTimegroup && oldRootTimegroup !== this.rootTimegroup) {
				oldRootTimegroup.removeController(this.#rootTimegroupUpdateController);
				this.#rootTimegroupUpdateController = void 0;
			}
		}
		this.updateTextContainers();
		if (changedProperties.has("captionsData") || changedProperties.has("captionsSrc") || changedProperties.has("captionsScript")) {
			this.emitContentChange("source");
			this.#cachedIntrinsicDurationMs = null;
			this.#captionsDataLoaded = false;
			this.#captionsDataValue = null;
			this.loadCaptionsData().catch(() => {});
			this.requestUpdate("intrinsicDurationMs");
			flushSequenceDurationCache();
			flushStartTimeMsCache();
			if (this.parentTimegroup) {
				this.parentTimegroup.requestUpdate("durationMs");
				this.parentTimegroup.requestUpdate("currentTime");
			}
		}
		if (changedProperties.has("ownCurrentTimeMs")) this.updateTextContainers();
	}
	updateTextContainers() {
		const captionsData = this.#captionsDataValue;
		if (!captionsData) return;
		let currentTimeMs = this.ownCurrentTimeMs;
		if (this.hasCustomCaptionsData && this.parentTimegroup) {
			const videoElement = Array.from(this.parentTimegroup.children).find((child) => child instanceof EFVideo);
			if (videoElement) {
				const sourceInMs = videoElement.sourceInMs ?? 0;
				currentTimeMs = videoElement.currentSourceTimeMs - sourceInMs;
				currentTimeMs = Math.max(0, Math.min(currentTimeMs, this.durationMs));
			}
		}
		const currentTimeSec = currentTimeMs / 1e3;
		const currentWord = captionsData.word_segments.find((word) => currentTimeSec >= word.start && currentTimeSec < word.end);
		const currentSegment = captionsData.segments.find((segment) => currentTimeSec >= segment.start && currentTimeSec < segment.end);
		for (const wordContainer of this.activeWordContainers) if (currentWord) {
			const wordIndex = captionsData.word_segments.findIndex((w) => w.start === currentWord.start && w.end === currentWord.end && w.text === currentWord.text);
			wordContainer.wordIndex = wordIndex >= 0 ? wordIndex : 0;
			wordContainer.wordText = currentWord.text;
		} else wordContainer.wordText = "";
		for (const segmentContainer of this.segmentContainers) if (currentSegment) segmentContainer.segmentText = currentSegment.text;
		else segmentContainer.segmentText = "";
		if (currentWord && currentSegment) {
			const segmentWords = captionsData.word_segments.filter((word) => word.start >= currentSegment.start && word.end <= currentSegment.end);
			const currentWordIndex = segmentWords.findIndex((word) => word.start === currentWord.start && word.end === currentWord.end);
			if (currentWordIndex !== -1) {
				const beforeWords = segmentWords.slice(0, currentWordIndex).map((w) => w.text.trim()).join(" ");
				const afterWords = segmentWords.slice(currentWordIndex + 1).map((w) => w.text.trim()).join(" ");
				for (const container of this.beforeActiveWordContainers) container.segmentText = beforeWords;
				for (const container of this.afterActiveWordContainers) container.segmentText = afterWords;
			}
		} else if (currentSegment) {
			const segmentWords = captionsData.word_segments.filter((word) => word.start >= currentSegment.start && word.end <= currentSegment.end);
			const firstWord = segmentWords[0];
			if (firstWord && currentTimeSec < firstWord.start) {
				const allWords = segmentWords.map((w) => w.text.trim()).join(" ");
				for (const container of this.beforeActiveWordContainers) container.segmentText = "";
				for (const container of this.afterActiveWordContainers) container.segmentText = allWords;
			} else {
				const allCompletedWords = segmentWords.map((w) => w.text.trim()).join(" ");
				for (const container of this.beforeActiveWordContainers) container.segmentText = allCompletedWords;
				for (const container of this.afterActiveWordContainers) container.segmentText = "";
			}
		} else {
			for (const container of this.beforeActiveWordContainers) container.segmentText = "";
			for (const container of this.afterActiveWordContainers) container.segmentText = "";
		}
	}
	get targetElement() {
		const target = this.targetSelector ? this.#findElementById(this.targetSelector) : null;
		if (target instanceof EFAudio || target instanceof EFVideo) return target;
		if (this.hasCustomCaptionsData) return null;
		return null;
	}
	get hasCustomCaptionsData() {
		return !!(this.captionsData || this.captionsSrc || this.captionsScript);
	}
	/**
	* Find element by ID, searching within clone scope first to avoid cross-boundary references.
	* @private
	*/
	#findElementById(id) {
		const container = this.closest("ef-timegroup, ef-configuration");
		if (container) {
			const result = container.querySelector(`#${CSS.escape(id)}`);
			if (result) return result;
		}
		return document.getElementById(id);
	}
	get intrinsicDurationMs() {
		if (this.#cachedIntrinsicDurationMs !== null) return this.#cachedIntrinsicDurationMs;
		let captionsData = null;
		if (this.captionsData) captionsData = this.captionsData;
		else if (this.captionsScript) {
			const scriptElement = this.#findElementById(this.captionsScript);
			if (scriptElement?.textContent) try {
				captionsData = JSON.parse(scriptElement.textContent);
			} catch {}
		} else if (this.#captionsDataValue) captionsData = this.#captionsDataValue;
		if (!captionsData) {
			if (!this.captionsData && !this.captionsScript && !this.captionsSrc) this.#cachedIntrinsicDurationMs = void 0;
			return;
		}
		let result;
		if (captionsData.segments.length === 0 && captionsData.word_segments.length === 0) result = 0;
		else {
			const maxSegmentEnd = captionsData.segments.length > 0 ? captionsData.segments.reduce((max, s) => s.end > max ? s.end : max, 0) : 0;
			const maxWordEnd = captionsData.word_segments.length > 0 ? captionsData.word_segments.reduce((max, w) => w.end > max ? w.end : max, 0) : 0;
			result = Math.max(maxSegmentEnd, maxWordEnd) * 1e3;
		}
		this.#cachedIntrinsicDurationMs = result;
		return result;
	}
	get hasOwnDuration() {
		return !!(this.captionsData || this.captionsScript || this.#captionsDataValue);
	}
};
__decorate([property({
	type: String,
	attribute: "target",
	reflect: true
})], EFCaptions.prototype, "targetSelector", void 0);
__decorate([property({ attribute: "word-style" })], EFCaptions.prototype, "wordStyle", void 0);
__decorate([property({
	type: String,
	attribute: "captions-src",
	reflect: true
})], EFCaptions.prototype, "captionsSrc", void 0);
__decorate([property({
	type: Object,
	attribute: false
})], EFCaptions.prototype, "captionsData", void 0);
__decorate([property({
	type: String,
	attribute: "captions-script",
	reflect: true
})], EFCaptions.prototype, "captionsScript", void 0);
EFCaptions = __decorate([customElement("ef-captions")], EFCaptions);

//#endregion
export { EFCaptions, EFCaptionsActiveWord, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsSegment };
//# sourceMappingURL=EFCaptions.js.map