// Nostr cryptographic utilities for key generation and encoding
// This implements proper secp256k1 key generation and bech32 encoding

// bech32 encoding implementation
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];

function bech32Polymod(values) {
    let chk = 1;
    for (let i = 0; i < values.length; i++) {
        const top = chk >> 25;
        chk = (chk & 0x1ffffff) << 5 ^ values[i];
        for (let j = 0; j < 5; j++) {
            chk ^= ((top >> j) & 1) ? GENERATOR[j] : 0;
        }
    }
    return chk;
}

function bech32HrpExpand(hrp) {
    const ret = [];
    for (let i = 0; i < hrp.length; i++) {
        ret.push(hrp.charCodeAt(i) >> 5);
    }
    ret.push(0);
    for (let i = 0; i < hrp.length; i++) {
        ret.push(hrp.charCodeAt(i) & 31);
    }
    return ret;
}

function bech32CreateChecksum(hrp, data) {
    const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
    const mod = bech32Polymod(values) ^ 1;
    const ret = [];
    for (let i = 0; i < 6; i++) {
        ret.push((mod >> 5 * (5 - i)) & 31);
    }
    return ret;
}

function bech32Encode(hrp, data) {
    const combined = data.concat(bech32CreateChecksum(hrp, data));
    let ret = hrp + '1';
    for (let i = 0; i < combined.length; i++) {
        ret += CHARSET.charAt(combined[i]);
    }
    return ret;
}

function convertBits(data, fromBits, toBits, pad = true) {
    let acc = 0;
    let bits = 0;
    const ret = [];
    const maxv = (1 << toBits) - 1;
    const maxAcc = (1 << (fromBits + toBits - 1)) - 1;
    
    for (let i = 0; i < data.length; i++) {
        const value = data[i];
        if (value < 0 || (value >> fromBits) !== 0) {
            return null;
        }
        acc = ((acc << fromBits) | value) & maxAcc;
        bits += fromBits;
        while (bits >= toBits) {
            bits -= toBits;
            ret.push((acc >> bits) & maxv);
        }
    }
    
    if (pad) {
        if (bits > 0) {
            ret.push((acc << (toBits - bits)) & maxv);
        }
    } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) {
        return null;
    }
    
    return ret;
}

// secp256k1 point operations (simplified implementation)
const CURVE = {
    p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn,
    n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n,
    Gx: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n,
    Gy: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n
};

function modInverse(a, m) {
    if (a < 0n) a = ((a % m) + m) % m;
    const egcd = (a, b) => {
        if (a === 0n) return [b, 0n, 1n];
        const [gcd, x1, y1] = egcd(b % a, a);
        return [gcd, y1 - (b / a) * x1, x1];
    };
    const [gcd, x] = egcd(a % m, m);
    if (gcd !== 1n) throw new Error('Modular inverse does not exist');
    return ((x % m) + m) % m;
}

function pointAdd(p1, p2) {
    if (!p1) return p2;
    if (!p2) return p1;
    
    const [x1, y1] = p1;
    const [x2, y2] = p2;
    
    if (x1 === x2) {
        if (y1 === y2) {
            // Point doubling
            const s = (3n * x1 * x1 * modInverse(2n * y1, CURVE.p)) % CURVE.p;
            const x3 = (s * s - 2n * x1) % CURVE.p;
            const y3 = (s * (x1 - x3) - y1) % CURVE.p;
            return [(x3 + CURVE.p) % CURVE.p, (y3 + CURVE.p) % CURVE.p];
        } else {
            return null; // Point at infinity
        }
    }
    
    const s = ((y2 - y1) * modInverse(x2 - x1, CURVE.p)) % CURVE.p;
    const x3 = (s * s - x1 - x2) % CURVE.p;
    const y3 = (s * (x1 - x3) - y1) % CURVE.p;
    return [(x3 + CURVE.p) % CURVE.p, (y3 + CURVE.p) % CURVE.p];
}

function pointMultiply(k, point = [CURVE.Gx, CURVE.Gy]) {
    if (k === 0n) return null;
    if (k === 1n) return point;
    
    let result = null;
    let addend = point;
    
    while (k > 0n) {
        if (k & 1n) {
            result = pointAdd(result, addend);
        }
        addend = pointAdd(addend, addend);
        k >>= 1n;
    }
    
    return result;
}

// Main key generation function
async function generateNostrKeyPair() {
    // Generate random 32-byte private key
    const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32));
    
    // Ensure the private key is within the valid range for secp256k1
    let privateKeyBigInt = 0n;
    for (let i = 0; i < 32; i++) {
        privateKeyBigInt = (privateKeyBigInt << 8n) + BigInt(privateKeyBytes[i]);
    }
    
    // Make sure it's less than the curve order
    if (privateKeyBigInt >= CURVE.n) {
        privateKeyBigInt = privateKeyBigInt % CURVE.n;
    }
    
    // Convert back to bytes
    const privateKeyHex = privateKeyBigInt.toString(16).padStart(64, '0');
    
    // Generate public key using secp256k1 point multiplication
    const publicPoint = pointMultiply(privateKeyBigInt);
    if (!publicPoint) {
        throw new Error('Failed to generate public key');
    }
    
    // Get x-coordinate only (compressed public key)
    const publicKeyHex = publicPoint[0].toString(16).padStart(64, '0');
    
    // Convert to bech32 encoding
    const privateKeyBytes32 = [];
    const publicKeyBytes32 = [];
    
    for (let i = 0; i < 64; i += 2) {
        privateKeyBytes32.push(parseInt(privateKeyHex.substr(i, 2), 16));
        publicKeyBytes32.push(parseInt(publicKeyHex.substr(i, 2), 16));
    }
    
    const privateBech32Data = convertBits(privateKeyBytes32, 8, 5);
    const publicBech32Data = convertBits(publicKeyBytes32, 8, 5);
    
    const nsec = bech32Encode('nsec', privateBech32Data);
    const npub = bech32Encode('npub', publicBech32Data);
    
    return {
        privateKey: privateKeyHex,
        publicKey: publicKeyHex,
        npub: npub,
        nsec: nsec
    };
}

// Export for use in other scripts
window.NostrCrypto = {
    generateKeyPair: generateNostrKeyPair,
    bech32Encode,
    convertBits
};