/**
 * 🔒 TAPROOT SIGNER - 100% LOCAL (ZERO-TRUST)
 * 
 * Assina transações Bitcoin Taproot LOCALMENTE.
 * Mnemonic NUNCA sai do dispositivo!
 * 
 * Suporta:
 * - SIGHASH_DEFAULT (0x00) - para sends normais
 * - SIGHASH_ALL (0x01) - para sends normais
 * - SIGHASH_SINGLE|ANYONECANPAY (0x83) - para marketplace listings
 * - SIGHASH_ALL|ANYONECANPAY (0x81) - para marketplace buys
 * - inputsToSign específicos
 * - autoFinalize configurável
 * 
 * Este arquivo será bundled com webpack para uso na extension.
 * 
 * ⚡ Uses @noble/secp256k1 for pure JS compatibility (no WASM)
 */

const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
const { BIP32Factory } = require('bip32');
const ecc = require('@bitcoinerlab/secp256k1');
const { schnorr, getPublicKey, getSharedSecret } = require('@noble/secp256k1');
const { sha256 } = require('@noble/hashes/sha2.js');
const { hmac } = require('@noble/hashes/hmac.js');

// Initialize bip32 with ecc (for key derivation only)
const bip32 = BIP32Factory(ecc);

// Initialize ECC for bitcoinjs-lib
bitcoin.initEccLib(ecc);

// 🔧 CRITICAL: Configure @noble/secp256k1 with hash functions
// v3 requires explicit hash function configuration for SYNC operations
const nobleSecp = require('@noble/secp256k1');
nobleSecp.hashes = nobleSecp.hashes || {};
nobleSecp.hashes.hmacSha256 = (key, ...msgs) => {
    const merged = msgs.reduce((acc, msg) => new Uint8Array([...acc, ...msg]), new Uint8Array());
    return hmac(sha256, key, merged);
};
nobleSecp.hashes.sha256 = (...msgs) => {
    const merged = msgs.reduce((acc, msg) => new Uint8Array([...acc, ...msg]), new Uint8Array());
    return sha256(merged);
};

const network = bitcoin.networks.bitcoin;

// SIGHASH constants
const SIGHASH_DEFAULT = 0x00;
const SIGHASH_ALL = 0x01;
const SIGHASH_NONE = 0x02;
const SIGHASH_SINGLE = 0x03;
const SIGHASH_ANYONECANPAY = 0x80;

/**
 * Assinar PSBT localmente (100% SEGURO) - VERSÃO COMPLETA
 * 
 * @param {string} mnemonic - 12 palavras (SÓ usado localmente!)
 * @param {string} psbtBase64 - PSBT unsigned
 * @param {Object} options - Opções de assinatura
 * @param {Array} options.inputsToSign - Índices dos inputs para assinar (null = todos)
 * @param {number|string} options.sighashType - Tipo de sighash (0x00, 0x01, 0x83, 'SINGLE|ANYONECANPAY', etc.)
 * @param {boolean} options.autoFinalize - Se deve finalizar automaticamente (default: true)
 * @returns {Object} { signedPsbt: string, txHex?: string }
 */
