"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniswapV3 = exports.TransactionType = void 0;
const tslib_1 = require("tslib");
/* eslint-disable unicorn/no-array-reduce */
/* eslint-disable no-return-await */
const thegraph_1 = require("./thegraph");
const cli_ux_1 = (0, tslib_1.__importDefault)(require("cli-ux"));
const listr_1 = (0, tslib_1.__importDefault)(require("listr"));
const dayjs_1 = (0, tslib_1.__importDefault)(require("dayjs"));
const pondjs_1 = require("pondjs");
const types_1 = require("pondjs/lib/types");
var TransactionType;
(function (TransactionType) {
    TransactionType["SWAP"] = "SWAP";
    TransactionType["MINT"] = "MINT";
    TransactionType["BURN"] = "BURN";
})(TransactionType = exports.TransactionType || (exports.TransactionType = {}));
class UniswapV3 extends thegraph_1.TheGraph {
    constructor() {
        super(...arguments);
        this.location = 'https://api.thegraph.com/subgraphs/name/agxmbhir/v3-november-subgraph';
        this.backupLocation = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3';
    }
    async findTokensBySymbol(tokenName, exact = false) {
        return await this.request(this.backupLocation, {
            query: `
        query getToken($name: String!){
          tokens(where: {
            ${exact ? 'symbol: $name' : 'symbol_contains: $name'}
          }, orderBy: volumeUSD, orderDirection: desc) {
            id
            name
            symbol
            decimals
          }
        }
      `,
            variables: {
                name: tokenName,
            },
        });
    }
    async findTokensByAddress(address) {
        return await this.request(this.backupLocation, {
            query: `
        query getToken($id: String!){
          tokens(where: {
            id: $id
          }) {
            id
            name
            symbol
            decimals
          }
        }
      `,
            variables: {
                id: address,
            },
        });
    }
    async findTokensByName(tokenName) {
        return await this.request(this.backupLocation, {
            query: `
        query getToken($name: String!){
          tokens(where: {
            name_contains: $name
          }, orderBy: volumeUSD, orderDirection: desc) {
            id
            name
            symbol
            decimals
          }
        }
      `,
            variables: {
                name: tokenName,
            },
        });
    }
    async findPoolsByToken0(tokenAddress, complementaryTokenAddresses = []) {
        const response = await this.request(this.backupLocation, {
            query: `
        query getPools($address: String!){
          pools(skip: 0, first:10, where: {
            token0: $address
            ${complementaryTokenAddresses ?
                `token1_in: [${complementaryTokenAddresses
                    .map(token => `"${token}"`)
                    .join(',')}]` :
                ''}
          }, orderBy: volumeUSD, orderDirection:desc){
            id
            feeTier
            token0 {
              decimals
              id
              name
              symbol
            }
            token1 {
              decimals
              id
              name
              symbol
            }
            createdAtTimestamp
            totalValueLockedUSD
            collectedFeesUSD
          }
        }
      `,
            variables: {
                address: tokenAddress,
            },
        });
        return response;
    }
    async findPoolsByToken1(tokenAddress) {
        const response = await this.request(this.backupLocation, {
            query: `
        query getPools($address: String!){
          pools(skip: 0, first: 10, where: {
            token1: $address
          }, orderBy: volumeUSD, orderDirection:desc){
            id
            feeTier
            token0 {
              name
              symbol
              decimals
              id
            }
            token1 {
              name
              symbol
              decimals
              id
            }
            createdAtTimestamp
            totalValueLockedUSD
            collectedFeesUSD
          }
        }
      `,
            variables: {
                address: tokenAddress,
            },
        });
        return response;
    }
    async findPoolsByToken(tokenAddress) {
        const token0Pools = await this.findPoolsByToken0(tokenAddress);
        const token1Pools = await this.findPoolsByToken1(tokenAddress);
        return [...token0Pools.data.pools, ...token1Pools.data.pools];
    }
    async getPoolDetails(poolAddress) {
        var _a;
        const response = await this.request(this.location, {
            query: `
      query getPoolDetails($address: String!){
        pool(id: $address){
          id
          feeTier
          token0 {
            name
            symbol
            id
            decimals
          }
          token1 {
            name
            symbol
            id
            decimals
          }
          createdAtTimestamp
          createdAtBlockNumber
          totalValueLockedUSD
          collectedFeesUSD
        }
      }
      `,
            variables: {
                address: poolAddress,
            },
        });
        return (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.pool;
    }
    displayTargetEntityDetails(poolDetails) {
        cli_ux_1.default.table([poolDetails], {
            id: {
                header: 'Address',
                get: (row) => row.id,
            },
            pair: {
                header: 'Pair',
                get: (row) => `${row.token0.name}/${row.token1.name}`,
            },
            feeTier: {
                header: 'Feed Tier',
                get: (row) => `${Number(row.feeTier) / 10000}%`,
            },
            createdAtTimestamp: {
                header: 'Created At',
                get: (row) => new Date(Number(row.createdAtTimestamp) * 1000).toDateString(),
            },
            Liquidity: {
                header: 'Liquidity',
                get: (row) => Number(row.totalValueLockedUSD).toFixed(2),
            },
        });
    }
    async getHistoricalData(poolAddress, startDate, endDate, sleep) {
        const data = [];
        const tasks = new listr_1.default([
            {
                title: 'Retrieve swaps',
                task: async (ctx, task) => {
                    const swaps = await this.retrieveSwaps(poolAddress, startDate, endDate, task, sleep);
                    data.push(...swaps.map(swap => ({
                        ...swap,
                        type: TransactionType.SWAP,
                    })));
                    return ctx;
                },
            },
            {
                title: 'Retrieve mints',
                task: async (ctx, task) => {
                    const increaseEvents = await this.retrieveIncreaseEvents(poolAddress, startDate, endDate, task, sleep);
                    const mints = await this.retrieveMints(poolAddress, startDate, endDate, task, sleep);
                    let noMatch = 0;
                    const joinedData = mints.reduce((acc, mint) => {
                        const match = increaseEvents.find(increaseEvent => increaseEvent.id.split('#')[0] === mint.id.split('#')[0]);
                        if (match === undefined) {
                            noMatch = noMatch++;
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                            // @ts-ignore
                            acc = [...acc, { ...mint, ...match }];
                        }
                        return acc;
                    }, []);
                    data.push(...joinedData.map(mint => ({
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-ignore
                        ...mint,
                        type: TransactionType.MINT,
                    })));
                    return ctx;
                },
            },
            {
                title: 'Retrieve burns',
                task: async (ctx, task) => {
                    const decreaseEvents = await this.retrieveDecreaseEvents(poolAddress, startDate, endDate, task, sleep);
                    const burns = await this.retrieveBurns(poolAddress, startDate, endDate, task, sleep);
                    const joinedData = burns.reduce((acc, burns) => {
                        const match = decreaseEvents.find(decreaseEvent => decreaseEvent.id.split('#')[0] === burns.id.split('#')[0]);
                        if (match) {
                            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                            // @ts-ignore
                            acc = [...acc, { ...burns, ...match }];
                        }
                        return acc;
                    }, []);
                    data.push(...joinedData.map(burn => ({
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-ignore
                        ...burn,
                        type: TransactionType.BURN,
                    })));
                    return ctx;
                },
            },
        ], {
            concurrent: true,
        });
        await tasks.run();
        return data;
    }
    async getPreTestData(poolAddress, startDate, endDate, sleep) {
        const data = [];
        const tasks = new listr_1.default([
            {
                title: 'Retrieve candles',
                task: async (ctx, task) => {
                    const swaps = await this.retrieveSwaps(poolAddress, startDate, endDate, task, sleep);
                    data.push(...swaps.map(swap => ({
                        ...swap,
                        type: TransactionType.SWAP,
                    })));
                    return ctx;
                },
            },
        ]);
        await tasks.run();
        return data;
    }
    // eslint-disable-next-line max-params
    async retrieveSwaps(poolAddress, startDate, endDate, task, sleep = 0) {
        var _a;
        const data = [];
        let page = 0;
        const limit = 1000;
        let beginingDate = (startDate.getTime() / 1000).toFixed(0);
        while (new Date(Number(((_a = data[data.length]) === null || _a === void 0 ? void 0 : _a.timestamp) || 0) * 1000) < endDate) {
            const response = await this.request(this.location, {
                query: `
          query getSwaps($address: String!, $startDate: BigInt!, $endDate: BigInt!, $limit: Int!, $skip: Int!){
            swaps(first: $limit, skip: $skip, where: {
              timestamp_gt: $startDate
              timestamp_lt: $endDate
              pool: $address
            }, orderBy: timestamp, orderDirection: asc){
              id
              timestamp
              amount0
              amount1
              transaction {
                id
                blockNumber
              }
              tick
              sqrtPriceX96
              sender
              recipient
              token0 {
                decimals
                id
                name
                symbol
              }
              pool {
                liquidity
                feeTier
              }
              token1 {
                decimals
                id
                name
                symbol
              }
              logIndex
            }
          }
        `,
                variables: {
                    address: poolAddress,
                    startDate: beginingDate,
                    endDate: (endDate.getTime() / 1000).toFixed(0),
                    skip: page * limit,
                    limit: limit,
                },
            });
            if (response.data.swaps.length === 0) {
                break;
            }
            data.push(...response.data.swaps);
            task.title = `Retrieve swaps (Days left to retrieve: ${Math.abs(Number(this.getTimeDiff(dayjs_1.default.unix(Number(data[data.length - 1].timestamp)).toDate(), endDate, 'day')))})`;
            if (page === 5) {
                page = 0;
                beginingDate = (Number(data[data.length - 1].timestamp) + 1).toFixed(0);
            }
            else {
                page++;
            }
            await new Promise(resolve => {
                setTimeout(resolve, Number(sleep) * 1000);
            });
        }
        return data;
    }
    // eslint-disable-next-line max-params
    async retrieveIncreaseEvents(poolAddress, startDate, endDate, task, sleep = 0) {
        var _a;
        const data = [];
        let page = 0;
        const limit = 1000;
        let beginingDate = (startDate.getTime() / 1000).toFixed(0);
        while (new Date(Number(((_a = data[data.length]) === null || _a === void 0 ? void 0 : _a.timestamp) || 0) * 1000) < endDate) {
            const response = await this.request(this.location, {
                query: `
          query getMints($address: String!, $startDate: BigInt!, $endDate: BigInt!, $limit: Int!, $skip: Int!){
            increaseEvents(first: $limit, skip: $skip, where: {
              timeStamp_gt: $startDate
              timeStamp_lt: $endDate
              pool: $address
            }, orderBy: timeStamp, orderDirection: asc){
              id
              timeStamp
              amount0
              amount1
              tokenID
              transaction {
                blockNumber
              }
              token0 {
                decimals
                id
                name
                symbol
              }
              token1 {
                decimals
                id
                name
                symbol
              }
              position {
                owner
              }
            }
          }
        `,
                variables: {
                    address: poolAddress,
                    startDate: beginingDate,
                    endDate: (endDate.getTime() / 1000).toFixed(0),
                    skip: page * limit,
                    limit: limit,
                },
            });
            if (response.data.increaseEvents.length === 0) {
                break;
            }
            data.push(...response.data.increaseEvents.map(event => {
                event.timestamp = event.timeStamp;
                return event;
            }));
            task.title = `Retrieve increase liquidity events (Days left to retrieve: ${Math.abs(Number(this.getTimeDiff(dayjs_1.default.unix(Number(data[data.length - 1].timestamp)).toDate(), endDate, 'day')))})`;
            if (page === 5) {
                page = 0;
                beginingDate = (Number(data[data.length - 1].timestamp) + 1).toFixed(0);
            }
            else {
                page++;
            }
            await new Promise(resolve => {
                setTimeout(resolve, Number(sleep) * 1000);
            });
        }
        return data;
    }
    // eslint-disable-next-line max-params
    async retrieveBurns(poolAddress, startDate, endDate, task, sleep = 0) {
        var _a;
        const data = [];
        let page = 0;
        const limit = 1000;
        let beginingDate = (startDate.getTime() / 1000).toFixed(0);
        while (new Date(Number(((_a = data[data.length]) === null || _a === void 0 ? void 0 : _a.timestamp) || 0) * 1000) < endDate) {
            const response = await this.request(this.backupLocation, {
                query: `
          query getBurns($address: String!, $startDate: BigInt!, $endDate: BigInt!, $limit: Int!, $skip: Int!){
            burns(first: $limit, skip: $skip, where: {
              timestamp_gt: $startDate
              timestamp_lt: $endDate
              pool: $address
            }, orderBy: timestamp, orderDirection: asc){
              id
              tickUpper
              tickLower
              amount0
              amount1
              amount
              timestamp
              owner
            }
          }
        `,
                variables: {
                    address: poolAddress,
                    startDate: beginingDate,
                    endDate: (endDate.getTime() / 1000).toFixed(0),
                    skip: page * limit,
                    limit: limit,
                },
            });
            if (response.data.burns.length === 0) {
                break;
            }
            data.push(...response.data.burns.map(event => {
                return event;
            }));
            task.title = `Retrieve increase liquidity events (Days left to retrieve: ${Math.abs(Number(this.getTimeDiff(dayjs_1.default.unix(Number(data[data.length - 1].timestamp)).toDate(), endDate, 'day')))})`;
            if (page === 5) {
                page = 0;
                beginingDate = (Number(data[data.length - 1].timestamp) + 1).toFixed(0);
            }
            else {
                page++;
            }
            await new Promise(resolve => {
                setTimeout(resolve, Number(sleep) * 1000);
            });
        }
        return data;
    }
    // eslint-disable-next-line max-params
    async retrieveMints(poolAddress, startDate, endDate, task, sleep = 0) {
        var _a;
        const data = [];
        let page = 0;
        const limit = 1000;
        let beginingDate = (startDate.getTime() / 1000).toFixed(0);
        while (new Date(Number(((_a = data[data.length]) === null || _a === void 0 ? void 0 : _a.timestamp) || 0) * 1000) < endDate) {
            const response = await this.request(this.backupLocation, {
                query: `
          query getMints($address: String!, $startDate: BigInt!, $endDate: BigInt!, $limit: Int!, $skip: Int!){
            mints(first: $limit, skip: $skip, where: {
              timestamp_gt: $startDate
              timestamp_lt: $endDate
              pool: $address
            }, orderBy: timestamp, orderDirection: asc){
              id
              tickUpper
              tickLower
              amount0
              amount1
              amount
              timestamp
              owner
            }
          }
        `,
                variables: {
                    address: poolAddress,
                    startDate: beginingDate,
                    endDate: (endDate.getTime() / 1000).toFixed(0),
                    skip: page * limit,
                    limit: limit,
                },
            });
            if (response.data.mints.length === 0) {
                break;
            }
            data.push(...response.data.mints.map(event => {
                return event;
            }));
            task.title = `Retrieve increase liquidity events (Days left to retrieve: ${Math.abs(Number(this.getTimeDiff(dayjs_1.default.unix(Number(data[data.length - 1].timestamp)).toDate(), endDate, 'day')))})`;
            if (page === 5) {
                page = 0;
                beginingDate = (Number(data[data.length - 1].timestamp) + 1).toFixed(0);
            }
            else {
                page++;
            }
            await new Promise(resolve => {
                setTimeout(resolve, Number(sleep) * 1000);
            });
        }
        return data;
    }
    // eslint-disable-next-line max-params
    async retrieveDecreaseEvents(poolAddress, startDate, endDate, task, sleep = 0) {
        var _a;
        const data = [];
        let page = 0;
        const limit = 1000;
        let beginingDate = (startDate.getTime() / 1000).toFixed(0);
        while (new Date(Number(((_a = data[data.length]) === null || _a === void 0 ? void 0 : _a.timestamp) || 0) * 1000) < endDate) {
            const response = await this.request(this.location, {
                query: `
          query getBurns($address: String!, $startDate: BigInt!, $endDate: BigInt!, $limit: Int!, $skip: Int!){
            decreaseEvents(first: $limit, skip: $skip, where: {
              timeStamp_gt: $startDate
              timeStamp_lt: $endDate
              pool: $address
            }, orderBy: timeStamp, orderDirection: asc){
              id
              timeStamp
              amount0
              amount1
              tokenID
              transaction {
                blockNumber
              }
              token0 {
                decimals
                id
              }
              token1 {
                decimals
                id
              }
              position {
                owner
              }
            }
          }
        `,
                variables: {
                    address: poolAddress,
                    startDate: beginingDate,
                    endDate: (endDate.getTime() / 1000).toFixed(0),
                    skip: page * limit,
                    limit: limit,
                },
            });
            if (response.data.decreaseEvents.length === 0) {
                break;
            }
            data.push(...response.data.decreaseEvents.map(event => {
                event.timestamp = event.timeStamp;
                return event;
            }));
            task.title = `Retrieve decrease liquidity events (Days left to retrieve: ${Math.abs(Number(this.getTimeDiff(dayjs_1.default.unix(Number(data[data.length - 1].timestamp)).toDate(), endDate, 'day')))})`;
            if (page === 5) {
                page = 0;
                beginingDate = (Number(data[data.length - 1].timestamp) + 1).toFixed(0);
            }
            else {
                page++;
            }
            await new Promise(resolve => {
                setTimeout(resolve, Number(sleep) * 1000);
            });
        }
        return data;
    }
    getTimeDiff(startDate, endDate, _unit = 'day') {
        return (0, dayjs_1.default)(startDate).diff((0, dayjs_1.default)(endDate), 'day');
    }
    async getCandles(data, candleSize) {
        const series = (0, pondjs_1.timeSeries)({
            name: 'swaps',
            columns: [...Object.keys(data[0])],
            points: data.map((point) => {
                return [...Object.values(point)].map(value => Number(value));
            }),
        });
        const windowRollup = series
            .fixedWindowRollup({
            window: (0, pondjs_1.window)((0, pondjs_1.duration)(candleSize)),
            aggregation: {
                high: ['value', (0, pondjs_1.max)()],
                low: ['value', (0, pondjs_1.min)()],
                close: ['value', (0, pondjs_1.last)()],
                open: ['value', (0, pondjs_1.first)()],
            },
        })
            .fill({
            fieldSpec: 'value',
            method: 2,
        });
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        return windowRollup
            .toJSON()
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            .points.map((p) => {
            // Convert time range to timestamp of beginning of range
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            p[0] = new pondjs_1.Index(p[0])
                .toTimeRange()
                .toTime(types_1.TimeAlignment.Begin)
                .toDate()
                .getTime();
            return p;
        })
            .map((p) => {
            return {
                timestamp: p[0],
                high: p[1],
                low: p[2],
                close: p[3],
                open: p[4],
            };
        });
    }
}
exports.UniswapV3 = UniswapV3;
