// ============================================================
// TRANSCRIBE-AWS.JS — Amazon Transcribe Streaming
// No S3 required. Audio streams directly to AWS.
//
// Audio conversion: ffmpeg converts browser WebM/Opus → PCM 16kHz
// which is the most reliable format for AWS Transcribe. If ffmpeg
// is not installed, falls back to sending ogg-opus directly.
//
// Env vars:
//   TRANSCRIBE_PROVIDER=aws          — use this instead of Whisper
//   AWS_TRANSCRIBE_MEDICAL=true      — use Transcribe Medical (better
//                                      accuracy for clinical dictation)
//   AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE  — medical specialty
//     Options: PRIMARYCARE, CARDIOLOGY, NEUROLOGY, ONCOLOGY,
//              RADIOLOGY, UROLOGY
//   AWS_BEDROCK_REGION               — reused for Transcribe region
//   AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY — reused credentials
// ============================================================

const { spawn } = require('child_process');

const CHUNK_SIZE = 8192; // 8 KB per audio chunk (AWS limit ~32KB per event frame)

// Convert any browser audio (WebM, OGG, etc.) to raw PCM s16le 16kHz mono
// using ffmpeg. Returns a Buffer of raw PCM bytes.
// Throws if ffmpeg is not installed.
function convertToPCM(inputBuffer) {
  return new Promise(function(resolve, reject) {
    var ff = spawn('ffmpeg', [
      '-i', 'pipe:0',       // read from stdin
      '-f', 's16le',        // raw signed 16-bit little-endian PCM
      '-ar', '16000',       // 16 kHz — ideal for speech recognition
      '-ac', '1',           // mono
      '-acodec', 'pcm_s16le',
      'pipe:1'              // write to stdout
    ]);

    var out = [];
    var errBuf = [];

    ff.stdout.on('data', function(d) { out.push(d); });
    ff.stderr.on('data', function(d) { errBuf.push(d); }); // ffmpeg logs to stderr, not an error

    ff.on('close', function(code) {
      if (code !== 0) {
        reject(new Error('ffmpeg conversion failed (code ' + code + '): ' + Buffer.concat(errBuf).toString().slice(-200)));
        return;
      }
      resolve(Buffer.concat(out));
    });

    ff.on('error', function(err) {
      reject(new Error('ffmpeg not found. Install ffmpeg on the server: ' + err.message));
    });

    ff.stdin.write(inputBuffer);
    ff.stdin.end();
  });
}

async function* makeAudioStream(buffer) {
  // 8KB chunks — larger sizes cause AWS SDK deserialization errors
  // ("to see the raw response, inspect the hidden field {error}.$response").
  // No artificial delay between chunks; yield with a microtask break
  // to avoid blocking the event loop without adding real latency.
  for (var i = 0; i < buffer.length; i += CHUNK_SIZE) {
    yield { AudioEvent: { AudioChunk: buffer.slice(i, i + CHUNK_SIZE) } };
    // Microtask break every 16 chunks (~128KB) to keep event loop responsive
    if ((i / CHUNK_SIZE) % 16 === 15) {
      await new Promise(function(r) { setImmediate(r); });
    }
  }
}

function logTranscribeError(label, err) {
  console.error('[Transcribe] ' + label + ':', err.message);
  if (err.name) console.error('[Transcribe]   name:', err.name);
  if (err.$metadata) console.error('[Transcribe]   metadata:', JSON.stringify(err.$metadata));
  if (err.cause) console.error('[Transcribe]   cause:', err.cause.message || JSON.stringify(err.cause));
  if (err.$response) {
    try {
      var resp = err.$response;
      console.error('[Transcribe]   status:', resp.statusCode);
      if (resp.headers) console.error('[Transcribe]   headers:', JSON.stringify(resp.headers));
      if (resp.body) console.error('[Transcribe]   body:', typeof resp.body === 'string' ? resp.body.slice(0, 500) : '(stream)');
    } catch (e) { /* ignore logging errors */ }
  }
}

