/**
 * Copyright (c) 2022
 *
 * Embed a ThoughtSpot Liveboard or visualization
 * https://developers.thoughtspot.com/docs/embed-liveboard
 * https://developers.thoughtspot.com/docs/embed-a-viz
 * @summary Liveboard & visualization embed
 * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
 */

import { getPreview } from '../utils/graphql/preview-service';
import { ERROR_MESSAGE } from '../errors';
import {
    EmbedEvent,
    MessagePayload,
    Param,
    RuntimeFilter,
    DOMSelector,
    HostEvent,
    SearchLiveboardCommonViewConfig as LiveboardOtherViewConfig,
    BaseViewConfig,
    LiveboardAppEmbedViewConfig,
    ErrorDetailsTypes,
    EmbedErrorCodes,
    ContextType,
} from '../types';
import { calculateVisibleElementData, getQueryParamString, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import { getAuthPromise } from './base';
import { TsEmbed, V1Embed } from './ts-embed';
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts';
import { logger } from '../utils/logger';
import { SpotterChatViewConfig } from './conversation';


/**
 * The configuration for the embedded Liveboard or visualization page view.
 * @group Embed components
 */
export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewConfig, LiveboardAppEmbedViewConfig {
    /**
     * If set to true, the embedded object container dynamically resizes
     * according to the height of the Liveboard.
     *
     * **Note**:  Using fullHeight loads all visualizations on the
     * Liveboard simultaneously, which results in multiple warehouse
     * queries and potentially a longer wait for the topmost
     * visualizations to display on the screen.
     * Setting `fullHeight` to `false` fetches visualizations
     * incrementally as users scroll the page to view the charts and tables.
     *
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed', {
     *   ... // other liveboard view config
     *  fullHeight: true,
     * });
     * ```
     */
    fullHeight?: boolean;
    /**
     * This is the minimum height (in pixels) for a full-height Liveboard.
     * Setting this height helps resolve issues with empty Liveboards and
     * other screens navigable from a Liveboard.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1
     * @deprecated Use `minimumHeight` instead.
     * @default 500
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed', {
     *   ... // other liveboard view config
     *   fullHeight: true,
     *   defaultHeight: 600,
     * });
     * ```
     */
    defaultHeight?: number;
    /**
     * This is the minimum height (in pixels) for a full-height Liveboard.
     * Setting this height helps resolve issues with empty Liveboards and
     * other screens navigable from a Liveboard.
     *
     * @version SDK: 1.44.2 | ThoughtSpot: 10.15.0.cl
     * @default 500
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed', {
     *   ... // other liveboard view config
     *   fullHeight: true,
     *   minimumHeight: 600,
     * });
     * ```
     */
    minimumHeight?: number;
    /**
     * If set to true, the context menu in visualizations will be enabled.
     * @version SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw
     * @deprecated this option is deprecated.
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    enableVizTransformations:true,
     * })
     * ```
     */
    enableVizTransformations?: boolean;
    /**
     * The Liveboard to display in the embedded view.
     * Use either liveboardId or pinboardId to reference the Liveboard to embed.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    liveboardId:'id of liveboard',
     * })
     * ```
     */
    liveboardId?: string;
    /**
     * To support backward compatibility
     * @hidden
     */
    pinboardId?: string;
    /**
     * The visualization within the Liveboard to display.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    vizId:'430496d6-6903-4601-937e-2c691821af3c',
     * })
     * ```
     */
    vizId?: string;
    /**
     * If set to true, all filter chips from a
     * Liveboard page will be read-only (no X buttons)
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1.sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    preventLiveboardFilterRemoval:true,
     * })
     * ```
     */
    preventLiveboardFilterRemoval?: boolean;
    /**
     * Array of visualization IDs which should be visible when the Liveboard
     * renders. This can be changed by triggering the `SetVisibleVizs`
     * event.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    visibleVizs: [
     *       '430496d6-6903-4601-937e-2c691821af3c',
     *       'f547ec54-2a37-4516-a222-2b06719af726'
     *     ]
     * })
     * ```
     */
    visibleVizs?: string[];
    /**
     * To support backward compatibility
     * @hidden
     */
    preventPinboardFilterRemoval?: boolean;
    /**
     * Render embedded Liveboards and visualizations in the
     * new Liveboard experience mode.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.14.0 | ThoughtSpot: 8.6.0.cl, 8.8.1-sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    liveboardV2:true,
     * })
     * ```
     */
    liveboardV2?: boolean;
    /**
     * Set a Liveboard tab as an active tab.
     * Specify the tab ID.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1-sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    activeTabId:'id-1234',
     * })
     * ```
     */
    activeTabId?: string;
    /**
     * The GUID of a saved personalized view to load.
     * A personalized view is a saved configuration of a Liveboard
     * that includes specific filter selections.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    liveboardId: 'liveboard-guid',
     *    personalizedViewId: 'view-guid',
     *    activeTabId: 'tab-guid',
     * })
     * ```
     */
    personalizedViewId?: string;
    /**
     * Show or hide the tab panel of the embedded Liveboard.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 9.8.0.sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    hideTabPanel:true,
     * })
     * ```
     */
    hideTabPanel?: boolean;
    /**
     * Show a preview image of the visualization before the visualization loads.
     * Only works for visualizations embeds with a viz id.
     *
     * Also, viz snapshot should be enabled in the ThoughtSpot instance.
     * Contact ThoughtSpot support to enable this feature.
     *
     * Since this will show preview images, be careful that it may show
     * undesired data to the user when using row level security.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *   liveboardId: 'liveboard-id',
     *   vizId: 'viz-id',
     *   showPreviewLoader: true,
     * });
     * embed.render();
     * ```
     */
    showPreviewLoader?: boolean;
    /**
     * The Liveboard to run on regular intervals to fetch the cdw token.
     *
     * Supported embed types: `LiveboardEmbed`
     * @hidden
     * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    oAuthPollingInterval: 30000,
     * })
     * ```
     */
    oAuthPollingInterval?: number;

    /**
     * The Liveboard is set to force a token fetch during the initial load.
     *
     * Supported embed types: `LiveboardEmbed`
     * @hidden
     * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    isForceRedirect: false,
     * })
     * ```
     */
    isForceRedirect?: boolean;

    /**
     * The source connection ID for authentication.
     *
     * Supported embed types: `LiveboardEmbed`
     * @hidden
     * @version SDK: 1.35.0 | ThoughtSpot: 10.6.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    dataSourceId: '',
     * })
     * ```
     */
    dataSourceId?: string;
    /**
     * The list of tab IDs to hide from the embedded Liveboard.
     * These tabs will be hidden from their respective Liveboards.
     * Use this to hide a tab ID.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... // other embed view config
     *   hiddenTabs: [
     *    '430496d6-6903-4601-937e-2c691821af3c',
     *    'f547ec54-2a37-4516-a222-2b06719af726'
     *   ]
     * });
     * ```
     */
    hiddenTabs?: string[];
    /**
     * The list of tab IDs to show in the embedded Liveboard.
     * Only the tabs specified in the array will be shown in the Liveboard.
     *
     * Use either `visibleTabs` or `hiddenTabs`.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    visibleTabs: [
     *       '430496d6-6903-4601-937e-2c691821af3c',
     *       'f547ec54-2a37-4516-a222-2b06719af726'
     *     ]
     * })
     * ```
     */
    visibleTabs?: string[];
    /**
     * This flag is used to enable/disable the styling and grouping in a Liveboard. Use {@link isLiveboardMasterpiecesEnabled} instead.
     * @deprecated This flag is deprecated.
     *
     * Supported embed types: `LiveboardEmbed`, `AppEmbed`
     * @type {boolean}
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
     * @example
     * ```js
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
     * const embed = new <EmbedComponent>('#tsEmbed', {
     *    ... // other embed view config
     *    isLiveboardStylingAndGroupingEnabled: true,
     * })
     * ```
     */
    isLiveboardStylingAndGroupingEnabled?: boolean;
    /**
     * This flag is used to enable/disable the png embedding of liveboard in scheduled
     * mails
     *
     * Supported embed types: `AppEmbed`, `LiveboardEmbed`
     * @type {boolean}
     * @version SDK: 1.42.0 | ThoughtSpot: 10.14.0.cl
     * @example
     * ```js
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
     * const embed = new <EmbedComponent>('#tsEmbed', {
     *    ... // other embed view config
     *    isPNGInScheduledEmailsEnabled: true,
     * })
     * ```
     */
    isPNGInScheduledEmailsEnabled?: boolean;
    /**
     * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
     * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
     * This feature is GA from version 26.5.0.cl. It is disabled by default in embed deployments.
     *
     * Supported embed types: `AppEmbed`, `LiveboardEmbed`
     * @type {boolean}
     * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
     * @example
     * ```js
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
     * const embed = new <EmbedComponent>('#tsEmbed', {
     *    ... // other embed view config
     *    isContinuousLiveboardPDFEnabled: true,
     * })
     * ```
     */
    isContinuousLiveboardPDFEnabled?: boolean;
    /**
     * This flag is used to enable/disable the XLSX/CSV download option for Liveboards
     *
     * Supported embed types: `AppEmbed`, `LiveboardEmbed`
     * @type {boolean}
     * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
     * @example
     * ```js
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
     * const embed = new <EmbedComponent>('#tsEmbed', {
     *    ... // other embed view config
     *    isLiveboardXLSXCSVDownloadEnabled: true,
     * })
     * ```
     */
    isLiveboardXLSXCSVDownloadEnabled?: boolean;
    /**
     * This flag is used to enable/disable the granular XLSX/CSV schedules feature
     *
     * Supported embed types: `AppEmbed`, `LiveboardEmbed`
     * @type {boolean}
     * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
     * @example
     * ```js
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
     * const embed = new <EmbedComponent>('#tsEmbed', {
     *    ... // other embed view config
     *    isGranularXLSXCSVSchedulesEnabled: true,
     * })
     * ```
     */
    isGranularXLSXCSVSchedulesEnabled?: boolean;
    /**
     * This flag is used to enable the full height lazy load data.
     *
     * @type {boolean}
     * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed-container', {
     *    // ...other options
     *    fullHeight: true,
     *    lazyLoadingForFullHeight: true,
     * })
     * ```
     */
    lazyLoadingForFullHeight?: boolean;
    /**
     * The margin to be used for lazy loading.
     *
     * For example, if the margin is set to '10px',
     * the visualization will be loaded 10px before its top edge is visible in the
     * viewport.
     *
     * The format is similar to CSS margin.
     *
     * @type {string}
     * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed-container', {
     *    // ...other options
     *    fullHeight: true,
     *    lazyLoadingForFullHeight: true,
     *   // Using 0px, the visualization will be only loaded when it's visible in the viewport.
     *    lazyLoadingMargin: '0px',
     * })
     * ```
     */
    lazyLoadingMargin?: string;
    /**
     * showSpotterLimitations : show limitation text
     * of the spotter underneath the chat input.
     * default is false.
     *
     * @type {boolean}
     * @version SDK: 1.41.1 | ThoughtSpot: 10.5.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed-container', {
     *    // ...other options
     *    showSpotterLimitations: true,
     * })
     * ```
     */
    showSpotterLimitations?: boolean;
    /**
     * updatedSpotterChatPrompt : Controls the updated spotter chat prompt.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    updatedSpotterChatPrompt : true,
     * })
     * ```
     */
    updatedSpotterChatPrompt?: boolean;
    /**
     * Enables the stop answer generation button in the Spotter embed UI,
     * allowing users to interrupt an ongoing answer generation.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
     * @default false
     */
    enableStopAnswerGenerationEmbed?: boolean;
    /**
     * Configuration for customizing Spotter chat UI
     * branding in tool response cards.
     *
     * Supported embed types: `LiveboardEmbed`
     * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    spotterChatConfig: {
     *        hideToolResponseCardBranding: true,
     *        toolResponseCardBrandingLabel: 'MyBrand',
     *    },
     * })
     * ```
     */
    spotterChatConfig?: SpotterChatViewConfig;
    /**
     * If set to true, enables visualization data caching on the Liveboard.
     * @type {boolean}
     * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
     * @example
     * ```js
     * const embed = new LiveboardEmbed('#embed-container', {
     *    ... // other options
     *    enableLiveboardDataCache: true,
     * })
     * ```
     */
    enableLiveboardDataCache?: boolean;
}

/**
 * Embed a ThoughtSpot Liveboard or visualization. When rendered it already
 * waits for the authentication to complete, so you need not wait for
 * `AuthStatus.SUCCESS`.
 * @group Embed components
 * @example
 * ```js
 * import { .. } from '@thoughtspot/visual-embed-sdk';
 * init({ ... });
 * const embed = new LiveboardEmbed("#container", {
 *   liveboardId: <your-id-here>,
 * // .. other params here.
 * })
 * ```
 */
export class LiveboardEmbed extends V1Embed {
    protected viewConfig: LiveboardViewConfig;

    private defaultHeight = 500;


    constructor(domSelector: DOMSelector, viewConfig: LiveboardViewConfig) {
        viewConfig.embedComponentType = 'LiveboardEmbed';
        super(domSelector, viewConfig);
        if (this.viewConfig.fullHeight === true) {
            if (this.viewConfig.vizId) {
                logger.warn('Full height is currently only supported for Liveboard embeds.' +
                    'Using full height with vizId might lead to unexpected behavior.');
            }

            this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
            this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
            this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
            this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler);
        }
    }

    /**
     * Construct a map of params to be passed on to the
     * embedded Liveboard or visualization.
     */
    protected getEmbedParams() {
        const params = this.getEmbedParamsObject();
        const queryParams = getQueryParamString(params, true);
        return queryParams;
    }

    protected getEmbedParamsObject() {
        let params: any = {};
        params = this.getBaseQueryParams(params);
        const {
            enableVizTransformations,
            fullHeight,
            defaultHeight,
            minimumHeight,
            visibleVizs,
            liveboardV2,
            vizId,
            hideTabPanel,
            activeTabId,
            hideLiveboardHeader,
            showLiveboardDescription,
            showLiveboardTitle,
            isLiveboardHeaderSticky = true,
            isLiveboardCompactHeaderEnabled = false,
            showLiveboardVerifiedBadge = true,
            showLiveboardReverifyBanner = true,
            hideIrrelevantChipsInLiveboardTabs = false,
            showMaskedFilterChip = false,
            isLiveboardMasterpiecesEnabled = false,
            newChartsLibrary,
            isEnhancedFilterInteractivityEnabled = false,
            enableAskSage,
            enable2ColumnLayout,
            dataPanelV2 = true,
            enableCustomColumnGroups = false,
            oAuthPollingInterval,
            isForceRedirect,
            dataSourceId,
            coverAndFilterOptionInPDF = false,
            isLiveboardStylingAndGroupingEnabled,
            isPNGInScheduledEmailsEnabled = false,
            isLiveboardXLSXCSVDownloadEnabled = false,
            isGranularXLSXCSVSchedulesEnabled = false,
            showSpotterLimitations,
            isCentralizedLiveboardFilterUXEnabled = false,
            isLinkParametersEnabled,
            updatedSpotterChatPrompt,
            enableStopAnswerGenerationEmbed,
            spotterChatConfig,
            isThisPeriodInDateFiltersEnabled,
            isContinuousLiveboardPDFEnabled = false,
            enableLiveboardDataCache,
        } = this.viewConfig;

        const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
            || this.viewConfig.preventPinboardFilterRemoval;

        if (fullHeight === true) {
            params[Param.fullHeight] = true;
            if (this.viewConfig.lazyLoadingForFullHeight) {
                params[Param.IsLazyLoadingForEmbedEnabled] = true;
                if (isValidCssMargin(this.viewConfig.lazyLoadingMargin)) {
                    params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin;
                }
            }
        }
        this.defaultHeight = minimumHeight || defaultHeight || this.defaultHeight;
        if (enableVizTransformations !== undefined) {
            params[Param.EnableVizTransformations] = enableVizTransformations.toString();
        }
        if (preventLiveboardFilterRemoval) {
            params[Param.preventLiveboardFilterRemoval] = true;
        }
        if (!isUndefined(updatedSpotterChatPrompt)) {
            params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt;
        }
        if (!isUndefined(enableStopAnswerGenerationEmbed)) {
            params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed;
        }
        if (visibleVizs) {
            params[Param.visibleVizs] = visibleVizs;
        }
        params[Param.livedBoardEmbed] = true;
        if (vizId) {
            params[Param.vizEmbed] = true;
        }
        if (liveboardV2 !== undefined) {
            params[Param.LiveboardV2Enabled] = liveboardV2;
        }
        if (enable2ColumnLayout !== undefined) {
            params[Param.Enable2ColumnLayout] = enable2ColumnLayout;
        }
        if (hideTabPanel) {
            params[Param.HideTabPanel] = hideTabPanel;
        }
        if (hideLiveboardHeader) {
            params[Param.HideLiveboardHeader] = hideLiveboardHeader;
        }
        if (showLiveboardDescription) {
            params[Param.ShowLiveboardDescription] = showLiveboardDescription;
        }
        if (showLiveboardTitle) {
            params[Param.ShowLiveboardTitle] = showLiveboardTitle;
        }
        if (enableAskSage) {
            params[Param.enableAskSage] = enableAskSage;
        }

        if (oAuthPollingInterval !== undefined) {
            params[Param.OauthPollingInterval] = oAuthPollingInterval;
        }

        if (isForceRedirect) {
            params[Param.IsForceRedirect] = isForceRedirect;
        }

        if (dataSourceId !== undefined) {
            params[Param.DataSourceId] = dataSourceId;
        }

        if (isLiveboardStylingAndGroupingEnabled !== undefined) {
            params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled;
        }

        if (isPNGInScheduledEmailsEnabled !== undefined) {
            params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled;
        }

        if (isLiveboardXLSXCSVDownloadEnabled !== undefined) {
            params[Param.isLiveboardXLSXCSVDownloadEnabled] = isLiveboardXLSXCSVDownloadEnabled;
        }

        if (isGranularXLSXCSVSchedulesEnabled !== undefined) {
            params[Param.isGranularXLSXCSVSchedulesEnabled] = isGranularXLSXCSVSchedulesEnabled;
        }

        if (showSpotterLimitations !== undefined) {
            params[Param.ShowSpotterLimitations] = showSpotterLimitations;
        }

        // Handle spotterChatConfig params
        if (spotterChatConfig) {
            const {
                hideToolResponseCardBranding,
                toolResponseCardBrandingLabel,
            } = spotterChatConfig;

            setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
            setParamIfDefined(params, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel);
        }

        if (isLinkParametersEnabled !== undefined) {
            params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
        }

        if (isCentralizedLiveboardFilterUXEnabled !== undefined) {
            params[
                Param.isCentralizedLiveboardFilterUXEnabled
            ] = isCentralizedLiveboardFilterUXEnabled;
        }

        if (isThisPeriodInDateFiltersEnabled !== undefined) {
            params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled;
        }

        if (isContinuousLiveboardPDFEnabled !== undefined) {
            params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled;
        }

        if (enableLiveboardDataCache !== undefined) {
            params[Param.EnableLiveboardDataCache] = enableLiveboardDataCache;
        }

        if (newChartsLibrary !== undefined) {
            params[Param.EnableNewChartLibrary] = newChartsLibrary;
        }

        params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
        params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
        params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
        params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner;
        params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs;
        params[Param.ShowMaskedFilterChip] = showMaskedFilterChip;
        params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled;
        params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled;
        params[Param.DataPanelV2Enabled] = dataPanelV2;
        params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups;
        params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF;

        const queryParams = getQueryParamString(params, true);

        return params;
    }

    private getIframeSuffixSrc(
        liveboardId: string,
        vizId: string,
        activeTabId: string,
        personalizedViewId?: string,
    ) {
        // Extract view from liveboardId if passed along with it (legacy
        // approach)
        // View must be appended as query param at the end, not
        // embedded in path
        let liveboardGuid = liveboardId;
        let legacyViewId: string | undefined;

        if (liveboardId?.includes('?')) {
            const [id, query] = liveboardId.split('?');
            liveboardGuid = id;
            const params = new URLSearchParams(query);
            legacyViewId = params.get('view') || undefined;
        }

        // personalizedViewId takes precedence over legacyViewId (when passed
        // as part of liveboardId)
        const effectiveViewId = personalizedViewId || legacyViewId;

        let suffix = `/embed/viz/${liveboardGuid}`;
        if (activeTabId) {
            suffix = `${suffix}/tab/${activeTabId}`;
        }
        if (vizId) {
            suffix = `${suffix}/${vizId}`;
        }
        const additionalParams: { [key: string]: string } = {};
        if (effectiveViewId) {
            additionalParams.view = effectiveViewId;
        }
        const tsPostHashParams = this.getThoughtSpotPostUrlParams(additionalParams);
        suffix = `${suffix}${tsPostHashParams}`;
        return suffix;
    }

    private sendFullHeightLazyLoadData = () => {
        const data = calculateVisibleElementData(this.iFrame);
        // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true
        if(this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight){
            this.trigger(HostEvent.VisibleEmbedCoordinates, data);
        }
    };

    /**
     * This is a handler for the RequestVisibleEmbedCoordinates event.
     * It is used to send the visible coordinates data to the host application.
     * @param data The event payload
     * @param responder The responder function
     */
    private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
        logger.info('Sending RequestVisibleEmbedCoordinates', data);
        const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
        responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
    }

    /**
     * Construct the URL of the embedded ThoughtSpot Liveboard or visualization
     * to be loaded within the iFrame.
     */
    private getIFrameSrc(): string {
        const { vizId, activeTabId, personalizedViewId } = this.viewConfig;
        const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId;

        if (!liveboardId) {
            this.handleError({
                errorType: ErrorDetailsTypes.VALIDATION_ERROR,
                message: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION,
                code: EmbedErrorCodes.LIVEBOARD_ID_MISSING,
                error: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION,
            });
        }
        return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(
            liveboardId,
            vizId,
            activeTabId,
            personalizedViewId,
        )}`;
    }

    /**
     * Set the iframe height as per the computed height received
     * from the ThoughtSpot app.
     * @param data The event payload
     */
    private updateIFrameHeight = (data: MessagePayload) => {
        this.setIFrameHeight(Math.max(data.data, this.defaultHeight));
        this.sendFullHeightLazyLoadData();
    };

    private embedIframeCenter = (data: MessagePayload, responder: any) => {
        const obj = this.getIframeCenter();
        responder({ type: EmbedEvent.EmbedIframeCenter, data: obj });
    };

    private setIframeHeightForNonEmbedLiveboard = (data: MessagePayload) => {
        const { height: frameHeight } = this.viewConfig.frameParams || {};

        const liveboardRelatedRoutes = [
            '/pinboard/',
            '/insights/pinboard/',
            '/schedules/',
            '/embed/viz/',
            '/embed/insights/viz/',
            '/liveboard/',
            '/insights/liveboard/',
            '/tsl-editor/PINBOARD_ANSWER_BOOK/',
            '/import-tsl/PINBOARD_ANSWER_BOOK/',
        ];

        if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) {
            // Ignore the height reset of the frame, if the navigation is
            // only within the liveboard page.
            return;
        }
        this.setIFrameHeight(frameHeight || this.defaultHeight);
    };

    private setActiveTab(data: { tabId: string }) {
        if (!this.viewConfig.vizId) {
            const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0];
            const path = `${prefixPath}/tab/${data.tabId}`;
            super.trigger(HostEvent.Navigate, path);
        }
    }

    private async showPreviewLoader() {
        if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) {
            return;
        }

        try {
            const preview = await getPreview(
                this.thoughtSpotHost,
                this.viewConfig.vizId,
                this.viewConfig.liveboardId,
            );

            if (!preview.vizContent) {
                return;
            }
            addPreviewStylesIfNotPresent();

            const div = document.createElement('div');
            div.innerHTML = `
                <div class=ts-viz-preview-loader>
                    ${preview.vizContent}
                </div>
                `;
            const previewDiv = div.firstElementChild as HTMLElement;
            this.el.appendChild(previewDiv);
            this.el.style.position = 'relative';
            this.on(EmbedEvent.Data, () => {
                previewDiv.remove();
            });
        } catch (error) {
            console.error('Error fetching preview', error);
        }
    }

    /**
     * @hidden
     * Internal state to track the current liveboard id.
     * This is used to navigate to the correct liveboard when the prerender is visible.
     */
    public currentLiveboardState = {
        liveboardId: this.viewConfig.liveboardId,
        vizId: this.viewConfig.vizId,
        activeTabId: this.viewConfig.activeTabId,
        personalizedViewId: this.viewConfig.personalizedViewId,
    };

    protected beforePrerenderVisible(): void {
        const embedObj = this.getPreRenderObj<LiveboardEmbed>();

        this.executeAfterEmbedContainerLoaded(() => {
            this.navigateToLiveboard(
                this.viewConfig.liveboardId,
                this.viewConfig.vizId,
                this.viewConfig.activeTabId,
                this.viewConfig.personalizedViewId,
            );
            if (embedObj) {
                embedObj.currentLiveboardState = {
                    liveboardId: this.viewConfig.liveboardId,
                    vizId: this.viewConfig.vizId,
                    activeTabId: this.viewConfig.activeTabId,
                    personalizedViewId: this.viewConfig.personalizedViewId,
                };
            }
        });
    }

    protected async handleRenderForPrerender(): Promise<TsEmbed> {
        if (isUndefined(this.viewConfig.liveboardId)) {
            return this.prerenderGeneric();
        }
        return super.handleRenderForPrerender();
    }

    /**
     * Triggers an event to the embedded app
     * @param {HostEvent} messageType The event type
     * @param {any} data The payload to send with the message
     * @returns A promise that resolves with the response from the embedded app
     */
    public trigger<HostEventT extends HostEvent, PayloadT, ContextT extends ContextType>(
        messageType: HostEventT,
        data: TriggerPayload<PayloadT, HostEventT> = ({} as any),
        context?: ContextT,
    ): Promise<TriggerResponse<PayloadT, HostEventT, ContextT>> {
        const dataWithVizId: any = data;
        if (messageType === HostEvent.SetActiveTab) {
            this.setActiveTab(data as { tabId: string });
            return Promise.resolve(null);
        }
        if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) {
            dataWithVizId.vizId = this.viewConfig.vizId;
        }
        return super.trigger(messageType, dataWithVizId, context);
    }
    /**
     * Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
     * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
     */
    public destroy() {
        super.destroy();
        this.unregisterLazyLoadEvents();
    }

    private postRender() {
        this.registerLazyLoadEvents();
    }

    private registerLazyLoadEvents() {
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
            // TODO: Use passive: true, install modernizr to check for passive
            window.addEventListener('resize', this.sendFullHeightLazyLoadData);
            window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
        }
    }

    private unregisterLazyLoadEvents() {
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
            window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
            window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
        }
    }

    /**
     * Render an embedded ThoughtSpot Liveboard or visualization
     * @param renderOptions An object specifying the Liveboard ID,
     * visualization ID and the runtime filters.
     */
    public async render(): Promise<LiveboardEmbed> {
        await super.render();

        const src = this.getIFrameSrc();
        await this.renderV1Embed(src);
        this.showPreviewLoader();

        this.postRender();
        return this;
    }

    public navigateToLiveboard(
        liveboardId: string,
        vizId?: string,
        activeTabId?: string,
        personalizedViewId?: string,
    ) {
        const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId, personalizedViewId);
        this.viewConfig.liveboardId = liveboardId;
        this.viewConfig.activeTabId = activeTabId;
        this.viewConfig.vizId = vizId;
        this.viewConfig.personalizedViewId = personalizedViewId;
        if (this.isRendered) {
            this.trigger(HostEvent.Navigate, path.substring(1));
        } else if (this.viewConfig.preRenderId) {
            this.preRender(true);
        } else {
            this.render();
        }
    }

    /**
     * Returns the full url of the Liveboard/visualization which can be used to open
     * this Liveboard inside the full ThoughtSpot application in a new tab.
     * @returns url string
     */
    public getLiveboardUrl(): string {
        let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`;
        if (this.viewConfig.activeTabId) {
            url = `${url}/tab/${this.viewConfig.activeTabId}`;
        }

        if (this.viewConfig.vizId) {
            url = `${url}/${this.viewConfig.vizId}`;
        }

        if (this.viewConfig.personalizedViewId) {
            url = `${url}?view=${this.viewConfig.personalizedViewId}`;
        }

        return url;
    }
}

/**
 * @hidden
 */
export class PinboardEmbed extends LiveboardEmbed { }
