// ============================================================
// LOCAL WHISPER TRANSCRIPTION — uses whisper.cpp or faster-whisper
// ============================================================
// Requires: whisper.cpp binary or faster-whisper-cli installed on the host.
// Set TRANSCRIBE_PROVIDER=local and optionally WHISPER_MODEL_SIZE, WHISPER_BINARY.

var { execFile } = require('child_process');
var fs = require('fs');
var path = require('path');
var os = require('os');
var crypto = require('crypto');
var logger = require('./logger');

var WHISPER_BINARY = process.env.WHISPER_BINARY || 'whisper-cpp';
var WHISPER_MODEL_SIZE = process.env.WHISPER_MODEL_SIZE || 'small';
var WHISPER_MODEL_PATH = process.env.WHISPER_MODEL_PATH || '';
var WHISPER_LANGUAGE = process.env.WHISPER_LANGUAGE || 'en';
var WHISPER_THREADS = process.env.WHISPER_THREADS || String(Math.max(2, os.cpus().length - 1));

/**
 * Check if local Whisper is available
 */
function isLocalWhisperConfigured() {
  return process.env.TRANSCRIBE_PROVIDER === 'local';
}

/**
 * Detect which whisper binary is available
 * Supports: whisper-cpp, whisper, faster-whisper
 */
function detectBinary(callback) {
  var candidates = [WHISPER_BINARY, 'whisper-cpp', 'whisper', 'faster-whisper'];
  var tried = 0;
  function tryNext() {
    if (tried >= candidates.length) return callback(null);
    var bin = candidates[tried++];
    execFile(bin, ['--help'], { timeout: 5000 }, function(err) {
      if (!err || (err.code !== 'ENOENT' && err.code !== 127)) return callback(bin);
      tryNext();
    });
  }
  tryNext();
}

/**
 * Convert audio buffer to WAV using ffmpeg (whisper.cpp needs 16kHz mono WAV)
 */
function convertToWav(inputPath, callback) {
  var wavPath = inputPath + '.wav';
  execFile('ffmpeg', [
    '-i', inputPath,
    '-ar', '16000',
    '-ac', '1',
    '-f', 'wav',
    '-y', wavPath
  ], { timeout: 30000 }, function(err) {
    if (err) return callback(err, null);
    callback(null, wavPath);
  });
}

/**
 * Transcribe audio buffer using local Whisper
 * @param {Buffer} audioBuffer - Audio data
 * @param {string} mimeType - MIME type of the audio
 * @returns {Promise<string>} Transcribed text
 */
function transcribeWithLocal(audioBuffer, mimeType) {
  return new Promise(function(resolve, reject) {
    // Write buffer to temp file
    var ext = '.webm';
    if (mimeType && mimeType.includes('wav')) ext = '.wav';
    else if (mimeType && mimeType.includes('mp3')) ext = '.mp3';
    else if (mimeType && mimeType.includes('ogg')) ext = '.ogg';
    else if (mimeType && mimeType.includes('mp4')) ext = '.mp4';

    var tmpDir = os.tmpdir();
    var tmpFile = path.join(tmpDir, 'whisper_' + crypto.randomBytes(8).toString('hex') + ext);

    fs.writeFile(tmpFile, audioBuffer, function(err) {
      if (err) return reject(new Error('Failed to write temp audio: ' + err.message));

      // Convert to WAV for whisper.cpp compatibility
      convertToWav(tmpFile, function(convErr, wavPath) {
        var audioPath = convErr ? tmpFile : wavPath;

        detectBinary(function(binary) {
          if (!binary) {
            cleanup(tmpFile, wavPath);
            return reject(new Error(
              'Local Whisper not found. Install whisper.cpp (https://github.com/ggerganov/whisper.cpp) ' +
              'or faster-whisper (pip install faster-whisper). Set WHISPER_BINARY env var if using a custom path.'
            ));
          }

          var args = buildArgs(binary, audioPath);
          logger.info('[LocalWhisper] Running: ' + binary + ' ' + args.join(' '));

          execFile(binary, args, { timeout: 120000, maxBuffer: 10 * 1024 * 1024 }, function(execErr, stdout, stderr) {
            cleanup(tmpFile, wavPath);

            if (execErr) {
              logger.error('[LocalWhisper] Error:', execErr.message);
              if (stderr) logger.error('[LocalWhisper] stderr:', stderr.substring(0, 500));
              return reject(new Error('Local Whisper transcription failed: ' + execErr.message));
            }

            // Parse output — whisper.cpp outputs timestamped lines like [00:00:00.000 --> 00:00:05.000] text
            var text = parseOutput(stdout);
            if (!text || !text.trim()) {
              return reject(new Error('Local Whisper returned empty transcription'));
            }

            resolve(text.trim());
          });
        });
      });
    });
  });
}

/**
 * Build command-line args based on the detected binary
 */
function buildArgs(binary: string, audioPath: string): any[] {
  if (binary.includes('faster-whisper')) {
    // faster-whisper CLI
    var args: any[] = ['--model', WHISPER_MODEL_SIZE, '--language', WHISPER_LANGUAGE];
    if (WHISPER_THREADS) args.push('--threads', WHISPER_THREADS);
    args.push(audioPath);
    return args;
  }

  // whisper.cpp style
  var args: any[] = ['-f', audioPath, '-l', WHISPER_LANGUAGE, '-t', WHISPER_THREADS, '--no-timestamps'];
  if (WHISPER_MODEL_PATH) {
    args.push('-m', WHISPER_MODEL_PATH);
  } else {
    // whisper.cpp uses model names like ggml-small.bin
    args.push('-m', 'models/ggml-' + WHISPER_MODEL_SIZE + '.bin');
  }
  // Medical vocabulary hint via initial prompt
  args.push('--prompt', 'Medical patient encounter. Pediatric. Clinical terms, diagnoses, medications.');
  return args;
}

/**
 * Parse whisper output into clean text
 */
function parseOutput(stdout) {
  if (!stdout) return '';
  // whisper.cpp with --no-timestamps outputs plain text
  // whisper.cpp with timestamps: [00:00:00.000 --> 00:00:05.000]   Hello world
  var lines = stdout.split('\n');
  var text = [];
  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    // Strip timestamp prefix if present
    var match = line.match(/^\[[\d:.]+\s*-->\s*[\d:.]+\]\s*(.*)$/);
    if (match) {
      text.push(match[1].trim());
    } else if (line.trim() && !line.startsWith('whisper_') && !line.startsWith('main:')) {
      // Plain text line (skip whisper.cpp log lines)
      text.push(line.trim());
    }
  }
  return text.join(' ');
}

/**
 * Clean up temp files
 */
function cleanup(tmpFile, wavPath) {
  try { fs.unlinkSync(tmpFile); } catch(e) {}
  if (wavPath) { try { fs.unlinkSync(wavPath); } catch(e) {} }
}

module.exports = { transcribeWithLocal, isLocalWhisperConfigured };
