/**
 * PSP Monero Wallet Creation Library
 * This file contains the exact wallet creation logic from the PSP reference implementation
 * Flow: Entropy → MD5 → SHA256 → Pad → Web3-XMR-accounts → Private Key + Address
 */

// CryptoJS SHA256 implementation (minimal version for PSP compatibility)
// This is embedded here to ensure we use the exact same SHA256 as PSP
var CryptoJS = CryptoJS || (function (Math, undefined) {
    var C = {};
    var C_lib = C.lib = {};
    var Base = C_lib.Base = (function () {
        function F() { }
        return {
            extend: function (overrides) {
                F.prototype = this;
                var subtype = new F();
                if (overrides) {
                    subtype.mixIn(overrides);
                }
                if (!subtype.hasOwnProperty('init')) {
                    subtype.init = function () {
                        subtype.$super.init.apply(this, arguments);
                    };
                }
                subtype.init.prototype = subtype;
                subtype.$super = this;
                return subtype;
            },
            create: function () {
                var instance = this.extend();
                instance.init.apply(instance, arguments);
                return instance;
            },
            init: function () {
            },
            mixIn: function (properties) {
                for (var propertyName in properties) {
                    if (properties.hasOwnProperty(propertyName)) {
                        this[propertyName] = properties[propertyName];
                    }
                }
                if (properties.hasOwnProperty('toString')) {
                    this.toString = properties.toString;
                }
            },
            clone: function () {
                return this.init.prototype.extend(this);
            }
        };
    }());
    var WordArray = C_lib.WordArray = Base.extend({
        init: function (words, sigBytes) {
            words = this.words = words || [];
            if (sigBytes != undefined) {
                this.sigBytes = sigBytes;
            } else {
                this.sigBytes = words.length * 4;
            }
        },
        toString: function (encoder) {
            return (encoder || Hex).stringify(this);
        },
        concat: function (wordArray) {
            var thisWords = this.words;
            var thatWords = wordArray.words;
            var thisSigBytes = this.sigBytes;
            var thatSigBytes = wordArray.sigBytes;
            this.clamp();
            if (thisSigBytes % 4) {
                for (var i = 0; i < thatSigBytes; i++) {
                    var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
                }
            } else if (thatWords.length > 0xffff) {
                for (var i = 0; i < thatSigBytes; i += 4) {
                    thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
                }
            } else {
                thisWords.push.apply(thisWords, thatWords);
            }
            this.sigBytes += thatSigBytes;
            return this;
        },
        clamp: function () {
            var words = this.words;
            var sigBytes = this.sigBytes;
            words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
            words.length = Math.ceil(sigBytes / 4);
        },
        clone: function () {
            var clone = Base.clone.call(this);
            clone.words = this.words.slice(0);
            return clone;
        },
        random: function (nBytes) {
            var words = [];
            var r = (function (m_w) {
                var m_w = m_w;
                var m_z = 0x3ade68b1;
                var mask = 0xffffffff;
                return function () {
                    m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
                    m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
                    var result = ((m_z << 0x10) + m_w) & mask;
                    result /= 0x100000000;
                    result += 0.5;
                    return result * (Math.random() > .5 ? 1 : -1);
                }
            });
            for (var i = 0, rcache; i < nBytes; i += 4) {
                var _r = r((rcache || Math.random()) * 0x100000000);
                rcache = _r() * 0x3ade67b7;
                words.push((_r() * 0x100000000) | 0);
            }
            return new WordArray.init(words, nBytes);
        }
    });
    var C_enc = C.enc = {};
    var Hex = C_enc.Hex = {
        stringify: function (wordArray) {
            var words = wordArray.words;
            var sigBytes = wordArray.sigBytes;
            var hexChars = [];
            for (var i = 0; i < sigBytes; i++) {
                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                hexChars.push((bite >>> 4).toString(16));
                hexChars.push((bite & 0x0f).toString(16));
            }
            return hexChars.join('');
        },
        parse: function (hexStr) {
            var hexStrLength = hexStr.length;
            var words = [];
            for (var i = 0; i < hexStrLength; i += 2) {
                words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
            }
            return new WordArray.init(words, hexStrLength / 2);
        }
    };
    var Latin1 = C_enc.Latin1 = {
        stringify: function (wordArray) {
            var words = wordArray.words;
            var sigBytes = wordArray.sigBytes;
            var latin1Chars = [];
            for (var i = 0; i < sigBytes; i++) {
                var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                latin1Chars.push(String.fromCharCode(bite));
            }
            return latin1Chars.join('');
        },
        parse: function (latin1Str) {
            var latin1StrLength = latin1Str.length;
            var words = [];
            for (var i = 0; i < latin1StrLength; i++) {
                words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
            }
            return new WordArray.init(words, latin1StrLength);
        }
    };
    var Utf8 = C_enc.Utf8 = {
        stringify: function (wordArray) {
            try {
                return decodeURIComponent(escape(Latin1.stringify(wordArray)));
            } catch (e) {
                throw new Error('Malformed UTF-8 data');
            }
        },
        parse: function (utf8Str) {
            return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
        }
    };
    var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
        reset: function () {
            this._data = new WordArray.init();
            this._nDataBytes = 0;
        },
        _append: function (data) {
            if (typeof data == 'string') {
                data = Utf8.parse(data);
            }
            this._data.concat(data);
            this._nDataBytes += data.sigBytes;
        },
        _process: function (doFlush) {
            var data = this._data;
            var dataWords = data.words;
            var dataSigBytes = data.sigBytes;
            var blockSize = this.blockSize;
            var blockSizeBytes = blockSize * 4;
            var nBlocksReady = dataSigBytes / blockSizeBytes;
            if (doFlush) {
                nBlocksReady = Math.ceil(nBlocksReady);
            } else {
                nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
            }
            var nWordsReady = nBlocksReady * blockSize;
            var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
            if (nWordsReady) {
                for (var offset = 0; offset < nWordsReady; offset += blockSize) {
                    this._doProcessBlock(dataWords, offset);
                }
                var processedWords = dataWords.splice(0, nWordsReady);
                data.sigBytes -= nBytesReady;
            }
            return new WordArray.init(processedWords, nBytesReady);
        },
        clone: function () {
            var clone = Base.clone.call(this);
            clone._data = this._data.clone();
            return clone;
        },
        _minBufferSize: 0
    });
    var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
        cfg: Base.extend(),
        init: function (cfg) {
            this.cfg = this.cfg.extend(cfg);
            this.reset();
        },
        reset: function () {
            BufferedBlockAlgorithm.reset.call(this);
            this._doReset();
        },
        update: function (messageUpdate) {
            this._append(messageUpdate);
            this._process();
            return this;
        },
        finalize: function (messageUpdate) {
            if (messageUpdate) {
                this._append(messageUpdate);
            }
            var hash = this._doFinalize();
            return hash;
        },
        blockSize: 512 / 32,
        _createHelper: function (hasher) {
            return function (message, cfg) {
                return new hasher.init(cfg).finalize(message);
            };
        },
        _createHmacHelper: function (hasher) {
            return function (message, key) {
                return new C_algo.HMAC.init(hasher, key).finalize(message);
            };
        }
    });
    var C_algo = C.algo = {};
    return C;
}(Math));

