{% extends "base.jinja" %}

{% block main %}
    {% include "includes/hwi/hwi.jinja" %}
    {% include "includes/qr-scanner.html" %}

    <form id="form" class="flex flex-col" action="{{ url_for('devices_endpoint.new_device_keys', device_type=device_class.device_type) }}" method="POST" onsubmit="showPacman();">
        {% if existing_device %}
            <input type="hidden" name="existing_device" value="{{ existing_device.alias }}">
        {% endif %}

        <input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>

        {% if device_class.hot_wallet %}
            <input type="hidden" name="mnemonic" value="{{ mnemonic }}"/>
            <input type="hidden" name="passphrase" value="{{ passphrase }}"/>
            <input type="hidden" name="file_password" value="{{ file_password }}"/>
            <input type="hidden" name="range_start" value="{{ range_start }}"/>
            <input type="hidden" name="range_end" value="{{ range_end }}"/>
        {% endif %}

        <div>
            <h1>{{ _("Upload Keys") }}</h1>

            {% if not existing_device %}
                <h3 class="mt-8">Information</h3>
                <div class="floating-wrapper mb-1">
                    <input type="text" pattern="^[^']+" title="Do not use single quotes" name="device_name" placeholder=" " class="floating-input peer" id="device_name" required/>
                    <label class="floating-label">Device Name</label>
                </div>
            {% endif %}
            
            <div id="coldcard-instructions" class="{%if device_class.device_type != 'coldcard' %}hidden{% endif %}">
                <p>{{ _("Connect your ColdCard to the computer via USB and unlock it or upload a wallet export file from micro SD card.") }}</p>

                <ol>
                    <li>{{ _("To export the wallet data file from the ColdCard, insert your micro SD card into the device, then:") }}</li>
                    <li>{{ _("To get the single sig xpubs, go to: Advanced &rarr; MicroSD Card &rarr; Export Wallet &rarr; Generic JSON. When asked for account, select account number (default is 0) and confirm the export.") }}</li>
                    <li>{{ _("To get the multisig xpubs, go to: Settings &rarr; Multisig Wallets &rarr; Export XPUB and confirm the export.") }}</li>
                </ol>
            </div>

            <div id="specter-instructions" class="{%if device_class.device_type != 'specter' %}hidden{% endif %}">
                <p>{{ _("Connect your Specter-DIY to the computer via USB and unlock it or scan the wallet master public key.") }}</p>

                <ol>
                    <li>{{ _("To get the master public key QR code to scan, click on: Master public keys, then select each key type you'd like to import and scan the displayed QR code for each into Specter.") }}</li>
                </ol>
            </div>

            <div id="cobo-instructions" class="{%if device_class.device_type != 'cobo' %}hidden{% endif %}">
                <p>{{ _("Scan your Cobo Vault master public key or upload a wallet export file.") }}</p>

                <ol>
                    <li>{{ _("To get the master public keys QR codes to scan:") }}</li>
                    <li>{{ _("For single sig, click on: Setting &rarr; Watch-Only Wallet &rarr; Generic Wallet, Click the top right 3 dots (...) &rarr; Wallet Info &rarr; Toggle Address Type, then select the wallet address type you would like to use for your wallet.") }}</li>
                    <li>{{ _("For multisig, click on: Multisig Wallet &rarr; Click the top right 3 dots (...) &rarr; Show/Export XPUB, then select the wallet address type you would like to use for your wallet.") }}</li>
                    <li>{{ _("Cobo Vault will then display the QR code which you should scan into Specter.") }}</li>
                    <li>{{ _("To import with SD card, click on \"touch here to export the file with microSD\" on the same screen as the QR code.") }}</li>
                </ol>
            </div>

            <div id="passport-instructions" class="{%if device_class.device_type != 'passport' %}hidden{% endif %}">
                <p>{{ _("Pair your Passport to Specter using QR codes or a microSD card.") }}</p>

                <ol>
                    <li>{{ _("To pair your Passport with Specter, navigate to Pair Wallet –> Specter and follow the instructions on your Passport.")}}</li>
                    <li>{{ _("If your webcam is having difficulty scanning QR codes on Passport’s screen, we recommend using a microSD.") }}</li>
                </ol>
            </div>

            <div id="hwi-only-instructions" class="{%if device_class.device_type in ['bitcoincore', 'bitcoincore_watchonly', 'cobo', 'coldcard', 'electrum', 'other', 'specter']%}hidden{% endif %}">
                <p>
                    {{ _("Connect your hardware device to the computer via USB.") }}
                    {% if device_class.device_type == 'ledger' %}
                        {{ _("Open the Bitcoin app on your Ledger.") }}
                    {% endif %}
                </p>
            </div>

            <h3 class="mt-8">Keys</h3>
            <table>
                <thead>
                    <tr>
                        <th>{{ _("Purpose") }}</th>
                        <th>{{ _("Derivation") }}</th>
                        <th class="table-key">{{ _("XPUB") }}</th>
                        <th><button type="button" class="btn" data-style="width: 60px;" id="edit-xpubs-table-btn" onclick="editXpubsTable(this)">{{ _("Edit") }}</button></th>
                    </tr>
                </thead>

                <tbody id="xpubs_table">

                </tbody>

                <input id="xpubs-table-rows-count" name="xpubs_rows_count" type="hidden"/>
            </table>

            <div class="grid grid-cols-2 gap-x-5 my-5">
                <div id="connect-hwi" class="button text-base {% if not device_class.hwi_support %}opacity-20 pointer-events-none{% endif %}">
                    <img src="{{ url_for('static', filename='img/usb.svg') }}">
                    {{ _("Get via USB") }}
                </div>

                <label id="connect-sdcard" class="{% if not device_class.sd_card_support %}opacity-20 pointer-events-none{% endif %}">
                    <input type="file" id="file" multiple class="hidden"/>
                    <div class="button text-base">
                        <img src="{{ url_for('static', filename='img/sd-card.svg') }}">
                        {{ _("Upload from SD") }}
                    </div>
                </label>

                <qr-scanner id="xpub-scan" class="{% if not device_class.qr_code_support %}opacity-20 pointer-events-none{% endif %}">
                    <a slot="button" href="#" class="button">
                        <img src="{{ url_for('static', filename='img/qr-code.svg') }}">
                        {{ _("Scan QR code") }}
                    </a>
                </qr-scanner>

                <button id="xpub-paste" class="button text-base {% if device_class.device_type not in ['electrum', 'other'] %}opacity-20 pointer-events-none{% endif %}" type="button" onclick="showPageOverlay('paste-xpub-popup')" class="btn centered">
                    <img src="{{ url_for('static', filename='img/copy.svg') }}">
                    {{ _("Paste xpub") }}
                </button>

                <div id="paste-xpub-popup" class="hidden bg-dark-800 p-4 w-96">
                    <div class="flex justify-between mb-2">
                        <h3 class="mb-0">{{ _("Paste xpub") }}</h3>
                        <p class="m-0 cursor-pointer" onclick="hidePageOverlay()" class="cursor-pointer">Close</p>
                    </div>
                    <div class="floating-wrapper mb-3 mt-1">
                        <input class="floating-input peer" type="text" id="paste-xpub-text" placeholder=" ">
                        <label class="floating-label" for="paste-xpub-text">xpub</label>
                    </div>
                    <button class="button bg-accent mr-0 mb-1 w-full" type="button" onclick="addXpubs(document.getElementById('paste-xpub-text').value);hidePageOverlay();document.getElementById('paste-xpub-text').value='';">{{ _("Add xpub") }}</button>
                </div>
            </div>
        </div>

        <button class="button w-[calc(680px/2)] bg-accent text-white self-end" type="submit" id="submit-keys">{{ _("Continue") }}</button>
    </form>
{% endblock %}

