/**
 * @file seedqr.ts
 * @summary Implements encoding and decoding for Seedsigner's SeedQR format.
 * @description This module provides functions to convert BIP39 mnemonics to and from the
 * SeedQR format, supporting both the Standard (numeric) and Compact (hex) variations.
 * The logic is adapted from the official Seedsigner specification and test vectors.
 */

import { BIP39_WORDLIST, WORD_INDEX, mnemonicToEntropy, entropyToMnemonic } from './seedblend';

// Helper to convert a hex string to a Uint8Array in a browser-compatible way.
function hexToUint8Array(hex: string): Uint8Array {
  if (hex.length % 2 !== 0) {
    throw new Error('Hex string must have an even number of characters');
  }
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
  }
  return bytes;
}

/**
 * Decodes a Standard SeedQR (numeric digit stream) into a mnemonic phrase.
 * @param digitStream A string containing 4-digit numbers representing BIP39 word indices.
 * @returns The decoded BIP39 mnemonic.
 */
function decodeStandardSeedQR(digitStream: string): string {
  if (digitStream.length % 4 !== 0) {
    throw new Error('Invalid Standard SeedQR: Length must be a multiple of 4.');
  }

  const wordIndices: number[] = [];
  for (let i = 0; i < digitStream.length; i += 4) {
    const indexStr = digitStream.slice(i, i + 4);
    const index = parseInt(indexStr, 10);
    if (isNaN(index) || index >= 2048) {
      throw new Error(`Invalid word index in SeedQR: ${indexStr}`);
    }
    wordIndices.push(index);
  }

  if (wordIndices.length !== 12 && wordIndices.length !== 24) {
    throw new Error(`Invalid word count from SeedQR: ${wordIndices.length}. Must be 12 or 24.`);
  }

  const mnemonicWords = wordIndices.map(index => BIP39_WORDLIST[index]);
  return mnemonicWords.join(' ');
}

/**
 * Decodes a Compact SeedQR (hex-encoded entropy) into a mnemonic phrase.
 * @param hexEntropy The hex-encoded entropy string.
 * @returns A promise that resolves to the decoded BIP39 mnemonic.
 */
async function decodeCompactSeedQR(hexEntropy: string): Promise<string> {
  const entropy = hexToUint8Array(hexEntropy);
  if (entropy.length !== 16 && entropy.length !== 32) {
    throw new Error(`Invalid entropy length for Compact SeedQR: ${entropy.length}. Must be 16 or 32 bytes.`);
  }
  return entropyToMnemonic(entropy);
}

/**
 * A unified decoder that automatically detects and parses a SeedQR string.
 * @param qrData The raw data from the QR code.
 * @returns A promise that resolves to the decoded BIP39 mnemonic.
 */
export async function decodeSeedQR(qrData: string): Promise<string> {
  const trimmed = qrData.trim();
  // Standard SeedQR is a string of only digits.
  if (/^\d+$/.test(trimmed)) {
    return decodeStandardSeedQR(trimmed);
  }
  // Compact SeedQR is a hex string.
  if (/^[0-9a-fA-F]+$/.test(trimmed)) {
    return decodeCompactSeedQR(trimmed);
  }
  throw new Error('Unsupported or invalid SeedQR format.');
}

/**
 * Encodes a mnemonic into the Standard SeedQR format (numeric digit stream).
 * @param mnemonic The BIP39 mnemonic string.
 * @returns A promise that resolves to the Standard SeedQR string.
 */
export async function encodeStandardSeedQR(mnemonic: string): Promise<string> {
  const words = mnemonic.trim().toLowerCase().split(/\s+/);
  if (words.length !== 12 && words.length !== 24) {
    throw new Error("Mnemonic must be 12 or 24 words to generate a SeedQR.");
  }

  const digitStream = words.map(word => {
    const index = WORD_INDEX.get(word);
    if (index === undefined) {
      throw new Error(`Invalid word in mnemonic: ${word}`);
    }
    return index.toString().padStart(4, '0');
  }).join('');

  return digitStream;
}

/**
 * Encodes a mnemonic into the Compact SeedQR format (raw entropy bytes).
 * @param mnemonic The BIP39 mnemonic string.
 * @returns A promise that resolves to the Compact SeedQR entropy as a Uint8Array.
 */
export async function encodeCompactSeedQREntropy(mnemonic: string): Promise<Uint8Array> {
  return await mnemonicToEntropy(mnemonic);
}
