// Native accelerators for the crypto ops whose JS/WASM runtime is slow
// enough to matter. The sync `CryptoBackend` interface stays on WASM because
// migrating every call-site to async would break most of the shared crypto
// code for no runtime win — per-message AEAD already runs in a worker and
// its 15µs-vs-80µs delta is lost in IPC overhead (~40µs round-trip).
//
// The two ops we actually accelerate:
//   * Argon2id password hashing (vault unlock, backup import/export)
//   * Randomness (fast enough inline, but we expose it so future Rust-only
//     flows don't need to route through the WASM backend).
//
// All fall back to WASM when the Tauri runtime isn't present (browser
// preview, dev server) so the same code paths keep working.

import _sodium from 'libsodium-wrappers-sumo';

// NOTE: After the Tauri → Electron migration the native Rust crypto
// commands are no longer available — we always fall through to the
// WASM path below. The `invoke`-based call-sites are kept in this file
// as reference / future re-enable points once we expose main-process
// crypto accelerators via IPC again, but `nativeAvailable()` now
// unconditionally returns false so they never run.

// Local shim so the unreachable `invoke` references still type-check
// without a dependency on the deleted Tauri package.
const invoke = async <T>(_cmd: string, _args?: unknown): Promise<T> => {
  throw new Error('native crypto invoke disabled post-Electron-migration');
};

function bytesToB64(bytes: Uint8Array): string {
  let s = '';
  for (const b of bytes) s += String.fromCharCode(b);
  return btoa(s);
}

function b64ToBytes(s: string): Uint8Array {
  const bin = atob(s);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

// Environment flag. Default ON in Tauri, OFF in browser-preview. Can be
// force-disabled by setting VITE_USE_NATIVE_CRYPTO=false for debugging
// a regression against the WASM baseline.
const flagEnabled = (() => {
  const raw = (import.meta as unknown as { env?: { VITE_USE_NATIVE_CRYPTO?: string } })
    .env?.VITE_USE_NATIVE_CRYPTO;
  if (raw === 'false' || raw === '0') return false;
  return true;
})();

function nativeAvailable(): boolean {
  // Crypto native commands deferred for post-Electron-migration. We
  // run everything through the WASM path until there's a measured
  // reason to re-introduce a main-process accelerator.
  void flagEnabled;
  return false;
}

// ---------------------------------------------------------------------------
// Argon2id password hashing
// ---------------------------------------------------------------------------

export interface PwhashOpts {
  password: string;
  salt: Uint8Array;
  outLen: number;
  preset?: 'interactive' | 'moderate' | 'sensitive';
}

export async function pwhashArgon2id(opts: PwhashOpts): Promise<Uint8Array> {
  if (nativeAvailable()) {
    try {
      const b64 = await invoke<string>('crypto_pwhash', {
        args: {
          password: opts.password,
          saltB64: bytesToB64(opts.salt),
          outLen: opts.outLen,
          preset: opts.preset ?? 'moderate',
        },
      });
      return b64ToBytes(b64);
    } catch (err: unknown) {
      // Tauri command not registered (e.g. older build) or unexpected failure.
      // Fall through to WASM so the app stays functional.
      console.warn('[nativeCryptoOps] pwhash native failed, falling back', err);
    }
  }
  await _sodium.ready;
  const s = _sodium;
  const presetPair = (() => {
    switch (opts.preset ?? 'moderate') {
      case 'interactive':
        return [s.crypto_pwhash_OPSLIMIT_INTERACTIVE, s.crypto_pwhash_MEMLIMIT_INTERACTIVE];
      case 'sensitive':
        return [s.crypto_pwhash_OPSLIMIT_SENSITIVE, s.crypto_pwhash_MEMLIMIT_SENSITIVE];
      default:
        return [s.crypto_pwhash_OPSLIMIT_MODERATE, s.crypto_pwhash_MEMLIMIT_MODERATE];
    }
  })();
  return s.crypto_pwhash(
    opts.outLen,
    opts.password,
    opts.salt,
    presetPair[0]!,
    presetPair[1]!,
    s.crypto_pwhash_ALG_ARGON2ID13,
  );
}

// ---------------------------------------------------------------------------
// Randombytes — native-first, WASM fallback. Used for non-hot-path nonces
// and tokens; inline callers that need sync random should stay on the
// backend interface.
// ---------------------------------------------------------------------------

export async function randomBytesAsync(len: number): Promise<Uint8Array> {
  if (nativeAvailable()) {
    try {
      const b64 = await invoke<string>('crypto_random_bytes', { len });
      return b64ToBytes(b64);
    } catch (err: unknown) {
      console.warn('[nativeCryptoOps] random native failed, falling back', err);
    }
  }
  await _sodium.ready;
  return _sodium.randombytes_buf(len);
}

// ---------------------------------------------------------------------------
// Secretbox + box accelerators — exposed but not auto-adopted. Call-sites
// can migrate individually if they measure a win. Round-trip IPC is ~40µs
// so per-op wins under 100µs usually lose to the WASM path.
// ---------------------------------------------------------------------------

export async function secretboxEncryptNative(
  plaintext: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array,
): Promise<Uint8Array> {
  if (!nativeAvailable()) throw new Error('native crypto unavailable');
  const b64 = await invoke<string>('crypto_secretbox_encrypt', {
    plaintextB64: bytesToB64(plaintext),
    nonceB64: bytesToB64(nonce),
    keyB64: bytesToB64(key),
  });
  return b64ToBytes(b64);
}

export async function secretboxDecryptNative(
  ciphertext: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array,
): Promise<Uint8Array> {
  if (!nativeAvailable()) throw new Error('native crypto unavailable');
  const b64 = await invoke<string>('crypto_secretbox_decrypt', {
    ciphertextB64: bytesToB64(ciphertext),
    nonceB64: bytesToB64(nonce),
    keyB64: bytesToB64(key),
  });
  return b64ToBytes(b64);
}
