/**
 * Copyright (c) 2022
 *
 * Full application embedding
 * https://developers.thoughtspot.com/docs/?pageid=full-embed
 * @summary Full app embed
 * @module
 * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
 */

import { logger } from '../utils/logger';
import { calculateVisibleElementData, getQueryParamString, isUndefined, isValidCssMargin, setParamIfDefined } from '../utils';
import {
    Param,
    DOMSelector,
    HostEvent,
    EmbedEvent,
    MessagePayload,
    AllEmbedViewConfig,
    DefaultAppInitData,
    VisualizationOverrides,
} from '../types';
import { V1Embed } from './ts-embed';
import { SpotterChatViewConfig, SpotterSidebarViewConfig } from './conversation';
import { buildSpotterSidebarAppInitData } from './spotter-utils';

/**
 * Pages within the ThoughtSpot app that can be embedded.
 */

export enum Page {
    /**
     * Home page
     */
    Home = 'home',
    /**
     * Search page
     */
    Search = 'search',
    /**
     * Saved answers listing page
     */
    Answers = 'answers',
    /**
     * Liveboards listing page
     */
    Liveboards = 'liveboards',
    /**
     * @hidden
     */
    Pinboards = 'pinboards',
    /**
     * Data management page
     */
    Data = 'data',
    /**
     * SpotIQ listing page
     */
    SpotIQ = 'insights',
    /**
     *  Monitor Alerts Page
     */
    Monitor = 'monitor',
}

/**
 * Define the initial state of column custom group accordions
 * in data panel v2.
 */
export enum DataPanelCustomColumnGroupsAccordionState {
    /**
     * Expand all the accordion initially in data panel v2.
     */
    EXPAND_ALL = 'EXPAND_ALL',
    /**
     * Collapse all the accordions initially in data panel v2.
     */
    COLLAPSE_ALL = 'COLLAPSE_ALL',
    /**
     * Expand the first accordion and collapse the rest.
     */
    EXPAND_FIRST = 'EXPAND_FIRST',
}

export enum HomePageSearchBarMode {
    OBJECT_SEARCH = 'objectSearch',
    AI_ANSWER = 'aiAnswer',
    NONE = 'none'
}

/**
 * Define the version of the primary navbar
 * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
 */
export enum PrimaryNavbarVersion {
    /**
     * Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher,
     * along with updates to the top navigation bar.
     * It serves as the foundational version of the PrimaryNavBar.
     */
    Sliding = 'v3',
}

/**
 * Define the version of the home page
 * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
 */
export enum HomePage {
    /**
     * Modular (v2) introduces the updated Modular Home Experience.
     * It serves as the foundational version of the home page.
     */
    Modular = 'v2',
    /**
     * ModularWithStylingChanges (v3) introduces Modular Home Experience
     * with styling changes.
     */
    ModularWithStylingChanges = 'v3',
}

/**
 * Define the version of the list page
 * @version SDK: 1.40.0 | ThoughtSpot: 10.12.0.cl
 */
export enum ListPage {
    /**
     * List (v2) is the traditional List Experience.
     * It serves as the foundational version of the list page.
     */
    List = 'v2',
    /**
     * ListWithUXChanges (v3) introduces the new updated list page with UX changes.
     */
    ListWithUXChanges = 'v3',
}

/**
 * Define the discovery experience
 * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
 */
export interface DiscoveryExperience {
    /**
     * primaryNavbarVersion determines the version of the primary navigation bar.
     */
    primaryNavbarVersion?: PrimaryNavbarVersion;
    /**
     * homePage determines the version of the home page.
     */
    homePage?: HomePage;
    /**
     * listPageVersion determines the version of the list page.
     */
    listPageVersion?: ListPage;
}

/**
 * The view configuration for full app embedding.
 * @group Embed components
 */
