"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnswerService = exports.OperationType = void 0;
const tslib_1 = require("tslib");
// import YAML from 'yaml';
const tokenizedFetch_1 = require("../../../tokenizedFetch");
const utils_1 = require("../../../utils");
const graphql_request_1 = require("../graphql-request");
const sourceService_1 = require("../sourceService");
const queries = tslib_1.__importStar(require("./answer-queries"));
// eslint-disable-next-line no-shadow
var OperationType;
(function (OperationType) {
    OperationType["GetChartWithData"] = "GetChartWithData";
    OperationType["GetTableWithHeadlineData"] = "GetTableWithHeadlineData";
})(OperationType = exports.OperationType || (exports.OperationType = {}));
/**
 * AnswerService provides a simple way to work with ThoughtSpot Answers.
 *
 * This service allows you to interact with ThoughtSpot Answers programmatically,
 * making it easy to customize visualizations, filter data, and extract insights
 * directly from your application.
 *
 * You can use this service to:
 * - Add or remove columns from Answers (`addColumns`, `removeColumns`, `addColumnsByName`)
 * - Apply filters to Answers (`addFilter`)
 * - Get data from Answers in different formats (JSON, CSV, PNG) (`fetchData`, `fetchCSVBlob`, `fetchPNGBlob`)
 * - Get data for specific points in visualizations (`getUnderlyingDataForPoint`)
 * - Run custom queries (`executeQuery`)
 * - Add visualizations to liveboards (`addDisplayedVizToLiveboard`)
 *
 * @example
 * ```js
 * // Get the answer service
 * embed.on(EmbedEvent.Data, async (e) => {
 *     const service = await embed.getAnswerService();
 *
 *     // Add columns to the answer
 *     await service.addColumnsByName(["Sales", "Region"]);
 *
 *     // Get the data
 *     const data = await service.fetchData();
 *     console.log(data);
 * });
 * ```
 *
 * @example
 * ```js
 * // Get data for a point in a visualization
 * embed.on(EmbedEvent.CustomAction, async (e) => {
 *     const underlying = await e.answerService.getUnderlyingDataForPoint([
 *       'Product Name',
 *       'Sales Amount'
 *     ]);
 *
 *     const data = await underlying.fetchData(0, 100);
 *     console.log(data);
 * });
 * ```
 *
 * @version SDK: 1.25.0| ThoughtSpot: 9.10.0.cl
 * @group Events
 */