(function (Math) {
    var C = CryptoJS;
    var C_lib = C.lib;
    var WordArray = C_lib.WordArray;
    var Hasher = C_lib.Hasher;
    var C_algo = C.algo;
    var H = [];
    var K = [];
    (function () {
        function isPrime(n) {
            var sqrtN = Math.sqrt(n);
            for (var factor = 2; factor <= sqrtN; factor++) {
                if (!(n % factor)) {
                    return false;
                }
            }
            return true;
        }
        function getFractionalBits(n) {
            return ((n - (n | 0)) * 0x100000000) | 0;
        }
        var n = 2;
        var nPrime = 0;
        while (nPrime < 64) {
            if (isPrime(n)) {
                if (nPrime < 8) {
                    H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2));
                }
                K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3));
                nPrime++;
            }
            n++;
        }
    }());
    var W = [];
    var SHA256 = C_algo.SHA256 = Hasher.extend({
        _doReset: function () {
            this._hash = new WordArray.init(H.slice(0));
        },
        _doProcessBlock: function (M, offset) {
            var H = this._hash.words;
            var a = H[0];
            var b = H[1];
            var c = H[2];
            var d = H[3];
            var e = H[4];
            var f = H[5];
            var g = H[6];
            var h = H[7];
            for (var i = 0; i < 64; i++) {
                if (i < 16) {
                    W[i] = M[offset + i] | 0;
                } else {
                    var gamma0x = W[i - 15];
                    var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^
                        ((gamma0x << 14) | (gamma0x >>> 18)) ^
                        (gamma0x >>> 3);
                    var gamma1x = W[i - 2];
                    var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^
                        ((gamma1x << 13) | (gamma1x >>> 19)) ^
                        (gamma1x >>> 10);
                    W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
                }
                var ch = (e & f) ^ (~e & g);
                var maj = (a & b) ^ (a & c) ^ (b & c);
                var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22));
                var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25));
                var t1 = h + sigma1 + ch + K[i] + W[i];
                var t2 = sigma0 + maj;
                h = g;
                g = f;
                f = e;
                e = (d + t1) | 0;
                d = c;
                c = b;
                b = a;
                a = (t1 + t2) | 0;
            }
            H[0] = (H[0] + a) | 0;
            H[1] = (H[1] + b) | 0;
            H[2] = (H[2] + c) | 0;
            H[3] = (H[3] + d) | 0;
            H[4] = (H[4] + e) | 0;
            H[5] = (H[5] + f) | 0;
            H[6] = (H[6] + g) | 0;
            H[7] = (H[7] + h) | 0;
        },
        _doFinalize: function () {
            var data = this._data;
            var dataWords = data.words;
            var nBitsTotal = this._nDataBytes * 8;
            var nBitsLeft = data.sigBytes * 8;
            dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);
            dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;
            data.sigBytes = dataWords.length * 4;
            this._process();
            return this._hash;
        },
        clone: function () {
            var clone = Hasher.clone.call(this);
            clone._hash = this._hash.clone();
            return clone;
        }
    });
    C.SHA256 = Hasher._createHelper(SHA256);
}(Math));