{% block scripts %}
    <script>
        let xpubsTableRows = 0;

        function addXpub(purpose, derivation, xpub='-') {
			let xpubsTable = document.getElementById('xpubs_table');
            derivation = derivation.replaceAll("'", "h");
            for (let existingRow of xpubsTable.children) {
                let existingRowDerivation = document.getElementById(existingRow.id + '-derivation')
                if (existingRowDerivation && existingRowDerivation.innerHTML == derivation) {
                    document.getElementById(existingRow.id + '-xpub').childNodes[0].innerHTML = xpub;
                    document.getElementById(existingRow.id + '-xpub-hidden').value = xpub;
                    if (purpose != 'Custom' && document.getElementById(existingRow.id + '-purpose').value == 'Custom') {
                        document.getElementById(existingRow.id + '-purpose').value = purpose;
                    }
                    return;
                }
            }
            xpubsTableRows++;
            let xpubsTableRowsInput = document.getElementById('xpubs-table-rows-count')
            xpubsTableRowsInput.value = xpubsTableRows;
            let xpubRowHTML = `
                <td><input id="xpubs-table-row-${xpubsTableRows}-purpose" name="xpubs-table-row-${xpubsTableRows}-purpose" type="text" value="${purpose}" placeholder="{{ _("XPUB purpose") }}" data-style="width: 90%; min-width: 100px;"></td>
                <td id="xpubs-table-row-${xpubsTableRows}-derivation" data-style="min-width: 0px;">${derivation}</td>
                <input id="xpubs-table-row-${xpubsTableRows}-derivation-hidden" name="xpubs-table-row-${xpubsTableRows}-derivation-hidden" type="hidden" value="${derivation}">
                <td id="xpubs-table-row-${xpubsTableRows}-xpub" class="xpub scroll" data-style="overflow: auto;"><div class="table-key">${xpub}</div></td>
                <input id="xpubs-table-row-${xpubsTableRows}-xpub-hidden" name="xpubs-table-row-${xpubsTableRows}-xpub-hidden" type="hidden" value="${xpub}">
                <td><button type="button" class="btn xpubs_edit" data-style="width: 60px;" onclick="document.getElementById('xpubs-table-row-${xpubsTableRows}').remove()">Remove</button></td>
            `
            let xpubRow = document.createElement('tr');
            xpubRow.id = `xpubs-table-row-${xpubsTableRows}`;
			xpubRow.innerHTML = xpubRowHTML;
            xpubsTable.insertBefore(xpubRow, xpubsTable.children[xpubsTable.children.length - 3]);
        }

        function addXpubs(xpubs) {
            xpubs.split('\n').filter(xpub => xpub != '').forEach(xpub => {
                if (xpub.startsWith("[")) {
                    let [fingerprint, ...derivation] = xpub.split('[')[1].split(']')[0].split('/')
                    derivation = derivation.join('/').replace("'", 'h').toLowerCase();
                    addXpub('Custom', 'm/' + derivation, ['[' + fingerprint.toLowerCase(), '/' , derivation, ']', xpub.split(']')[1]].join(''))
                } else {
                    let xpubPurpose = 'Custom'
                    if (xpub.startsWith("ypub") || xpub.startsWith("upub")) {
                        xpubPurpose = `{{ _("#0 Single Sig (Nested)") }}`;
                    } else if (xpub.startsWith("vpub") || xpub.startsWith("zpub")) {
                        xpubPurpose = `{{ _("#0 Single Sig (Segwit)") }}`;
                    }
                    addXpub(xpubPurpose, 'm/', xpub)
                }
            })
            
            if (document.getElementById('edit-xpubs-table-btn').innerHTML != `{{ _("Edit") }}`) {
                for (let el of document.getElementsByClassName('xpubs_edit')) {
                    el.style.visibility = 'visible';
                }
            }
        }

        function setupXpubsTable(device='null') {
			let xpubsTable = document.getElementById('xpubs_table');
            xpubsTable.innerHTML = '';
            let xpubAddAccountRowHTML =  `
                <td><input id="account_number_xpubs" value="0" min="0" type="number" step="1" data-style="width: 150px;" placeholder="{{ _("Account #") }}"></td>
                <td><button type="button" class="btn" data-style="width: 150px;" onclick="addAccountXpubs()">{{ _("Add account") }}</button></td>
                <td></td>
            `
            let xpubAddAccountRow = document.createElement('tr');
            xpubAddAccountRow.id = `edit_add_account`;
            xpubAddAccountRow.style.display = 'none';
			xpubAddAccountRow.innerHTML = xpubAddAccountRowHTML;
            xpubsTable.appendChild(xpubAddAccountRow);

            let xpubAddCustomRowHTML =  `
                <td><input id="new_xpub_purpose" type="text" placeholder="{{ _("XPUB purpose") }}"></td>
                <td><input id="new_xpub_derivation" type="text" placeholder="m/..."></td>
                <td></td>
                <td><button type="button" class="btn" data-style="width: 60px;" onclick="addCustomDerivation()">{{ _("Add") }}</button></td>
            `
            let xpubAddCustomRow = document.createElement('tr');
            xpubAddCustomRow.id = `edit_custom_der`;
            xpubAddCustomRow.style.display = 'none';
			xpubAddCustomRow.innerHTML = xpubAddCustomRowHTML;
            xpubsTable.appendChild(xpubAddCustomRow);

            let xpubAddRowHTML = `
                <td><button type="button" class="btn" data-style="width: 150px;" onclick="document.getElementById('edit_add_account').style.display='table-row';">{{ _("Add account") }}</button></td>
                <td><button type="button" class="btn" data-style="width: 150px;" onclick="document.getElementById('edit_custom_der').style.display='table-row';">{{ _("Add custom derivation") }}</button></td>
                <td></td>
            `
            let xpubAddRow = document.createElement('tr');
            xpubAddRow.id = `edit_select_add_method`;
            xpubAddRow.classList.add(`xpubs_edit`);
			xpubAddRow.innerHTML = xpubAddRowHTML;
            xpubsTable.appendChild(xpubAddRow);
            if (device && device != 'electrum') {
                addAccountXpubs();
            } else {
                addXpub('Custom', 'm/');
            }
            for (let el of document.getElementsByClassName('xpubs_edit')) {
                el.style.visibility = 'hidden';
            }
        }

        function addAccountXpubs() {
            let accountNumberField = document.getElementById('account_number_xpubs')
            let accountNumber = accountNumberField.value;
            addXpub('#' + accountNumber + ' Single Sig (Nested)', 'm/49h/{{ specter.network_parameters.bip32 }}h/' + accountNumber + 'h');
            addXpub('#' + accountNumber + ' Single Sig (Segwit)', 'm/84h/{{ specter.network_parameters.bip32 }}h/' + accountNumber + 'h');
            {% if specter.taproot_support and device_class.taproot_support %}
            addXpub('#' + accountNumber + ' Single Sig (Taproot)', 'm/86h/{{ specter.network_parameters.bip32 }}h/' + accountNumber + 'h');
            {% endif %}
            addXpub('#' + accountNumber + ' Multisig Sig (Nested)', 'm/48h/{{ specter.network_parameters.bip32 }}h/' + accountNumber + 'h/1h');
            addXpub('#' + accountNumber + ' Multisig Sig (Segwit)', 'm/48h/{{ specter.network_parameters.bip32 }}h/' + accountNumber + 'h/2h');
            document.getElementById('edit_add_account').style.display = 'none';
            accountNumberField.value = 0;
            for (let el of document.getElementsByClassName('xpubs_edit')) {
                el.style.visibility = 'visible';
            }
        }

        function addCustomDerivation() {
            let purposeTextField = document.getElementById('new_xpub_purpose')
            let derivationTextField = document.getElementById('new_xpub_derivation')
            addXpub(purposeTextField.value, derivationTextField.value);
            document.getElementById('edit_custom_der').style.display = 'none';
            purposeTextField.value = '';
            derivationTextField.value = '';
            for (let el of document.getElementsByClassName('xpubs_edit')) {
                el.style.visibility = 'visible';
            }
        }

        document.addEventListener("DOMContentLoaded", function(){
            setupXpubsTable();
        });

        function editXpubsTable(editBtn) {
            let visibility = '';
            if (editBtn.innerHTML == `{{ _("Edit") }}`) {
                editBtn.innerHTML = `{{ _("Done") }}`;
                visibility = 'visible';
            } else {
                editBtn.innerHTML = `{{ _("Edit") }}`;
                visibility = 'hidden';
            }
            document.getElementById('edit_select_add_method').style.display = 'table-row';
            document.getElementById('edit_add_account').style.display = 'none';
            document.getElementById('edit_custom_der').style.display = 'none';
            for (let el of document.getElementsByClassName('xpubs_edit')) {
                el.style.visibility = visibility;
            }
        }
    </script>

    <script type="text/javascript">
        document.getElementById('connect-hwi').addEventListener('click', async e => {
            // detect devices
            let deviceType = `{{ device_class.device_type }}`;
            let devices = await enumerate(deviceType);
            if(devices == null){
                return
            }
            // this shouldn't happen actually
            if(devices.length == 0){
                showError(`{{ _("No devices found :(") }}`);
                return;
            }

            let device;
            if (devices.length == 1) {
                device = devices[0];
                let passphrase = await unlockDevice(device);
                if (passphrase != null){
                    device.passphrase = passphrase;
                }
            } else {
                // first only for now
                device = await selectDevice(devices);
            }

            // nothing to do if user cancelled
            if(device == null){
                return;
            }

            let xpubsTable = document.getElementById('xpubs_table');
            
            for (let xpubRow of [].slice.call(xpubsTable.children, 0, xpubsTable.children.length - 3)) {
                if (document.getElementById(xpubRow.id + '-xpub').childNodes[0].innerHTML != '-') {
                    continue
                }

                let xpub = await getXpub(device,
                    document.getElementById(xpubRow.id + '-derivation').innerHTML,
                    "",
                    "{specter.chain}"
                );
                if(xpub == null){
                    showError(`{{ _("Failed to retrive device data. Please try again.") }}`);
                    return;
                }

                addXpub('Custom', document.getElementById(xpubRow.id + '-derivation').innerHTML, xpub)
            }
        });
    </script>

    <script type="text/javascript">
        document.addEventListener("DOMContentLoaded", function(){
            var el = document.getElementById("file");

            if (el != null) {
                el.addEventListener("change", (e) => {
                    files = e.currentTarget.files;
                    console.log(files);
                    for(let i=0; i<files.length; i++){
                        console.log(files[i].name);
                        let reader = new FileReader();
                        reader.onload = function(e) {
                            let str = reader.result
                            let data = checkColdcardXpubsFormat(str);
                            if (data == "") {
                                str.split("\n").forEach((line)=>{
                                    if(line.indexOf("(") >= 0){
                                        let arr = line.split("(");
                                        let args = arr[arr.length-1].split(")")[0];
                                        // if multisig
                                        args.split(",").forEach((arg) => {
                                            // must have pub in xpub
                                            if(arg.indexOf("pub") > 0){
                                                let re = /(.*pub[^/]*).*/g.exec(arg);
                                                if(re){
                                                    data += re[re.length-1].replaceAll("'","h") + "\n";
                                                }
                                            }
                                        });
                                    }else{
                                        data += line+"\n";
                                    }
                                });
                            }
                            addXpubs(data);
                        }
                        reader.readAsText(files[i]);
                    }
                    el.value = null;
                });
            }
        });
    </script>

    <script type="text/javascript">
        let scanner = document.getElementById('xpub-scan');
        if(scanner != null) {
            scanner.addEventListener('scan', async (e) => {
                let result = e.detail.result;
                if(result==null){
                    return;
                }

                // foundation's passport returns coldcard format encoded in bcur2
                if(result.type == "bytes"){
                    let b = window.URlib.Bytes.fromCBOR(result.cbor);
                    let str = new TextDecoder().decode(b.getData());
                    let data = checkColdcardXpubsFormat(str);
                    addXpubs(data);
                    return;
                }

                // keystone returns bcur2 result
                if(result.type == "crypto-account"){
                    let account = window.URlib.CryptoAccount.fromCBOR(result.cbor);
                    let mfp = window.buffer.Buffer.from(account.getMasterFingerprint()).toString("hex");
                    let txtkeys = "";
                    account.getOutputDescriptors().forEach( async (e) => {
                        let k = e.getHDKey();
                        let path = k.getOrigin().getPath().replaceAll("'","h");
                        let is_testnet = false;
                        if (k.getOrigin().getDepth() >= 2){
                            is_testnet = (k.getOrigin().getComponents()[1].getIndex() == 1);
                        }
                        // version, depth, fgp, childnumber, chaincode, key, checksum
                        let xpub = new Uint8Array(4+1+4+4+32+33+4);
                        if(is_testnet){
                            // testnet version
                            xpub.set(new Uint8Array([0x04, 0x35, 0x87, 0xcf]), 0);
                        }else{
                            // mainnet version
                            xpub.set(new Uint8Array([0x04, 0x88, 0xb2, 0x1e]), 0);
                        }
                        let key = k.getKey();
                        xpub[4] = k.getOrigin().getDepth();
                        xpub.set(k.getParentFingerprint(), 5);
                        let der = k.getOrigin().getComponents();
                        let childnum = der[der.length-1];
                        let num = childnum.getIndex();
                        if(childnum.isHardened()){
                            num += 0x80000000;
                        }
                        for(let i = 0; i < 4; i++){
                            xpub[9+3-i] = (num % 256);
                            num = (num >>> 8);
                        }
                        xpub.set(k.getChainCode(), 13);
                        xpub.set(key, 13+32);
                        // calc checksum
                        let checksum = await crypto.subtle.digest('SHA-256',
                                        await crypto.subtle.digest('SHA-256', xpub.slice(0,78))
                        );
                        xpub.set(new Uint8Array(checksum).slice(0,4), 78);
                        addXpubs(`[${mfp}/${path}]${Base58.encode(xpub)}`);
                    });
                } else if(result.includes("{")){
                    // cobo uses json format
                    let obj = JSON.parse(result);
                    if(("ExtPubKey" in obj) && 
                        ("AccountKeyPath" in obj) && 
                        ("MasterFingerprint" in obj)){
                        console.log(obj);
                        let path = obj.AccountKeyPath.replace(/'/g,'h');
                        let str = `[${obj.MasterFingerprint}/${path}]${obj.ExtPubKey}`;
                        addXpubs(str);
                    } else if( ("xfp" in obj) &&
                            ("xpub" in obj) &&
                            ("path" in obj)){
                        console.log(obj);
                        let path = obj.path.replace(/'/g,'h').replace("m/","");
                        let str = `[${obj.xfp}/${path}]${obj.xpub}`;
                        addXpubs(str);
                    }else{
                        showError(`{{ _("Unknown key format") }}`);
                    }
                }else{
                    addXpubs(result);
                }
            });
        }

        function checkColdcardXpubsFormat(str) {
            let data = "";
            if (str.indexOf("{") >= 0) {
                let json = JSON.parse(str);
                console.log(str);
                if ("keystore" in json) { // ColdCard electrum file
                    let prefix = "";
                    let fingerprint = "";
                    if ("ckcc_xfp" in json.keystore){
                        let num = json.keystore.ckcc_xfp;
                        for (let i = 0; i < 4; i++) {
                            fingerprint += ('0' + (num % 256).toString(16)).slice(-2);
                            num = num >>> 8;
                        }
                    }
                    if ("root_fingerprint" in json.keystore){
                        fingerprint = json.keystore.root_fingerprint;
                    }
                    if ((fingerprint.length > 0) && ("derivation" in json.keystore)) {
                        prefix = "[";
                        prefix += fingerprint;
                        prefix += json.keystore.derivation.substring(1).replace(/'/g,"h");
                        prefix += "]";
                    }
                    let s = prefix + json.keystore.xpub + "\n";
                    data += s;
                } else if ("ExtPubKey" in json){
                    // Probably Cobo single key txt file
                    let der = '/' + json.AccountKeyPath.replace("m","").replace(/'/g,"h");
                    data += `[${json.MasterFingerprint}${der}]${json.ExtPubKey}\n`;
                } else if (("bip84" in json) && ("xfp" in json)){
                // coldcard generic file
                    let s = "";
                    ["bip44","bip49","bip84","bip48_1","bip48_2"].forEach((bip)=>{
                        if(bip in json){
                            let der = json[bip].deriv.replace("m","").replace(/'/g,"h");
                            let xpub = json[bip].xpub;
                            if("_pub" in json[bip]){
                                xpub = json[bip]["_pub"];
                            }
                            s += `[${json.xfp}${der}]${xpub}\n`;
                        }
                    });
                    data += s;
                } else if (("xpub" in json) && ("path" in json) && ("xfp" in json)) {
                    // Probably Cobo single key file
                    let der = json.path.replace("m","").replace(/'/g,"h");
                    data += `[${json.xfp}${der}]${json.xpub}\n`;
                } else if ("xfp" in json) {
                // probably ColdCard multisig file
                    let s = "";
                    for (let k in json) {
                        if (k+"_deriv" in json) {
                            s += "["+json.xfp+json[k+"_deriv"].substring(1)+"]"+json[k]+"\n";
                        }
                    }
                    data += s;
                }
            }
            return data;
        }
    </script>
{% endblock %}
