import { durationContext } from "./durationContext.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { DEFAULT_PIXELS_PER_MS, timelineStateContext } from "./timeline/timelineStateContext.js";
import { consume } from "@lit/context";
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";
import { createRef, ref } from "lit/directives/ref.js";

//#region src/gui/EFTimelineRuler.ts
const MIN_LABEL_SPACING_PX = 80;
const MIN_FRAME_SPACING_PX = 5;
/** Maximum canvas width for ruler virtualization */
const MAX_RULER_CANVAS_WIDTH = 2e3;
/** Buffer pixels on each side of viewport for smooth scrolling */
const RULER_CANVAS_BUFFER = 200;
/**
* Quantize a time value to the nearest frame boundary.
* This ensures frame markers align perfectly with playhead position.
*/
function quantizeToFrameTimeMs(timeMs, fps) {
	if (!fps || fps <= 0) return timeMs;
	const frameDurationS = 1 / fps;
	const timeSeconds = timeMs / 1e3;
	return Math.round(timeSeconds / frameDurationS) * frameDurationS * 1e3;
}
/**
* Calculate the duration of a single frame in milliseconds.
*/
function calculateFrameIntervalMs(fps) {
	if (fps <= 0) return 1e3 / 30;
	return 1e3 / fps;
}
/**
* Calculate pixels per frame given frame interval and zoom scale.
* @param frameIntervalMs Duration of one frame in ms
* @param pixelsPerMs Current zoom level (pixels per millisecond)
*/
function calculatePixelsPerFrame(frameIntervalMs, pixelsPerMs) {
	return frameIntervalMs * pixelsPerMs;
}
/**
* Determine if frame markers should be visible at the current zoom level.
* Frame markers appear when each frame is at least MIN_FRAME_SPACING_PX wide.
*/
function shouldShowFrameMarkers(pixelsPerFrame, minSpacing = MIN_FRAME_SPACING_PX) {
	return pixelsPerFrame >= minSpacing;
}
let EFTimelineRuler = class EFTimelineRuler$1 extends LitElement {
	constructor(..._args) {
		super(..._args);
		this.durationMs = 0;
		this.contextDurationMs = 0;
		this.fps = 30;
		this.contentWidth = 0;
		this.containerRef = createRef();
		this.canvasRef = createRef();
		this.viewportWidth = 0;
		this._canvasViewportLeft = 0;
		this._canvasViewportWidth = 0;
		this._lastRenderedScrollLeft = -1;
		this._lastRenderedViewportWidth = 0;
	}
	static {
		this.styles = [css`
      :host {
        display: block;
        position: relative;
        height: 100%;
        pointer-events: none;
      }
      
      .ruler-container {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      
      .canvas-viewport {
        position: absolute;
        top: 0;
        height: 100%;
        /* left and width set via inline styles for virtualization */
      }
      
      canvas {
        display: block;
        position: absolute;
        top: 0;
        left: 0;
        pointer-events: none;
      }
      
      .label {
        position: absolute;
        top: 50%;
        font-size: 10px;
        color: var(--ef-color-text-muted);
        font-family: ui-monospace, monospace;
        white-space: nowrap;
        pointer-events: none;
        user-select: none;
      }
    `];
	}
	get effectiveDurationMs() {
		return this.durationMs || this.contextDurationMs || 0;
	}
	get pixelsPerMs() {
		return this.timelineState?.pixelsPerMs ?? DEFAULT_PIXELS_PER_MS;
	}
	get scrollLeft() {
		return this.timelineState?.viewportScrollLeft ?? 0;
	}
	/**
	* Calculate canvas viewport bounds for virtualization.
	* Returns the left position and width of the canvas viewport.
	*/
	calculateCanvasViewport() {
		const totalWidth = this.contentWidth || this.viewportWidth;
		if (totalWidth <= MAX_RULER_CANVAS_WIDTH) return {
			left: 0,
			width: totalWidth
		};
		const viewportScrollLeft = this.scrollLeft;
		const viewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;
		const canvasLeft = Math.max(0, viewportScrollLeft - RULER_CANVAS_BUFFER);
		let canvasWidth = Math.min(totalWidth, viewportScrollLeft + viewportWidth + RULER_CANVAS_BUFFER) - canvasLeft;
		if (canvasWidth > MAX_RULER_CANVAS_WIDTH) canvasWidth = MAX_RULER_CANVAS_WIDTH;
		return {
			left: canvasLeft,
			width: canvasWidth
		};
	}
	connectedCallback() {
		super.connectedCallback();
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		if (this.resizeObserver) this.resizeObserver.disconnect();
	}
	firstUpdated() {
		const container = this.containerRef.value;
		if (container) {
			this.resizeObserver = new ResizeObserver((entries) => {
				const entry = entries[0];
				if (entry) {
					const width = entry.contentRect.width;
					if (width !== this.viewportWidth) this.viewportWidth = width;
				}
			});
			this.resizeObserver.observe(container);
			this.viewportWidth = container.clientWidth;
		}
	}
	updated(changedProperties) {
		const currentScrollLeft = this.scrollLeft;
		const currentViewportWidth = this.timelineState?.viewportWidth ?? this.viewportWidth;
		const scrollChanged = currentScrollLeft !== this._lastRenderedScrollLeft;
		const viewportChanged = currentViewportWidth !== this._lastRenderedViewportWidth;
		const pixelsPerMsChanged = changedProperties.has("timelineState") || changedProperties.has("pixelsPerMs");
		const contentWidthChanged = changedProperties.has("contentWidth");
		const durationChanged = changedProperties.has("durationMs") || changedProperties.has("contextDurationMs");
		if (scrollChanged || viewportChanged || pixelsPerMsChanged || contentWidthChanged || durationChanged || this._lastRenderedScrollLeft < 0) {
			this.renderCanvas();
			this._lastRenderedScrollLeft = currentScrollLeft;
			this._lastRenderedViewportWidth = currentViewportWidth;
		}
	}
	calculateLabelInterval() {
		const pixelsPerSecond = this.pixelsPerMs * 1e3;
		for (const intervalS of [
			.1,
			.25,
			.5,
			1,
			2,
			5,
			10,
			30,
			60
		]) if (intervalS * pixelsPerSecond >= MIN_LABEL_SPACING_PX) return intervalS * 1e3;
		return 6e4;
	}
	getVisibleLabels() {
		const canvasWidth = this._canvasViewportWidth;
		if (canvasWidth <= 0) return [];
		const pixelsPerMs = this.pixelsPerMs;
		const canvasLeft = this._canvasViewportLeft;
		const intervalMs = this.calculateLabelInterval();
		const visibleStartTimeMs = Math.max(0, canvasLeft / pixelsPerMs - intervalMs);
		const visibleEndTimeMs = (canvasLeft + canvasWidth) / pixelsPerMs + intervalMs;
		const firstLabelIndex = Math.floor(visibleStartTimeMs / intervalMs);
		const lastLabelIndex = Math.ceil(visibleEndTimeMs / intervalMs);
		const labels = [];
		for (let i = firstLabelIndex; i <= lastLabelIndex; i++) {
			const timeMs = i * intervalMs;
			if (timeMs < 0) continue;
			const viewportX = timeMs * pixelsPerMs - canvasLeft;
			if (viewportX >= -50 && viewportX <= canvasWidth + 50) {
				const timeSeconds = timeMs / 1e3;
				const text = timeSeconds % 1 === 0 ? `${timeSeconds}s` : `${timeSeconds.toFixed(1)}s`;
				labels.push({
					timeMs,
					viewportX,
					text
				});
			}
		}
		return labels;
	}
	renderCanvas() {
		const canvas = this.canvasRef.value;
		const container = this.containerRef.value;
		if (!canvas || !container) return;
		const viewport = this.calculateCanvasViewport();
		this._canvasViewportLeft = viewport.left;
		this._canvasViewportWidth = viewport.width;
		const width = viewport.width;
		const height = container.getBoundingClientRect().height;
		if (width <= 0 || height <= 0) return;
		const dpr = window.devicePixelRatio || 1;
		canvas.width = width * dpr;
		canvas.height = height * dpr;
		canvas.style.width = `${width}px`;
		canvas.style.height = `${height}px`;
		const ctx = canvas.getContext("2d");
		if (!ctx) return;
		ctx.scale(dpr, dpr);
		ctx.clearRect(0, 0, width, height);
		const pixelsPerMs = this.pixelsPerMs;
		const canvasLeft = viewport.left;
		ctx.strokeStyle = getComputedStyle(this).getPropertyValue("--ef-color-text-muted").trim() || "rgb(156, 163, 175)";
		ctx.lineWidth = 1;
		const labelIntervalMs = this.calculateLabelInterval();
		const visibleStartTimeMs = Math.max(0, canvasLeft / pixelsPerMs - labelIntervalMs);
		const visibleEndTimeMs = (canvasLeft + width) / pixelsPerMs + labelIntervalMs;
		const firstTickIndex = Math.floor(visibleStartTimeMs / labelIntervalMs);
		const lastTickIndex = Math.ceil(visibleEndTimeMs / labelIntervalMs);
		for (let i = firstTickIndex; i <= lastTickIndex; i++) {
			const timeMs = i * labelIntervalMs;
			if (timeMs < 0) continue;
			const canvasX = timeMs * pixelsPerMs - canvasLeft;
			if (canvasX >= -1 && canvasX <= width + 1) {
				ctx.beginPath();
				ctx.moveTo(canvasX, 0);
				ctx.lineTo(canvasX, height * .4);
				ctx.stroke();
			}
		}
		const frameIntervalMs = 1e3 / this.fps;
		if (frameIntervalMs * pixelsPerMs >= MIN_FRAME_SPACING_PX) {
			ctx.strokeStyle = getComputedStyle(this).getPropertyValue("--ef-color-border-subtle").trim() || "rgb(107, 114, 128)";
			ctx.lineWidth = 1;
			const firstFrameIndex = Math.floor(visibleStartTimeMs / frameIntervalMs);
			const lastFrameIndex = Math.ceil(visibleEndTimeMs / frameIntervalMs);
			for (let i = firstFrameIndex; i <= lastFrameIndex; i++) {
				const timeMs = i * frameIntervalMs;
				if (timeMs < 0) continue;
				if (timeMs % labelIntervalMs === 0) continue;
				const canvasX = timeMs * pixelsPerMs - canvasLeft;
				if (canvasX >= -1 && canvasX <= width + 1) {
					ctx.beginPath();
					ctx.moveTo(canvasX, 0);
					ctx.lineTo(canvasX, height * .25);
					ctx.stroke();
				}
			}
		}
	}
	render() {
		const visibleLabels = this.getVisibleLabels();
		const canvasViewportStyles = styleMap({
			left: `${this._canvasViewportLeft}px`,
			width: `${this._canvasViewportWidth}px`
		});
		return html`
      <div ${ref(this.containerRef)} class="ruler-container">
        <div class="canvas-viewport" style=${canvasViewportStyles}>
          <canvas ${ref(this.canvasRef)}></canvas>
          ${visibleLabels.map(({ viewportX, text }) => html`
            <span 
              class="label" 
              style="transform: translateX(${viewportX}px)"
            >${text}</span>
          `)}
        </div>
      </div>
    `;
	}
};
__decorate([property({
	type: Number,
	attribute: "duration-ms"
})], EFTimelineRuler.prototype, "durationMs", void 0);
__decorate([consume({
	context: durationContext,
	subscribe: true
})], EFTimelineRuler.prototype, "contextDurationMs", void 0);
__decorate([consume({
	context: timelineStateContext,
	subscribe: true
})], EFTimelineRuler.prototype, "timelineState", void 0);
__decorate([property({
	type: Number,
	attribute: "fps"
})], EFTimelineRuler.prototype, "fps", void 0);
__decorate([property({
	type: Number,
	attribute: "content-width"
})], EFTimelineRuler.prototype, "contentWidth", void 0);
__decorate([state()], EFTimelineRuler.prototype, "viewportWidth", void 0);
__decorate([state()], EFTimelineRuler.prototype, "_canvasViewportLeft", void 0);
__decorate([state()], EFTimelineRuler.prototype, "_canvasViewportWidth", void 0);
EFTimelineRuler = __decorate([customElement("ef-timeline-ruler")], EFTimelineRuler);

//#endregion
export { EFTimelineRuler, calculateFrameIntervalMs, calculatePixelsPerFrame, quantizeToFrameTimeMs, shouldShowFrameMarkers };
//# sourceMappingURL=EFTimelineRuler.js.map