"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mintblue = void 0;
const bytes_js_1 = __importDefault(require("@root/encoding/bytes.js"));
const hex_js_1 = __importDefault(require("@root/encoding/hex.js"));
const core_1 = require("@ts-bitcoin/core");
const axios_1 = __importDefault(require("axios"));
const univrse_1 = require("univrse");
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const mintblue_js_1 = require("../types/mintblue.js");
const v1_js_1 = require("./envelopes/doc/v1.js");
const envelopes_js_1 = require("./envelopes/envelopes.js");
const v1_js_2 = require("./envelopes/hash/v1.js");
const v1_js_3 = require("./envelopes/peppol/v1.js");
const utils = __importStar(require("./utils.js"));
const DEFAULT_OPTIONS = {
    url: 'https://api.mintblue.com',
    version: '2',
    SDKTokenPrefix: 'secret-token:mintBlue.com/sdk/',
    ServerTokenPrefix: 'secret-token:mintBlue.com/api/',
};
class Mintblue {
    // private keys: Keys;
    // Supply token as 'server-token'
    /**
     * @ignore
     */
    constructor(options) {
        this.options = { ...DEFAULT_OPTIONS, ...options };
        this.api =
            this.options.mockApi ||
                axios_1.default.create({
                    headers: {
                        Authorization: 'Bearer ' + this.options.token,
                    },
                    baseURL: `${this.options.url}/api/v${this.options.version}/`,
                });
    }
    /**
     * Instantiate client with accesstoken (SDK-token)
     *
     * @param options needs to contain accesstoken
     * @returns MintBlue SDK instance
     */
    static async create(options) {
        // Get secret from accesstoken
        options = { ...DEFAULT_OPTIONS, ...options };
        const token = options.token;
        let tokenId;
        let tokenSecret;
        // TODO: refactor to token parsing func
        if (token.startsWith(options.SDKTokenPrefix)) {
            tokenId = token.substring(options.SDKTokenPrefix.length).split('.')[0];
            tokenSecret = token.substring(options.SDKTokenPrefix.length).split('.')[1];
        }
        else if (token.includes(':')) {
            [tokenId, tokenSecret] = token.split(':');
        }
        const serverAccessToken = `${options.ServerTokenPrefix}${tokenId}.${await utils.sha256s(tokenSecret)}`;
        let userKeyAttributes;
        if (!options.userKeyAttributes) {
            const res = await axios_1.default.get(`${options.url}/api/v${options.version}/accesstoken/${tokenId}`, {
                headers: {
                    authorization: `Bearer ${serverAccessToken}`,
                },
            });
            userKeyAttributes = res.data.data.userKeyAttributes;
        }
        else {
            userKeyAttributes = options.userKeyAttributes;
        }
        const keys = await utils.getKeysFromUserKeyAttributes(tokenSecret, userKeyAttributes);
        return new Mintblue({
            ...options,
            keys,
            userKeyAttributes,
            token: serverAccessToken,
        });
    }
    /**
     * Instantiate client with login (e-mail and password)
     *
     * @param email
     * @param password
     * @param options optional options for advanced use
     * @returns MintBlue SDK instance
     */
    static async login(email, password, options) {
        options = { ...DEFAULT_OPTIONS, ...options };
        const hashedPassword = await utils.sha256s(password);
        const res = await axios_1.default
            .post(`${options.url}/api/v${options.version}/auth`, {
            email,
            password: hashedPassword,
        })
            .then((res) => res.data);
        const userKeyAttributes = res.userKeyAttributes;
        const keys = await utils.getKeysFromUserKeyAttributes(password, userKeyAttributes);
        return new Mintblue({ token: res.token, userKeyAttributes, keys, ...options });
    }
    /**
     * Create access token
     *
     * @param options
     * @returns SDK token & API token
     */
    async createAccesstoken(options) {
        const secret = (await import('nanoid')).customAlphabet(alphabet, 20)();
        const hashedSecret = await utils.sha256s(secret);
        const { userKeyAttributes } = await utils.generateUserKeyAttributes(secret, this.options.keys);
        const body = {
            ...options,
            secret: hashedSecret,
            userKeyAttributes,
        };
        const res = await this.api.post('accesstoken', body);
        const id = res.data.data.accesstoken.id;
        // TODO: return sdkToken and apiToken
        return {
            sdkToken: `${DEFAULT_OPTIONS.SDKTokenPrefix}${id}.${secret}`,
            apiToken: `${DEFAULT_OPTIONS.ServerTokenPrefix}${id}.${hashedSecret}`,
        };
    }
    /**
     * @ignore
     */
    async createPigi(fileContent) {
        return this.api
            .postForm('pigi/upload', {
            file: new Blob([fileContent]),
        })
            .then((res) => res.data);
    }
    /**
     * @ignore
     */
    async createAPIOptions(options) {
        const convertedKey = univrse_1.util.fromBsvPrivKey(this.options.keys.privateKey);
        const apiOptions = {
            outputs: [],
            project_id: options.project_id,
            metadata: options.metadata,
        };
        apiOptions.outputs = await Promise.all(options.outputs.map(async (output) => {
            if (output.type === 'data' || output.type === 'file') {
                const nonce = await utils.generateSaltToDeriveKey(16);
                const headers = output.type === 'file'
                    ? {
                        proto: 'mintBlue',
                        meta: { type: 'file', contentType: output.value.contentType },
                    }
                    : { proto: 'mintBlue', meta: {} };
                const env = univrse_1.Envelope.wrap(output.value, headers);
                // TODO: determine algos
                if (output.sign) {
                    await env.sign(convertedKey, { alg: 'ES256K' });
                }
                if (output.encrypt) {
                    // TODO: derive key
                    await env.encrypt(convertedKey.toPublic(), { alg: 'ECDH-ES+A128GCM', nonce });
                }
                delete output.sign;
                delete output.encrypt;
                return { type: 'env', value: env.toString() };
            }
            else if (output.type === 'pigi') {
                if (output.version === 1) {
                    //
                }
                else if (output.version === 2) {
                    const signingKey = await utils.crypto.subtle.importKey('jwk', output.signingKey, {
                        name: 'ECDSA',
                        namedCurve: 'P-256',
                    }, true, ['sign']);
                    const p = await envelopes_js_1.pigiV2.create(output.value, [signingKey], output.options);
                    // Todo remove @bitcoin-ts/core in favor of bsv wasm or nimble to get rid of these buffers
                    const script = core_1.Script.fromSafeDataArray([
                        Buffer.from(bytes_js_1.default.strToBuf(envelopes_js_1.pigiV2.PROTOCOL_ID)),
                        Buffer.from(p.encryptedData),
                    ]);
                    return { type: 'script', encoding: 'hex', value: script.toHex() };
                }
            }
            else if (output.type == 'peppol') {
                if (output.version !== 1) {
                    throw new Error('Peppol unsupported version. Try version 1');
                }
                const p = await v1_js_3.PeppolV1.create(output);
                return { type: 'script', encoding: 'hex', value: p.toOpReturnScriptHex() };
            }
            else if (output.type == 'mB:doc1') {
                const docv1 = await v1_js_1.DocV1.create(output);
                return { type: 'script', encoding: 'hex', value: docv1.toOpReturnScriptHex() };
            }
            else if (output.type === mintblue_js_1.OutputType.hash) {
                const hashed = await v1_js_2.Hash.create({ ...output, key: convertedKey });
                return { type: 'script', encoding: 'hex', value: hashed.toOpReturnScriptHex() };
            }
            else {
                if (!Object.values(mintblue_js_1.OutputType).includes(output.type)) {
                    throw new Error('Unsupported output type');
                }
            }
            return output;
        }));
        return apiOptions;
    }
    /**
     * Create a blockchain transaction
     *
     * @param options
     * @returns Published transaction
     */
    async createTransaction(options) {
        const apiOptions = await this.createAPIOptions(options);
        return this.api.post('transaction', apiOptions).then((res) => res.data.data);
    }
    /**
     * Fetch a blockchain transaction
     *
     * @param txid Transaction ID of the blockchain transaction
     * @param secret Optional secret to decrypt content (Unnecessary when encrypted with your keys, decryption will automatically run)
     * @returns Array of decrypted & parsed outputs
     */
    async getTransaction(txid, secret) {
        const tx = await this.api.get(`transaction/${txid}`);
        const data = tx.data.data;
        // Even when default options now points to version 2 there's nothing that stops the user to request v1 data, hence a compatibility check.
        const rawtx = this.options.version === '1' ? hex_js_1.default.bufToHex(new Uint8Array(data.rawtx.data)) : data.rawtx;
        return this.parseTransaction(txid, rawtx, secret);
    }
    /**
     * @ignore
     */
    async parseTransaction(txid, rawtx, secret) {
        const convertedKey = univrse_1.util.fromBsvPrivKey(this.options.keys.privateKey);
        const bsvTx = core_1.Tx.fromHex(rawtx);
        const outputs = [];
        for (const txOut of bsvTx.txOuts) {
            outputs.push(await this.parseOutput(txid, txOut, secret ? secret : convertedKey));
        }
        return outputs;
    }
    /**
     * List transactions
     *
     * @param options
     * @returns Array of transactions (without payload)
     */
    listTransactions(options) {
        var _a, _b;
        const params = {
            ...options,
            start_date: (_a = options.startDate) === null || _a === void 0 ? void 0 : _a.toISOString(),
            end_date: (_b = options.startDate) === null || _b === void 0 ? void 0 : _b.toISOString(),
        };
        return this.api.get('transaction', { params }).then((res) => res.data.data);
    }
    /**
     * Create a project
     *
     * @param options
     * @returns Array of transactions (without payload)
     */
    createProject(options) {
        return this.api.post('project', options).then((res) => res.data.data);
    }
    /**
     * Fetch a project
     *
     * @param id Project ID
     * @returns Project properties
     */
    getProject(id) {
        return this.api.get(`project/${id}`).then((res) => res.data.data);
    }
    /**
     * Update a project
     *
     * @param id Project ID
     * @param options Project properties
     * @returns Project properties
     */
    updateProject(id, options) {
        return this.api.patch(`project/${id}`, options).then((res) => res.data.data);
    }
    /**
     * List all projects
     *
     * @returns List of projects
     */
    listProjects() {
        return this.api.get('project').then((res) => res.data.data);
    }
    /**
     * Delete a project
     *
     * @param id Project ID
     */
    destroyProject(id) {
        return this.api.delete(`project/${id}`).then((res) => res.data.data);
    }
    /**
     * @experimental
     * Get Peppol Key information
     * @returns Object containing uncompressed public key in hex format. \
     * Read more about compressed and decompressed keys [here](https://medium.com/asecuritysite-when-bob-met-alice/02-03-or-04-so-what-are-compressed-and-uncompressed-public-keys-6abcb57efeb6)
     */
    experimentalGetPeppolKeyInfo() {
        const key = this.options.keys.additionalKeys.find((x) => x.info === 'peppol');
        const uncompressedPublicKey = utils.JWKToUncompressedHex(key.jwk);
        return {
            uncompressedPublicKey,
            compressedPublicKey: utils.compressPublicKey(uncompressedPublicKey),
            jwk: key.jwk,
            kid: key.kid,
            info: key.info,
        };
    }
    /**
     * @experimental
     * Get Peppol Key information
     * @returns Object containing uncompressed public key in hex format. \
     * Read more about compressed and decompressed keys [here](https://medium.com/asecuritysite-when-bob-met-alice/02-03-or-04-so-what-are-compressed-and-uncompressed-public-keys-6abcb57efeb6)
     */
    async experimentalGetKVKKeyInfo() {
        const key = this.options.keys.additionalKeys.find((x) => x.info === 'kvk');
        const uncompressedPublicKey = utils.JWKToUncompressedHex(key.jwk);
        return {
            uncompressedPublicKey,
            compressedPublicKey: utils.compressPublicKey(uncompressedPublicKey),
            jwk: key.jwk,
            kid: key.kid,
            info: key.info,
            SHA1Fingerprint: await utils.getSHA1Fingerprint(key.jwk),
        };
    }
    /**
     * @ignore
     */
    async parseOutput(_txid, txOut, secret) {
        const outputType = utils.identifyOutput(txOut);
        let output;
        if (outputType === mintblue_js_1.OutputType.data || outputType === mintblue_js_1.OutputType.file) {
            let envelope = univrse_1.Envelope.fromScript(txOut.script);
            output = { type: mintblue_js_1.OutputType[outputType], value: null, encrypt: false, sign: false };
            if (envelope.recipient) {
                envelope = await envelope.decrypt(secret);
                output.encrypt = true;
            }
            if (envelope.signature) {
                // TODO: verify
                output.sign = true;
            }
            output.value = envelope.payload;
        }
        else if (outputType === mintblue_js_1.OutputType.payment) {
            const address = core_1.Address.fromTxOutScript(txOut.script).toString();
            output = { type: mintblue_js_1.OutputType[outputType], to: address, satoshis: txOut.valueBn.toNumber() };
        }
        else if (outputType === mintblue_js_1.OutputType.peppol) {
            const key = this.options.keys.additionalKeys.find((key) => {
                return key.info === 'peppol';
            });
            const p = await v1_js_3.PeppolV1.fromOpReturnScriptHex(txOut.script.toHex(), {
                privateKey: key.jwk,
            });
            output = p.info;
        }
        else if (outputType === mintblue_js_1.OutputType.docv1) {
            const data = txOut.script.chunks[3].buf;
            // Use peppol key for now
            const key = this.options.keys.additionalKeys.find((key) => {
                return key.info === 'peppol';
            });
            // For auto parsing lets just decrypt with some key we have
            // TODO provide ways to parse an output more completely with additional keys and interactivity
            const payload = await v1_js_1.DocV1.parse({
                encryptedData: data,
                receiverKey: key.cryptoKey,
                secret1: null,
                secret2: null,
                verifyKey: null,
            });
            return payload.info;
        }
        else if (outputType === mintblue_js_1.OutputType.hash) {
            const hash = await v1_js_2.Hash.fromOpReturnScript(txOut.script, secret);
            return hash.info;
        }
        return output;
    }
}
exports.Mintblue = Mintblue;
//# sourceMappingURL=data:application/json;base64, 