import { updateAnimations } from "./updateAnimations.js";
import { durationConverter } from "./durationConverter.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { EFTemporal } from "./EFTemporal.js";
import { evaluateEasing } from "./easingUtils.js";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";

//#region src/elements/EFText.ts
const animationPropsToPropagate = [
	"animation-name",
	"animation-duration",
	"animation-timing-function",
	"animation-fill-mode",
	"animation-iteration-count",
	"animation-direction"
];
let EFText = class EFText$1 extends EFTemporal(LitElement) {
	constructor(..._args) {
		super(..._args);
		this.split = "word";
		this.easing = "linear";
		this.lastTextContent = "";
		this._textContent = null;
		this._templateElement = null;
		this._segmentsReadyResolvers = [];
	}
	static {
		this.styles = [css`
      :host {
        display: inline;
      }
      :host([split="line"]) {
        display: inline-block;
      }
    `];
	}
	validateSplit(value) {
		if (value === "line" || value === "word" || value === "char") return value;
		console.warn(`Invalid split value "${value}". Must be "line", "word", or "char". Defaulting to "word".`);
		return "word";
	}
	validateStagger(value) {
		if (value === void 0) return void 0;
		if (value < 0) {
			console.warn(`Invalid stagger value ${value}ms. Must be >= 0. Using 0.`);
			return 0;
		}
		return value;
	}
	#segmentsInitialized = false;
	#lastPropagatedAnimation = "";
	render() {
		return html`<slot></slot>`;
	}
	set textContent(value) {
		const newValue = value || "";
		if (this._textContent !== newValue) {
			this._textContent = newValue;
			if (!this._templateElement && this.isConnected) this._templateElement = this.querySelector("template");
			const textNodes = [];
			for (const node of Array.from(this.childNodes)) if (node.nodeType === Node.TEXT_NODE) textNodes.push(node);
			for (const node of textNodes) node.remove();
			if (newValue) {
				const textNode = document.createTextNode(newValue);
				this.appendChild(textNode);
			}
			if (this.isConnected) {
				this.emitContentChange("content");
				this.splitText();
			}
		}
	}
	get textContent() {
		if (this._textContent === null) return this.getTextContent();
		return this._textContent;
	}
	/**
	* Get all ef-text-segment elements directly
	* @public
	*/
	get segments() {
		return Array.from(this.querySelectorAll("ef-text-segment[data-segment-created]"));
	}
	/**
	* Returns a promise that resolves when segments are ready (created and connected)
	* Use this to wait for segments after setting textContent or other properties
	* @public
	*/
	async whenSegmentsReady() {
		await this.updateComplete;
		const text = this._textContent !== null ? this._textContent : this.getTextContent();
		if (!text || text.trim().length === 0) return [];
		if (this.#segmentsInitialized && this.segments.length > 0) {
			await Promise.all(this.segments.map((seg) => seg.updateComplete));
			return this.segments;
		}
		let segments = this.segments;
		if (segments.length > 0) {
			await Promise.all(segments.map((seg) => seg.updateComplete));
			this.#segmentsInitialized = true;
			return segments;
		}
		return new Promise((resolve, reject) => {
			const timeout = setTimeout(() => {
				const index = this._segmentsReadyResolvers.indexOf(resolveWithSegments);
				if (index > -1) this._segmentsReadyResolvers.splice(index, 1);
				reject(/* @__PURE__ */ new Error("Timeout waiting for text segments to be created"));
			}, 5e3);
			const resolveWithSegments = () => {
				clearTimeout(timeout);
				const segments$1 = this.segments;
				Promise.all(segments$1.map((seg) => seg.updateComplete)).then(() => {
					this.#segmentsInitialized = true;
					resolve(segments$1);
				});
			};
			this._segmentsReadyResolvers.push(resolveWithSegments);
			if (segments.length === 0 && this.isConnected) this.splitText();
		});
	}
	connectedCallback() {
		super.connectedCallback();
		this._templateElement = this.querySelector("template");
		if (this._textContent === null) {
			this._textContent = this.getTextContent();
			this.lastTextContent = this._textContent;
		}
		requestAnimationFrame(() => {
			this.setupMutationObserver();
			this.setupAnimationObserver();
			this.splitText();
		});
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		this.mutationObserver?.disconnect();
		this.animationObserver?.disconnect();
	}
	updated(changedProperties) {
		if (changedProperties.has("split") || changedProperties.has("staggerMs") || changedProperties.has("easing") || changedProperties.has("durationMs")) {
			this.emitContentChange("content");
			this.splitText();
		}
	}
	setupMutationObserver() {
		this.mutationObserver = new MutationObserver(() => {
			const currentText = this._textContent || this.getTextContent();
			if (currentText !== this.lastTextContent) {
				this._textContent = currentText;
				this.lastTextContent = currentText;
				this.splitText();
			}
		});
		this.mutationObserver.observe(this, {
			childList: true,
			characterData: true,
			subtree: true
		});
	}
	setupAnimationObserver() {
		this.animationObserver = new MutationObserver(() => {
			this.propagateAnimationToSegments();
		});
		this.animationObserver.observe(this, {
			attributes: true,
			attributeFilter: ["class", "style"]
		});
	}
	propagateAnimationToSegments() {
		const segments = this.segments;
		if (segments.length === 0) return;
		const computed = window.getComputedStyle(this);
		const animationName = computed.animationName;
		const fingerprint = animationPropsToPropagate.map((prop) => computed.getPropertyValue(prop)).join("|");
		if (fingerprint === this.#lastPropagatedAnimation) return;
		this.#lastPropagatedAnimation = fingerprint;
		const hasAnimation = animationName && animationName !== "none";
		const isLineMode = this.split === "line";
		for (const segment of segments) if (hasAnimation) {
			const isWhitespace = /^\s+$/.test(segment.segmentText || "");
			if (!isLineMode && !isWhitespace) segment.setAttribute("data-animated", "");
			for (const prop of animationPropsToPropagate) segment.style.setProperty(prop, computed.getPropertyValue(prop));
		} else {
			segment.removeAttribute("data-animated");
			for (const prop of animationPropsToPropagate) segment.style.removeProperty(prop);
		}
	}
	getTextContent() {
		let text = "";
		for (const node of Array.from(this.childNodes)) if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";
		else if (node.nodeType === Node.ELEMENT_NODE) {
			const element = node;
			if (element.tagName === "EF-TEXT-SEGMENT") continue;
			if (element.tagName === "TEMPLATE") continue;
			text += element.textContent || "";
		}
		return text;
	}
	splitText() {
		const validatedSplit = this.validateSplit(this.split);
		if (validatedSplit !== this.split) {
			this.split = validatedSplit;
			return;
		}
		const validatedStagger = this.validateStagger(this.staggerMs);
		if (validatedStagger !== this.staggerMs) {
			this.staggerMs = validatedStagger;
			return;
		}
		const text = this._textContent !== null ? this._textContent : this.getTextContent();
		const trimmedText = text.trim();
		const textStartOffset = text.indexOf(trimmedText);
		if (this.#segmentsInitialized && this.segments.length > 0 && this.lastTextContent === text) return;
		if (!text || trimmedText.length === 0) {
			const existingSegments = Array.from(this.querySelectorAll("ef-text-segment"));
			for (const segment of existingSegments) segment.remove();
			const textNodes = [];
			for (const node of Array.from(this.childNodes)) if (node.nodeType === Node.TEXT_NODE) textNodes.push(node);
			for (const node of textNodes) node.remove();
			this.lastTextContent = "";
			this._segmentsReadyResolvers.forEach((resolve) => {
				resolve();
			});
			this._segmentsReadyResolvers = [];
			this.#segmentsInitialized = false;
			return;
		}
		this.#segmentsInitialized = false;
		const segments = this.splitTextIntoSegments(text);
		const durationMs = this.durationMs || 1e3;
		let wordBoundaries = null;
		if (this.split === "char") wordBoundaries = this.detectWordBoundaries(text);
		const fragment = document.createDocumentFragment();
		if (!this._templateElement) this._templateElement = this.querySelector("template");
		const templateContent = this._templateElement?.content;
		const templateSegments = templateContent ? Array.from(templateContent.querySelectorAll("ef-text-segment")) : [];
		const useTemplate = templateSegments.length > 0;
		const segmentsPerTextSegment = useTemplate ? templateSegments.length : 1;
		let currentWordIndex = null;
		let currentWordSpan = null;
		let charIndex = 0;
		const wordSegmentCount = (this.split === "word" ? segments.filter((seg) => !/^\s+$/.test(seg)) : segments).length;
		let wordStaggerIndex = 0;
		segments.forEach((segmentText, textIndex) => {
			let staggerOffset;
			if (this.staggerMs !== void 0) {
				const isWhitespace = /^\s+$/.test(segmentText);
				let wordIndexForStagger;
				if (this.split === "word" && isWhitespace) wordIndexForStagger = Math.max(0, wordStaggerIndex - 1);
				else if (this.split === "word") {
					wordIndexForStagger = wordStaggerIndex;
					wordStaggerIndex++;
				} else wordIndexForStagger = textIndex;
				const normalizedProgress = wordSegmentCount > 1 ? wordIndexForStagger / (wordSegmentCount - 1) : 0;
				staggerOffset = evaluateEasing(this.easing, normalizedProgress) * ((wordSegmentCount - 1) * this.staggerMs);
			}
			if (useTemplate && templateContent) {
				const clonedContent = templateContent.cloneNode(true);
				Array.from(clonedContent.querySelectorAll("ef-text-segment")).forEach((segment, templateIndex) => {
					segment.segmentText = segmentText;
					segment.segmentIndex = textIndex * segmentsPerTextSegment + templateIndex;
					segment.segmentStartMs = 0;
					segment.segmentEndMs = durationMs;
					segment.staggerOffsetMs = staggerOffset ?? 0;
					if (this.split === "line") segment.setAttribute("data-line-segment", "true");
					segment.setAttribute("data-segment-created", "true");
					if (this.split === "char" && wordBoundaries) {
						const originalCharIndex = textStartOffset + charIndex;
						const wordIndex = wordBoundaries.get(originalCharIndex);
						if (wordIndex !== void 0) {
							if (wordIndex !== currentWordIndex) {
								if (currentWordSpan) fragment.appendChild(currentWordSpan);
								currentWordIndex = wordIndex;
								currentWordSpan = document.createElement("span");
								currentWordSpan.style.whiteSpace = "nowrap";
							}
							if (currentWordSpan) currentWordSpan.appendChild(segment);
							else fragment.appendChild(segment);
						} else {
							if (currentWordSpan) {
								fragment.appendChild(currentWordSpan);
								currentWordSpan = null;
								currentWordIndex = null;
							}
							fragment.appendChild(segment);
						}
						charIndex += segmentText.length;
					} else fragment.appendChild(segment);
				});
			} else {
				const segment = document.createElement("ef-text-segment");
				segment.segmentText = segmentText;
				segment.segmentIndex = textIndex;
				segment.segmentStartMs = 0;
				segment.segmentEndMs = durationMs;
				segment.staggerOffsetMs = staggerOffset ?? 0;
				if (this.split === "line") segment.setAttribute("data-line-segment", "true");
				segment.setAttribute("data-segment-created", "true");
				if (this.split === "char" && wordBoundaries) {
					const originalCharIndex = textStartOffset + charIndex;
					const wordIndex = wordBoundaries.get(originalCharIndex);
					if (wordIndex !== void 0) {
						if (wordIndex !== currentWordIndex) {
							if (currentWordSpan) fragment.appendChild(currentWordSpan);
							currentWordIndex = wordIndex;
							currentWordSpan = document.createElement("span");
							currentWordSpan.style.whiteSpace = "nowrap";
						}
						if (currentWordSpan) currentWordSpan.appendChild(segment);
						else fragment.appendChild(segment);
					} else {
						if (currentWordSpan) {
							fragment.appendChild(currentWordSpan);
							currentWordSpan = null;
							currentWordIndex = null;
						}
						fragment.appendChild(segment);
					}
					charIndex += segmentText.length;
				} else fragment.appendChild(segment);
			}
		});
		if (this.split === "char" && currentWordSpan) fragment.appendChild(currentWordSpan);
		const templateToPreserve = this._templateElement;
		while (this.firstChild) {
			const child = this.firstChild;
			if (child === templateToPreserve) {
				this.removeChild(child);
				continue;
			}
			this.removeChild(child);
		}
		this.appendChild(fragment);
		if (templateToPreserve) this.appendChild(templateToPreserve);
		this.#lastPropagatedAnimation = "";
		this.propagateAnimationToSegments();
		const segmentElements = this.segments;
		Promise.all(segmentElements.map((seg) => seg.updateComplete)).then(() => {
			updateAnimations(this.rootTimegroup || this);
		});
		this.lastTextContent = text;
		this._textContent = text;
		this.#segmentsInitialized = true;
		this._segmentsReadyResolvers.forEach((resolve) => {
			resolve();
		});
		this._segmentsReadyResolvers = [];
	}
	detectWordBoundaries(text) {
		const boundaries = /* @__PURE__ */ new Map();
		const trimmedText = text.trim();
		if (!trimmedText) return boundaries;
		const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
		const segments = Array.from(segmenter.segment(trimmedText));
		const textStart = text.indexOf(trimmedText);
		let wordIndex = 0;
		for (const seg of segments) if (seg.isWordLike) {
			for (let i = 0; i < seg.segment.length; i++) {
				const charPos = textStart + seg.index + i;
				boundaries.set(charPos, wordIndex);
			}
			wordIndex++;
		}
		return boundaries;
	}
	splitTextIntoSegments(text) {
		const trimmedText = text.trim();
		if (!trimmedText) return [];
		switch (this.split) {
			case "line": return trimmedText.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
			case "word": {
				const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
				const segments = Array.from(segmenter.segment(trimmedText));
				const result = [];
				for (const seg of segments) if (seg.isWordLike) result.push(seg.segment);
				else if (/^\s+$/.test(seg.segment)) result.push(seg.segment);
				else if (result.length > 0) {
					const lastItem = result[result.length - 1];
					if (lastItem && !/^\s+$/.test(lastItem)) result[result.length - 1] = lastItem + seg.segment;
					else result.push(seg.segment);
				} else result.push(seg.segment);
				return result;
			}
			case "char": {
				const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
				return Array.from(segmenter.segment(trimmedText)).map((seg) => seg.segment);
			}
			default: return [trimmedText];
		}
	}
	get intrinsicDurationMs() {
		if (this.hasExplicitDuration) return;
		if (this.parentTimegroup) {
			const mode = this.parentTimegroup.mode;
			if (mode === "fixed" || mode === "fit") return;
		}
		const text = this._textContent !== null ? this._textContent : this.getTextContent();
		if (!text || text.trim().length === 0) return 0;
		const segments = this.splitTextIntoSegments(text);
		return (this.split === "word" ? segments.filter((seg) => !/^\s+$/.test(seg)).length || 1 : segments.length || 1) * 1e3;
	}
};
__decorate([property({
	type: String,
	reflect: true
})], EFText.prototype, "split", void 0);
__decorate([property({
	type: Number,
	attribute: "stagger",
	converter: durationConverter
})], EFText.prototype, "staggerMs", void 0);
__decorate([property({
	type: String,
	reflect: true
})], EFText.prototype, "easing", void 0);
EFText = __decorate([customElement("ef-text")], EFText);

//#endregion
export { EFText };
//# sourceMappingURL=EFText.js.map