async function signPsbtTaprootLocal(mnemonic, psbtBase64, options = {}) {
    try {
        console.log('🔐 ═══════════════════════════════════════════════════════════════');
        console.log('   SIGNING PSBT 100% LOCALLY - MNEMONIC NEVER LEAVES DEVICE!');
        console.log('═══════════════════════════════════════════════════════════════');
        
        // Parse options
        const inputsToSign = options.inputsToSign || null;
        const autoFinalize = options.autoFinalize !== false; // default true
        let sighashType = parseSighashType(options.sighashType);
        
        console.log('   📋 Options:');
        console.log(`      inputsToSign: ${inputsToSign ? JSON.stringify(inputsToSign) : 'ALL'}`);
        console.log(`      sighashType: 0x${sighashType.toString(16)} (${getSighashName(sighashType)})`);
        console.log(`      autoFinalize: ${autoFinalize}`);
        
        // 1. Derivar seed do mnemonic
        const seed = await bip39.mnemonicToSeed(mnemonic);
        console.log('   ✅ Seed derived from mnemonic');
        
        // 2. Derivar chave BIP86 Taproot
        const root = bip32.fromSeed(seed, network);
        const path = "m/86'/0'/0'/0/0";
        const childRaw = root.derivePath(path);
        console.log('   ✅ Key derived:', path);
        
        // 3. Obter internal key (x-only)
        const xOnlyInternal = Buffer.from(childRaw.publicKey.subarray(1, 33));
        
        // 4. Aplicar Taproot tweak
        const tapTweak = bitcoin.crypto.taggedHash('TapTweak', xOnlyInternal);
        let tweakedPrivateKey = ecc.privateAdd(childRaw.privateKey, tapTweak);
        
        if (!tweakedPrivateKey) {
            throw new Error('Failed to tweak private key');
        }
        
        // 5. Garantir Y coordinate even (Taproot requirement)
        const tweakedPubkey = ecc.pointFromScalar(tweakedPrivateKey);
        const hasEvenY = (tweakedPubkey[0] === 0x02);
        
        if (!hasEvenY) {
            console.log('   ⚙️  Negating private key for even Y...');
            tweakedPrivateKey = ecc.privateNegate(tweakedPrivateKey);
        }
        
        const tweakedPubkeyFull = ecc.pointFromScalar(tweakedPrivateKey);
        const tweakedPubkeyXOnly = Buffer.from(tweakedPubkeyFull.subarray(1, 33));
        
        console.log('   ✅ Taproot key ready (tweaked)');
        console.log(`      Internal pubkey: ${xOnlyInternal.toString('hex').substring(0, 16)}...`);
        console.log(`      Tweaked pubkey: ${tweakedPubkeyXOnly.toString('hex').substring(0, 16)}...`);
        
        // 6. Load PSBT
        const psbt = bitcoin.Psbt.fromBase64(psbtBase64, { network });
        console.log(`   ✅ PSBT loaded, ${psbt.data.inputs.length} inputs, ${psbt.txOutputs.length} outputs`);
        
        // 7. Determinar quais inputs assinar e seus sighashTypes individuais
        // ⚠️ CRITICAL: Cada input pode ter seu próprio sighashType!
        const inputsInfo = inputsToSign 
            ? inputsToSign.map(i => {
                if (typeof i === 'number') {
                    return { index: i, sighashType: sighashType }; // Use global sighashType
                }
                // Extract sighashType from input config (sighashTypes array or sighashType)
                const inputSighash = i.sighashTypes?.[0] || i.sighashType || sighashType;
                return { index: i.index, sighashType: parseSighashType(inputSighash) };
            })
            : Array.from({ length: psbt.data.inputs.length }, (_, i) => ({ index: i, sighashType }));
        
        const indicesToSign = inputsInfo.map(info => info.index);
        console.log(`   🎯 Will sign inputs: [${indicesToSign.join(', ')}]`);
        console.log(`   🎯 Input sighashTypes:`, inputsInfo.map(info => `[${info.index}]: 0x${info.sighashType.toString(16)}`));
        
        // 9. Assinar cada input com seu sighashType específico
        for (const inputInfo of inputsInfo) {
            const i = inputInfo.index;
            const inputSighashType = inputInfo.sighashType;
            const input = psbt.data.inputs[i];
            
            // Verificar se já está assinado
            if (input.tapKeySig || input.tapScriptSig) {
                console.log(`   ⏭️  Input ${i}: Already signed, skipping`);
                continue;
            }
            
            // Verificar se é nosso input (tem witnessUtxo)
            if (!input.witnessUtxo) {
                console.log(`   ⏭️  Input ${i}: No witnessUtxo, skipping (not ours?)`);
                continue;
            }
            
            console.log(`\n   🔏 Signing input ${i}...`);
            console.log(`      Value: ${input.witnessUtxo.value} sats`);
            console.log(`      SighashType: 0x${inputSighashType.toString(16)} (${getSighashName(inputSighashType)})`);
            
            // Atualizar tapInternalKey se necessário
            if (!input.tapInternalKey) {
                psbt.updateInput(i, { tapInternalKey: xOnlyInternal });
            }
            
            // ═══════════════════════════════════════════════════════════════
            // Check if this is SCRIPT-PATH or KEY-PATH spend
            // ═══════════════════════════════════════════════════════════════
            const isScriptPathSpend = input.tapLeafScript && input.tapLeafScript.length > 0;
            
            if (isScriptPathSpend) {
                // SCRIPT-PATH SPENDING (for inscriptions reveal TX)
                console.log(`      📜 Script-path spend detected`);
                await signInputScriptPath(psbt, i, childRaw.privateKey, xOnlyInternal, inputSighashType);
            } else {
                // KEY-PATH SPENDING (standard Taproot TX)
                console.log(`      🔑 Key-path spend`);
                await signInputManually(psbt, i, tweakedPrivateKey, inputSighashType);
            }
        }
        
        // 10. Finalizar se solicitado
        let txHex = null;
        
        if (autoFinalize) {
            console.log('\n   🔧 Finalizing all inputs...');
            
            for (const i of indicesToSign) {
                try {
                    psbt.finalizeInput(i);
                    console.log(`      ✅ Input ${i} finalized`);
                } catch (error) {
                    console.log(`      ⚠️  Input ${i} finalization skipped:`, error.message);
                }
            }
            
            // Tentar extrair TX se todos inputs finalizados
            try {
                const allFinalized = psbt.data.inputs.every((input, idx) => {
                    return !indicesToSign.includes(idx) || input.finalScriptWitness;
                });
                
                if (allFinalized) {
                    txHex = psbt.extractTransaction().toHex();
                    console.log(`   ✅ Transaction extracted: ${txHex.length / 2} bytes`);
                }
            } catch (error) {
                console.log(`   ⚠️  Could not extract TX (partial signing?):`, error.message);
            }
        } else {
            console.log('\n   ⏸️  autoFinalize=false - Returning PSBT without finalizing');
        }
        
        // 11. Limpar mnemonic da memória
        mnemonic = null;
        
        const signedPsbtBase64 = psbt.toBase64();
        
        console.log('\n🎉 ═══════════════════════════════════════════════════════════════');
        console.log('   PSBT SIGNED 100% LOCALLY - SUCCESS!');
        console.log(`   Signed PSBT: ${signedPsbtBase64.length} chars`);
        console.log('═══════════════════════════════════════════════════════════════\n');
        
        return {
            signedPsbt: signedPsbtBase64,
            txHex: txHex
        };
        
    } catch (error) {
        console.error('❌ Error signing PSBT locally:', error);
        // Limpar mnemonic mesmo em erro
        mnemonic = null;
        throw error;
    }
}

/**
 * Sign input using SCRIPT-PATH spending (for inscriptions reveal TX)
 * Uses the ORIGINAL (non-tweaked) private key
 */
async function signInputScriptPath(psbt, inputIndex, originalPrivateKey, internalPubkey, sighashType) {
    console.log(`      🔨 Script-path signing input ${inputIndex}`);
    
    const input = psbt.data.inputs[inputIndex];
    const leafScript = input.tapLeafScript[0];
    const leafVersion = leafScript.leafVersion || 0xc0;
    
    // Calculate leaf hash
    const leafHash = calculateTapLeafHash(leafScript.script, leafVersion);
    console.log(`      🌿 Leaf hash: ${Buffer.from(leafHash).toString('hex').substring(0, 16)}...`);
    
    // Build prevouts for sighash calculation
    const prevouts = [];
    for (let i = 0; i < psbt.data.inputs.length; i++) {
        const inp = psbt.data.inputs[i];
        const txInput = psbt.txInputs[i];
        
        prevouts.push({
            txid: Buffer.from(txInput.hash).reverse().toString('hex'),
            vout: txInput.index,
            value: inp.witnessUtxo?.value || 0,
            scriptPubKey: inp.witnessUtxo?.script || Buffer.alloc(0)
        });
    }
    
    // Calculate script-path sighash
    const sighash = calculateTaprootScriptPathSighash(psbt, inputIndex, prevouts, sighashType, leafHash, leafVersion);
    console.log(`      Sighash: ${Buffer.from(sighash).toString('hex').substring(0, 32)}...`);
    
    // For script-path, we need to ensure the private key produces even Y pubkey
    let signingKey = Buffer.from(originalPrivateKey);
    const fullPubkey = ecc.pointFromScalar(signingKey);
    if (fullPubkey[0] === 0x03) {
        // Negate private key for odd Y
        signingKey = ecc.privateNegate(signingKey);
    }
    
    // Sign with Schnorr
    const signature = schnorr.sign(new Uint8Array(sighash), new Uint8Array(signingKey));
    console.log(`      Signature: ${Buffer.from(signature).toString('hex').substring(0, 32)}...`);
    
    // Add sighash byte if not SIGHASH_DEFAULT
    let finalSig;
    if (sighashType === SIGHASH_DEFAULT) {
        finalSig = Buffer.from(signature);
    } else {
        finalSig = Buffer.concat([Buffer.from(signature), Buffer.from([sighashType])]);
    }
    
    // Add signature as tapScriptSig
    psbt.updateInput(inputIndex, {
        tapScriptSig: [{
            pubkey: internalPubkey,
            signature: finalSig,
            leafHash: Buffer.from(leafHash)
        }]
    });
    
    console.log(`      ✅ Input ${inputIndex} signed with tapScriptSig (script-path)`);
}

