import { createLogger } from "../../../utils/logger/index.js";
import { init_logger } from "../../../utils/logger.js";
import { getDefaultModelSync, init_providers, resolveModel } from "../../../providers/index.js";
import { resolveTtsProviderConfigSlice } from "../../../voice/tts/config-slice.js";
import { isTTSAvailable } from "../../../voice/tts/factory.js";
import { speak } from "../../../voice/tts/speak-core.js";
import { mergeTtsConfigFromAppConfig } from "../../../voice/tts/merge-config.js";
import { resolveSttProviderConfigSlice } from "../../../voice/stt/config-slice.js";
import { transcribe } from "../../../voice/stt/transcribe-core.js";
import { isSTTAvailable } from "../../../voice/stt/availability.js";
import "../../../voice/stt/index.js";
import { mergeSttConfigFromAppConfig } from "../../../channels/attachments/voice-stt-webchat.js";
import { listSttProvidersForApi } from "../../../voice/stt/list-providers.js";
import "../../../voice/tts/index.js";
import { listTtsProvidersForApi } from "../../../voice/tts/list-providers.js";
import { complete } from "@earendil-works/pi-ai";
//#region src/gateway/hono/routes/voice.ts
init_providers();
init_logger();
const log = createLogger("Gateway:Voice");
function readVoiceApiKeyFromConfigFileOnly(cfg, kind, providerId) {
	const id = providerId.trim();
	if (!id) return void 0;
	const key = (kind === "stt" ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio) : resolveTtsProviderConfigSlice(id, cfg.messages?.tts)).apiKey;
	return typeof key === "string" && key.trim() ? key.trim() : void 0;
}
const REFINE_TIMEOUT_MS = 15e3;
const MAX_AUDIO_BYTES = 25 * 1024 * 1024;
const REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。

