/**
 * Spark AI Voice Chat - Next.js / React SDK
 *
 * Drop-in React component + hook for voice-to-voice AI chat.
 *
 * Usage (Hook):
 *   import { useSparkVoiceChat } from './SparkVoiceChat';
 *
 *   function VoiceChat() {
 *     const { startRecording, stopAndSend, isRecording, isProcessing,
 *             transcript, aiResponse, error } = useSparkVoiceChat({
 *       apiKey: 'sk_your_key',
 *       endpoint: 'https://your-app.vercel.app/api/voice-chat',
 *     });
 *     return (
 *       <button onClick={isRecording ? stopAndSend : startRecording}>
 *         {isRecording ? 'Stop' : 'Speak'}
 *       </button>
 *     );
 *   }
 *
 * Usage (Component):
 *   import { SparkVoiceChat } from './SparkVoiceChat';
 *
 *   <SparkVoiceChat
 *     apiKey="sk_your_key"
 *     endpoint="https://your-app.vercel.app/api/voice-chat"
 *   />
 */

"use client";

import { useCallback, useRef, useState } from "react";

// --- Types ---

interface SparkVoiceChatConfig {
	apiKey: string;
	endpoint: string;
	sessionId?: string;
	sampleRate?: number;
	onTranscript?: (text: string) => void;
	onResponse?: (text: string) => void;
	onAudioPlayed?: () => void;
	onError?: (error: string) => void;
}

interface SparkVoiceChatState {
	isRecording: boolean;
	isProcessing: boolean;
	transcript: string;
	aiResponse: string;
	error: string | null;
	startRecording: () => Promise<void>;
	stopAndSend: () => void;
}

// --- WAV Encoding ---

function encodeWav(samples: Float32Array, sampleRate: number): ArrayBuffer {
	const numSamples = samples.length;
	const buffer = new ArrayBuffer(44 + numSamples * 2);
	const view = new DataView(buffer);

	const writeString = (offset: number, str: string) => {
		for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));
	};

	writeString(0, "RIFF");
	view.setUint32(4, 36 + numSamples * 2, true);
	writeString(8, "WAVE");
	writeString(12, "fmt ");
	view.setUint32(16, 16, true);
	view.setUint16(20, 1, true); // PCM
	view.setUint16(22, 1, true); // Mono
	view.setUint32(24, sampleRate, true);
	view.setUint32(28, sampleRate * 2, true); // byte rate
	view.setUint16(32, 2, true); // block align
	view.setUint16(34, 16, true); // bits per sample
	writeString(36, "data");
	view.setUint32(40, numSamples * 2, true);

	for (let i = 0; i < numSamples; i++) {
		const s = Math.max(-1, Math.min(1, samples[i]));
		view.setInt16(44 + i * 2, s * 0x7fff, true);
	}

	return buffer;
}

// --- Hook ---