/**
 * Calculate TapLeaf hash for script-path spending
 */
function calculateTapLeafHash(script, leafVersion = 0xc0) {
    // Compact size encoding for script length
    const scriptLen = script.length;
    let lenBuf;
    if (scriptLen < 253) {
        lenBuf = Buffer.from([scriptLen]);
    } else if (scriptLen < 0x10000) {
        lenBuf = Buffer.alloc(3);
        lenBuf[0] = 253;
        lenBuf.writeUInt16LE(scriptLen, 1);
    } else {
        lenBuf = Buffer.alloc(5);
        lenBuf[0] = 254;
        lenBuf.writeUInt32LE(scriptLen, 1);
    }
    
    const preimage = Buffer.concat([
        Buffer.from([leafVersion]),
        lenBuf,
        script
    ]);
    
    return bitcoin.crypto.taggedHash('TapLeaf', preimage);
}

/**
 * Calculate sighash for SCRIPT-PATH spending (BIP341)
 */
function calculateTaprootScriptPathSighash(psbt, inputIndex, prevouts, sighashType, leafHash, leafVersion) {
    const tx = psbt.__CACHE.__TX;
    const buffers = [];
    
    const sighashBase = sighashType & 0x1f;
    const anyoneCanPay = (sighashType & SIGHASH_ANYONECANPAY) !== 0;
    
    // 1. Epoch (0x00)
    buffers.push(Buffer.from([0x00]));
    
    // 2. Hash type
    buffers.push(Buffer.from([sighashType]));
    
    // 3. nVersion
    const versionBuf = Buffer.allocUnsafe(4);
    versionBuf.writeUInt32LE(tx.version);
    buffers.push(versionBuf);
    
    // 4. nLockTime
    const locktimeBuf = Buffer.allocUnsafe(4);
    locktimeBuf.writeUInt32LE(tx.locktime);
    buffers.push(locktimeBuf);
    
    // Precomputed hashes (if not ANYONECANPAY)
    if (!anyoneCanPay) {
        buffers.push(hashPrevouts(tx));
        buffers.push(hashAmounts(prevouts));
        buffers.push(hashScriptPubKeys(prevouts));
        
        if (sighashBase !== SIGHASH_NONE && sighashBase !== SIGHASH_SINGLE) {
            buffers.push(hashSequences(tx));
        }
    }
    
    // Outputs hash (for SIGHASH_ALL)
    if (sighashBase === SIGHASH_DEFAULT || sighashBase === SIGHASH_ALL) {
        buffers.push(hashOutputs(tx));
    }
    
    // spend_type = 0x02 (ext_flag=1 for script-path, annex_present=0)
    buffers.push(Buffer.from([0x02]));
    
    // Input data
    if (anyoneCanPay) {
        const input = prevouts[inputIndex];
        const txInput = tx.ins[inputIndex];
        
        // outpoint
        const outpointBuf = Buffer.alloc(36);
        Buffer.from(input.txid, 'hex').reverse().copy(outpointBuf, 0);
        outpointBuf.writeUInt32LE(input.vout, 32);
        buffers.push(outpointBuf);
        
        // amount
        const amountBuf = Buffer.allocUnsafe(8);
        amountBuf.writeBigUInt64LE(BigInt(input.value));
        buffers.push(amountBuf);
        
        // scriptPubKey
        const script = input.scriptPubKey;
        const scriptLenBuf = Buffer.from([script.length]);
        buffers.push(Buffer.concat([scriptLenBuf, script]));
        
        // sequence
        const seqBuf = Buffer.allocUnsafe(4);
        seqBuf.writeUInt32LE(txInput.sequence);
        buffers.push(seqBuf);
    } else {
        // input_index
        const idxBuf = Buffer.allocUnsafe(4);
        idxBuf.writeUInt32LE(inputIndex);
        buffers.push(idxBuf);
    }
    
    // SIGHASH_SINGLE output
    if (sighashBase === SIGHASH_SINGLE && inputIndex < tx.outs.length) {
        const output = tx.outs[inputIndex];
        const valueBuf = Buffer.allocUnsafe(8);
        valueBuf.writeBigUInt64LE(BigInt(output.value));
        
        const scriptLen = Buffer.from([output.script.length]);
        const singleOutput = Buffer.concat([valueBuf, scriptLen, output.script]);
        buffers.push(sha256(singleOutput));
    }
    
    // ═══════════════════════════════════════════════════════════════
    // SCRIPT-PATH SPECIFIC: tapleaf_hash + key_version + codesep_pos
    // ═══════════════════════════════════════════════════════════════
    buffers.push(leafHash);
    buffers.push(Buffer.from([0x00])); // key_version
    const codesepBuf = Buffer.allocUnsafe(4);
    codesepBuf.writeUInt32LE(0xffffffff); // no OP_CODESEPARATOR
    buffers.push(codesepBuf);
    
    const preimage = Buffer.concat(buffers);
    return bitcoin.crypto.taggedHash('TapSighash', preimage);
}