/**
 * Pad string to specific length with leading characters
 * This matches the pad function from PSP brainwallet.js line 165
 * @param {string} str - String to pad
 * @param {number} len - Target length
 * @param {string} ch - Character to pad with
 * @returns {string} - Padded string
 */
function pad(str, len, ch) {
    var padding = '';
    for (var i = 0; i < len - str.length; i++) {
        padding += ch;
    }
    return padding + str;
}

/**
 * PSP Monero Wallet Creation Function
 * This function replicates the EXACT wallet creation logic from the PSP reference implementation
 * Flow: Entropy → MD5 → SHA256 → Pad → Web3-XMR-accounts → Private Key + Address
 * 
 * @param {string} input - The input string (brain wallet passphrase or existing MD5 hash)
 * @returns {object} - Object containing { address, privateKey }
 */
function createPspEthereumWallet(input) {
    try {
        var md5Hash;

        // Check if input is likely already an MD5 hash (32 character hex string)
        // If it is, use it directly as the seed. If not, treat it as raw entropy and MD5 it.
        // This allows the MD5 hash to be used as the "Brain Wallet" key/URL.
        var isMD5 = /^[a-fA-F0-9]{32}$/.test(input);

        if (isMD5) {
            console.log('PSP Wallet Creation - Input detected as MD5 Hash, using directly.');
            md5Hash = input;
        } else {
            console.log('PSP Wallet Creation - Hashing raw entropy to MD5.');
            // Step 1: Create MD5 hash of the entropy
            md5Hash = md5(input);
        }

        console.log('PSP Wallet Creation - MD5 Hash (Brain Wallet):', md5Hash);

        // Step 2: Create SHA256 hash of the MD5 hash
        // This matches: Crypto.SHA256($('#pass').val(), { asBytes: true }) from PSP line 497
        // The MD5 hash becomes the 'pass' value
        var sha256Hash = CryptoJS.SHA256(md5Hash).toString();
        console.log('PSP Wallet Creation - SHA256 Hash:', sha256Hash);

        // Step 3: Pad the SHA256 hash to exactly 64 characters with leading zeros
        // This matches: pad($('#hash').val(), 64, '0') from PSP line 248
        var paddedHash = pad(sha256Hash, 64, '0');
        console.log('PSP Wallet Creation - Padded Hash:', paddedHash);
        console.log('PSP Wallet Creation - Padded Hash Length:', paddedHash.length);

        // Step 4: Create Monero wallet using Web3XMRAccounts
        // This EXACTLY matches reference index.html line 660-661:
        // const account = new Web3XMRAccounts();
        // const keyPair = account.create(paddedHash);
        var account = new Web3EthAccounts();
        var keyPair = account.create(paddedHash);

        // Step 5: Extract address and private key
        // This matches reference index.html lines 664-665:
        // const addr = JSON.parse(JSON.stringify(keyPair.address));
        // const sec = JSON.parse(JSON.stringify(keyPair.privateKey));
        var addr = JSON.parse(JSON.stringify(keyPair.address));
        var sec = JSON.parse(JSON.stringify(keyPair.privateKey));

        console.log('PSP Wallet Creation - Address:', addr);
        console.log('PSP Wallet Creation - Private Key:', sec);

        return {
            address: addr,
            privateKey: sec
        };
    } catch (error) {
        console.error('Error in createPspMoneromWallet:', error);
        throw new Error('Failed to create PSP Monero wallet: ' + error.message);
    }
}

