/**
 * Sarmad web wallet crypto.
 * - BIP39 + SHA-256 derive path matches Android Wallet.kt
 * - ML-DSA-87 (@noble) matches Android BouncyCastle 1.79 MLDSAParameters.ml_dsa_87
 *   from the same sha3_256(fullSeed) — same mnemonic yields the same address on APK and web.
 */
import { sha3_256 } from "https://esm.sh/@noble/hashes@1.7.1/sha3.js";
import { sha256 } from "https://esm.sh/@noble/hashes@1.7.1/sha256.js";
import { hmac } from "https://esm.sh/@noble/hashes@1.7.1/hmac.js";
import { ml_dsa87 } from "https://esm.sh/@noble/post-quantum@0.2.1/ml-dsa.js";
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "https://esm.sh/@scure/bip39@1.5.4";
import { wordlist } from "https://esm.sh/@scure/bip39@1.5.4/wordlists/english.js";
import { Sha1Prng, bytesToHex, hexToBytes } from "./sha1prng.js";

const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

export function normalizeSeedPhrase(phrase) {
  return String(phrase || "")
    .trim()
    .toLowerCase()
    .replace(/\s+/g, " ");
}

export function isValidSeedPhrase(phrase) {
  try {
    return validateMnemonic(normalizeSeedPhrase(phrase), wordlist);
  } catch {
    return false;
  }
}

export function createMnemonic() {
  return normalizeSeedPhrase(generateMnemonic(wordlist, 256));
}

export function bip39Seed(phrase) {
  return mnemonicToSeedSync(normalizeSeedPhrase(phrase), "");
}

/** Matches Wallet.deriveKeyPair: SHA256(seed||label) || SHA256(seed||label-1). */
export function deriveFullSeed(bipSeed, index = 0) {
  const label = new TextEncoder().encode(`sarmad-wallet-${index}`);
  const label1 = new TextEncoder().encode(`sarmad-wallet-${index}-1`);
  const a = sha256(concat(bipSeed, label));
  const b = sha256(concat(bipSeed, label1));
  const out = new Uint8Array(64);
  out.set(a, 0);
  out.set(b, 32);
  return out;
}

export function generateKeyPairFromSeed(fullSeed) {
  const seedBytes = sha3_256(fullSeed);
  // Deterministic ML-DSA-87 from protocol seed material (web standard).
  const keys = ml_dsa87.keygen(seedBytes);
  return {
    publicKey: keys.publicKey,
    privateKey: keys.secretKey,
    publicKeyHex: bytesToHex(keys.publicKey),
  };
}

export function publicKeyToAddress(publicKey) {
  return "SRMD" + base58Encode(sha3_256(publicKey));
}

export function sign(data, secretKey) {
  return ml_dsa87.sign(secretKey, data);
}

export function verify(data, signature, publicKey) {
  return ml_dsa87.verify(publicKey, data, signature);
}

export function sanitizeInviter(raw) {
  if (typeof raw !== "string") return null;
  const cleaned = raw
    .replace(/[\s\u0000-\u001f\u007f\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff]/g, "")
    .slice(0, 64);
  return cleaned || null;
}

export function createWalletFromPhrase(phrase, inviter = null) {
  const normalized = normalizeSeedPhrase(phrase);
  if (!isValidSeedPhrase(normalized)) throw new Error("Invalid seed phrase");
  const seed = bip39Seed(normalized);
  const full = deriveFullSeed(seed, 0);
  const kp = generateKeyPairFromSeed(full);
  const address = publicKeyToAddress(kp.publicKey);
  const cleaned = sanitizeInviter(inviter);
  return {
    seedPhrase: normalized,
    keyPairs: [kp],
    currentAddressIndex: 0,
    inviterAddress: cleaned && isValidSrmdAddress(cleaned) ? cleaned : null,
    getAddress: () => address,
    getPublicKeyHex: () => kp.publicKeyHex,
    generateAddress() {
      const idx = this.keyPairs.length;
      const fullN = deriveFullSeed(bip39Seed(this.seedPhrase), idx);
      const next = generateKeyPairFromSeed(fullN);
      this.keyPairs.push(next);
      return publicKeyToAddress(next.publicKey);
    },
  };
}

export function createNewWallet(inviter = null) {
  const mnemonic = createMnemonic();
  return { wallet: createWalletFromPhrase(mnemonic, inviter), mnemonic };
}

export function isValidSrmdAddress(addr) {
  return typeof addr === "string" && addr.startsWith("SRMD") && addr.length > 8;
}

export function sha3Hex(bytes) {
  return bytesToHex(sha3_256(bytes));
}

export function hmacSha256Hex(keyUtf8, msgUtf8) {
  const key = new TextEncoder().encode(String(keyUtf8));
  const msg = new TextEncoder().encode(String(msgUtf8));
  return bytesToHex(hmac(sha256, key, msg));
}

export { bytesToHex, hexToBytes, Sha1Prng, sha3_256 };

function concat(a, b) {
  const out = new Uint8Array(a.length + b.length);
  out.set(a, 0);
  out.set(b, a.length);
  return out;
}

function base58Encode(input) {
  if (!input.length) return "";
  const bytes = Uint8Array.from(input);
  let count = 0;
  for (const b of bytes) {
    if (b === 0) count++;
    else break;
  }
  const temp = new Uint8Array(bytes.length * 2);
  let j = temp.length;
  let startAt = count;
  while (startAt < bytes.length) {
    let carry = bytes[startAt];
    let i = 0;
    let k = temp.length - 1;
    while (k >= 0 && (carry !== 0 || i < j - (temp.length - bytes.length))) {
      carry += 256 * temp[k];
      temp[k] = carry % 58;
      carry = (carry / 58) | 0;
      k--;
      i++;
    }
    startAt++;
  }
  let result = "";
  for (const b of temp) {
    if (b !== 0 || result.length) result += ALPHABET[b];
  }
  for (let i = 0; i < count; i++) result = ALPHABET[0] + result;
  return result;
}

/** Parity self-check for SHA1PRNG + derive (run from console). */
export function runParitySelfCheck() {
  const fullSeed = new Uint8Array(64);
  for (let i = 0; i < 64; i++) fullSeed[i] = i;
  const sha3Seed = sha3_256(fullSeed);
  const rng = new Sha1Prng();
  rng.setSeed(sha3Seed);
  const out = new Uint8Array(32);
  rng.nextBytes(out);
  const exp = "916f515452b06dcf50c95187506e4790d351241f3c0515366b25070be24a7da2";
  const sha1ok = bytesToHex(out) === exp;
  const bipSeed = new Uint8Array(64).fill(7);
  const derived = deriveFullSeed(bipSeed, 0);
  const deriveExp =
    "219670a9821bf546d42210d51cee76233fcfebcee05c5e3baf1cdc0a8baa50f97343528c283395b2267c91796c6526d8ef4e10d1a0f2ac350ff39c990f0888a7";
  const deriveOk = bytesToHex(derived) === deriveExp;
  return { sha1ok, deriveOk };
}
