"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkSubgraph = void 0;
const axios_1 = __importDefault(require("axios"));
const common_ts_1 = require("@graphprotocol/common-ts");
const graphql_1 = require("graphql");
const core_1 = require("@urql/core");
class NetworkSubgraph {
    constructor(options) {
        this.logger = options.logger;
        if (options.endpoint) {
            this.endpointClient = axios_1.default.create({
                baseURL: options.endpoint,
                headers: { 'content-type': 'application/json' },
                // Don't parse responses as JSON
                responseType: 'text',
                // Don't transform responses
                transformResponse: (data) => data,
            });
        }
        if (options.deployment) {
            const client = axios_1.default.create({
                baseURL: new URL(`/subgraphs/id/${options.deployment.id.ipfsHash}`, options.deployment.graphNodeQueryEndpoint).toString(),
                headers: { 'content-type': 'application/json' },
                // Don't parse responses as JSON
                responseType: 'text',
                // Don't transform responses
                transformResponse: (data) => data,
            });
            const status = options.deployment.status;
            this.deployment = {
                id: options.deployment.id,
                client,
                status,
            };
        }
    }
    static create({ logger: parentLogger, endpoint, deployment, }) {
        return __awaiter(this, void 0, void 0, function* () {
            // Either an endpoint or a deployment needs to be provided; the CLI
            // validation should already guarantee that but we're asserting this again
            // here, just to be on the safe side
            console.assert(endpoint || deployment);
            const logger = parentLogger.child({
                component: 'NetworkSubgraph',
                endpoint,
                deployment: deployment === null || deployment === void 0 ? void 0 : deployment.deployment.ipfsHash,
            });
            let deploymentInfo;
            if (deployment) {
                const status = yield monitorDeployment({
                    logger,
                    indexingStatusResolver: deployment.indexingStatusResolver,
                    deployment: deployment.deployment,
                });
                deploymentInfo = {
                    id: deployment.deployment,
                    status,
                    graphNodeQueryEndpoint: deployment.graphNodeQueryEndpoint,
                };
            }
            // Create the network subgraph instance
            const networkSubgraph = new NetworkSubgraph({
                logger,
                endpoint,
                deployment: deploymentInfo,
            });
            // If we don't have a network subgraph endpoint configured, we
            // need to wait until the deployment is synced
            if (!endpoint) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                yield deploymentInfo.status.filter((status) => status.synced).value();
            }
            return networkSubgraph;
        });
    }
    getClient() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.deployment) {
                const status = yield this.deployment.status.value();
                const healthy = status.synced && status.health === 'healthy';
                if (healthy) {
                    this.logger.trace('Use own deployment for network subgraph query');
                    return this.deployment.client;
                }
                else if (this.endpointClient) {
                    this.logger.trace('Use provided endpoint for network subgraph query');
                    return this.endpointClient;
                }
                else {
                    // We have no endpoint and our deployment is not synced or unhealthy;
                    // there's no way to proceed from here, so crash
                    this.logger.critical(`No network subgraph deployment endpoint provided and network subgraph deployment is unhealthy`);
                    process.exit(1);
                }
            }
            else {
                this.logger.trace('Use provided endpoint for network subgraph query');
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return this.endpointClient;
            }
        });
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    query(query, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    variables) {
        return __awaiter(this, void 0, void 0, function* () {
            const client = yield this.getClient();
            const response = yield client.post('', { query: graphql_1.print(query), variables });
            const data = JSON.parse(response.data);
            if (data.errors) {
                return { error: new core_1.CombinedError({ graphQLErrors: data.errors }) };
            }
            return data;
        });
    }
    queryRaw(body) {
        return __awaiter(this, void 0, void 0, function* () {
            const client = yield this.getClient();
            return yield client.post('', body);
        });
    }
}
exports.NetworkSubgraph = NetworkSubgraph;
const monitorDeployment = ({ logger, indexingStatusResolver, deployment, }) => __awaiter(void 0, void 0, void 0, function* () {
    const initialStatus = {
        health: 'healthy',
        synced: false,
        latestBlock: undefined,
        chainHeadBlock: undefined,
        fatalError: undefined,
    };
    return common_ts_1.timer(60000).reduce((lastStatus) => __awaiter(void 0, void 0, void 0, function* () {
        var _a, _b;
        try {
            logger.trace(`Checking the network subgraph deployment status`);
            const indexingStatuses = yield indexingStatusResolver.indexingStatus([deployment]);
            const indexingStatus = indexingStatuses.pop();
            if (!indexingStatus) {
                throw `No indexing status found`;
            }
            const status = {
                health: indexingStatus.health,
                synced: indexingStatus.synced,
                latestBlock: indexingStatus.chains[0].latestBlock,
                chainHeadBlock: indexingStatus.chains[0].chainHeadBlock,
                fatalError: indexingStatus.fatalError,
            };
            // If failed for the first time, log an error
            if (!lastStatus.fatalError && status.fatalError) {
                logger.error(`Failed to index network subgraph deployment`, {
                    err: status.fatalError,
                    latestBlock: status.latestBlock,
                });
            }
            // Don't log anything else after the subgraph has failed
            if (status.fatalError) {
                return status;
            }
            // If not synced yet, log the progress so far
            if (!status.synced) {
                const latestBlock = ((_a = status.latestBlock) === null || _a === void 0 ? void 0 : _a.number) || 0;
                const chainHeadBlock = ((_b = status.chainHeadBlock) === null || _b === void 0 ? void 0 : _b.number) || 1;
                const syncedPercent = ((100 * latestBlock) / chainHeadBlock).toFixed(2);
                logger.info(`Network subgraph is synced ${syncedPercent}% (block #${latestBlock} of #${chainHeadBlock})`);
            }
            return status;
        }
        catch (err) {
            return lastStatus;
        }
    }), initialStatus);
});
//# sourceMappingURL=network-subgraph.js.map