/**
 * Assinar input manualmente com cálculo de sighash customizado
 * Necessário para ANYONECANPAY e outros tipos especiais
 */
async function signInputManually(psbt, inputIndex, privateKey, sighashType) {
    console.log(`      🔨 Manual signing input ${inputIndex}`);
    console.log(`      📌 sighashType received: ${sighashType} (type: ${typeof sighashType})`);
    console.log(`      📌 sighashType hex: 0x${sighashType?.toString(16) || 'undefined'}`);
    console.log(`      📌 SIGHASH_DEFAULT: ${SIGHASH_DEFAULT}, SIGHASH_ALL: ${SIGHASH_ALL}`);
    
    // Construir prevouts para cálculo de sighash
    const prevouts = [];
    for (let i = 0; i < psbt.data.inputs.length; i++) {
        const input = psbt.data.inputs[i];
        const txInput = psbt.txInputs[i];
        
        prevouts.push({
            txid: Buffer.from(txInput.hash).reverse().toString('hex'),
            vout: txInput.index,
            value: input.witnessUtxo?.value || 0,
            scriptPubKey: input.witnessUtxo?.script || Buffer.alloc(0)
        });
    }
    
    // Calcular sighash
    const sighash = calculateTaprootSighash(psbt, inputIndex, prevouts, sighashType);
    console.log(`      Sighash: ${sighash.toString('hex').substring(0, 32)}...`);
    
    // Assinar com Schnorr usando @noble/secp256k1
    const signature = schnorr.sign(new Uint8Array(sighash), new Uint8Array(privateKey));
    
    if (!signature) {
        throw new Error(`Failed to create Schnorr signature for input ${inputIndex}`);
    }
    
    console.log(`      Signature: ${Buffer.from(signature).toString('hex').substring(0, 32)}...`);
    
    // Adicionar assinatura ao PSBT
    // Para SIGHASH_DEFAULT (0x00), a assinatura é só 64 bytes
    // Para outros, precisa adicionar o byte de sighash (65 bytes total)
    let finalSig;
    if (sighashType === SIGHASH_DEFAULT) {
        finalSig = Buffer.from(signature);
    } else {
        // ⚠️ CRITICAL: Append sighash byte to signature for non-default types!
        finalSig = Buffer.concat([Buffer.from(signature), Buffer.from([sighashType])]);
    }
    
    // Use updateInput for proper signature application (same as mobile app)
    // Note: sighashType field in PSBT is NOT required - the byte is IN the signature!
    psbt.updateInput(inputIndex, { tapKeySig: finalSig });
    
    console.log(`      ✅ Input ${inputIndex} signed manually`);
    console.log(`      📝 Final signature: ${finalSig.length} bytes (last byte: 0x${finalSig[finalSig.length-1]?.toString(16) || '??'})`);
}

/**
 * Calcular sighash BIP 341 para Taproot key-path spending
 */
function calculateTaprootSighash(psbt, inputIndex, prevouts, sighashType) {
    const tx = psbt.__CACHE.__TX;
    const buffers = [];
    
    // Extrair flags do sighashType
    const sighashBase = sighashType & 0x1f;
    const anyoneCanPay = (sighashType & SIGHASH_ANYONECANPAY) !== 0;
    
    // 1. Epoch (0x00)
    buffers.push(Buffer.from([0x00]));
    
    // 2. Hash type
    buffers.push(Buffer.from([sighashType]));
    
    // 3. nVersion (4 bytes LE)
    const versionBuf = Buffer.allocUnsafe(4);
    versionBuf.writeUInt32LE(tx.version);
    buffers.push(versionBuf);
    
    // 4. nLockTime (4 bytes LE)
    const locktimeBuf = Buffer.allocUnsafe(4);
    locktimeBuf.writeUInt32LE(tx.locktime);
    buffers.push(locktimeBuf);
    
    // 5. sha_prevouts (se NÃO for ANYONECANPAY)
    if (!anyoneCanPay) {
        buffers.push(hashPrevouts(tx));
    }
    
    // 6. sha_amounts (se NÃO for ANYONECANPAY)
    if (!anyoneCanPay) {
        buffers.push(hashAmounts(prevouts));
    }
    
    // 7. sha_scriptpubkeys (se NÃO for ANYONECANPAY)
    if (!anyoneCanPay) {
        buffers.push(hashScriptPubKeys(prevouts));
    }
    
    // 8. sha_sequences (se NÃO for ANYONECANPAY E NÃO for NONE|SINGLE)
    if (!anyoneCanPay && sighashBase !== SIGHASH_NONE && sighashBase !== SIGHASH_SINGLE) {
        buffers.push(hashSequences(tx));
    }
    
    // 9. sha_outputs (ONLY for ALL/DEFAULT, NOT for SINGLE!)
    if (sighashBase !== SIGHASH_NONE && sighashBase !== SIGHASH_SINGLE) {
        buffers.push(hashOutputs(psbt));
    }
    
    // 10. spend_type (0x00 para key-path, sem annex)
    buffers.push(Buffer.from([0x00]));
    
    // 11. Input data
    if (anyoneCanPay) {
        // outpoint (txid + vout)
        const input = tx.ins[inputIndex];
        buffers.push(Buffer.from(input.hash)); // já está em little-endian
        const voutBuf = Buffer.allocUnsafe(4);
        voutBuf.writeUInt32LE(input.index);
        buffers.push(voutBuf);
        
        // amount (8 bytes LE)
        const amountBuf = Buffer.allocUnsafe(8);
        amountBuf.writeBigUInt64LE(BigInt(prevouts[inputIndex].value));
        buffers.push(amountBuf);
        
        // scriptPubKey
        let scriptPubKey = prevouts[inputIndex].scriptPubKey;
        if (typeof scriptPubKey === 'string') {
            scriptPubKey = Buffer.from(scriptPubKey, 'hex');
        } else if (!(scriptPubKey instanceof Buffer)) {
            scriptPubKey = Buffer.from(scriptPubKey);
        }
        buffers.push(encodeCompactSize(scriptPubKey.length));
        buffers.push(scriptPubKey);
        
        // sequence (4 bytes LE)
        const seqBuf = Buffer.allocUnsafe(4);
        seqBuf.writeUInt32LE(input.sequence);
        buffers.push(seqBuf);
    } else {
        // input_index (4 bytes LE)
        const indexBuf = Buffer.allocUnsafe(4);
        indexBuf.writeUInt32LE(inputIndex);
        buffers.push(indexBuf);
    }
    
    // 12. sha_single_output (ONLY for SIGHASH_SINGLE)
    if (sighashBase === SIGHASH_SINGLE) {
        if (inputIndex < psbt.txOutputs.length) {
            buffers.push(hashSingleOutput(psbt, inputIndex));
        } else {
            buffers.push(Buffer.alloc(32, 0));
        }
    }
    
    // Concatenar e criar tagged hash
    const message = Buffer.concat(buffers);
    const tag = 'TapSighash';
    const tagHash = bitcoin.crypto.sha256(Buffer.from(tag, 'utf8'));
    const taggedMessage = Buffer.concat([tagHash, tagHash, message]);
    
    return bitcoin.crypto.sha256(taggedMessage);
}

