import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { parseTtsDirectives } from "./directives.js";
import { resolveSpeechProvider, resolveSpeechProviderChain } from "./factory.js";
import { preprocessText } from "./preprocess.js";
import { summarizeForTts } from "./summarize.js";
//#region src/voice/tts/speak-core.ts
init_logger();
const log = createLogger("TTS");
/** Pulls the override bucket for a given provider id from the directive overrides. */
function pickProviderOverrides(overrides, providerId) {
	if (!overrides) return void 0;
	const bucket = overrides[providerId];
	if (bucket && typeof bucket === "object") return bucket;
}
/** Merges TTSOptions (caller-supplied) with directive bucket. Directive wins on overlap. */
function mergeOverrides(ttsOptions, directiveBucket) {
	const merged = {};
	if (ttsOptions?.voice) merged.voice = ttsOptions.voice;
	if (ttsOptions?.model) merged.model = ttsOptions.model;
	if (ttsOptions?.speed) merged.speed = ttsOptions.speed;
	if (directiveBucket) Object.assign(merged, directiveBucket);
	return Object.keys(merged).length > 0 ? merged : void 0;
}
function classifyError(error) {
	if (error instanceof Error) {
		const message = error.message.toLowerCase();
		if (message.includes("not configured") || message.includes("api key")) return "not_configured";
		if (message.includes("timeout") || message.includes("timed out") || message.includes("aborted")) return "timeout";
		if (message.includes("too long") || message.includes("text length") || message.includes("exceeds")) return "text_too_long";
		return "provider_error";
	}
	return "unknown";
}
/** Common pre-flight: directive parse → preprocess → optional summarize. */
async function prepareTextForSynthesis(text, config, options) {
	let ttsText = text;
	let ttsOptions = options?.tts;
	let directiveOverrides;
	let selectedProvider = config.provider;
	if (options?.parseDirectives !== false && config.modelOverrides?.enabled) {
		const directiveResult = parseTtsDirectives(text, config.modelOverrides);
		ttsText = directiveResult.ttsText || directiveResult.cleanedText;
		directiveOverrides = directiveResult.overrides;
		if (directiveResult.overrides?.provider) selectedProvider = directiveResult.overrides.provider;
	}
	const maxLength = config.maxTextLength || 4096;
	const preprocessOptions = {
		maxLength,
		stripMarkdown: true,
		normalizeWhitespace: true,
		...options?.preprocess
	};
	let preprocessResult = preprocessText(ttsText, preprocessOptions);
	const wasPreprocessed = preprocessResult.wasTruncated || preprocessResult.originalLength !== preprocessResult.finalLength;
	let wasSummarized = false;
	const sumCfg = config.summarization;
	const threshold = sumCfg?.threshold ?? maxLength;
	if (sumCfg?.enabled !== false && preprocessResult.text.length > threshold) {
		const summarizeResult = await summarizeForTts({
			text: preprocessResult.text,
			targetLength: sumCfg?.targetLength ?? maxLength,
			config: options?.appConfig,
			modelRef: sumCfg?.model
		});
		if (summarizeResult.wasSummarized) {
			preprocessResult = {
				...preprocessResult,
				text: summarizeResult.summary,
				finalLength: summarizeResult.summaryLength,
				wasTruncated: false
			};
			wasSummarized = true;
			log.info({
				originalLength: summarizeResult.originalLength,
				summaryLength: summarizeResult.summaryLength
			}, "Text summarized before TTS");
		}
	}
	return {
		text: preprocessResult.text,
		wasPreprocessed,
		wasSummarized,
		ttsOptions,
		directiveOverrides,
		selectedProvider
	};
}
/** Build the SpeechSynthesisRequest from a resolved provider + prepared text. */
function buildSynthesisRequest(resolved, preparedText, ttsOptions, directiveOverrides, appConfig, target) {
	const providerOverrides = mergeOverrides(ttsOptions, pickProviderOverrides(directiveOverrides, resolved.providerId));
	const callTimeoutMs = ttsOptions?.timeoutMs ?? resolved.timeoutMs;
	return {
		text: preparedText,
		cfg: appConfig,
		providerConfig: resolved.providerConfig,
		target,
		timeoutMs: callTimeoutMs,
		...providerOverrides ? { providerOverrides } : {}
	};
}
/** Convert SpeechSynthesisResult → TTSResult shape (audio/format/provider). */
function toLegacyResult(resolved, result, durationSeconds) {
	return {
		audio: result.audioBuffer,
		format: result.outputFormat,
		provider: resolved.providerId,
		...durationSeconds !== void 0 ? { duration: durationSeconds } : {}
	};
}
/**
* Run the TTS chain (primary → fallback) and return the first successful result.
*/
async function speak(text, config, options) {
	if (!config.enabled) throw new Error("TTS is not enabled");
	if (config.trigger === "tagged" && !/\[\[tts/.test(text)) throw new Error("TTS trigger is tagged but no [[tts]] directive found");
	const prepared = await prepareTextForSynthesis(text, config, options);
	const chain = resolveSpeechProviderChain(config);
	const orderedChain = (() => {
		const idx = chain.findIndex((c) => c.providerId === prepared.selectedProvider);
		if (idx <= 0) return chain;
		const reordered = [...chain];
		const [picked] = reordered.splice(idx, 1);
		if (picked) reordered.unshift(picked);
		return reordered;
	})();
	const attempts = [];
	const attemptedProviders = [];
	let lastError;
	for (const resolved of orderedChain) {
		const startTime = Date.now();
		attemptedProviders.push(resolved.providerId);
		if (typeof resolved.plugin.synthesize !== "function") {
			attempts.push({
				provider: resolved.providerId,
				outcome: "skipped",
				reasonCode: "not_configured",
				latencyMs: Date.now() - startTime,
				error: `Provider "${resolved.providerId}" does not implement synthesize()`
			});
			continue;
		}
		try {
			log.debug({
				textLength: prepared.text.length,
				provider: resolved.providerId
			}, "Converting text to speech");
			const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file");
			const result = await resolved.plugin.synthesize(request);
			const durationSeconds = (Date.now() - startTime) / 1e3;
			attempts.push({
				provider: resolved.providerId,
				outcome: "success",
				reasonCode: "success",
				latencyMs: Date.now() - startTime
			});
			const primaryProvider = orderedChain[0].providerId;
			const fallbackFrom = resolved.providerId !== primaryProvider ? primaryProvider : void 0;
			if (fallbackFrom) log.info({
				primaryProvider: fallbackFrom,
				actualProvider: resolved.providerId,
				attempts
			}, "TTS used fallback provider");
			else log.info({
				provider: resolved.providerId,
				format: result.outputFormat,
				size: result.audioBuffer.length
			}, "TTS succeeded");
			return {
				...toLegacyResult(resolved, result, durationSeconds),
				attempts,
				...fallbackFrom ? { fallbackFrom } : {},
				attemptedProviders,
				wasPreprocessed: prepared.wasPreprocessed,
				ttsText: prepared.text,
				wasSummarized: prepared.wasSummarized
			};
		} catch (error) {
			const latencyMs = Date.now() - startTime;
			const reasonCode = classifyError(error);
			const errorMsg = error instanceof Error ? error.message : String(error);
			attempts.push({
				provider: resolved.providerId,
				outcome: "failed",
				reasonCode,
				latencyMs,
				error: errorMsg
			});
			lastError = error instanceof Error ? error : new Error(String(error));
			log.warn({
				provider: resolved.providerId,
				errorMessage: errorMsg,
				reasonCode,
				latencyMs
			}, "TTS provider failed, trying next");
		}
	}
	log.error({
		attempts,
		attemptedProviders
	}, "All TTS providers failed");
	throw lastError ?? /* @__PURE__ */ new Error(`All TTS providers failed: ${attempts.map((a) => a.error).join("; ")}`);
}
/** Single-provider variant; bypasses the fallback chain. */
async function speakWithProvider(text, config, providerName, options) {
	if (!config.enabled) throw new Error("TTS is not enabled");
	const resolved = resolveSpeechProvider(providerName, config);
	if (!resolved) throw new Error(`Provider '${providerName}' is not available`);
	if (typeof resolved.plugin.synthesize !== "function") throw new Error(`Provider '${providerName}' does not implement synthesize()`);
	const prepared = await prepareTextForSynthesis(text, config, options);
	const startTime = Date.now();
	const request = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "audio-file");
	return {
		...toLegacyResult(resolved, await resolved.plugin.synthesize(request), (Date.now() - startTime) / 1e3),
		attempts: [{
			provider: providerName,
			outcome: "success",
			reasonCode: "success",
			latencyMs: Date.now() - startTime
		}],
		attemptedProviders: [providerName],
		wasPreprocessed: prepared.wasPreprocessed,
		ttsText: prepared.text,
		wasSummarized: prepared.wasSummarized
	};
}
/**
* Streaming TTS — tries the chain and returns the first provider's stream.
* Providers without `synthesizeStream` are wrapped via wrapBufferAsStream.
*/
async function speakStream(text, config, options) {
	if (!config.enabled) throw new Error("TTS is not enabled");
	if (config.trigger === "tagged" && !/\[\[tts/.test(text)) throw new Error("TTS trigger is tagged but no [[tts]] directive found");
	const prepared = await prepareTextForSynthesis(text, config, options);
	const chain = resolveSpeechProviderChain(config);
	let lastError;
	for (const resolved of chain) try {
		const baseRequest = buildSynthesisRequest(resolved, prepared.text, prepared.ttsOptions, prepared.directiveOverrides, options?.appConfig, "voice-note");
		let streamResult;
		if (typeof resolved.plugin.synthesizeStream === "function") streamResult = await resolved.plugin.synthesizeStream(baseRequest);
		else if (typeof resolved.plugin.synthesize === "function") streamResult = wrapBufferAsStream(await resolved.plugin.synthesize(baseRequest));
		else throw new Error(`Provider "${resolved.providerId}" implements neither synthesize nor synthesizeStream`);
		log.info({
			provider: resolved.providerId,
			outputFormat: streamResult.outputFormat,
			voiceCompatible: streamResult.voiceCompatible,
			streamed: typeof resolved.plugin.synthesizeStream === "function"
		}, "TTS stream ready");
		const release = streamResult.release ?? (async () => {});
		return {
			audioStream: streamResult.audioStream,
			outputFormat: streamResult.outputFormat,
			fileExtension: streamResult.fileExtension,
			voiceCompatible: streamResult.voiceCompatible,
			provider: resolved.providerId,
			release,
			ttsText: prepared.text,
			wasPreprocessed: prepared.wasPreprocessed,
			wasSummarized: prepared.wasSummarized
		};
	} catch (error) {
		lastError = error instanceof Error ? error : new Error(String(error));
		log.warn({
			err: lastError,
			provider: resolved.providerId
		}, `TTS stream provider "${resolved.providerId}" failed, trying next`);
	}
	throw lastError ?? /* @__PURE__ */ new Error("All TTS providers failed (streaming)");
}
/** Wrap a one-shot synthesis result into a single-chunk stream (no-op release). */
function wrapBufferAsStream(result) {
	const { audioBuffer } = result;
	return {
		audioStream: new ReadableStream({ start(controller) {
			controller.enqueue(new Uint8Array(audioBuffer));
			controller.close();
		} }),
		outputFormat: result.outputFormat,
		fileExtension: result.fileExtension,
		voiceCompatible: result.voiceCompatible
	};
}
//#endregion
export { speak, speakStream, speakWithProvider };

//# sourceMappingURL=speak-core.js.map