import { CONVERT_URL, ASR_URL, OCR_URL } from './config.js'
import { buildHeaders } from './fetch.js'

import { parseSseEvent } from './sse.js'

async function consumeSseResult(res) {
    if (!res.ok) {
        const errorText = await res.text()
        throw new Error(`HTTP ${res.status}: ${errorText}`)
    }

    if (!res.body) {
        throw new Error('流式响应不可用')
    }

    const reader = res.body.getReader()
    const decoder = new TextDecoder()
    let buffer = ''
    let finalResult = null

    while (true) {
        const { done, value } = await reader.read()
        if (done) break
        buffer += decoder.decode(value, { stream: true })

        let boundary = buffer.indexOf('\n\n')
        while (boundary >= 0) {
            const chunk = buffer.slice(0, boundary)
            buffer = buffer.slice(boundary + 2)
            const parsed = parseSseEvent(chunk)
            const data = parsed.data ? JSON.parse(parsed.data) : {}

            if (parsed.event === 'done') {
                finalResult = data.result || data
                return finalResult
            }
            if (parsed.event === 'error') {
                throw new Error(String(data.error || '请求失败'))
            }
            if (parsed.event === 'cancelled') {
                throw new Error('请求已取消')
            }

            boundary = buffer.indexOf('\n\n')
        }
    }

    return finalResult
}

function readFileAsBase64(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader()
        reader.onload = () => {
            const dataUrl = typeof reader.result === 'string' ? reader.result : ''
            const splitIndex = dataUrl.indexOf(',')
            if (splitIndex === -1) {
                reject(new Error('Invalid file data'))
                return
            }
            resolve(dataUrl.slice(splitIndex + 1))
        }
        reader.onerror = () => reject(reader.error || new Error('Failed to read file'))
        reader.readAsDataURL(file)
    })
}

export async function convertFileToMarkdown(file) {
    const base64 = await readFileAsBase64(file)
    const headers = {
        'Content-Type': 'application/json',
    }
    const res = await fetch(CONVERT_URL, {
        method: 'POST',
        headers,
        credentials: 'include',
        body: JSON.stringify({
            file: base64,
            filename: file.name || 'document',
        }),
    })

    const data = await consumeSseResult(res)
    if (!data || typeof data.markdown !== 'string') {
        throw new Error('No markdown returned')
    }
    return data.markdown
}

export async function convertVideoToText(file, language = 'auto') {
    const base64 = await readFileAsBase64(file)
    const headers = {
        'Content-Type': 'application/json',
    }
    const res = await fetch(OCR_URL, {
        method: 'POST',
        headers,
        credentials: 'include',
        body: JSON.stringify({
            image: base64,
            filename: file.name || 'video.mp4',
            language,
            media_type: 'video',
            mime_type: file.type || 'video/mp4',
        }),
    })

    const data = await consumeSseResult(res)
    if (!data || typeof data.text !== 'string' || !data.text.trim()) {
        throw new Error('视频解析结果为空')
    }
    return data.text
}

/**
 * Encode AudioBuffer as WAV (16kHz mono, 16-bit PCM) and return base64 string
 */