// Hash helpers
function hashPrevouts(tx) {
    const buffers = [];
    for (const input of tx.ins) {
        buffers.push(Buffer.from(input.hash));
        const voutBuf = Buffer.allocUnsafe(4);
        voutBuf.writeUInt32LE(input.index);
        buffers.push(voutBuf);
    }
    return bitcoin.crypto.sha256(Buffer.concat(buffers));
}

function hashAmounts(prevouts) {
    const buffers = [];
    for (const prevout of prevouts) {
        const amountBuf = Buffer.allocUnsafe(8);
        amountBuf.writeBigUInt64LE(BigInt(prevout.value));
        buffers.push(amountBuf);
    }
    return bitcoin.crypto.sha256(Buffer.concat(buffers));
}

function hashScriptPubKeys(prevouts) {
    const buffers = [];
    for (const prevout of prevouts) {
        let script = prevout.scriptPubKey;
        if (typeof script === 'string') {
            script = Buffer.from(script, 'hex');
        } else if (!(script instanceof Buffer)) {
            script = Buffer.from(script);
        }
        buffers.push(encodeCompactSize(script.length));
        buffers.push(script);
    }
    return bitcoin.crypto.sha256(Buffer.concat(buffers));
}

function hashSequences(tx) {
    const buffers = [];
    for (const input of tx.ins) {
        const seqBuf = Buffer.allocUnsafe(4);
        seqBuf.writeUInt32LE(input.sequence);
        buffers.push(seqBuf);
    }
    return bitcoin.crypto.sha256(Buffer.concat(buffers));
}

function hashOutputs(psbt) {
    const buffers = [];
    for (const output of psbt.txOutputs) {
        const amountBuf = Buffer.allocUnsafe(8);
        amountBuf.writeBigUInt64LE(BigInt(output.value));
        buffers.push(amountBuf);
        buffers.push(encodeCompactSize(output.script.length));
        buffers.push(output.script);
    }
    return bitcoin.crypto.sha256(Buffer.concat(buffers));
}

function hashSingleOutput(psbt, outputIndex) {
    const output = psbt.txOutputs[outputIndex];
    const amountBuf = Buffer.allocUnsafe(8);
    amountBuf.writeBigUInt64LE(BigInt(output.value));
    const lengthBuf = encodeCompactSize(output.script.length);
    return bitcoin.crypto.sha256(Buffer.concat([amountBuf, lengthBuf, output.script]));
}

function encodeCompactSize(n) {
    if (n < 0xfd) {
        const buf = Buffer.allocUnsafe(1);
        buf.writeUInt8(n);
        return buf;
    } else if (n <= 0xffff) {
        const buf = Buffer.allocUnsafe(3);
        buf.writeUInt8(0xfd);
        buf.writeUInt16LE(n, 1);
        return buf;
    } else if (n <= 0xffffffff) {
        const buf = Buffer.allocUnsafe(5);
        buf.writeUInt8(0xfe);
        buf.writeUInt32LE(n, 1);
        return buf;
    } else {
        const buf = Buffer.allocUnsafe(9);
        buf.writeUInt8(0xff);
        buf.writeBigUInt64LE(BigInt(n), 1);
        return buf;
    }
}

/**
 * Criar signer object para bitcoinjs-lib
 */
function createSigner(privateKey, publicKey) {
    return {
        publicKey: Buffer.from(publicKey),
        privateKey: privateKey,
        network: network,
        sign: (hash, lowR) => {
            return ecc.sign(hash, privateKey, lowR);
        },
        signSchnorr: (hash) => {
            const auxRand = Buffer.alloc(32, 0);
            return ecc.signSchnorr(hash, privateKey, auxRand);
        }
    };
}

/**
 * Parse sighashType de string ou número
 */
function parseSighashType(sighashType) {
    if (sighashType === undefined || sighashType === null) {
        return SIGHASH_DEFAULT;
    }
    
    if (typeof sighashType === 'number') {
        return sighashType;
    }
    
    // Parse string
    const str = String(sighashType).toUpperCase();
    
    if (str === 'DEFAULT') return SIGHASH_DEFAULT;
    if (str === 'ALL') return SIGHASH_ALL;
    if (str === 'NONE') return SIGHASH_NONE;
    if (str === 'SINGLE') return SIGHASH_SINGLE;
    if (str === 'NONE|ANYONECANPAY') return SIGHASH_NONE | SIGHASH_ANYONECANPAY;
    if (str === 'SINGLE|ANYONECANPAY') return SIGHASH_SINGLE | SIGHASH_ANYONECANPAY;
    if (str === 'ALL|ANYONECANPAY') return SIGHASH_ALL | SIGHASH_ANYONECANPAY;
    
    // Try parsing as hex
    if (str.startsWith('0X')) {
        return parseInt(str, 16);
    }
    
    return SIGHASH_DEFAULT;
}

/**
 * Get human-readable sighash name
 */
