import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
import { LRUCache } from "../../../utils/LRUCache.js";
import { Task } from "@lit/task";
const DECAY_WEIGHT = .8;
function processFFTData(fftData, zeroThresholdPercent = .1) {
	const totalBins = fftData.length;
	const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
	let zeroCount = 0;
	let cutoffIndex = totalBins;
	for (let i = totalBins - 1; i >= 0; i--) if (fftData[i] ?? true) zeroCount++;
	else if (zeroCount >= zeroThresholdCount) {
		cutoffIndex = i + 1;
		break;
	}
	if (cutoffIndex < zeroThresholdCount) return fftData;
	const goodData = fftData.slice(0, cutoffIndex);
	const resampledData = interpolateData(goodData, fftData.length);
	const attenuationStartIndex = Math.floor(totalBins * .9);
	for (let i = attenuationStartIndex; i < totalBins; i++) {
		const attenuationProgress = (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + .2;
		const attenuationFactor = Math.max(0, 1 - attenuationProgress);
		resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);
	}
	return resampledData;
}
function interpolateData(data, targetSize) {
	const resampled = new Uint8Array(targetSize);
	const dataLength = data.length;
	for (let i = 0; i < targetSize; i++) {
		const ratio = i / (targetSize - 1) * (dataLength - 1);
		const index = Math.floor(ratio);
		const fraction = ratio - index;
		if (index >= dataLength - 1) resampled[i] = data[dataLength - 1] ?? 0;
		else resampled[i] = Math.round((data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction);
	}
	return resampled;
}
function makeAudioFrequencyAnalysisTask(element) {
	const cache = new LRUCache(100);
	return new Task(element, {
		autoRun: EF_INTERACTIVE,
		onError: (error) => {
			console.error("frequencyDataTask error", error);
		},
		args: () => [
			element.audioBufferTask.status,
			element.currentSourceTimeMs,
			element.fftSize,
			element.fftDecay,
			element.fftGain,
			element.shouldInterpolateFrequencies
		],
		task: async (_, { signal }) => {
			await element.audioBufferTask.taskComplete;
			signal.throwIfAborted();
			if (!element.audioBufferTask.value) return null;
			if (element.currentSourceTimeMs < 0) return null;
			const currentTimeMs = element.currentSourceTimeMs;
			const frameIntervalMs = 1e3 / 30;
			const earliestFrameMs = currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;
			const fromMs = Math.max(0, earliestFrameMs);
			const maxToMs = currentTimeMs + frameIntervalMs;
			const videoDurationMs = element.intrinsicDurationMs || 0;
			const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
			if (fromMs >= toMs) return null;
			const preliminaryCacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${fromMs}:${currentTimeMs}`;
			const cachedSmoothedData = cache.get(preliminaryCacheKey);
			if (cachedSmoothedData) return cachedSmoothedData;
			const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
			const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);
			if (!audioSpan || !audioSpan.blob) {
				console.warn("Frequency analysis skipped: no audio data available");
				return null;
			}
			const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
			const arrayBuffer = await audioSpan.blob.arrayBuffer();
			const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
			const startOffsetMs = audioSpan.startMs;
			const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_$1, i) => {
				const frameOffset = i * (1e3 / 30);
				const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
				const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
				const cachedFrame = cache.get(cacheKey);
				if (cachedFrame) return cachedFrame;
				const SIZE = 48e3 / 30;
				let audioContext;
				try {
					audioContext = new OfflineAudioContext(2, SIZE, 48e3);
				} catch (error) {
					throw new Error(`[EFMedia.frequencyDataTask] Failed to create OfflineAudioContext(2, ${SIZE}, 48000) for frame ${i} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio frequency analysis.`);
				}
				const analyser = audioContext.createAnalyser();
				analyser.fftSize = element.fftSize;
				analyser.minDecibels = -90;
				analyser.maxDecibels = -10;
				const gainNode = audioContext.createGain();
				gainNode.gain.value = element.fftGain;
				const filter = audioContext.createBiquadFilter();
				filter.type = "bandpass";
				filter.frequency.value = 15e3;
				filter.Q.value = .05;
				const audioBufferSource = audioContext.createBufferSource();
				audioBufferSource.buffer = audioBuffer;
				audioBufferSource.connect(filter);
				filter.connect(gainNode);
				gainNode.connect(analyser);
				analyser.connect(audioContext.destination);
				audioBufferSource.start(0, startTime, 1 / 30);
				try {
					await audioContext.startRendering();
					const frameData = new Uint8Array(element.fftSize / 2);
					analyser.getByteFrequencyData(frameData);
					cache.set(cacheKey, frameData);
					return frameData;
				} finally {
					audioBufferSource.disconnect();
					analyser.disconnect();
				}
			}));
			const frameLength = framesData[0]?.length ?? 0;
			const smoothedData = new Uint8Array(frameLength);
			for (let i = 0; i < frameLength; i++) {
				let weightedSum = 0;
				let weightSum = 0;
				framesData.forEach((frame, frameIndex) => {
					const decayWeight = DECAY_WEIGHT ** frameIndex;
					weightedSum += (frame[i] ?? 0) * decayWeight;
					weightSum += decayWeight;
				});
				smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
			}
			smoothedData.forEach((value, i) => {
				const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
				smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
			});
			const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));
			const processedData = element.shouldInterpolateFrequencies ? processFFTData(slicedData) : slicedData;
			cache.set(preliminaryCacheKey, processedData);
			return processedData;
		}
	});
}
export { makeAudioFrequencyAnalysisTask };
