import { __awaiter, __generator } from "tslib";
import Arweave from "arweave";
import axios from "axios";
import { create } from "superstruct";
import { ARWEAVE_TAG_APP_NAME, ARWEAVE_TAG_APP_VERSION, DEFAULT_ARWEAVE_STORAGE_ADDRESS } from "../../constants";
import { BigNumber, isDefined, jsonReplacerWithBigNumbers, toBN } from "../../utils";
var ArweaveClient = /** @class */ (function () {
    function ArweaveClient(arweaveJWT, logger, gatewayURL, protocol, port) {
        if (gatewayURL === void 0) { gatewayURL = "arweave.net"; }
        if (protocol === void 0) { protocol = "https"; }
        if (port === void 0) { port = 443; }
        this.arweaveJWT = arweaveJWT;
        this.logger = logger;
        this.gatewayUrl = "".concat(protocol, "://").concat(gatewayURL, ":").concat(port);
        this.client = new Arweave({
            host: gatewayURL,
            port: port,
            protocol: protocol,
            timeout: 20000,
            logging: false,
        });
        this.logger.debug({
            at: "ArweaveClient:constructor",
            message: "Arweave client initialized",
            gateway: this.gatewayUrl,
        });
    }
    /**
     * Stores an arbitrary record in the Arweave network. The record is stored as a JSON string and uses
     * JSON.stringify to convert the record to a string. The record has all of its big numbers converted
     * to strings for convenience.
     * @param value The value to store
     * @param topicTag An optional topic tag to add to the transaction
     * @returns The transaction ID of the stored value
     * @
     */
    ArweaveClient.prototype.set = function (value, topicTag) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function () {
            var transaction, result, message, _d, _e;
            var _f;
            return __generator(this, function (_g) {
                switch (_g.label) {
                    case 0: return [4 /*yield*/, this.client.createTransaction({ data: JSON.stringify(value, jsonReplacerWithBigNumbers) }, this.arweaveJWT)];
                    case 1:
                        transaction = _g.sent();
                        // Add tags to the transaction
                        transaction.addTag("Content-Type", "application/json");
                        transaction.addTag("App-Name", ARWEAVE_TAG_APP_NAME);
                        transaction.addTag("App-Version", ARWEAVE_TAG_APP_VERSION.toString());
                        if (isDefined(topicTag)) {
                            transaction.addTag("Topic", topicTag);
                        }
                        // Sign the transaction
                        return [4 /*yield*/, this.client.transactions.sign(transaction, this.arweaveJWT)];
                    case 2:
                        // Sign the transaction
                        _g.sent();
                        return [4 /*yield*/, this.client.transactions.post(transaction)];
                    case 3:
                        result = _g.sent();
                        if (!(result.status !== 200)) return [3 /*break*/, 6];
                        message = (_c = (_b = (_a = result === null || result === void 0 ? void 0 : result.data) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.msg) !== null && _c !== void 0 ? _c : "Unknown error";
                        _e = (_d = this.logger).error;
                        _f = {
                            at: "ArweaveClient:set",
                            message: message,
                            result: result,
                            txn: transaction.id
                        };
                        return [4 /*yield*/, this.getAddress()];
                    case 4:
                        _f.address = _g.sent();
                        return [4 /*yield*/, this.getBalance()];
                    case 5:
                        _e.apply(_d, [(_f.balance = (_g.sent()).toString(),
                                _f)]);
                        throw new Error(message);
                    case 6:
                        this.logger.debug({
                            at: "ArweaveClient:set",
                            message: "Arweave transaction posted with ".concat(transaction.id),
                        });
                        _g.label = 7;
                    case 7: return [2 /*return*/, transaction.id];
                }
            });
        });
    };
    /**
     * Retrieves a record from the Arweave network. The record is expected to be a JSON string and is
     * parsed using JSON.parse. All numeric strings are converted to big numbers for convenience.
     * @param transactionID The transaction ID of the record to retrieve
     * @param structValidator An optional struct validator to validate the retrieved value. If the value does not match the struct, null is returned.
     * @returns The record if it exists, otherwise null
     */
    ArweaveClient.prototype.get = function (transactionID, validator) {
        return __awaiter(this, void 0, void 0, function () {
            var transactionUrl, _a, data, responseStatus;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        transactionUrl = "".concat(this.gatewayUrl, "/").concat(transactionID);
                        return [4 /*yield*/, axios.get(transactionUrl)];
                    case 1:
                        _a = _b.sent(), data = _a.data, responseStatus = _a.status;
                        // Ensure that the result is successful. If it is not, the retrieved value is not our expected type
                        // but rather a {status: string, statusText: string} object. We can detect that and return null.
                        if (responseStatus !== 200 || ("status" in data && data["status"] !== 200)) {
                            this.logger.debug({
                                at: "ArweaveClient:get",
                                message: "Failed to retrieve value from Arweave - network error or invalid response status",
                                transactionID: transactionID,
                                responseStatus: responseStatus,
                                data: data,
                            });
                            return [2 /*return*/, null];
                        }
                        try {
                            // We should validate the data and perform any logical coercion here.
                            return [2 /*return*/, create(data, validator)];
                        }
                        catch (e) {
                            // If the data does not match the validator, log a warning and return null.
                            this.logger.warn({
                                at: "ArweaveClient:get",
                                message: "Retrieved value from Arweave does not match the expected type: ".concat(e),
                            });
                            return [2 /*return*/, null];
                        }
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Retrieves a list of records from the Arweave network that have a specific tag.
     * The records are expected to be a JSON string and are pre-filtered by the Across
     * protocol tag, the content-type tag, and this client's address. Furthermore, the
     * records are expected to be an array of the given type and will be discarded if
     * they do not match the given validator.
     * @param tag The tag to filter all the transactions by
     * @param validator The validator to validate the retrieved values
     * @param originQueryAddress An optional flag to override the originating address for the query. By default,
     *                           the address of the RL Arweave storage wallet is used.
     * @returns The records if they exist, otherwise an empty array
     */
    ArweaveClient.prototype.getByTopic = function (tag, validator, originQueryAddress) {
        var _a, _b, _c, _d;
        if (originQueryAddress === void 0) { originQueryAddress = DEFAULT_ARWEAVE_STORAGE_ADDRESS; }
        return __awaiter(this, void 0, void 0, function () {
            var transactions, entries, results;
            var _this = this;
            return __generator(this, function (_e) {
                switch (_e.label) {
                    case 0: return [4 /*yield*/, this.client.api.post("/graphql", {
                            query: "\n        { \n          transactions (\n            owners: [\"".concat(originQueryAddress, "\"]\n            tags: [\n              { name: \"App-Name\", values: [\"").concat(ARWEAVE_TAG_APP_NAME, "\"] },\n              { name: \"Content-Type\", values: [\"application/json\"] },\n              { name: \"App-Version\", values: [\"").concat(ARWEAVE_TAG_APP_VERSION, "\"] },\n              ").concat(tag ? "{ name: \"Topic\", values: [\"".concat(tag, "\"] } ") : "", "\n            ]\n          ) { edges { node { id } } } \n        }"),
                        })];
                    case 1:
                        transactions = _e.sent();
                        entries = (_d = (_c = (_b = (_a = transactions === null || transactions === void 0 ? void 0 : transactions.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.transactions) === null || _c === void 0 ? void 0 : _c.edges) !== null && _d !== void 0 ? _d : [];
                        this.logger.debug({
                            at: "ArweaveClient:getByTopic",
                            message: "Retrieved ".concat(entries.length, " matching transactions from Arweave"),
                            transactions: entries.map(function (edge) { return edge.node.id; }),
                            metaInformation: {
                                tag: tag,
                                originQueryAddress: originQueryAddress,
                                appVersion: ARWEAVE_TAG_APP_VERSION,
                            },
                        });
                        return [4 /*yield*/, Promise.all(entries.map(function (edge) { return __awaiter(_this, void 0, void 0, function () {
                                var data, e_1;
                                return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0:
                                            _a.trys.push([0, 2, , 3]);
                                            return [4 /*yield*/, this.get(edge.node.id, validator)];
                                        case 1:
                                            data = _a.sent();
                                            return [2 /*return*/, isDefined(data)
                                                    ? {
                                                        data: data,
                                                        hash: edge.node.id,
                                                    }
                                                    : null];
                                        case 2:
                                            e_1 = _a.sent();
                                            this.logger.warn({
                                                at: "ArweaveClient:getByTopic",
                                                message: "Bad request for Arweave topic ".concat(edge.node.id, ": ").concat(e_1),
                                            });
                                            return [2 /*return*/, null];
                                        case 3: return [2 /*return*/];
                                    }
                                });
                            }); }))];
                    case 2:
                        results = _e.sent();
                        return [2 /*return*/, results.filter(isDefined)];
                }
            });
        });
    };
    /**
     * Retrieves the metadata of a transaction
     * @param transactionID The transaction ID of the record to retrieve
     * @returns The metadata of the transaction if it exists, otherwise null
     */
    ArweaveClient.prototype.getMetadata = function (transactionID) {
        return __awaiter(this, void 0, void 0, function () {
            var transaction, tags;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.client.transactions.get(transactionID)];
                    case 1:
                        transaction = _a.sent();
                        if (!isDefined(transaction)) {
                            return [2 /*return*/, null];
                        }
                        tags = Object.fromEntries(transaction.tags.map(function (tag) { return [
                            tag.get("name", { decode: true, string: true }),
                            tag.get("value", { decode: true, string: true }),
                        ]; }));
                        return [2 /*return*/, {
                                contentType: tags["Content-Type"],
                                appName: tags["App-Name"],
                                topic: tags.Topic,
                            }];
                }
            });
        });
    };
    /**
     * Returns the address of the signer of the JWT
     * @returns The address of the signer in this client
     */
    ArweaveClient.prototype.getAddress = function () {
        return this.client.wallets.jwkToAddress(this.arweaveJWT);
    };
    /**
     * The balance of the signer
     * @returns The balance of the signer in winston units
     */
    ArweaveClient.prototype.getBalance = function () {
        return __awaiter(this, void 0, void 0, function () {
            var address, balanceInFloat, _a, balance, exponent, resultingBN;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, this.getAddress()];
                    case 1:
                        address = _b.sent();
                        return [4 /*yield*/, this.client.wallets.getBalance(address)];
                    case 2:
                        balanceInFloat = _b.sent();
                        // Sometimes the balance is returned in scientific notation, so we need to
                        // convert it to a BigNumber
                        if (balanceInFloat.includes("e")) {
                            _a = balanceInFloat.split("e"), balance = _a[0], exponent = _a[1];
                            resultingBN = BigNumber.from(balance).mul(toBN(10).pow(exponent.replace("+", "")));
                            return [2 /*return*/, BigNumber.from(resultingBN.toString())];
                        }
                        else {
                            return [2 /*return*/, BigNumber.from(balanceInFloat)];
                        }
                        return [2 /*return*/];
                }
            });
        });
    };
    return ArweaveClient;
}());
export { ArweaveClient };
//# sourceMappingURL=ArweaveClient.js.map