function getSighashName(sighashType) {
    const base = sighashType & 0x1f;
    const anyoneCanPay = (sighashType & SIGHASH_ANYONECANPAY) !== 0;
    
    let name = '';
    
    switch (base) {
        case SIGHASH_DEFAULT: name = 'DEFAULT'; break;
        case SIGHASH_ALL: name = 'ALL'; break;
        case SIGHASH_NONE: name = 'NONE'; break;
        case SIGHASH_SINGLE: name = 'SINGLE'; break;
        default: name = 'UNKNOWN';
    }
    
    if (anyoneCanPay) {
        name += '|ANYONECANPAY';
    }
    
    return name;
}

// ═══════════════════════════════════════════════════════════════════════════
// 💬 KRAYCHAT E2E ENCRYPTION - ECDH + AES-256-GCM
// ═══════════════════════════════════════════════════════════════════════════

/**
 * 🔐 Get Chat Public Key (66-char compressed format)
 * Derives the BIP86 public key for KrayChat
 * 
 * @param {string} mnemonic - 12 words (ONLY used locally!)
 * @returns {Promise<string>} - Compressed public key (66 hex chars, starts with 02/03)
 */
async function getChatPublicKey(mnemonic) {
    try {
        console.log('🔑 KrayChat: Deriving public key...');
        
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = bip32.fromSeed(seed, network);
        const path = "m/86'/0'/0'/0/0";
        const child = root.derivePath(path);
        
        // Return 33-byte compressed public key as hex (66 chars)
        const compressedPubkey = Buffer.from(child.publicKey).toString('hex');
        console.log('✅ KrayChat: Public key derived (66 chars)');
        
        return compressedPubkey;
    } catch (error) {
        console.error('❌ Error deriving chat public key:', error);
        throw error;
    }
}

/**
 * 🔐 Derive shared secret using ECDH
 * Uses @noble/secp256k1 getSharedSecret
 * 
 * @param {Buffer|Uint8Array} myPrivateKey - 32 bytes
 * @param {Buffer|Uint8Array|string} theirPublicKey - Compressed pubkey (33 bytes or hex)
 * @returns {Uint8Array} - 32-byte shared secret (SHA256 of ECDH x-coord)
 */
function deriveSharedSecret(myPrivateKey, theirPublicKey) {
    try {
        // Convert pubkey to Uint8Array if hex string
        let pubkeyBytes;
        if (typeof theirPublicKey === 'string') {
            pubkeyBytes = new Uint8Array(Buffer.from(theirPublicKey, 'hex'));
        } else {
            pubkeyBytes = new Uint8Array(theirPublicKey);
        }
        
        // Convert private key to Uint8Array
        const privkeyBytes = new Uint8Array(myPrivateKey);
        
        // ECDH: get shared point
        const sharedPoint = getSharedSecret(privkeyBytes, pubkeyBytes);
        
        // Take x-coordinate (bytes 1-32 of uncompressed point)
        const xCoord = sharedPoint.slice(1, 33);
        
        // SHA256 of x-coordinate = shared secret
        return sha256(xCoord);
    } catch (error) {
        console.error('❌ ECDH shared secret derivation failed:', error);
        throw error;
    }
}

/**
 * 🔒 Encrypt a chat message using AES-256-GCM
 * 
 * @param {string} message - Plaintext message
 * @param {string} mnemonic - 12 words (ONLY used locally!)
 * @param {string} theirPublicKey - Recipient's public key (hex)
 * @returns {Promise<string>} - Base64 encoded encrypted payload (iv + ciphertext + tag)
 */
async function encryptChatMessage(message, mnemonic, theirPublicKey) {
    try {
        console.log('🔐 KrayChat: Encrypting message...');
        
        // 1. Derive private key from mnemonic
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = bip32.fromSeed(seed, network);
        const child = root.derivePath("m/86'/0'/0'/0/0");
        const privateKey = child.privateKey;
        
        // 2. Derive shared secret using ECDH
        const sharedSecret = deriveSharedSecret(privateKey, theirPublicKey);
        
        // 3. Generate random IV (12 bytes for GCM)
        const iv = crypto.getRandomValues(new Uint8Array(12));
        
        // 4. Convert message to bytes
        const messageBytes = new TextEncoder().encode(message);
        
        // 5. Import key for AES-256-GCM
        const cryptoKey = await crypto.subtle.importKey(
            'raw',
            sharedSecret,
            { name: 'AES-GCM' },
            false,
            ['encrypt']
        );
        
        // 6. Encrypt
        const encrypted = await crypto.subtle.encrypt(
            { name: 'AES-GCM', iv: iv },
            cryptoKey,
            messageBytes
        );
        
        // 7. Split into ciphertext and tag (GCM appends 16-byte auth tag)
        const encryptedBytes = new Uint8Array(encrypted);
        const ciphertext = encryptedBytes.slice(0, -16);
        const tag = encryptedBytes.slice(-16);
        
        // 8. Return as JSON (compatible with mobile format!)
        const result = JSON.stringify({
            nonce: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join(''),
            ciphertext: Array.from(ciphertext).map(b => b.toString(16).padStart(2, '0')).join(''),
            tag: Array.from(tag).map(b => b.toString(16).padStart(2, '0')).join('')
        });
        
        console.log('✅ KrayChat: Message encrypted (mobile-compatible format)');
        return result;
    } catch (error) {
        console.error('❌ Error encrypting chat message:', error);
        throw error;
    }
}

/**
 * 🔓 Decrypt a chat message using AES-256-GCM
 * 
 * @param {string} encryptedBase64 - Base64 encoded encrypted payload
 * @param {string} mnemonic - 12 words (ONLY used locally!)
 * @param {string} theirPublicKey - Sender's public key (hex)
 * @returns {Promise<string>} - Decrypted plaintext message
 */