class AnswerService {
    /**
     * Should not need to be called directly.
     * @param session
     * @param answer
     * @param thoughtSpotHost
     * @param selectedPoints
     */
    constructor(session, answer, thoughtSpotHost, selectedPoints) {
        this.session = session;
        this.thoughtSpotHost = thoughtSpotHost;
        this.selectedPoints = selectedPoints;
        this.tmlOverride = {};
        this.session = (0, utils_1.removeTypename)(session);
        this.answer = answer;
    }
    /**
     * Get the details about the source used in the answer.
     * This can be used to get the list of all columns in the data source for example.
     */
    async getSourceDetail() {
        const sourceId = (await this.getAnswer()).sources[0].header.guid;
        return (0, sourceService_1.getSourceDetail)(this.thoughtSpotHost, sourceId);
    }
    /**
     * Remove columnIds and return updated answer session.
     * @param columnIds
     * @returns
     */
    async removeColumns(columnIds) {
        return this.executeQuery(queries.removeColumns, {
            logicalColumnIds: columnIds,
        });
    }
    /**
     * Add columnIds and return updated answer session.
     * @param columnIds
     * @returns
     */
    async addColumns(columnIds) {
        return this.executeQuery(queries.addColumns, {
            columns: columnIds.map((colId) => ({ logicalColumnId: colId })),
        });
    }
    /**
     * Add columns by names and return updated answer session.
     * @param columnNames
     * @returns
     * @example
     * ```js
     * embed.on(EmbedEvent.Data, async (e) => {
     *    const service = await embed.getAnswerService();
     *    await service.addColumnsByName([
     *      "col name 1",
     *      "col name 2"
     *    ]);
     *    console.log(await service.fetchData());
     * });
     */
    async addColumnsByName(columnNames) {
        const sourceDetail = await this.getSourceDetail();
        const columnGuids = getGuidsFromColumnNames(sourceDetail, columnNames);
        return this.addColumns([...columnGuids]);
    }
    /**
     * Add a filter to the answer.
     * @param columnName
     * @param operator
     * @param values
     * @returns
     */
    async addFilter(columnName, operator, values) {
        const sourceDetail = await this.getSourceDetail();
        const columnGuids = getGuidsFromColumnNames(sourceDetail, [columnName]);
        return this.executeQuery(queries.addFilter, {
            params: {
                filterContent: [{
                        filterType: operator,
                        value: values.map((v) => {
                            const [type, prefix] = (0, utils_1.getTypeFromValue)(v);
                            return {
                                type: type.toUpperCase(),
                                [`${prefix}Value`]: v,
                            };
                        }),
                    }],
                filterGroupId: {
                    logicalColumnId: columnGuids.values().next().value,
                },
            },
        });
    }
    async getSQLQuery() {
        const { sql } = await this.executeQuery(queries.getSQLQuery, {});
        return sql;
    }
    /**
     * Fetch data from the answer.
     * @param offset
     * @param size
     * @returns
     */
    async fetchData(offset = 0, size = 1000) {
        const { answer } = await this.executeQuery(queries.getAnswerData, {
            deadline: 0,
            dataPaginationParams: {
                isClientPaginated: true,
                offset,
                size,
            },
        });
        const { columns, data } = answer.visualizations.find((viz) => !!viz.data) || {};
        return {
            columns,
            data,
        };
    }
    /**
     * Fetch the data for the answer as a CSV blob. This might be
     * quicker for larger data.
     * @param userLocale
     * @param includeInfo Include the CSV header in the output
     * @returns Response
     */
    async fetchCSVBlob(userLocale = 'en-us', includeInfo = false) {
        const fetchUrl = this.getFetchCSVBlobUrl(userLocale, includeInfo);
        return (0, tokenizedFetch_1.tokenizedFetch)(fetchUrl, {
            credentials: 'include',
        });
    }
    /**
     * Fetch the data for the answer as a PNG blob. This might be
     * quicker for larger data.
     * @param userLocale
     * @param includeInfo
     * @param omitBackground Omit the background in the PNG
     * @param deviceScaleFactor The scale factor for the PNG
     * @return Response
     */
    async fetchPNGBlob(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) {
        const fetchUrl = this.getFetchPNGBlobUrl(userLocale, omitBackground, deviceScaleFactor);
        return (0, tokenizedFetch_1.tokenizedFetch)(fetchUrl, {
            credentials: 'include',
        });
    }
    /**
     * Just get the internal URL for this answer's data
     * as a CSV blob.
     * @param userLocale
     * @param includeInfo
     * @returns
     */
    getFetchCSVBlobUrl(userLocale = 'en-us', includeInfo = false) {
        return `${this.thoughtSpotHost}/prism/download/answer/csv?sessionId=${this.session.sessionId}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data&hideCsvHeader=${!includeInfo}`;
    }
    /**
     * Just get the internal URL for this answer's data
     * as a PNG blob.
     * @param userLocale
     * @param omitBackground
     * @param deviceScaleFactor
     */
    getFetchPNGBlobUrl(userLocale = 'en-us', omitBackground = false, deviceScaleFactor = 2) {
        return `${this.thoughtSpotHost}/prism/download/answer/png?sessionId=${this.session.sessionId}&deviceScaleFactor=${deviceScaleFactor}&omitBackground=${omitBackground}&genNo=${this.session.genNo}&userLocale=${userLocale}&exportFileName=data`;
    }
    /**
     * Get underlying data given a point and the output column names.
     * In case of a context menu action, the selectedPoints are
     * automatically passed.
     * @param outputColumnNames
     * @param selectedPoints
     * @example
     * ```js
     *  embed.on(EmbedEvent.CustomAction, e => {
     *     const underlying = await e.answerService.getUnderlyingDataForPoint([
     *       'col name 1' // The column should exist in the data source.
     *     ]);
     *     const data = await underlying.fetchData(0, 100);
     *  })
     * ```
     * @version SDK: 1.25.0| ThoughtSpot: 9.10.0.cl
     */
    async getUnderlyingDataForPoint(outputColumnNames, selectedPoints) {
        if (!selectedPoints && !this.selectedPoints) {
            throw new Error('Needs to be triggered in context of a point');
        }
        if (!selectedPoints) {
            selectedPoints = getSelectedPointsForUnderlyingDataQuery(this.selectedPoints);
        }
        const sourceDetail = await this.getSourceDetail();
        const ouputColumnGuids = getGuidsFromColumnNames(sourceDetail, outputColumnNames);
        const unAggAnswer = await (0, graphql_request_1.graphqlQuery)({
            query: queries.getUnaggregatedAnswerSession,
            variables: {
                session: this.session,
                columns: selectedPoints,
            },
            thoughtSpotHost: this.thoughtSpotHost,
        });
        const unaggAnswerSession = new AnswerService(unAggAnswer.id, unAggAnswer.answer, this.thoughtSpotHost);
        const currentColumns = new Set(unAggAnswer.answer.visualizations[0].columns
            .map((c) => c.column.referencedColumns[0].guid));
        const columnsToAdd = [...ouputColumnGuids].filter((col) => !currentColumns.has(col));
        if (columnsToAdd.length) {
            await unaggAnswerSession.addColumns(columnsToAdd);
        }
        const columnsToRemove = [...currentColumns].filter((col) => !ouputColumnGuids.has(col));
        if (columnsToRemove.length) {
            await unaggAnswerSession.removeColumns(columnsToRemove);
        }
        return unaggAnswerSession;
    }
    /**
     * Execute a custom graphql query in the context of the answer.
     * @param query graphql query
     * @param variables graphql variables
     * @returns
     */
    async executeQuery(query, variables) {
        const data = await (0, graphql_request_1.graphqlQuery)({
            query,
            variables: {
                session: this.session,
                ...variables,
            },
            thoughtSpotHost: this.thoughtSpotHost,
            isCompositeQuery: false,
        });
        this.session = (0, utils_1.deepMerge)(this.session, (data === null || data === void 0 ? void 0 : data.id) || {});
        return data;
    }
    /**
     * Get the internal session details for the answer.
     * @returns
     */
    getSession() {
        return this.session;
    }
    async getAnswer() {
        if (this.answer) {
            return this.answer;
        }
        this.answer = this.executeQuery(queries.getAnswer, {}).then((data) => data === null || data === void 0 ? void 0 : data.answer);
        return this.answer;
    }
    async getTML() {
        const { object } = await this.executeQuery(queries.getAnswerTML, {});
        const edoc = object[0].edoc;
        const YAML = await Promise.resolve().then(() => tslib_1.__importStar(require('yaml')));
        const parsedDoc = YAML.parse(edoc);
        return {
            answer: {
                ...parsedDoc.answer,
                ...this.tmlOverride,
            },
        };
    }
    async addDisplayedVizToLiveboard(liveboardId) {
        const { displayMode, visualizations } = await this.getAnswer();
        const viz = getDisplayedViz(visualizations, displayMode);
        return this.executeQuery(queries.addVizToLiveboard, {
            liveboardId,
            vizId: viz.id,
        });
    }
    setTMLOverride(override) {
        this.tmlOverride = override;
    }
}
exports.AnswerService = AnswerService;
/**
 *
 * @param sourceDetail
 * @param colNames
 */