export function useSparkVoiceChat(config: SparkVoiceChatConfig): SparkVoiceChatState {
	const [isRecording, setIsRecording] = useState(false);
	const [isProcessing, setIsProcessing] = useState(false);
	const [transcript, setTranscript] = useState("");
	const [aiResponse, setAiResponse] = useState("");
	const [error, setError] = useState<string | null>(null);

	const _mediaRecorder = useRef<MediaRecorder | null>(null);
	const audioContext = useRef<AudioContext | null>(null);
	const sourceNode = useRef<MediaStreamAudioSourceNode | null>(null);
	const processorNode = useRef<ScriptProcessorNode | null>(null);
	const recordedChunks = useRef<Float32Array[]>([]);
	const streamRef = useRef<MediaStream | null>(null);
	const sessionId = useRef(config.sessionId || crypto.randomUUID());

	const sampleRate = config.sampleRate || 16000;

	const startRecording = useCallback(async () => {
		try {
			setError(null);
			const stream = await navigator.mediaDevices.getUserMedia({
				audio: { sampleRate: { ideal: sampleRate }, channelCount: 1, echoCancellation: true },
			});

			streamRef.current = stream;
			const ctx = new AudioContext({ sampleRate });
			audioContext.current = ctx;

			const source = ctx.createMediaStreamSource(stream);
			sourceNode.current = source;

			// Use ScriptProcessorNode for raw PCM capture
			const processor = ctx.createScriptProcessor(4096, 1, 1);
			processorNode.current = processor;
			recordedChunks.current = [];

			processor.onaudioprocess = (e) => {
				const data = e.inputBuffer.getChannelData(0);
				recordedChunks.current.push(new Float32Array(data));
			};

			source.connect(processor);
			processor.connect(ctx.destination);
			setIsRecording(true);
		} catch (err) {
			const msg = err instanceof Error ? err.message : "Microphone access denied";
			setError(msg);
			config.onError?.(msg);
		}
	}, [sampleRate, config]);

	const stopAndSend = useCallback(async () => {
		// Stop recording
		processorNode.current?.disconnect();
		sourceNode.current?.disconnect();
		audioContext.current?.close();
		const tracks = streamRef.current?.getTracks();
		if (tracks) {
			for (const t of tracks) t.stop();
		}
		setIsRecording(false);

		const chunks = recordedChunks.current;
		if (!chunks.length) {
			setError("No audio recorded");
			config.onError?.("No audio recorded");
			return;
		}

		// Merge chunks
		const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
		const merged = new Float32Array(totalLength);
		let offset = 0;
		for (const chunk of chunks) {
			merged.set(chunk, offset);
			offset += chunk.length;
		}

		const wavBuffer = encodeWav(merged, sampleRate);

		// Send to API
		setIsProcessing(true);
		setError(null);

		try {
			const formData = new FormData();
			formData.append("audio", new Blob([wavBuffer], { type: "audio/wav" }), "audio.wav");
			formData.append("sessionId", sessionId.current);

			const res = await fetch(config.endpoint, {
				method: "POST",
				headers: { "X-Api-Key": config.apiKey },
				body: formData,
			});

			if (!res.ok) {
				const errData = await res.json().catch(() => ({ error: "Request failed" }));
				throw new Error(errData.error || `HTTP ${res.status}`);
			}

			// Parse headers
			const transcriptHeader = res.headers.get("X-Transcript");
			const responseHeader = res.headers.get("X-AI-Response");

			if (transcriptHeader) {
				const decoded = decodeURIComponent(transcriptHeader);
				setTranscript(decoded);
				config.onTranscript?.(decoded);
			}
			if (responseHeader) {
				const decoded = decodeURIComponent(responseHeader);
				setAiResponse(decoded);
				config.onResponse?.(decoded);
			}

			// Play audio response
			const audioData = await res.arrayBuffer();
			const playCtx = new AudioContext();
			const audioBuffer = await playCtx.decodeAudioData(audioData);
			const source = playCtx.createBufferSource();
			source.buffer = audioBuffer;
			source.connect(playCtx.destination);
			source.onended = () => {
				playCtx.close();
				config.onAudioPlayed?.();
			};
			source.start();
		} catch (err) {
			const msg = err instanceof Error ? err.message : "Voice chat failed";
			setError(msg);
			config.onError?.(msg);
		} finally {
			setIsProcessing(false);
		}
	}, [config, sampleRate]);

	return { isRecording, isProcessing, transcript, aiResponse, error, startRecording, stopAndSend };
}

// --- Component ---

interface SparkVoiceChatProps {
	apiKey: string;
	endpoint: string;
	sessionId?: string;
	className?: string;
}

export function SparkVoiceChat({ apiKey, endpoint, sessionId, className }: SparkVoiceChatProps) {
	const { isRecording, isProcessing, transcript, aiResponse, error, startRecording, stopAndSend } =
		useSparkVoiceChat({ apiKey, endpoint, sessionId });

	return (
		<div className={className} style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
			<button
				type="button"
				onClick={isRecording ? stopAndSend : startRecording}
				disabled={isProcessing}
				style={{
					padding: "12px 24px",
					borderRadius: "10px",
					border: "none",
					background: isRecording ? "#ef4444" : "#818cf8",
					color: "white",
					fontWeight: 600,
					fontSize: "15px",
					cursor: isProcessing ? "default" : "pointer",
					opacity: isProcessing ? 0.6 : 1,
					transition: "all 0.2s",
				}}
			>
				{isProcessing ? "Processing..." : isRecording ? "Stop & Send" : "Start Recording"}
			</button>

			{transcript && (
				<div style={{ fontSize: "14px", color: "#a1a1aa" }}>
					<strong style={{ color: "#fafafa" }}>You:</strong> {transcript}
				</div>
			)}
			{aiResponse && (
				<div style={{ fontSize: "14px", color: "#a1a1aa" }}>
					<strong style={{ color: "#818cf8" }}>AI:</strong> {aiResponse}
				</div>
			)}
			{error && <div style={{ fontSize: "13px", color: "#f87171" }}>{error}</div>}
		</div>
	);
}
