/**
 * Local voice transcription for the staff-chat module (whisper.cpp + ffmpeg).
 *
 * Chat-owned on purpose (isolation rule): it mirrors the pipeline in
 * server/api/mobile/transcribe.post.ts but is self-contained and RETURNS the
 * transcript instead of writing it onto an iDempiere request — the chat caller
 * decides what to do with it. Fully fail-soft: any missing binary / model /
 * ffmpeg / decode error resolves to '' so a voice message still sends, just
 * without a transcript. Uses async exec so it never blocks the event loop.
 */

import { exec } from 'child_process'
import { promisify } from 'util'
import { writeFile, unlink, mkdir } from 'fs/promises'
import { existsSync } from 'fs'
import { join } from 'path'

const pexec = promisify(exec)

// Whisper language. IMPORTANT: `-l` is a HARD override, not a hint — forcing the
// wrong language makes whisper decode (effectively translate) the audio into that
// language. The spoken language is NOT the UI language, so we default to whisper's
// own auto-detection and only pin a language when a caller explicitly asks for one.
const langArg = (lang?: string): string => {
  const l = (lang || '').toLowerCase().slice(0, 2)
  if (l === 'de' || l === 'en' || l === 'es') return l
  return 'auto'
}

// Light, dictionary-free cleanup of a raw whisper transcript before it lands in
// chat. Safe for any language — only fixes whitespace, punctuation spacing,
// sentence capitalization and a missing final stop. It does NOT correct
// misspelled words (that needs a real spell/grammar engine — see notes).
const cleanupTranscript = (raw: string): string => {
  let s = (raw || '').replace(/\s+/g, ' ').trim()
  if (!s) return ''
  s = s.replace(/\s+([,.;:!?])/g, '$1')              // no space before punctuation
  s = s.replace(/([.!?])([A-Za-zÄÖÜäöü])/g, '$1 $2')  // space after . ! ? if glued to a word
  s = s.replace(/\s{2,}/g, ' ').trim()
  s = s.charAt(0).toUpperCase() + s.slice(1)          // capitalize the first word
  s = s.replace(/([.!?]\s+)(\p{Ll})/gu, (_m, p, c) => p + c.toUpperCase()) // …and each new sentence
  if (!/[.!?…]$/.test(s)) s += '.'                    // ensure a terminal stop
  return s
}

const resolveWhisper = () => {
  // Persistent install (survives npm install / deploys) with a node_modules fallback for dev.
  const persistentDir = '/opt/whisper-cpp'
  const nodeModulesDir = join(process.cwd(), 'node_modules/whisper-node/lib/whisper.cpp')
  const bin = existsSync(join(persistentDir, 'main')) ? join(persistentDir, 'main') : join(nodeModulesDir, 'main')
  const model = existsSync(join(persistentDir, 'ggml-base.bin'))
    ? join(persistentDir, 'ggml-base.bin')
    : join(nodeModulesDir, 'models/ggml-base.bin')
  return { bin, model }
}

/**
 * Transcribe an audio buffer. Returns the recognised text, or '' on any failure.
 * @param buffer   raw audio (typically audio/webm from MediaRecorder)
 * @param filename original filename (only used for the temp extension)
 * @param lang     'de' | 'en' | 'es' | undefined (→ auto)
 */
export const transcribeAudio = async (buffer: Buffer, filename = 'audio.webm', lang?: string): Promise<string> => {
  const { bin, model } = resolveWhisper()
  if (!existsSync(bin) || !existsSync(model)) {
    console.warn('[ChatTranscribe] whisper binary/model not found — skipping transcription')
    return ''
  }

  const tmpDir = join(process.cwd(), '.tmp-chat-voice')
  const safeName = filename.replace(/[^\w.\-]/g, '_')
  const stamp = `${Date.now()}-${Math.floor(Math.random() * 1e9)}`
  const srcPath = join(tmpDir, `${stamp}-${safeName}`)
  const wavPath = srcPath.replace(/\.\w+$/, '') + '.wav'

  try {
    if (!existsSync(tmpDir)) await mkdir(tmpDir, { recursive: true })
    await writeFile(srcPath, buffer)

    // 1) normalise to 16 kHz mono WAV
    await pexec(`ffmpeg -i "${srcPath}" -ar 16000 -ac 1 -y "${wavPath}"`, { timeout: 30000 })

    // 2) run whisper.cpp
    const { stdout } = await pexec(
      `"${bin}" -m "${model}" -l ${langArg(lang)} -f "${wavPath}"`,
      { timeout: 120000, maxBuffer: 10 * 1024 * 1024 }
    )

    // whisper prints "[00:00:00.000 --> 00:00:05.000]  text"
    const text = (stdout || '')
      .split('\n')
      .filter(line => line.includes('-->'))
      .map(line => { const m = line.match(/\]\s*(.*)$/); return m ? m[1].trim() : '' })
      .filter(Boolean)
      .join(' ')
      .trim()

    return cleanupTranscript(text)
  } catch (err: any) {
    console.error('[ChatTranscribe] failed:', err?.message || err)
    return ''
  } finally {
    try { await unlink(srcPath) } catch {}
    try { await unlink(wavPath) } catch {}
  }
}