function getGuidsFromColumnNames(sourceDetail, colNames) {
    const cols = sourceDetail.columns.reduce((colSet, col) => {
        colSet[col.name.toLowerCase()] = col;
        return colSet;
    }, {});
    return new Set(colNames.map((colName) => {
        const col = cols[colName.toLowerCase()];
        return col.id;
    }));
}
/**
 *
 * @param selectedPoints
 */
function getSelectedPointsForUnderlyingDataQuery(selectedPoints) {
    const underlyingDataPoint = [];
    /**
     *
     * @param colVal
     */
    function addPointFromColVal(colVal) {
        var _a;
        const dataType = colVal.column.dataType;
        const id = colVal.column.id;
        let dataValue;
        if (dataType === 'DATE') {
            if (Number.isFinite(colVal.value)) {
                dataValue = [{
                        epochRange: {
                            startEpoch: colVal.value,
                        },
                    }];
                // Case for custom calendar.
            }
            else if ((_a = colVal.value) === null || _a === void 0 ? void 0 : _a.v) {
                dataValue = [{
                        epochRange: {
                            startEpoch: colVal.value.v.s,
                            endEpoch: colVal.value.v.e,
                        },
                    }];
            }
        }
        else {
            dataValue = [{ value: colVal.value }];
        }
        underlyingDataPoint.push({
            columnId: colVal.column.id,
            dataValue,
        });
    }
    selectedPoints.forEach((p) => {
        p.selectedAttributes.forEach(addPointFromColVal);
    });
    return underlyingDataPoint;
}
/**
 *
 * @param visualizations
 * @param displayMode
 */
function getDisplayedViz(visualizations, displayMode) {
    if (displayMode === 'CHART_MODE') {
        return visualizations.find(
        // eslint-disable-next-line no-underscore-dangle
        (viz) => viz.__typename === 'ChartViz');
    }
    return visualizations.find(
    // eslint-disable-next-line no-underscore-dangle
    (viz) => viz.__typename === 'TableViz');
}
//# sourceMappingURL=answerService.js.map