export interface AppViewConfig extends AllEmbedViewConfig {
    /**
     * If true, the top navigation bar within the ThoughtSpot app
     * is displayed. By default, the navigation bar is hidden.
     * This flag also controls the homepage left navigation bar.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.2.0 | ThoughtSpot: 8.4.0.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    showPrimaryNavbar:true,
     * })
     * ```
     */
    showPrimaryNavbar?: boolean;
    /**
     * Control the visibility of the left navigation panel on the home page
     * in the V2 and V3 navigation and home page experience.
     * If `showPrimaryNavbar` is true, that is, if the Global and Homepage
     * navigation bars are visible, this flag will only hide the left navigation bar
     * on the home page.
     * The `showPrimaryNavbar` flag takes precedence over the `hideHomepageLeftNav`.
     *
     * **Note**: This attribute is not supported in the classic (V1) experience.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideHomepageLeftNav : true,
     * })
     * ```
     */
    hideHomepageLeftNav?: boolean;
    /**
     * Control the visibility of the help (?) and profile
     * buttons on the top navigation bar.
     * These buttons are visible if the
     * navigation bar is not hidden via `showPrimaryNavbar`.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.2.0 | ThoughtSpot: 8.4.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    disableProfileAndHelp: true,
     * })
     * ```
     */
    disableProfileAndHelp?: boolean;
    /**
     * Whether the help menu in the top navigation bar should be served
     * from Pendo or ThoughtSpot's internal help items.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.36.3 | ThoughtSpot: 10.1.0.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *   ... // other embed view config
     *  enablePendoHelp: false,
     * });
     * ```
     */
    enablePendoHelp?: boolean;
    /**
     * Control the visibility of the hamburger icon on
     * the top navigation bar in the V3 navigation experience.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideHamburger : true,
     * })
     * ```
     */
    hideHamburger?: boolean;
    /**
     * Control the visibility of the object search
     * on the top navigation bar in the
     * V2 and V3 navigation experience.
     *
     * **Note**: This attribute is not supported
     * in the classic (V1) experience.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideObjectSearch: false,
     * })
     * ```
     */
    hideObjectSearch?: boolean;
    /**
     * Control the visibility of the notification icon
     * on the top navigation bar in V3 navigation experience.
     *
     * **Note**: This attribute is not supported
     * in the classic (V1) and V2 experience modes.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideNotification: false,
     * })
     * ```
     */
    hideNotification?: boolean;
    /**
     * Control the visibility of the application selection menu
     * in the top navigation bar in the V2 experience.
     * In the V3 experience, it shows or hides application selection
     * icons on the left navigation panel.
     * By default, the application selection menu and icons are
     * shown in the UI.
     *
     * **Note**: This attribute is not supported in the classic (V1) experience.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideApplicationSwitcher : true,
     * })
     * ```
     */
    hideApplicationSwitcher?: boolean;
    /**
     * Control the visibility of the Org switcher button on the nav-bar.
     * By default, the Org switcher button is shown.
     *
     * **Note**: This attribute is not supported in the classic (V1) experience.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideOrgSwitcher : true,
     * })
     * ```
     */
    hideOrgSwitcher?: boolean;
    /**
     * A URL path to the embedded application page
     * If both path and pageId attributes are defined, the path definition
     * takes precedence. This is the path post the `#/` in the URL of the standalone
     * ThoughtSpot app. Use this to open the embedded view to a specific path.
     *
     * For example, if you want the component to open to a specific Liveboard
     * you could set the path to `pinboard/<liveboardId>/tab/<tabId>`.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    path:"pinboard/1234/tab/7464",
     * })
     * ```
     */
    path?: string;
    /**
     * The application page to set as the start page
     * in the embedded view.
     *
     * Use this to open to a particular page in the app. To open to a specific
     * path within the app, use the `path` attribute which is more flexible.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    pageId: Page.Answers, // or Page.Data
     * })
     * ```
     */
    pageId?: Page;
    /**
     * This puts a filter tag on the application. All metadata lists in the
     * application, such as Liveboards and answers, would be filtered by this
     * tag.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.1.0 | ThoughtSpot: 9.4.0.cl
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    tag:'value',
     * })
     * ```
     */
    tag?: string;
    /**
     * Hide tag filter chips that appear when content is filtered by tags.
     * When enabled, this automatically:
     * - Hides tag filter indicators/chips from the UI
     *
     * This provides a clean interface without tag-related UI elements.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.44.0 | ThoughtSpot: 10.15.0.cl
     * @example
     * ```js
     * // Simple usage - automatically hides all tag-related UI
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    tag: 'Some Tag',
     *    hideTagFilterChips: true, // This is all you need!
     * });
     * ```
     */
    hideTagFilterChips?: boolean;
    /**
     * The array of GUIDs to be hidden
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1-sw
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    hideObjects: [
     *       '430496d6-6903-4601-937e-2c691821af3c',
     *       'f547ec54-2a37-4516-a222-2b06719af726'
     *     ]
     * })
     * ```
     */
    hideObjects?: string[];
    /**
     * Render liveboards using the new v2 rendering mode
     * This is a transient flag which is primarily meant for internal use
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1-sw
     * @hidden
     */
    liveboardV2?: boolean;
    /**
     * If set to true, the Search Assist feature is enabled.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.13.0 | ThoughtSpot: 8.5.0.cl, 8.8.1-sw
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    enableSearchAssist: true,
     * })
     * ```
     */
    enableSearchAssist?: boolean;
    /**
     * If set to true, the Liveboard 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: `AppEmbed`
     * @version SDK: 1.21.0 | ThoughtSpot: 9.4.0.cl, 9.4.0-sw
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    fullHeight: true,
     * })
     * ```
     */
    fullHeight?: boolean;
    /**
     * Enables the V2 navigation and modular home page experience.
     * For more information,
     * see link:https://developers.thoughtspot.com/docs/full-app-customize[full app embed documentation].
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.28.0 | ThoughtSpot: 9.12.5.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    modularHomeExperience : true,
     * })
     * ```
     */
    modularHomeExperience?: boolean;
    /**
     * Configures the V3 navigation and home page experience.
     * For more information, see
     * link:https://developers.thoughtspot.com/docs/full-app-customize[full app embed documentation].
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    // Enable V3 navigation and home page experience
     *    discoveryExperience : {
     *      primaryNavbarVersion: PrimaryNavbarVersion.Sliding, // Enable V3 navigation
     *      homePage: HomePage.ModularWithStylingChanges, // Enable V3 modular home page
     *      ... // other embed view config
     *    },
     * })
     * ```
     */
    discoveryExperience?: DiscoveryExperience;
    /**
     * To set the initial state of the search bar in case of saved-answers. Use {@link collapseSearchBar} instead.
     * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl
     * @deprecated This flag is deprecated.
     * @default false
     */
    collapseSearchBarInitially?: boolean;
    /**
     * This controls the initial behaviour of custom column groups accordion.
     * It takes DataPanelCustomColumnGroupsAccordionState enum values as input.
     * List of different enum values:-
     * - EXPAND_ALL: Expand all the accordion initially in data panel v2.
     * - COLLAPSE_ALL: Collapse all the accordions initially in data panel v2.
     * - EXPAND_FIRST: Expand the first accordion and collapse the rest.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl
     * @default DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL
     * @example
     * ```js
     * const embed = new AppEmbed('#embed', {
     *   ... // other app view config
     *   dataPanelCustomGroupsAccordionInitialState:
     *      DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL,
     * });
     * ```
     */
    dataPanelCustomGroupsAccordionInitialState?: DataPanelCustomColumnGroupsAccordionState;
    /**
     * Flag to use home page search bar mode
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.33.0 | ThoughtSpot: 10.3.0.cl
     */
    homePageSearchBarMode?: HomePageSearchBarMode;
    /**
     * This flag is used to enable unified search experience for full app embed.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.34.0 | ThoughtSpot: 10.5.0.cl
     * @default true
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    isUnifiedSearchExperienceEnabled: true,
     * })
     * ```
     */
    isUnifiedSearchExperienceEnabled?: boolean;

