"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __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.StorageDownloader = void 0;
const index_js_1 = require("../overlay-tools/index.js");
const StorageUtils = __importStar(require("./StorageUtils.js"));
const PushDrop_js_1 = __importDefault(require("../script/templates/PushDrop.js"));
const Transaction_js_1 = __importDefault(require("../transaction/Transaction.js"));
const index_js_2 = require("../primitives/index.js");
class StorageDownloader {
    constructor(config) {
        this.networkPreset = 'mainnet';
        this.networkPreset = config?.networkPreset ?? 'mainnet';
        this.lookupResolver = new index_js_1.LookupResolver({ networkPreset: this.networkPreset });
    }
    /**
     * Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
     * @param uhrpUrl The UHRP URL to resolve.
     * @returns A promise that resolves to an array of HTTP URLs.
     */
    async resolve(uhrpUrl) {
        // Use UHRP lookup service
        const response = await this.lookupResolver.query({ service: 'ls_uhrp', query: { uhrpUrl } });
        if (response.type !== 'output-list') {
            throw new Error('Lookup answer must be an output list');
        }
        const decodedResults = [];
        const currentTime = Math.floor(Date.now() / 1000);
        for (const output of response.outputs) {
            const tx = Transaction_js_1.default.fromBEEF(output.beef);
            const { fields } = PushDrop_js_1.default.decode(tx.outputs[output.outputIndex].lockingScript);
            const expiryTime = new index_js_2.Utils.Reader(fields[3]).readVarIntNum();
            if (expiryTime < currentTime) {
                continue;
            }
            decodedResults.push(index_js_2.Utils.toUTF8(fields[2]));
        }
        return decodedResults;
    }
    /**
     * Downloads the content from the UHRP URL after validating the hash for integrity.
     * @param uhrpUrl The UHRP URL to download.
     * @returns A promise that resolves to the downloaded content.
     */
    async download(uhrpUrl) {
        if (!StorageUtils.isValidURL(uhrpUrl)) {
            throw new Error('Invalid parameter UHRP url');
        }
        const hash = StorageUtils.getHashFromURL(uhrpUrl);
        const expected = index_js_2.Utils.toHex(hash);
        const downloadURLs = await this.resolve(uhrpUrl);
        if (!Array.isArray(downloadURLs) || downloadURLs.length === 0) {
            throw new Error('No one currently hosts this file!');
        }
        for (const url of downloadURLs) {
            try {
                // The url is fetched
                const result = await fetch(url, { method: 'GET' });
                // If the request fails, continue to the next url
                if (!result.ok || result.status >= 400 || result.body == null) {
                    continue;
                }
                const reader = result.body.getReader();
                const hashStream = new index_js_2.Hash.SHA256();
                const chunks = [];
                let totalLength = 0;
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    hashStream.update(Array.from(value));
                    chunks.push(value);
                    totalLength += value.length;
                }
                const digest = index_js_2.Utils.toHex(hashStream.digest());
                if (digest !== expected) {
                    throw new Error('Data integrity error: value of content does not match hash of the url given');
                }
                const data = new Uint8Array(totalLength);
                let offset = 0;
                for (const chunk of chunks) {
                    data.set(chunk, offset);
                    offset += chunk.length;
                }
                return {
                    data,
                    mimeType: result.headers.get('Content-Type')
                };
            }
            catch {
                continue;
            }
        }
        throw new Error(`Unable to download content from ${uhrpUrl}`);
    }
}
exports.StorageDownloader = StorageDownloader;
//# sourceMappingURL=StorageDownloader.js.map