function audioBufferToWavBase64(audioBuffer) {
    // Resample to 16kHz if needed using OfflineAudioContext
    const targetSampleRate = 16000

    if (audioBuffer.sampleRate === targetSampleRate) {
        // No resampling needed, just convert to mono and encode WAV
    } else {
        const offlineCtx = new OfflineAudioContext(1, audioBuffer.length * (targetSampleRate / audioBuffer.sampleRate), targetSampleRate)
        const source = offlineCtx.createBufferSource()
        source.buffer = audioBuffer
        source.connect(offlineCtx.destination)
        // We need to wait for the offline context to finish rendering
    }

    return new Promise((resolve, reject) => {
        const processBuffer = async (buffer) => {
            // Convert to mono if stereo/multi-channel
            let channels = buffer.numberOfChannels
            const length = buffer.length

            if (channels === 1) {
                // Already mono, use directly
                const channelData = buffer.getChannelData(0)
            } else {
                // Mix down to mono by averaging channels
                const channelData = new Float32Array(length)
                for (let i = 0; i < length; i++) {
                    let sum = 0
                    for (let ch = 0; ch < channels; ch++) {
                        sum += buffer.getChannelData(ch)[i]
                    }
                    channelData[i] = sum / channels
                }
            }

            // Encode as 16-bit PCM WAV (simplified - we'll use the actual channel data)
            const sampleRate = buffer.sampleRate
            const numSamples = buffer.length

            // Get mono data properly
            let samples
            if (buffer.numberOfChannels === 1) {
                samples = buffer.getChannelData(0)
            } else {
                const monoSamples = new Float32Array(numSamples)
                for (let i = 0; i < numSamples; i++) {
                    let sum = 0
                    for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
                        sum += buffer.getChannelData(ch)[i]
                    }
                    monoSamples[i] = sum / buffer.numberOfChannels
                }
            }

            // Convert float32 [-1, 1] to int16 PCM
            const pcmData = new Int16Array(numSamples)
            for (let i = 0; i < numSamples; i++) {
                const s = Math.max(-1, Math.min(1, samples[i]))
                pcmData[i] = s < 0 ? s * 32768 : s * 32767
            }

            // Build WAV file (RIFF format)
            const wavBuffer = new ArrayBuffer(44 + numSamples * 2)
            const view = new DataView(wavBuffer)

            // RIFF header
            writeString(view, 0, 'RIFF')
            view.setUint32(4, 36 + numSamples * 2, true)
            writeString(view, 8, 'WAVE')

            // fmt chunk
            writeString(view, 12, 'fmt ')
            view.setUint32(16, 16, true) // chunk size
            view.setUint16(20, 1, true) // PCM format
            view.setUint16(22, 1, true) // mono channels
            view.setUint32(24, sampleRate, true) // sample rate
            view.setUint32(28, sampleRate * 2, true) // byte rate
            view.setUint16(32, 2, true) // block align
            view.setUint16(34, 16, true) // bits per sample

            // data chunk
            writeString(view, 36, 'data')
            view.setUint32(40, numSamples * 2, true)

            // Write PCM data
            let offset = 44
            for (let i = 0; i < numSamples; i++) {
                view.setInt16(offset, pcmData[i], true)
                offset += 2
            }

            // Convert to base64
            const bytes = new Uint8Array(wavBuffer)
            let binary = ''
            for (let i = 0; i < bytes.length; i++) {
                binary += String.fromCharCode(bytes[i])
            }

            resolve(btoa(binary))
        }

        // Handle resampling if needed
        const targetSampleRate = 16000

        if (audioBuffer.sampleRate === targetSampleRate) {
            processBuffer(audioBuffer).catch(reject)
        } else {
            const offlineCtx = new OfflineAudioContext(1, Math.ceil(audioBuffer.duration * targetSampleRate), targetSampleRate)
            const source = offlineCtx.createBufferSource()
            source.buffer = audioBuffer
            source.connect(offlineCtx.destination)

            offlineCtx.oncomplete = (e) => {
                processBuffer(e.renderedBuffer).catch(reject)
            }

            offlineCtx.startRendering()
        }
    })
}

function writeString(view, offset, string) {
    for (let i = 0; i < string.length; i++) {
        view.setUint8(offset + i, string.charCodeAt(i))
    }
}

/**
 * Convert audio file to WAV base64 (16kHz mono, 16-bit PCM)
 * Uses Web Audio API to decode and resample if needed.
 */
export async function audioToWavBase64(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader()

        reader.onload = async () => {
            try {
                const arrayBuffer = reader.result

                // Decode audio data using Web Audio API
                const audioContext = new (window.AudioContext || window.webkitAudioContext)()

                // Use a short timeout to avoid hanging on unsupported formats
                const decodePromise = audioContext.decodeAudioData(arrayBuffer.slice(0))

                // Set a timeout (10 seconds)
                const timeoutPromise = new Promise((_, rej) => {
                    setTimeout(() => rej(new Error('音频解码超时，格式可能不支持')), 10000)
                })

                const audioBuffer = await Promise.race([decodePromise, timeoutPromise])

                // Close the context
                audioContext.close()

                const wavBase64 = await audioBufferToWavBase64(audioBuffer)
                resolve(wavBase64)
            } catch (err) {
                reject(err)
            }
        }

        reader.onerror = () => reject(reader.error || new Error('Failed to read audio file'))
        reader.readAsArrayBuffer(file)
    })
}

/**
 * Convert audio file to text using ASR endpoint.
 * Returns the recognized text string.
 */
export async function convertAudioToText(file, language = 'zh-CN') {
    // Step 1: Convert to WAV base64 (handles format conversion)
    const wavBase64 = await audioToWavBase64(file)

    // Step 2: Send to ASR endpoint
    const res = await fetch(ASR_URL, {
        method: 'POST',
        headers: buildHeaders({
            'Content-Type': 'application/json',
        }),
        credentials: 'include',
        body: JSON.stringify({
            audio_base64: wavBase64,
            language: language || 'zh-CN',
        }),
    })

    if (res.status === 501) {
        throw new Error('ASR 功能不可用，当前环境不支持语音识别')
    }

    const data = await consumeSseResult(res)
    if (!data || typeof data.text !== 'string') {
        throw new Error('ASR 返回结果为空')
    }

    return data.text
}