规则：
1. 修正明显的语音识别错误
2. 添加正确的标点符号
3. 去除口语赘词（嗯、啊、那个、就是说、然后就是）
4. 保持原意不变，不要扩写或改变语义
5. 如果原文已经很好，原样输出
6. 只输出整理后的文字，不要解释`;
function resolveRefineModel(config) {
	const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();
	if (envRef) try {
		return resolveModel(envRef);
	} catch {}
	for (const candidate of ["openai/gpt-4o-mini", "google/gemini-2.0-flash"]) try {
		return resolveModel(candidate);
	} catch {}
	try {
		return resolveModel(getDefaultModelSync(config));
	} catch {
		return null;
	}
}
async function refineTranscript(raw, config, signal) {
	if (!raw.trim()) return void 0;
	const model = resolveRefineModel(config);
	if (!model) {
		log.debug("No LLM model available for voice refine; returning raw only");
		return;
	}
	try {
		const userMsg = {
			role: "user",
			content: `${REFINE_SYSTEM_PROMPT}\n\n原文：${raw}`,
			timestamp: Date.now()
		};
		const timeoutSignal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(REFINE_TIMEOUT_MS) : void 0;
		const mergedSignal = signal && timeoutSignal && typeof AbortSignal.any === "function" ? AbortSignal.any([signal, timeoutSignal]) : signal ?? timeoutSignal;
		const result = await complete(model, { messages: [userMsg] }, {
			maxTokens: Math.min(raw.length * 3, 4096),
			temperature: .2,
			signal: mergedSignal
		});
		let out = "";
		if (Array.isArray(result.content)) {
			for (const c of result.content) if (c && typeof c === "object" && c.type === "text") out += String(c.text || "");
		}
		const refined = out.trim();
		if (!refined || refined === raw.trim()) return void 0;
		return refined;
	} catch (error) {
		const msg = error instanceof Error ? error.message : String(error);
		log.warn({ errorMessage: msg }, "Voice refine failed; returning raw only");
		return;
	}
}
function registerVoiceRoutes(authenticated, deps) {
	const { service, strictRateLimitMiddleware } = deps;
	/**
	* GET /api/voice/providers
	*
	* Lists registered SpeechProviderPlugin ids with configured state for the
	* current gateway config (OpenClaw `tts.providers` equivalent).
	*/
	authenticated.get("/api/voice/providers", (c) => {
		const config = service.currentConfig;
		const payload = listTtsProvidersForApi(config);
		return c.json({
			ok: true,
			payload
		});
	});
	/**
	* GET /api/voice/stt-providers
	*
	* Lists registered MediaUnderstandingProvider ids with configured state for
	* the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).
	*/
	authenticated.get("/api/voice/stt-providers", (c) => {
		const config = service.currentConfig;
		const payload = listSttProvidersForApi(config);
		return c.json({
			ok: true,
			payload
		});
	});
	/**
	* POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.
	* Body: `{ kind: 'stt' | 'tts', provider: string }`
	*/
	authenticated.post("/api/voice/reveal-api-key", strictRateLimitMiddleware, async (c) => {
		let body = {};
		try {
			body = await c.req.json();
		} catch {
			return c.json({
				ok: false,
				error: { message: "Invalid JSON body" }
			}, 400);
		}
		const kind = body.kind === "stt" || body.kind === "tts" ? body.kind : null;
		const provider = typeof body.provider === "string" ? body.provider.trim() : "";
		if (!kind) return c.json({
			ok: false,
			error: { message: "kind must be \"stt\" or \"tts\"" }
		}, 400);
		if (!provider) return c.json({
			ok: false,
			error: { message: "provider is required" }
		}, 400);
		const cfg = service.currentConfig;
		const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);
		return c.json({
			ok: true,
			payload: {
				kind,
				provider,
				apiKey: apiKey ?? null,
				source: apiKey ? "config" : "none"
			}
		});
	});
	/**
	* POST /api/voice/tts-test
	*
	* Body: { text: string, provider?: string, model?: string, voice?: string }
	* Response: { ok: true, payload: { audio: string, format: string, provider: string } }
	*/
	authenticated.post("/api/voice/tts-test", strictRateLimitMiddleware, async (c) => {
		let body = {};
		try {
			body = await c.req.json();
		} catch {
			return c.json({
				ok: false,
				error: { message: "Invalid JSON body" }
			}, 400);
		}
		const text = typeof body.text === "string" ? body.text.trim() : "";
		if (!text) return c.json({
			ok: false,
			error: { message: "text is required" }
		}, 400);
		if (text.length > 1e3) return c.json({
			ok: false,
			error: { message: "text exceeds 1000 characters" }
		}, 400);
		const config = service.currentConfig;
		const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);
		const provider = typeof body.provider === "string" && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;
		const ttsConfig = {
			...baseTtsConfig,
			enabled: true,
			provider,
			fallback: {
				enabled: false,
				order: [provider]
			}
		};
		if (!isTTSAvailable(ttsConfig)) return c.json({
			ok: false,
			error: { message: `TTS provider "${provider}" is not configured.` }
		}, 503);
		try {
			const result = await speak(text, ttsConfig, {
				appConfig: config,
				parseDirectives: false,
				tts: {
					...typeof body.model === "string" && body.model.trim() ? { model: body.model.trim() } : {},
					...typeof body.voice === "string" && body.voice.trim() ? { voice: body.voice.trim() } : {}
				}
			});
			return c.json({
				ok: true,
				payload: {
					audio: result.audio.toString("base64"),
					format: result.format,
					provider: result.provider,
					...result.duration !== void 0 ? { duration: result.duration } : {}
				}
			});
		} catch (error) {
			const msg = error instanceof Error ? error.message : String(error);
			log.error({
				errorMessage: msg,
				provider
			}, "Voice TTS test failed");
			return c.json({
				ok: false,
				error: { message: `TTS test failed: ${msg}` }
			}, 502);
		}
	});
	/**
	* POST /api/voice/transcribe
	*
	* Body: { audio: string (base64), mimeType: string, language?: string }
	* Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }
	*/
	authenticated.post("/api/voice/transcribe", async (c) => {
		let body = {};
		try {
			body = await c.req.json();
		} catch {
			return c.json({
				ok: false,
				error: { message: "Invalid JSON body" }
			}, 400);
		}
		const { audio, mimeType, language } = body;
		if (!audio || typeof audio !== "string") return c.json({
			ok: false,
			error: { message: "Missing required field: audio (base64)" }
		}, 400);
		if (!mimeType || typeof mimeType !== "string") return c.json({
			ok: false,
			error: { message: "Missing required field: mimeType" }
		}, 400);
		let audioBuffer;
		try {
			audioBuffer = Buffer.from(audio, "base64");
		} catch {
			return c.json({
				ok: false,
				error: { message: "Invalid base64 audio data" }
			}, 400);
		}
		if (audioBuffer.length === 0) return c.json({
			ok: false,
			error: { message: "Empty audio data" }
		}, 400);
		if (audioBuffer.length > MAX_AUDIO_BYTES) return c.json({
			ok: false,
			error: { message: "Audio data exceeds 25 MB limit" }
		}, 400);
		const config = service.currentConfig;
		const sttConfigRaw = config.tools?.media?.audio;
		const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);
		if (!isSTTAvailable(sttConfig)) return c.json({
			ok: false,
			error: { message: "STT is not configured. Enable STT in gateway config (tools.media.audio)." }
		}, 503);
		let raw;
		let detectedLanguage;
		try {
			const result = await transcribe(audioBuffer, sttConfig, { language: language || (sttConfig.provider === "alibaba" ? "zh" : void 0) });
			raw = result.text;
			detectedLanguage = result.language ?? language;
		} catch (error) {
			const msg = error instanceof Error ? error.message : String(error);
			log.error({ errorMessage: msg }, "Voice transcription failed");
			return c.json({
				ok: false,
				error: { message: `Transcription failed: ${msg}` }
			}, 502);
		}
		if (!raw.trim()) return c.json({
			ok: true,
			payload: {
				raw: "",
				language: detectedLanguage
			}
		});
		const refined = await refineTranscript(raw, config);
		return c.json({
			ok: true,
			payload: {
				raw,
				...refined ? { refined } : {},
				...detectedLanguage ? { language: detectedLanguage } : {}
			}
		});
	});
}
//#endregion
export { registerVoiceRoutes };

//# sourceMappingURL=voice.js.map