    /**
     * 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: `AppEmbed`, `LiveboardEmbed`
     * @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 AppEmbed('#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 AppEmbed('#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;

    /**
     * updatedSpotterChatPrompt : Controls the updated spotter chat prompt.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.45.0 | ThoughtSpot: 26.2.0.cl
     * @default false
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    updatedSpotterChatPrompt : true,
     * })
     * ```
     */
    updatedSpotterChatPrompt?: boolean;
    /**
     * Controls the visibility of the past conversations sidebar.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
     * @deprecated from SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
     * Use `spotterSidebarConfig.enablePastConversationsSidebar`.
     * @default false
     */
    enablePastConversationsSidebar?: boolean;
    /**
     * Configuration for the Spotter sidebar UI customization.
     * Only applicable when navigating to Spotter within the app.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.47.0 | ThoughtSpot: 26.4.0.cl
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    // Deprecated standalone flag (backward compatibility)
     *    enablePastConversationsSidebar: false,
     *    // Recommended config; this value takes precedence
     *    spotterSidebarConfig: {
     *        enablePastConversationsSidebar: true,
     *        spotterSidebarTitle: 'My Conversations',
     *    },
     * })
     * ```
     */
    spotterSidebarConfig?: SpotterSidebarViewConfig;
    /**
     * Configuration for customizing Spotter chat UI
     * branding in tool response cards.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.46.0 | ThoughtSpot: 26.4.0.cl
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... //other embed view config
     *    spotterChatConfig: {
     *        hideToolResponseCardBranding: true,
     *        toolResponseCardBrandingLabel: 'MyBrand',
     *    },
     * })
     * ```
     */
    spotterChatConfig?: SpotterChatViewConfig;
    /**
     * Enables the stop answer generation button in the Spotter embed UI,
     * allowing users to interrupt an ongoing answer generation.
     *
     * Supported embed types: `AppEmbed`
     * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
     * @default false
     */
    enableStopAnswerGenerationEmbed?: boolean;
    /**
     * This is the minimum height (in pixels) for a full-height App.
     * Setting this height helps resolve issues with empty Apps and
     * other screens navigable from an App.
     *
     * @version SDK: 1.44.2 | ThoughtSpot: 10.15.0.cl
     * @default 500
     * @example
     * ```js
     * const embed = new AppEmbed('#embed', {
     *   ... // other app view config
     *   fullHeight: true,
     *   minimumHeight: 600,
     * });
     * ```
     */
    minimumHeight?: number;
    /**
     * To enable the homepage announcement banner.
     * Controls the visibility of the announcement section
     * on the homepage.
     *
     * Supported embed types: `AppEmbed`
     * @example
     * ```js
     * const embed = new AppEmbed('#tsEmbed', {
     *    ... // other embed view config
     *    enableHomepageAnnouncement: true,
     * })
     * ```
     * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
     */
    enableHomepageAnnouncement?: boolean;
    /**
     * 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 AppEmbed('#tsEmbed', {
     *    ... // other options
     *    enableLiveboardDataCache: true,
     * })
     * ```
     */
    enableLiveboardDataCache?: boolean;