/**
 * PSP Monero Wallet Creation Function
 * Same flow as Monero: Entropy → MD5 → SHA256 → Pad → Monero create_address
 * Reference: PSP_wallet PSP_app/js/brainwallet.js generateMonero()
 *
 * @param {string} input - The input (brain wallet passphrase, MD5 hash, or entropy+password)
 * @returns {object} - Object containing { address, viewKey, spendKey, mnemonic }
 */
function createPspMoneroWallet(input) {
    if (typeof cnUtil === 'undefined') {
        throw new Error('Monero library (cnUtil) not loaded. Ensure monero.js is loaded before psp-wallet.js.');
    }
    if (typeof mn_encode === 'undefined') {
        throw new Error('Mnemonic library (mn_encode) not loaded. Ensure mnemonic.js is loaded.');
    }
    try {
        var md5Hash;
        var isMD5 = /^[a-fA-F0-9]{32}$/.test(input);

        if (isMD5) {
            console.log('PSP Monero - Input detected as MD5 Hash, using directly.');
            md5Hash = input;
        } else {
            console.log('PSP Monero - Hashing input to MD5.');
            md5Hash = md5(input);
        }

        // Step 2: SHA256 of MD5 (same as Monero flow)
        var sha256Hash = CryptoJS.SHA256(md5Hash).toString();
        var paddedHash = pad(sha256Hash, 64, '0');

        // Step 3: Create Monero address and keys
        var keyPair = cnUtil.create_address(paddedHash);
        var mnemonic = mn_encode(paddedHash);

        return {
            address: keyPair.public_addr,
            viewKey: keyPair.view.sec,
            spendKey: keyPair.spend.sec,
            mnemonic: mnemonic
        };
    } catch (error) {
        console.error('Error in createPspMoneroWallet:', error);
        throw new Error('Failed to create PSP Monero wallet: ' + error.message);
    }
}

console.log('PSP Wallet Library loaded successfully');
