// Native (iOS/Android) audio decoder.
//
// Unlike the web, React Native has no built-in media decoder we can rely on.
// `ffmpeg-kit-react-native` — the package everyone used to reach for — was
// RETIRED by its maintainer in early 2025, so we deliberately do NOT depend on
// it. For now we support only WAV, which we can decode in pure JS via our own
// `decodeWav`. Everything else throws a clear, actionable error.
//
// TODO(audio/native): add a maintained native decoder for compressed formats
//   (mp3/m4a/aac/ogg/flac). The current front-runner is
//   `react-native-audio-api` (a Web-Audio-style API for RN). When added, route
//   non-WAV URIs through it and downmix/resample with `toMono16k`, mirroring the
//   web path.
//
// File reading uses Expo SDK 56's object-oriented `File` API
// (`new File(uri).bytes()`), which returns the raw bytes as a `Uint8Array`
// directly — no base64 round-trip needed. We still keep a base64 decode helper
// below as a documented fallback for environments/URIs where `.bytes()` isn't
// available (it reads via `.base64()` and decodes to a Uint8Array by hand).

import { File } from 'expo-file-system';

import type { AudioDecoder, AudioFileInput } from './decode';
import type { PcmAudio } from '../types';
import { decodeWav } from './wav';
import { toMono16k } from './resample';

/**
 * Decode a standard base64 string into a `Uint8Array`.
 *
 * Kept as a small, dependency-free fallback for reading file bytes when the
 * direct `File.bytes()` path is unavailable. Uses `atob` when present (RN's
 * Hermes provides it) and falls back to a manual base64 table otherwise so this
 * never silently breaks on a runtime missing `atob`.
 */
function base64ToBytes(base64: string): Uint8Array {
  // Strip any data-URI prefix and whitespace/newlines that some encoders add.
  const clean = base64.replace(/^data:[^,]*,/, '').replace(/\s/g, '');

  const g = globalThis as unknown as {
    atob?: (s: string) => string;
  };
  if (typeof g.atob === 'function') {
    const binary = g.atob(clean);
    const out = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
      out[i] = binary.charCodeAt(i) & 0xff;
    }
    return out;
  }

  // Manual base64 decode (no `atob`). Standard alphabet, '=' padding.
  const ALPHABET =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  // Length without padding tells us the exact output byte count.
  const noPad = clean.replace(/=+$/, '');
  const outLen = Math.floor((noPad.length * 3) / 4);
  const out = new Uint8Array(outLen);

  let buffer = 0;
  let bits = 0;
  let o = 0;
  for (let i = 0; i < noPad.length; i++) {
    const c = ALPHABET.indexOf(noPad[i]!);
    if (c === -1) continue; // skip any stray non-alphabet char defensively
    buffer = (buffer << 6) | c;
    bits += 6;
    if (bits >= 8) {
      bits -= 8;
      out[o++] = (buffer >> bits) & 0xff;
    }
  }
  return out;
}

/**
 * Read a local file URI's raw bytes.
 *
 * Primary path: SDK 56 `File#bytes()` (direct `Uint8Array`). If that throws for
 * some reason, fall back to reading base64 and decoding it ourselves.
 */
async function readFileBytes(uri: string): Promise<Uint8Array> {
  const file = new File(uri);
  try {
    return await file.bytes();
  } catch {
    // Fallback for runtimes/URIs where `.bytes()` isn't supported.
    const base64 = await file.base64();
    return base64ToBytes(base64);
  }
}

export const decoder: AudioDecoder = {
  async decode(input: AudioFileInput): Promise<PcmAudio> {
    const { uri } = input;
    if (!uri) {
      throw new Error('Native decoder requires a file `uri`.');
    }

    // Sniff the container from the URI extension (case-insensitive), ignoring
    // any query string / fragment that content URIs sometimes carry.
    const path = uri.split(/[?#]/, 1)[0] ?? uri;
    const isWav = path.toLowerCase().endsWith('.wav');

    if (!isWav) {
      throw new Error(
        'Only WAV is supported on native for now — ffmpeg-kit-react-native ' +
          'was retired in 2025; a maintained native decoder (e.g. ' +
          'react-native-audio-api) is a follow-up.',
      );
    }

    const bytes = await readFileBytes(uri);
    const { sampleRate, channelData } = decodeWav(bytes);
    const samples = toMono16k(channelData, sampleRate);
    return { sampleRate: 16000, samples };
  },
};