async function decryptChatMessage(encryptedPayload, mnemonic, theirPublicKey) {
    try {
        console.log('🔓 KrayChat: Decrypting message...');
        
        // 1. Derive private key from mnemonic
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = bip32.fromSeed(seed, network);
        const child = root.derivePath("m/86'/0'/0'/0/0");
        const privateKey = child.privateKey;
        
        // 2. Derive shared secret using ECDH
        const sharedSecret = deriveSharedSecret(privateKey, theirPublicKey);
        
        // 3. Parse payload - support both JSON (mobile format) and base64 (legacy)
        let iv, ciphertext, tag;
        
        try {
            // Try JSON format first (mobile-compatible)
            const parsed = JSON.parse(encryptedPayload);
            if (parsed.nonce && parsed.ciphertext && parsed.tag) {
                // Mobile format: { nonce, ciphertext, tag } in hex
                iv = new Uint8Array(parsed.nonce.match(/.{2}/g).map(b => parseInt(b, 16)));
                ciphertext = new Uint8Array(parsed.ciphertext.match(/.{2}/g).map(b => parseInt(b, 16)));
                tag = new Uint8Array(parsed.tag.match(/.{2}/g).map(b => parseInt(b, 16)));
            }
        } catch (e) {
            // Fallback to base64 format (legacy)
            const combined = new Uint8Array(atob(encryptedPayload).split('').map(c => c.charCodeAt(0)));
            iv = combined.slice(0, 12);
            const rest = combined.slice(12);
            ciphertext = rest.slice(0, -16);
            tag = rest.slice(-16);
        }
        
        // 4. Reconstruct GCM format (ciphertext + tag)
        const encryptedData = new Uint8Array(ciphertext.length + tag.length);
        encryptedData.set(ciphertext);
        encryptedData.set(tag, ciphertext.length);
        
        // 5. Import key for AES-256-GCM
        const cryptoKey = await crypto.subtle.importKey(
            'raw',
            sharedSecret,
            { name: 'AES-GCM' },
            false,
            ['decrypt']
        );
        
        // 6. Decrypt
        const decrypted = await crypto.subtle.decrypt(
            { name: 'AES-GCM', iv: iv },
            cryptoKey,
            encryptedData
        );
        
        // 7. Convert to string
        const message = new TextDecoder().decode(decrypted);
        
        console.log('✅ KrayChat: Message decrypted');
        return message;
    } catch (error) {
        console.error('❌ Error decrypting chat message:', error);
        throw error;
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// 🔐 SECURE SESSION FUNCTIONS - Like mobile!
// Derive key once, use many times, mnemonic is NEVER stored
// ═══════════════════════════════════════════════════════════════════════════

/**
 * 🔐 Derive chat private key from mnemonic
 * Returns ONLY the derived key - mnemonic should be discarded after this!
 * 
 * @param {string} mnemonic - 12 words (use once and discard!)
 * @returns {Promise<{privateKey: Uint8Array, publicKey: string}>}
 */
async function deriveChatPrivateKey(mnemonic) {
    try {
        console.log('🔐 KrayChat: Deriving private key (mnemonic will be discarded)...');
        
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = bip32.fromSeed(seed, network);
        const path = "m/86'/0'/0'/0/0";
        const child = root.derivePath(path);
        
        // Return private key as Uint8Array and public key as hex
        const privateKey = new Uint8Array(child.privateKey);
        const publicKey = Buffer.from(child.publicKey).toString('hex');
        
        console.log('✅ KrayChat: Private key derived');
        console.log('   🔐 Mnemonic should now be discarded by caller!');
        
        return { privateKey, publicKey };
    } catch (error) {
        console.error('❌ Error deriving chat private key:', error);
        throw error;
    }
}

/**
 * 🔒 Encrypt using pre-derived private key (no mnemonic needed!)
 * This is the secure session-based encryption like mobile
 * 
 * @param {string} message - Plaintext message
 * @param {Uint8Array} privateKey - Pre-derived private key
 * @param {string} theirPublicKey - Recipient's public key (hex)
 * @returns {Promise<string>} - Encrypted payload
 */
async function encryptWithPrivateKey(message, privateKey, theirPublicKey) {
    try {
        console.log('🔐 KrayChat: Encrypting with session key...');
        
        // 1. Derive shared secret using ECDH
        const sharedSecret = deriveSharedSecret(privateKey, theirPublicKey);
        
        // 2. Generate random IV (12 bytes for GCM)
        const iv = crypto.getRandomValues(new Uint8Array(12));
        
        // 3. Convert message to bytes
        const messageBytes = new TextEncoder().encode(message);
        
        // 4. Import key for AES-256-GCM
        const cryptoKey = await crypto.subtle.importKey(
            'raw',
            sharedSecret,
            { name: 'AES-GCM' },
            false,
            ['encrypt']
        );
        
        // 5. Encrypt
        const encrypted = await crypto.subtle.encrypt(
            { name: 'AES-GCM', iv: iv },
            cryptoKey,
            messageBytes
        );
        
        // 6. Split into ciphertext and tag (GCM appends 16-byte auth tag)
        const encryptedBytes = new Uint8Array(encrypted);
        const ciphertext = encryptedBytes.slice(0, -16);
        const tag = encryptedBytes.slice(-16);
        
        // 7. Return as JSON (compatible with mobile format!)
        const result = JSON.stringify({
            nonce: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join(''),
            ciphertext: Array.from(ciphertext).map(b => b.toString(16).padStart(2, '0')).join(''),
            tag: Array.from(tag).map(b => b.toString(16).padStart(2, '0')).join('')
        });
        
        console.log('✅ KrayChat: Encrypted with session key (mobile-compatible)');
        return result;
    } catch (error) {
        console.error('❌ Error encrypting with private key:', error);
        throw error;
    }
}

/**
 * 🔓 Decrypt using pre-derived private key (no mnemonic needed!)
 * This is the secure session-based decryption like mobile
 * 
 * @param {string} encryptedBase64 - Encrypted payload
 * @param {Uint8Array} privateKey - Pre-derived private key
 * @param {string} theirPublicKey - Sender's public key (hex)
 * @returns {Promise<string>} - Decrypted message
 */
async function decryptWithPrivateKey(encryptedPayload, privateKey, theirPublicKey) {
    try {
        console.log('🔓 KrayChat: Decrypting with session key...');
        
        // 1. Derive shared secret using ECDH
        const sharedSecret = deriveSharedSecret(privateKey, theirPublicKey);
        
        // 2. Parse payload - support both JSON (mobile format) and base64 (legacy)
        let iv, ciphertext, tag;
        
        try {
            // Try JSON format first (mobile-compatible)
            const parsed = JSON.parse(encryptedPayload);
            if (parsed.nonce && parsed.ciphertext && parsed.tag) {
                // Mobile format: { nonce, ciphertext, tag } in hex
                iv = new Uint8Array(parsed.nonce.match(/.{2}/g).map(b => parseInt(b, 16)));
                ciphertext = new Uint8Array(parsed.ciphertext.match(/.{2}/g).map(b => parseInt(b, 16)));
                tag = new Uint8Array(parsed.tag.match(/.{2}/g).map(b => parseInt(b, 16)));
            }
        } catch (e) {
            // Fallback to base64 format (legacy)
            const combined = new Uint8Array(atob(encryptedPayload).split('').map(c => c.charCodeAt(0)));
            iv = combined.slice(0, 12);
            const rest = combined.slice(12);
            ciphertext = rest.slice(0, -16);
            tag = rest.slice(-16);
        }
        
        // 3. Reconstruct GCM format (ciphertext + tag)
        const encryptedData = new Uint8Array(ciphertext.length + tag.length);
        encryptedData.set(ciphertext);
        encryptedData.set(tag, ciphertext.length);
        
        // 4. Import key for AES-256-GCM
        const cryptoKey = await crypto.subtle.importKey(
            'raw',
            sharedSecret,
            { name: 'AES-GCM' },
            false,
            ['decrypt']
        );
        
        // 5. Decrypt
        const decrypted = await crypto.subtle.decrypt(
            { name: 'AES-GCM', iv: iv },
            cryptoKey,
            encryptedData
        );
        
        // 6. Convert to string
        const message = new TextDecoder().decode(decrypted);
        
        console.log('✅ KrayChat: Decrypted with session key');
        return message;
    } catch (error) {
        console.error('❌ Error decrypting with private key:', error);
        throw error;
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// 🔐 SIGN MESSAGE LOCAL - For L2 / Chat / Group Actions
// Returns 64-byte Schnorr signature (compatible with backend verification)
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Sign a message locally using Schnorr signature
 * 🔐 LEI PRIMORDIAL: Mnemonic NEVER leaves the device!
 * 
 * @param {string} message - Message to sign
 * @param {string} mnemonic - 12 words (ONLY used locally!)
 * @returns {Promise<{signature: string, pubkey: string, address: string}>}
 */
async function signMessageLocal(message, mnemonic) {
    try {
        console.log('🔐 [LOCAL SIGNER] Signing message locally...');
        console.log('   Message:', message.substring(0, 50) + '...');
        
        // 1. Derive key from mnemonic (BIP86: m/86'/0'/0'/0/0)
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = bip32.fromSeed(seed, network);
        const child = root.derivePath("m/86'/0'/0'/0/0");
        
        if (!child.privateKey) {
            throw new Error('Failed to derive private key');
        }
        
        // 2. Get x-only public key (32 bytes)
        const xOnlyPubkey = child.publicKey.slice(1, 33);
        
        // 3. Derive Taproot address
        const { address } = bitcoin.payments.p2tr({
            internalPubkey: xOnlyPubkey,
            network: network
        });
        
        // 4. Hash the message with SHA256 (simple hash, not BIP-322)
        const messageBuffer = new TextEncoder().encode(message);
        const messageHash = sha256(messageBuffer);
        
        // 5. Sign with Schnorr using @noble/secp256k1
        // Note: schnorr.sign returns 64-byte signature
        const signature = schnorr.sign(new Uint8Array(messageHash), new Uint8Array(child.privateKey));
        
        // 6. Convert to hex
        const signatureHex = Buffer.from(signature).toString('hex');
        const pubkeyHex = Buffer.from(xOnlyPubkey).toString('hex');
        
        console.log('✅ [LOCAL SIGNER] Message signed successfully');
        console.log('   Signature length:', signatureHex.length, 'chars (', signatureHex.length / 2, 'bytes)');
        console.log('   Pubkey:', pubkeyHex.substring(0, 16) + '...');
        console.log('   Address:', address);
        
        // Clear sensitive data
        mnemonic = null;
        
        return {
            success: true,
            signature: signatureHex,
            pubkey: pubkeyHex,
            address: address
        };
    } catch (error) {
        console.error('❌ [LOCAL SIGNER] Error signing message:', error);
        throw new Error(`Message signing failed: ${error.message}`);
    }
}

// Export para webpack
module.exports = {
    signPsbtTaprootLocal,
    signMessageLocal,  // 🆕 Added for L2/Chat/Group signing!
    SIGHASH_DEFAULT,
    SIGHASH_ALL,
    SIGHASH_NONE,
    SIGHASH_SINGLE,
    SIGHASH_ANYONECANPAY,
    // KrayChat E2E Encryption
    getChatPublicKey,
    encryptChatMessage,
    decryptChatMessage,
    deriveSharedSecret,
    // Secure session functions (like mobile!)
    deriveChatPrivateKey,
    encryptWithPrivateKey,
    decryptWithPrivateKey
};

// Export para window (browser)
if (typeof window !== 'undefined') {
    window.TaprootSigner = {
        signPsbtTaprootLocal,
        signMessageLocal,  // 🆕 Added for L2/Chat/Group signing!
        SIGHASH_DEFAULT,
        SIGHASH_ALL,
        SIGHASH_NONE,
        SIGHASH_SINGLE,
        SIGHASH_ANYONECANPAY,
        // KrayChat E2E Encryption
        getChatPublicKey,
        encryptChatMessage,
        decryptChatMessage,
        deriveSharedSecret,
        // Secure session functions (like mobile!)
        deriveChatPrivateKey,
        encryptWithPrivateKey,
        decryptWithPrivateKey
    };
}

// Export para Service Worker (self)
if (typeof self !== 'undefined' && typeof window === 'undefined') {
    self.TaprootSigner = {
        signPsbtTaprootLocal,
        signMessageLocal,  // 🆕 Added for L2/Chat/Group signing!
        SIGHASH_DEFAULT,
        SIGHASH_ALL,
        SIGHASH_NONE,
        SIGHASH_SINGLE,
        SIGHASH_ANYONECANPAY,
        // KrayChat E2E Encryption
        getChatPublicKey,
        encryptChatMessage,
        decryptChatMessage,
        deriveSharedSecret,
        // Secure session functions (like mobile!)
        deriveChatPrivateKey,
        encryptWithPrivateKey,
        decryptWithPrivateKey
    };
}
