import * as ed25519 from './vendor/@noble/ed25519/index.js';
import { sha512 } from './vendor/@noble/hashes/sha2.js';
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from './vendor/@scure/bip39/index.js';
import { wordlist } from './vendor/@scure/bip39/wordlists/english.js';

const WALLET_VERSION = 'mazex-wallet-1.0';
const ADDRESS_PREFIX = 'mazex1';
const PRIVATE_DER_PREFIX = '302e020100300506032b657004220420';
const PUBLIC_DER_PREFIX = '302a300506032b6570032100';
const DERIVATION_DOMAIN = 'mazexchain-native-ed25519-v1';

ed25519.hashes.sha512 = sha512;

const textEncoder = new TextEncoder();

function canonical(value) {
  if (Array.isArray(value)) return value.map(canonical);
  if (value && typeof value === 'object') {
    return Object.keys(value)
      .sort()
      .reduce((acc, key) => {
        acc[key] = canonical(value[key]);
        return acc;
      }, {});
  }
  return value;
}

function canonicalString(value) {
  return JSON.stringify(canonical(value));
}

function bytesToHex(bytes) {
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
}

function hexToBytes(hex) {
  const clean = String(hex).replace(/^0x/i, '').replace(/\s+/g, '');
  if (clean.length % 2 !== 0) throw new Error('Invalid hex length');
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < out.length; i += 1) out[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
  return out;
}

function concatBytes(...chunks) {
  const total = chunks.reduce((sum, item) => sum + item.length, 0);
  const out = new Uint8Array(total);
  let offset = 0;
  for (const item of chunks) {
    out.set(item, offset);
    offset += item.length;
  }
  return out;
}

function bytesToBase64(bytes) {
  if (typeof Buffer !== 'undefined') return Buffer.from(bytes).toString('base64');
  let binary = '';
  for (let i = 0; i < bytes.length; i += 0x8000) {
    binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
  }
  return btoa(binary);
}

function base64ToBytes(base64) {
  if (typeof Buffer !== 'undefined') return new Uint8Array(Buffer.from(base64, 'base64'));
  const binary = atob(base64);
  const out = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
  return out;
}

async function sha256Bytes(bytes) {
  const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
  return new Uint8Array(digest);
}

async function sha256Hex(value) {
  const bytes = typeof value === 'string' ? textEncoder.encode(value) : value;
  return `0x${bytesToHex(await sha256Bytes(bytes))}`;
}

function derToPem(derBytes, label) {
  const base64 = bytesToBase64(derBytes);
  const lines = base64.match(/.{1,64}/g) || [];
  return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----`;
}

function pemToDer(pem, label) {
  const clean = String(pem || '')
    .replace(`-----BEGIN ${label}-----`, '')
    .replace(`-----END ${label}-----`, '')
    .replace(/\s+/g, '');
  if (!clean) throw new Error(`Invalid ${label.toLowerCase()} PEM`);
  return base64ToBytes(clean);
}

function privatePemFromSeed(secretKey) {
  return derToPem(concatBytes(hexToBytes(PRIVATE_DER_PREFIX), secretKey), 'PRIVATE KEY');
}

function publicPemFromBytes(publicKey) {
  return derToPem(concatBytes(hexToBytes(PUBLIC_DER_PREFIX), publicKey), 'PUBLIC KEY');
}

function seedFromPrivatePem(privateKeyPem) {
  const der = pemToDer(privateKeyPem, 'PRIVATE KEY');
  const hex = bytesToHex(der);
  if (!hex.startsWith(PRIVATE_DER_PREFIX) || der.length !== 48) {
    throw new Error('Only Ed25519 PKCS#8 private keys are supported.');
  }
  return der.slice(16);
}

async function addressFromPublicKeyBytes(publicKey) {
  const der = concatBytes(hexToBytes(PUBLIC_DER_PREFIX), publicKey);
  const digest = await sha256Bytes(der);
  return `${ADDRESS_PREFIX}${bytesToHex(digest).slice(0, 40)}`;
}

async function walletFromSecretKey(secretKey, extra = {}) {
  if (!(secretKey instanceof Uint8Array) || secretKey.length !== 32) {
    throw new Error('Native wallet secret key must be 32 bytes.');
  }
  const publicKey = await ed25519.getPublicKeyAsync(secretKey);
  const publicKeyPem = publicPemFromBytes(publicKey);
  const privateKeyPem = privatePemFromSeed(secretKey);
  return {
    version: WALLET_VERSION,
    type: 'native',
    algorithm: 'ed25519',
    address: await addressFromPublicKeyBytes(publicKey),
    public_key: publicKeyPem,
    private_key: privateKeyPem,
    ...extra,
  };
}

async function secretFromMnemonic(mnemonic, passphrase = '') {
  const normalized = String(mnemonic || '').trim().replace(/\s+/g, ' ');
  if (!validateMnemonic(normalized, wordlist)) throw new Error('Invalid BIP-39 mnemonic phrase.');
  const bip39Seed = mnemonicToSeedSync(normalized, passphrase);
  return sha256Bytes(concatBytes(textEncoder.encode(DERIVATION_DOMAIN), bip39Seed));
}

export async function createWallet({ strength = 256, passphrase = '' } = {}) {
  const mnemonic = generateMnemonic(wordlist, strength);
  const secretKey = await secretFromMnemonic(mnemonic, passphrase);
  return walletFromSecretKey(secretKey, {
    mnemonic,
    created_at: new Date().toISOString(),
    derivation: DERIVATION_DOMAIN,
  });
}

export async function importMnemonic(mnemonic, { passphrase = '' } = {}) {
  const normalized = String(mnemonic || '').trim().replace(/\s+/g, ' ');
  const secretKey = await secretFromMnemonic(normalized, passphrase);
  return walletFromSecretKey(secretKey, {
    mnemonic: normalized,
    imported_at: new Date().toISOString(),
    derivation: DERIVATION_DOMAIN,
  });
}

export async function importPrivateKey(privateKeyPem) {
  const secretKey = seedFromPrivatePem(privateKeyPem);
  return walletFromSecretKey(secretKey, {
    imported_at: new Date().toISOString(),
    derivation: 'ed25519-pkcs8',
  });
}

export async function payloadHash(payload) {
  return sha256Hex(canonicalString({ wallet_version: WALLET_VERSION, payload }));
}

export async function signPayload(wallet, payload) {
  if (!wallet) throw new Error('Native wallet is not connected.');
  let activeWallet = wallet;
  if (!activeWallet.private_key && activeWallet.mnemonic) {
    activeWallet = await importMnemonic(activeWallet.mnemonic);
  }
  if (!activeWallet.private_key) throw new Error('Native wallet key material is missing.');
  const secretKey = seedFromPrivatePem(activeWallet.private_key);
  const message = textEncoder.encode(canonicalString({ wallet_version: WALLET_VERSION, payload }));
  const signature = await ed25519.signAsync(message, secretKey);
  return {
    algorithm: 'ed25519',
    address: activeWallet.address,
    public_key: activeWallet.public_key,
    payload,
    payload_hash: await payloadHash(payload),
    signature: bytesToBase64(signature),
  };
}

export function isMnemonic(value) {
  return validateMnemonic(String(value || '').trim().replace(/\s+/g, ' '), wordlist);
}

export const walletCrypto = {
  createWallet,
  importMnemonic,
  importPrivateKey,
  isMnemonic,
  payloadHash,
  signPayload,
};

if (typeof window !== 'undefined') {
  window.MazeXWalletCrypto = walletCrypto;
}