    /**
     * Visual overrides to customize the chart or table properties.
     * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
     */
    visualOverrides?: VisualizationOverrides;
}

/**
 * APP_INIT data shape for AppEmbed.
 * @internal
 */
export interface AppEmbedAppInitData extends DefaultAppInitData {
    embedParams?: {
        spotterSidebarConfig?: SpotterSidebarViewConfig;
    };
}

/**
 * Embeds full ThoughtSpot experience in a host application.
 * @group Embed components
 */
export class AppEmbed extends V1Embed {
    protected viewConfig: AppViewConfig;

    private defaultHeight = 500;

    constructor(domSelector: DOMSelector, viewConfig: AppViewConfig) {
        viewConfig.embedComponentType = 'AppEmbed';
        super(domSelector, viewConfig);
        if (this.viewConfig.fullHeight === true) {
            this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
            this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
            this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
            this.on(
                EmbedEvent.RequestVisibleEmbedCoordinates,
                this.requestVisibleEmbedCoordinatesHandler,
            );
        }
    }

    /**
     * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig`
     * so the conv-assist app can read sidebar configuration on initialisation.
     *
     * Precedence for `enablePastConversationsSidebar`:
     * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the
     * deprecated top-level `enablePastConversationsSidebar` flag; if the former
     * is absent the latter is used as a fallback.
     *
     * An invalid `spotterDocumentationUrl` triggers a validation error and is
     * excluded from the payload rather than forwarded to the app.
     */
    protected async getAppInitData(): Promise<AppEmbedAppInitData> {
        const defaultAppInitData = await super.getAppInitData();
        return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this));
    }

    /**
     * Constructs a map of parameters to be passed on to the
     * embedded Liveboard or visualization.
     */
    protected getEmbedParams() {
        const {
            tag,
            hideTagFilterChips,
            hideObjects,
            liveboardV2,
            showPrimaryNavbar,
            disableProfileAndHelp,
            hideHamburger,
            hideObjectSearch,
            hideNotification,
            hideApplicationSwitcher,
            hideOrgSwitcher,
            enableSearchAssist,
            fullHeight,
            dataPanelV2 = true,
            hideLiveboardHeader = false,
            showLiveboardTitle = true,
            showLiveboardDescription = true,
            showMaskedFilterChip = false,
            isLiveboardMasterpiecesEnabled = false,
            newChartsLibrary,
            hideHomepageLeftNav = false,
            modularHomeExperience = false,
            isLiveboardHeaderSticky = true,
            enableAskSage,
            collapseSearchBarInitially = false,
            enable2ColumnLayout,
            enableCustomColumnGroups = false,
            dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL,
            collapseSearchBar = true,
            isLiveboardCompactHeaderEnabled = false,
            showLiveboardVerifiedBadge = true,
            showLiveboardReverifyBanner = true,
            hideIrrelevantChipsInLiveboardTabs = false,
            isEnhancedFilterInteractivityEnabled = false,
            homePageSearchBarMode,
            isUnifiedSearchExperienceEnabled = true,
            enablePendoHelp = true,
            discoveryExperience,
            coverAndFilterOptionInPDF = false,
            isLiveboardStylingAndGroupingEnabled,
            isPNGInScheduledEmailsEnabled = false,
            isLiveboardXLSXCSVDownloadEnabled = false,
            isGranularXLSXCSVSchedulesEnabled = false,
            isCentralizedLiveboardFilterUXEnabled = false,
            isLinkParametersEnabled,
            updatedSpotterChatPrompt,
            enableStopAnswerGenerationEmbed,
            spotterChatConfig,
            minimumHeight,
            isThisPeriodInDateFiltersEnabled,
            enableHomepageAnnouncement = false,
            isContinuousLiveboardPDFEnabled = false,
            enableLiveboardDataCache,
        } = this.viewConfig;

        let params: any = {};
        params[Param.PrimaryNavHidden] = !showPrimaryNavbar;
        params[Param.HideProfleAndHelp] = !!disableProfileAndHelp;
        params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher;
        params[Param.HideOrgSwitcher] = !!hideOrgSwitcher;
        params[Param.HideLiveboardHeader] = hideLiveboardHeader;
        params[Param.ShowLiveboardTitle] = showLiveboardTitle;
        params[Param.ShowLiveboardDescription] = !!showLiveboardDescription;
        params[Param.ShowMaskedFilterChip] = showMaskedFilterChip;
        params[Param.IsLiveboardMasterpiecesEnabled] = isLiveboardMasterpiecesEnabled;
        if (newChartsLibrary !== undefined) {
            params[Param.EnableNewChartLibrary] = newChartsLibrary;
        }
        params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
        params[Param.IsFullAppEmbed] = true;
        params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
        params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled;
        params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
        params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner;
        params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs;
        if (isUnifiedSearchExperienceEnabled !== undefined) {
            params[Param.IsUnifiedSearchExperienceEnabled] = isUnifiedSearchExperienceEnabled;
        }
        params[Param.CoverAndFilterOptionInPDF] = !!coverAndFilterOptionInPDF;

        params = this.getBaseQueryParams(params);

        if (!isUndefined(updatedSpotterChatPrompt)) {
            params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt;
        }
        if (!isUndefined(enableStopAnswerGenerationEmbed)) {
            params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed;
        }

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

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

        if (hideObjectSearch) {
            params[Param.HideObjectSearch] = !!hideObjectSearch;
        }

        if (hideHamburger) {
            params[Param.HideHamburger] = !!hideHamburger;
        }

        if (hideNotification) {
            params[Param.HideNotification] = !!hideNotification;
        }

        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;
                }
            }
        }

        if (tag) {
            params[Param.Tag] = tag;
        }
        if (hideObjects && hideObjects.length) {
            params[Param.HideObjects] = JSON.stringify(hideObjects);
        }
        if (liveboardV2 !== undefined) {
            params[Param.LiveboardV2Enabled] = liveboardV2;
        }

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

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

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

        if (homePageSearchBarMode) {
            params[Param.HomePageSearchBarMode] = homePageSearchBarMode;
        }

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

        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 (hideTagFilterChips !== undefined) {
            params[Param.HideTagFilterChips] = hideTagFilterChips;
        }

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

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

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

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

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

        this.defaultHeight = minimumHeight || this.defaultHeight;

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

        params[Param.DataPanelV2Enabled] = dataPanelV2;
        params[Param.HideHomepageLeftNav] = hideHomepageLeftNav;
        params[Param.ModularHomeExperienceEnabled] = modularHomeExperience;
        params[Param.CollapseSearchBarInitially] = collapseSearchBarInitially || collapseSearchBar;
        params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups;
        if (dataPanelCustomGroupsAccordionInitialState
            === DataPanelCustomColumnGroupsAccordionState.COLLAPSE_ALL
            || dataPanelCustomGroupsAccordionInitialState
            === DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST
        ) {

            params[
                Param.DataPanelCustomGroupsAccordionInitialState
            ] = dataPanelCustomGroupsAccordionInitialState;
        } else {

            params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL;
        }

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

        // Set navigation to v2 by default to avoid problems like the app
        // switcher (9-dot menu) not showing when v3 navigation is turned on
        // at the cluster level.
        // To use v3 navigation, we must manually set the discoveryExperience
        // settings.
        params[Param.NavigationVersion] = 'v2';
        // Set homePageVersion to v2 by default to reset the LD flag value
        // for the homepageVersion.
        params[Param.HomepageVersion] = 'v2';
        if (discoveryExperience) {
            // primaryNavbarVersion v3 will enabled the new left navigation
            if (discoveryExperience.primaryNavbarVersion === PrimaryNavbarVersion.Sliding) {
                params[Param.NavigationVersion] = discoveryExperience.primaryNavbarVersion;
                // Enable the modularHomeExperience when Sliding is enabled.
                params[Param.ModularHomeExperienceEnabled] = true;
            }

            // homePage v2 will enable the modular home page
            // and it will override the modularHomeExperience value
            if (discoveryExperience.homePage === HomePage.Modular) {
                params[Param.ModularHomeExperienceEnabled] = true;
            }

            // ModularWithStylingChanges (v3) introduces the styling changes
            // to the Modular Homepage.
            // v3 will be the base version of homePageVersion.
            if (discoveryExperience.homePage === HomePage.ModularWithStylingChanges) {
                params[Param.HomepageVersion] = HomePage.ModularWithStylingChanges;
            }

            // listPageVersion can be changed to v2 or v3
            if (discoveryExperience.listPageVersion !== undefined) {
                params[Param.ListPageVersion] = discoveryExperience.listPageVersion;
            }
        }

        const queryParams = getQueryParamString(params, true);

        return queryParams;
    }

    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 });
    }

    /**
     * Constructs the URL of the ThoughtSpot app page to be rendered.
     * @param pageId The ID of the page to be embedded.
     */
    public getIFrameSrc(): string {
        const { pageId, path, modularHomeExperience } = this.viewConfig;
        const pageRoute = this.formatPath(path) || this.getPageRoute(pageId, modularHomeExperience);
        let url = `${this.getRootIframeSrc()}/${pageRoute}`;

        const tsPostHashParams = this.getThoughtSpotPostUrlParams();
        url = `${url}${tsPostHashParams}`;

        return url;
    }

    /**
     * Set the iframe height as per the computed height received
     * from the ThoughtSpot app.
     * @param data The event payload
     */
    protected 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);
    };

    /**
     * Gets the ThoughtSpot route of the page for a particular page ID.
     * @param pageId The identifier for a page in the ThoughtSpot app.
     * @param modularHomeExperience
     */
    private getPageRoute(pageId: Page, modularHomeExperience = false) {
        switch (pageId) {
            case Page.Search:
                return 'answer';
            case Page.Answers:
                return modularHomeExperience ? 'home/answers' : 'answers';
            case Page.Liveboards:
                return modularHomeExperience ? 'home/liveboards' : 'pinboards';
            case Page.Pinboards:
                return modularHomeExperience ? 'home/liveboards' : 'pinboards';
            case Page.Data:
                return 'data/tables';
            case Page.SpotIQ:
                return modularHomeExperience ? 'home/spotiq-analysis' : 'insights/results';
            case Page.Monitor:
                return modularHomeExperience ? 'home/monitor-alerts' : 'insights/monitor-alerts';
            case Page.Home:
            default:
                return 'home';
        }
    }

    /**
     * Formats the path provided by the user.
     * @param path The URL path.
     * @returns The URL path that the embedded app understands.
     */
    private formatPath(path: string) {
        if (!path) {
            return null;
        }

        // remove leading slash
        if (path.indexOf('/') === 0) {
            return path.substring(1);
        }

        return path;
    }

    /**
     * Navigate to particular page for app embed. eg:answers/pinboards/home
     * This is used for embedding answers, pinboards, visualizations and full application
     * only.
     * @param path string | number The string, set to iframe src and navigate to new page
     * eg: appEmbed.navigateToPage('pinboards')
     * When used with `noReload` (default: true) this can also be a number
     * like 1/-1 to go forward/back.
     * @param noReload boolean Trigger the navigation without reloading the page
     * @version SDK: 1.12.0 | ThoughtSpot: 8.4.0.cl, 8.4.1-sw
     */
    public navigateToPage(path: string | number, noReload = false): void {
        if (!this.iFrame) {
            logger.log('Please call render before invoking this method');
            return;
        }
        if (noReload) {
            this.trigger(HostEvent.Navigate, path);
        } else {
            if (typeof path !== 'string') {
                logger.warn('Path can only by a string when triggered without noReload');
                return;
            }
            const iframeSrc = this.iFrame.src;
            const embedPath = '#/embed';
            const currentPath = iframeSrc.includes(embedPath) ? embedPath : '#';
            this.iFrame.src = `${iframeSrc.split(currentPath)[0]}${currentPath}/${path.replace(
                /^\/?#?\//,
                '',
            )}`;
        }
    }

    /**
     * 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);
        }
    }

    /**
     * Renders the embedded application pages in the ThoughtSpot app.
     * @param renderOptions An object containing the page ID
     * to be embedded.
     */
    public async render(): Promise<AppEmbed> {
        await super.render();

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

        this.postRender();
        return this;
    }
}