async function transcribeWithAWS(audioBuffer, mimeType) {
  var startTime = Date.now();
  var TranscribeModule = require('@aws-sdk/client-transcribe-streaming');
  var TranscribeStreamingClient = TranscribeModule.TranscribeStreamingClient;

  var region = process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION || 'us-east-1';
  var credentials = process.env.AWS_ACCESS_KEY_ID ? {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  } : undefined;

  // Try ffmpeg conversion to PCM (most reliable with AWS Transcribe).
  // Falls back to ogg-opus passthrough if ffmpeg is not installed.
  var audioToSend = audioBuffer;
  var mediaEncoding = 'ogg-opus';
  var sampleRate = 48000;

  try {
    var ffStart = Date.now();
    audioToSend = await convertToPCM(audioBuffer);
    mediaEncoding = 'pcm';
    sampleRate = 16000;
    console.log('[Transcribe] ffmpeg: ' + (Date.now() - ffStart) + 'ms — ' + audioBuffer.length + ' → ' + audioToSend.length + ' bytes');
  } catch (ffErr) {
    console.warn('[Transcribe] ffmpeg unavailable, sending ogg-opus directly:', ffErr.message);
    if (mimeType && mimeType.indexOf('wav') !== -1) {
      mediaEncoding = 'pcm';
      sampleRate = 16000;
    }
  }

  // Minimum ~0.5s of PCM audio at 16kHz mono 16-bit = 16000 bytes
  if (mediaEncoding === 'pcm' && audioToSend.length < 16000) {
    console.warn('[Transcribe] Audio too short (' + audioToSend.length + ' bytes), skipping');
    return '';
  }

  var useMedical = process.env.AWS_TRANSCRIBE_MEDICAL === 'true';
  var specialty = process.env.AWS_TRANSCRIBE_SPECIALTY || 'PRIMARYCARE';
  var transcript = '';

  // Helper to collect transcript from a result stream
  async function collectTranscript(resultStream) {
    var text = '';
    for await (var event of resultStream) {
      if (event.TranscriptEvent && event.TranscriptEvent.Transcript) {
        var results = event.TranscriptEvent.Transcript.Results || [];
        for (var i = 0; i < results.length; i++) {
          var r = results[i];
          if (!r.IsPartial && r.Alternatives && r.Alternatives[0]) {
            text += r.Alternatives[0].Transcript + ' ';
          }
        }
      }
    }
    return text;
  }

  // Try Medical first if enabled, with fallback to Standard
  var streamStart = Date.now();
  if (useMedical) {
    try {
      var client = new TranscribeStreamingClient({ region: region, credentials: credentials });
      var medCmd = new TranscribeModule.StartMedicalStreamTranscriptionCommand({
        LanguageCode: 'en-US',
        MediaSampleRateHertz: sampleRate,
        MediaEncoding: mediaEncoding,
        Specialty: specialty,
        Type: 'DICTATION',
        AudioStream: makeAudioStream(audioToSend)
      });
      var medResp = await client.send(medCmd);
      transcript = await collectTranscript(medResp.TranscriptResultStream);
      console.log('[Transcribe] Medical OK — ' + (Date.now() - streamStart) + 'ms, ' + transcript.length + ' chars');
    } catch (medErr) {
      logTranscribeError('Medical failed', medErr);
      console.warn('[Transcribe] Falling back to standard...');
      streamStart = Date.now();
      try {
        var client2 = new TranscribeStreamingClient({ region: region, credentials: credentials });
        var stdCmd = new TranscribeModule.StartStreamTranscriptionCommand({
          LanguageCode: 'en-US',
          MediaSampleRateHertz: sampleRate,
          MediaEncoding: mediaEncoding,
          AudioStream: makeAudioStream(audioToSend)
        });
        var stdResp = await client2.send(stdCmd);
        transcript = await collectTranscript(stdResp.TranscriptResultStream);
        console.log('[Transcribe] Standard fallback OK — ' + (Date.now() - streamStart) + 'ms, ' + transcript.length + ' chars');
      } catch (stdErr) {
        logTranscribeError('Standard also failed', stdErr);
        throw stdErr;
      }
    }
  } else {
    var client = new TranscribeStreamingClient({ region: region, credentials: credentials });
    var cmd = new TranscribeModule.StartStreamTranscriptionCommand({
      LanguageCode: 'en-US',
      MediaSampleRateHertz: sampleRate,
      MediaEncoding: mediaEncoding,
      AudioStream: makeAudioStream(audioToSend)
    });
    var resp = await client.send(cmd);
    transcript = await collectTranscript(resp.TranscriptResultStream);
    console.log('[Transcribe] Standard OK — ' + (Date.now() - streamStart) + 'ms, ' + transcript.length + ' chars');
  }

  console.log('[Transcribe] Total: ' + (Date.now() - startTime) + 'ms');
  return transcript.trim();
}

// Check if AWS Transcribe is available and configured
function isAWSTranscribeConfigured() {
  try {
    require('@aws-sdk/client-transcribe-streaming');
    return !!(process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION);
  } catch (e) {
    return false;
  }
}

module.exports = { transcribeWithAWS, isAWSTranscribeConfigured };
