/**
 * Tree Map Components
 */

import { Component, NotifyPropertyChanges, INotifyPropertyChanged, Property, extend, Fetch, SanitizeHtmlHelper } from '@syncfusion/ej2-base';
import { Complex, Collection, ModuleDeclaration } from '@syncfusion/ej2-base';
import { Event, EmitType, Internationalization } from '@syncfusion/ej2-base';
import { SvgRenderer } from '@syncfusion/ej2-svg-base';
import { isNullOrUndefined, createElement, EventHandler, Browser, remove } from '@syncfusion/ej2-base';
import { BorderModel, TitleSettingsModel, MarginModel, LevelSettingsModel, FontModel } from './model/base-model';
import { LeafItemSettingsModel, TooltipSettingsModel, LegendSettingsModel, InitialDrillSettingsModel } from './model/base-model';
import { HighlightSettingsModel, SelectionSettingsModel } from './model/base-model';
import { Border, Margin, TitleSettings, LegendSettings, InitialDrillSettings } from './model/base';
import { SelectionSettings, TooltipSettings, LevelSettings, LeafItemSettings, HighlightSettings } from './model/base';
import { TreeMapModel } from './treemap-model';
import { LayoutMode, TreeMapTheme, RenderingMode } from './utils/enum';
import { ILoadEventArgs, ILoadedEventArgs, IPrintEventArgs } from '../treemap/model/interface';
import { ILegendItemRenderingEventArgs, ILegendRenderingEventArgs } from '../treemap/model/interface';
import { IItemRenderingEventArgs, IResizeEventArgs, IDoubleClickEventArgs, IRightClickEventArgs } from '../treemap/model/interface';
import { IItemClickEventArgs, IItemMoveEventArgs, IClickEventArgs, IMouseMoveEventArgs } from '../treemap/model/interface';
import { IDrillStartEventArgs, IItemSelectedEventArgs, ITreeMapTooltipRenderEventArgs } from '../treemap/model/interface';
import { IItemHighlightEventArgs, IDrillEndEventArgs, IThemeStyle } from '../treemap/model/interface';
import { Size, stringToNumber, RectOption, Rect, textTrim, measureText, findChildren, removeElement, setItemTemplateContent, legendMaintain, removeLegend } from '../treemap/utils/helper';
import { removeClassNames, textFormatter } from '../treemap/utils/helper';
import { findPosition, Location, TextOption, renderTextElement, isContainsData, TreeMapAjax } from '../treemap/utils/helper';
import { load, loaded, drillStart, drillEnd } from '../treemap/model/constants';
import { itemClick, itemMove, click, mouseMove, resize, doubleClick, rightClick } from '../treemap/model/constants';
import { LayoutPanel } from './layout/render-panel';
import { TreeMapTooltip } from './user-interaction/tooltip';
import { ExportType } from '../treemap/utils/enum';
import { PdfPageOrientation } from '@syncfusion/ej2-pdf-export';
import { TreeMapHighlight, TreeMapSelection } from './user-interaction/highlight-selection';
import { TreeMapLegend } from './layout/legend';
import { DataManager, Query } from '@syncfusion/ej2-data';
import { getThemeStyle } from './model/theme';
import { Print } from './model/print';
import { ImageExport } from './model/image-export';
import { PdfExport } from './model/pdf-export';
/**
 * Represents the treemap control. It is used to visualize both hierarchical and flat data.
 * ```html
 * <div id="container"/>
 * <script>
 *   var treemap = new TreeMap();
 *   treemap.appendTo("#container");
 * </script>
 * ```
 */

@NotifyPropertyChanges
export class TreeMap extends Component<HTMLElement> implements INotifyPropertyChanged {
    //Module Declaration for treemap.
    /**
     * Sets and gets the module that is used to add tooltip in the treemap.
     *
     * @private
     */
    public treeMapTooltipModule: TreeMapTooltip;
    /**
     * Sets and gets the module that is used to add highlight functionality in the treemap.
     *
     * @private
     */
    public treeMapHighlightModule: TreeMapHighlight;
    /**
     * Sets and gets the module that is used to add selection functionality in the treemap.
     *
     * @private
     */
    public treeMapSelectionModule: TreeMapSelection;
    /**
     * Sets and gets the module that is used to add legend in the treemap.
     *
     * @private
     */
    public treeMapLegendModule: TreeMapLegend;
    /**
     * Sets and gets the module that is used to add print functionality in the treemap.
     *
     * @private
     */
    public printModule: Print;
    /**
     * Sets and gets the module that is used to add imageExport functionality in the treemap.
     *
     * @private
     */
    public imageExportModule: ImageExport;
    /**
     * Sets and gets the module that is used to add pdf export functionality in the treemap.
     *
     * @private
     */
    public pdfExportModule: PdfExport ;
    /**
     * Enables and disables the print functionality in treemap.
     *
     * @default false
     */
    @Property(false)
    public allowPrint: boolean;
    /**
     * Enables and disables the export to image functionality in treemap.
     *
     * @default false
     */
    @Property(false)
    public allowImageExport: boolean;
    /**
     * Enables and disables the export to pdf functionality in treemap.
     *
     * @default false
     */
    @Property(false)
    public allowPdfExport: boolean;
    /**
     * Sets and gets the width of the treemap.
     *
     * @default null
     */
    @Property(null)
    public width: string;
    /**
     * Sets and gets the height of the treemap.
     *
     * @default null
     */
    @Property(null)
    public height: string;
    /**
     * Sets and gets the options for customizing the color and width of the treemap border.
     */
    @Complex<BorderModel>({}, Border)
    public border: BorderModel;
    /**
     * Sets and gets the options for customizing the margin in the treemap.
     */
    @Complex<MarginModel>({}, Margin)
    public margin: MarginModel;
    /**
     * Sets and gets the background color of the treemap.
     *
     * @default null
     */
    @Property(null)
    public background: string;
    /**
     * Sets and gets the theme styles supported for treemap. When the theme is set, the styles associated with the theme will be set in the treemap.
     *
     * @default Material
     */
    @Property('Material')
    public theme: TreeMapTheme;
    /**
     * Sets and gets the options for customizing the title of the treemap.
     */
    @Complex<TitleSettingsModel>({}, TitleSettings)
    public titleSettings: TitleSettingsModel;
    /**
     * Specifies the rendering type for the layout of the treemap.
     *
     * @default 'Squarified'
     */
    @Property('Squarified')
    public layoutType: LayoutMode;
    /**
     * Sets and gets the data source for the treemap.
     *
     * @isGenericType true
     * @isObservable true
     * @default null
     */
    @Property(null)
    public dataSource: DataManager | TreeMapAjax | Object[];
    /**
     * Sets and gets the query to select particular data from the shape data.
     * This property is applicable only when the data source is created by data manager.
     *
     * @default null
     */
    @Property(null)
    public query: Query;
    /**
     * Sets and gets the value path of the weight from the data source, based on which the treemap item is rendered.
     *
     * @default null
     */
    @Property(null)
    public weightValuePath: string;
    /**
     * Sets and gets the value path from the data source, based on it color is filled in treemap.
     * This property is used when range color mapping is set in the treemap.
     *
     * @default ''
     */
    @Property('')
    public rangeColorValuePath: string;
    /**
     * Sets and gets the value path from the data source, based on it color is filled in treemap.
     * This property is used when equal color mapping is set in the treemap.
     *
     * @default ''
     */
    @Property('')
    public equalColorValuePath: string;
    /**
     * Sets and gets the value path from the data source, based on it color is filled in treemap.
     *
     * @default null
     */
    @Property(null)
    public colorValuePath: string;
    /**
     * Sets and gets a set of colors to apply in the treemap items.
     *
     * @default []
     */
    @Property([])
    public palette: string[];
    /**
     * Specifies the rendering direction of layout of the treemap items.
     *
     * @default TopLeftBottomRight
     */
    @Property('TopLeftBottomRight')
    public renderDirection: RenderingMode;
    /**
     * Enables or disables the drill down functionality in treemap.
     *
     * @default false
     */
    @Property(false)
    public enableDrillDown: boolean;
    /**
     * Enables or disables the connection text in the header of the treemap when drill down is enabled.
     *
     * @default false
     */
    @Property(false)
    public enableBreadcrumb: boolean;
    /**
     * Specifies the symbol to show connection between the two words in the header of the treemap during drill down.
     *
     * @default ' - '
     */
    @Property(' - ')
    public breadcrumbConnector: string;
    /**
     * Enables or disables the initial drill in the treemap.
     *
     * @default false
     */
    @Property(false)
    public drillDownView: boolean;
    /**
     * Specifies whether to enable the rendering of untrusted HTML values in the TreeMap. If `enableHtmlSanitizer` set to **true**, the component will sanitize any suspected untrusted strings and scripts before rendering them.
     *
     * @default false
     */
    @Property(false)
    public enableHtmlSanitizer: boolean;
    /**
     * Specifies the options for customizing the initial drill down in treemap.
     */
    @Complex<InitialDrillSettingsModel>({}, InitialDrillSettings)
    public initialDrillDown: InitialDrillSettingsModel;
    /**
     * Sets and gets the options for customizing the leaf item of the treemap.
     */
    @Complex<LeafItemSettingsModel>({}, LeafItemSettings)
    public leafItemSettings: LeafItemSettingsModel;
    /**
     * Sets and gets the options to configure and customize the levels of treemap items.
     */
    @Collection<LevelSettingsModel>([], LevelSettings)
    public levels: LevelSettingsModel[];
    /**
     * Sets and gets the options to customize the highlight functionality of the treemap.
     */
    @Complex<HighlightSettingsModel>({}, HighlightSettings)
    public highlightSettings: HighlightSettingsModel;
    /**
     * Sets and gets the options for customizing the selection functionality of the treemap.
     */
    @Complex<SelectionSettingsModel>({}, SelectionSettings)
    public selectionSettings: SelectionSettingsModel;
    /**
     * Sets and gets the options for customizing the tooltip of the treemap.
     */
    @Complex<TooltipSettingsModel>({}, TooltipSettings)
    public tooltipSettings: TooltipSettingsModel;
    /**
     * Sets and gets the options for customizing the legend of the treemap.
     */
    @Complex<LegendSettingsModel>({}, LegendSettings)
    public legendSettings: LegendSettingsModel;
    /**
     * Enables or disables the visibility state of the separator for grouping.
     *
     * @default false
     */
    @Property(false)
    public useGroupingSeparator: boolean;
    /**
     * Sets and gets the description for treemap.
     *
     * @default null
     */
    @Property(null)
    public description: string;

    /**
     * Sets and gets the tab index value for treemap.
     *
     * @default 0
     */
    @Property(0)
    public tabIndex: number;
    /**
     * Sets and gets format for the texts in the treemap. This property accepts any global string format like 'C', 'N1', 'P' etc.
     *
     * @default null
     */
    @Property(null)
    public format: string;
    /**
     * Triggers before the treemap is rendered.
     *
     * @event load
     */
    @Event()
    public load: EmitType<ILoadEventArgs>;
    /**
     * Triggers before the print gets started.
     *
     * @event beforePrint
     */
    @Event()
    public beforePrint: EmitType<IPrintEventArgs>;
    /**
     * Triggers after treemap is rendered.
     *
     * @event loaded
     */
    @Event()
    public loaded: EmitType<ILoadedEventArgs>;
    /**
     * Triggers before item rendering in the treemap.
     *
     * @event itemRendering
     */
    @Event()
    public itemRendering: EmitType<IItemRenderingEventArgs>;
    /**
     * Triggers on performing drill down functionality in the treemap.
     *
     * @event drillStart
     */
    @Event()
    public drillStart: EmitType<IDrillStartEventArgs>;
    /**
     * Triggers after drill down functionality gets completed in the treemap.
     *
     * @event drillEnd
     */
    @Event()
    public drillEnd: EmitType<IDrillEndEventArgs>;
    /**
     * Triggers after selecting a treemap item.
     *
     * @event itemSelected
     */
    @Event()
    public itemSelected: EmitType<IItemSelectedEventArgs>;
    /**
     * Triggers after highlighting on the treemap item.
     *
     * @event itemHighlight
     */
    @Event()
    public itemHighlight: EmitType<IItemHighlightEventArgs>;
    /**
     * Triggers on rendering of the tooltip in the treemap.
     *
     * @event tooltipRendering
     */
    @Event()
    public tooltipRendering: EmitType<ITreeMapTooltipRenderEventArgs>;
    /**
     * Triggers after clicking an item in the treemap.
     *
     * @event itemClick
     */
    @Event()
    public itemClick: EmitType<IItemClickEventArgs>;
    /**
     * Triggers after mouse hover on the treemap item.
     *
     * @event itemMove
     */
    @Event()
    public itemMove: EmitType<IItemMoveEventArgs>;
    /**
     * Triggers after clicking on the treemap.
     *
     * @event click
     */
    @Event()
    public click: EmitType<IItemClickEventArgs>;
    /**
     * Triggers after double clicking on the treemap.
     *
     * @event doubleClick
     */
    @Event()
    public doubleClick: EmitType<IDoubleClickEventArgs>;
    /**
     * Triggers after right clicking on the treemap.
     *
     * @event rightClick
     */
    @Event()
    public rightClick: EmitType<IMouseMoveEventArgs>;
    /**
     * Triggers after mouse hover on the treemap.
     *
     * @event mouseMove
     */
    @Event()
    public mouseMove: EmitType<IMouseMoveEventArgs>;
    /**
     * Triggers to notify the resize of the treemap when the window is resized.
     *
     * @event resize
     */
    @Event()
    public resize: EmitType<IResizeEventArgs>;
    /**
     * Triggers before rendering each legend item in the treemap.
     *
     * @event legendItemRendering
     */
    @Event()
    public legendItemRendering: EmitType<ILegendItemRenderingEventArgs>;
    /**
     * Triggers before rendering the legend items in the treemap.
     *
     * @event legendRendering
     * @deprecated
     */
    @Event()
    public legendRendering: EmitType<ILegendRenderingEventArgs>;

    /**
     * resize the treemap
     */
    private isResize: boolean = false;
    /**
     * svg renderer object.
     *
     * @private
     */
    public renderer: SvgRenderer;
    /**
     * treemap svg element object
     *
     * @private
     */
    public svgObject: Element;
    /**
     * Stores the exact size of treemap.
     *
     * @private
     */
    public availableSize: Size;
    /**
     * Internal use of internationalization instance.
     *
     * @private
     */
    public intl: Internationalization;
    /**
     * Stores the area bounds.
     *
     * @private
     */
    public areaRect: Rect;
    /**
     * Define the theme style for treemap.
     *
     * @private
     */
    public themeStyle: IThemeStyle;
    /**
     * Stores the legend bounds.
     *
     * @private
     */
    public totalRect: Rect;
    /** @private */
    public layout: LayoutPanel;
    /** @private */
    public orientation: string = 'Horizontal';
    /** @private */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public drilledItems: any[] = [];
    /** @private */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public drilledLegendItems: any;
    /** @private */
    public currentLevel: number;
    /** @private */
    public isHierarchicalData: boolean = false;
    /** @private */
    private resizeTo: number;
    /** @private */
    private mouseDown: boolean;
    /** @private */
    private drillMouseMove: boolean;
    /** @private */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public doubleTapTimer: any;
    /** @private */
    public levelSelection: string[] = [];
    /** @private */
    public legendId: string[] = [];
    /** @private */
    public selectionId: string;
    /** @private */
    public treemapLevelData: LevelsData;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    private resizeEvent: any;

    /**
     * Constructor for TreeMap.
     *
     * @param {TreeMapModel} options - Specifies the treemap instance.
     * @param {string | HTMLElement} element - Specifies the treemap element.
     */
    constructor(options?: TreeMapModel, element?: string | HTMLElement) {
        super(options, element);
    }

    protected preRender(): void {
        this.trigger(load, { treemap: this }, () => {
            this.initPrivateVariable();
            this.unWireEVents();
            this.createSvg();
            this.wireEVents();
            this.setCulture();
        });

    }

    protected render(): void {
        this.renderElements();
    }

    private renderElements(): void {
        this.treemapLevelData = new LevelsData();
        this.treemapLevelData.levelsData = null;
        this.treemapLevelData.defaultLevelsData = null;
        this.treemapLevelData.hierarchyData = null;
        this.createSecondaryElement();
        this.addTabIndex();
        this.themeStyle = getThemeStyle(this.theme);
        this.renderBorder();
        this.renderTitle(this.titleSettings, 'title', null, null);
        if (!isNullOrUndefined(this.treemapLevelData.levelsData)) {
            this.treemapLevelData.defaultLevelsData = this.treemapLevelData.levelsData;
        }
        this.processDataManager();
    }

    private processDataManager(): void {
        let dataModule: DataManager; let queryModule: Query; let fetchApiModule: Fetch;
        let localAjax: TreeMapAjax;
        if (this.dataSource instanceof DataManager) {
            dataModule = this.dataSource;
            queryModule = this.query instanceof Query ? this.query : new Query();
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const dataManager: Promise<any> = dataModule.executeQuery(queryModule);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dataManager.then((e: any) => {
                this.dataSource = e['result'];
                this.renderTreeMapElements();
            });
        } else if (this.dataSource instanceof TreeMapAjax) {
            localAjax = this.dataSource as TreeMapAjax;
            fetchApiModule = new Fetch(localAjax.dataOptions, localAjax.type, localAjax.contentType);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            fetchApiModule.onSuccess = (args: any) => {
                if (!isNullOrUndefined(args.type) && args.type === 'application/octet-stream') {
                    const reader: FileReader = new FileReader();
                    // eslint-disable-next-line @typescript-eslint/no-this-alias
                    const treemap: TreeMap = this;
                    reader.onload = function (): void {
                        args = JSON.parse(reader.result.toString());
                        treemap.dataSource = args;
                        treemap.renderTreeMapElements();
                    };
                    reader.readAsText(args);
                } else {
                    this.dataSource = args;
                    this.renderTreeMapElements();
                }
            };
            fetchApiModule.send(localAjax.sendData);
        } else {
            this.renderTreeMapElements();
        }
    }

    private renderTreeMapElements(): void {

        this.processingData();

        if (this.treeMapLegendModule && this.legendSettings.visible) {

            this.treeMapLegendModule.renderLegend();

        }

        this.layout.processLayoutPanel();

        this.element.appendChild(this.svgObject);
        if (!isNullOrUndefined(this.treeMapLegendModule) && this.legendSettings.visible) {
            legendMaintain(this, this.treeMapLegendModule.legendGroup);
        }
        this.elementChange();

        this.trigger(loaded, {treemap: this, isResized: this.isResize});
        this.isResize = false;

        this.renderComplete();
    }

    protected createSvg(): void {
        if (this.svgObject) {
            while (this.svgObject.childNodes.length > 0) {
                this.svgObject.removeChild(this.svgObject.firstChild);
            }
            if (!this.svgObject.hasChildNodes() && this.svgObject.parentNode) {
                remove(this.svgObject);
            }
        }
        if (isNullOrUndefined(this.renderer)) {
            this.renderer = new SvgRenderer(this.element.id);
        }
        if (isNullOrUndefined(this.layout)) {
            this.layout = new LayoutPanel(this);
        }
        this.clearTemplate();
        const containerWidth: number = this.element.clientWidth;
        const containerHeight: number = this.element.clientHeight;
        this.availableSize = new Size(
            stringToNumber(this.width, containerWidth) || containerWidth || 600,
            stringToNumber(this.height, containerHeight) || containerHeight || 450
        );
        this.svgObject = this.renderer.createSvg({
            id: this.element.id + '_svg',
            width: this.availableSize.width,
            height: this.availableSize.height
        });
    }


    /**
     * To initilize the private varibales of treemap.
     *
     * @returns {void}
     */
    private initPrivateVariable(): void {
        if (this.element.id === '') {
            const collection: number = document.getElementsByClassName('e-treemap').length;
            this.element.id = 'treemap_control_' + collection;
        }
        this.renderer = new SvgRenderer(this.element.id);

        this.layout = new LayoutPanel(this);

    }

    private createSecondaryElement(): void {
        const secondaryEle: Element = document.getElementById(this.element.id + '_Secondary_Element');
        if (secondaryEle && secondaryEle.childElementCount > 0) {
            secondaryEle.parentNode.removeChild(secondaryEle);
        }
        if (isNullOrUndefined(document.getElementById(this.element.id + '_Secondary_Element'))) {
            const secondaryElement: Element = createElement('div', {
                id: this.element.id + '_Secondary_Element'
            });
            (secondaryElement as HTMLElement).style.cssText = 'position: absolute;z-index:1;';
            this.element.appendChild(secondaryElement);
        }
    }

    private elementChange(): void {
        if (this.treeMapLegendModule && this.legendSettings.visible && this.treeMapLegendModule.legendGroup && this.layout.layoutGroup
            && !isNullOrUndefined(this.svgObject) && !isNullOrUndefined(document.getElementById(this.layout.layoutGroup.id))
            && !isNullOrUndefined(document.getElementById(this.treeMapLegendModule.legendGroup.id))) {
            this.svgObject.insertBefore(this.layout.layoutGroup, this.treeMapLegendModule.legendGroup);
        }
    }

    /**
     * Render the treemap border
     *
     * @private
     * @returns {void}
     */
    private renderBorder(): void {
        const width: number = this.border.width;
        const borderElement: Element = this.svgObject.querySelector('#' + this.element.id + '_TreeMap_Border');
        if ((this.border.width > 0 || (this.background || this.themeStyle.backgroundColor)) && isNullOrUndefined(borderElement)) {
            const borderRect: RectOption = new RectOption(
                this.element.id + '_TreeMap_Border', this.background || this.themeStyle.backgroundColor, this.border, 1,
                new Rect(width / 2, width / 2, this.availableSize.width - width, this.availableSize.height - width));
            this.svgObject.appendChild(this.renderer.drawRectangle(borderRect) as SVGRectElement);
        } else if (borderElement) {
            borderElement.setAttribute('fill', this.background || this.themeStyle.backgroundColor);
        }
    }

    private renderTitle(title: TitleSettingsModel, type: string, bounds: Rect, groupEle: Element): void {
        const style: FontModel = {
            color: title.textStyle.color, size: title.textStyle.size, fontFamily: title.textStyle.fontFamily,
            fontStyle: title.textStyle.fontStyle, fontWeight: title.textStyle.fontWeight, opacity: title.textStyle.opacity
        };
        let height: number; const titlePadding: number = 10;
        const width: number = (this.availableSize.width - this.margin.right - this.margin.left);
        style.fontFamily = style.fontFamily || this.themeStyle.fontFamily;
        style.fontWeight = style.fontWeight || this.themeStyle.titleFontWeight;
        style.size = style.size || (type === 'title' ? this.themeStyle.fontSize : this.themeStyle.subtitleFontSize);
        if (title.text) {
            if (isNullOrUndefined(groupEle)) {
                groupEle = this.renderer.createGroup({ id: this.element.id + '_Title_Group' });
            }
            const titleText: string = this.enableHtmlSanitizer ? (SanitizeHtmlHelper.sanitize(title.text)) : title.text;
            const trimmedTitle: string = textTrim(width, titleText, style);
            const elementSize: Size = measureText(trimmedTitle, style);
            const rect: Rect = (isNullOrUndefined(bounds)) ? new Rect(
                this.margin.left, this.margin.top, this.availableSize.width, this.availableSize.height) : bounds;
            const location: Location = findPosition(rect, title.alignment, elementSize, type);
            const options: TextOption = new TextOption(
                this.element.id + '_TreeMap_' + type, location.x, location.y, 'start', trimmedTitle
            );
            const titleBounds: Rect = new Rect(location.x, location.y, elementSize.width, elementSize.height);
            const element: Element = renderTextElement(
                options, style, style.color || (type === 'title' ? this.themeStyle.titleFontColor : this.themeStyle.subTitleFontColor),
                groupEle
            );
            element.setAttribute('aria-label', title.description || titleText);
            element.setAttribute('role', 'region');
            element.setAttribute('tabindex', this.tabIndex.toString());
            if ((type === 'title' && !title.subtitleSettings.text) || (type === 'subtitle')) {
                height = (this.availableSize.height - titleBounds.y - titlePadding - this.margin.bottom);
                this.areaRect = new Rect(this.margin.left, titleBounds.y + titlePadding, width, height);
            }
            if (type !== 'subtitle' && title.subtitleSettings.text) {
                this.renderTitle(title.subtitleSettings, 'subtitle', titleBounds, groupEle);
            } else {
                this.svgObject.appendChild(groupEle);
            }
        } else {
            height = (this.availableSize.height - this.margin.top - this.margin.bottom);
            this.areaRect = new Rect(this.margin.left, this.margin.top, width, height);
        }
    }

    protected processingData(): void {
        let path: string;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.dataSource = this.dataSource as any[];
        if (!isNullOrUndefined(this.dataSource) && this.dataSource.length > 0 && this.weightValuePath) {
            this.treemapLevelData.levelsData = [];
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            this.dataSource.map((data: any) => {
                data[this.weightValuePath] = (data[this.weightValuePath]) ? (data[this.weightValuePath] as string).toString() :
                    data[this.weightValuePath];
            });
            this.leafItemSettings.labelPath = this.leafItemSettings.labelPath || this.weightValuePath;
            this.checkIsHierarchicalData();
            if (this.levels.length === 0) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const data: any = {};
                data['level'] = 0;
                path = this.leafItemSettings.labelPath;
                data[path as string] = [];
                for (let i: number = 0; i < this.dataSource.length; i++) {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    const child: any[] = findChildren(this.dataSource[i as number] as any)['values'];
                    if (this.isHierarchicalData && child && child.length > 0) {
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        child.forEach((currentData: any) => {
                            if (currentData[path as string]) {
                                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                (<any[]>data[path as string]).push({
                                    groupIndex: 0, name: currentData[path as string],
                                    levelOrderName: (currentData[path as string] as string).toString(),
                                    data: currentData, weight: currentData[this.weightValuePath]
                                });
                            }
                        });
                    } else {
                        if (this.dataSource[i as number][path as string]) {
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                            (<any[]>data[path as string]).push({
                                groupIndex: 0, name: this.dataSource[i as number][path as string], levelOrderName: (
                                    this.dataSource[i as number][path as string] as string).toString(), data: this.dataSource[i as number],
                                weight: this.dataSource[i as number][this.weightValuePath]
                            });
                        }
                    }
                }
                this.treemapLevelData.levelsData.push(data);
            } else {
                if (this.isHierarchicalData) {
                    this.treemapLevelData.hierarchyData = [];
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    this.treemapLevelData.hierarchyData = extend([], this.dataSource, this.treemapLevelData.hierarchyData, true) as any[];
                    for (let i: number = 0; i < this.treemapLevelData.hierarchyData.length; i++) {
                        this.processHierarchicalData(this.treemapLevelData.hierarchyData[i as number], i);
                    }
                    this.treemapLevelData.levelsData = this.treemapLevelData.hierarchyData;
                } else {
                    this.processFlatJsonData();
                    if (this.treemapLevelData.levelsData.length > 1) {
                        this.reOrderLevelData(this.treemapLevelData.levelsData.length - 1);
                    }
                }
                path = this.levels[0].groupPath;
            }
            if (!this.isHierarchicalData) {
                this.findTotalWeight(this.treemapLevelData.levelsData[0][path as string], 'Parent');
            }
        }
    }

    private checkIsHierarchicalData(): void {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let child: any[]; this.dataSource = this.dataSource as any[];
        for (let i: number = 0; i < this.dataSource.length; i++) {
            child = findChildren(this.dataSource[i as number])['values'];
            if (child && child.length) {
                this.isHierarchicalData = true;
                break;
            } else if (i === this.dataSource.length - 1) {
                this.isHierarchicalData = false;
            }
        }
    }

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    private processHierarchicalData(data: any, dataCount: number): void {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let childData: any[];
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let newData: any = {};
        let levelIndex: number;
        const path: string = this.leafItemSettings.labelPath ? this.leafItemSettings.labelPath : this.weightValuePath;
        let key: string;
        newData = findChildren(data);
        childData = newData ? newData['values'] : null;
        if (childData && childData.length > 0) {
            key = newData['key'];
            for (let i: number = 0; i < this.levels.length; i++) {
                if (key === this.levels[i as number].groupPath) {
                    levelIndex = i;
                }
            }
            for (let j: number = 0; j < childData.length; j++) {
                childData[j as number]['name'] = childData[j as number][path as string];
                childData[j as number]['levelOrderName'] = (levelIndex === 0 ? childData[j as number]['name'] :
                    data['levelOrderName'] + '#' + childData[j as number]['name']) + '';
                const childItemLevel: string = childData[j as number]['levelOrderName']; let childLevel: number;
                if (childItemLevel.search('#') > 0) {
                    childLevel = childItemLevel.split('#').length - 1;
                }
                childData[j as number]['groupIndex'] = isNullOrUndefined(levelIndex) ? childLevel === this.levels.length
                    ? this.levels.length : childLevel : levelIndex;
                if (levelIndex !== 0) {
                    childData[j as number]['parent'] = data;
                }
                childData[j as number]['groupName'] = key;
                childData[j as number]['data'] = childData[j as number];
                childData[j as number]['isDrilled'] = false;
                childData[j as number]['weight'] = childData[j as number][this.weightValuePath];
            }
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            childData.forEach((currentData: any) => {
                this.processHierarchicalData(currentData, dataCount);
            });
        }
        if (dataCount === this.treemapLevelData.hierarchyData.length - 1) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            let mainData: any[] = this.treemapLevelData.hierarchyData[0][this.levels[0].groupPath];
            if (!isNullOrUndefined(mainData)) {
                for (let k: number = 0; k < this.treemapLevelData.hierarchyData.length; k++) {
                    childData = findChildren(this.treemapLevelData.hierarchyData[k as number])['values'];
                    if (k !== 0 && childData) {
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        childData.forEach((currentData: any) => { mainData.push(currentData); });
                        this.treemapLevelData.hierarchyData.splice(k, 1);
                        k -= 1;
                    }
                }
                mainData = this.treemapLevelData.hierarchyData[0][this.levels[0].groupPath];
                for (let l: number = 0; l < mainData.length; l++) {
                    newData[this.levels[0].groupPath] = mainData;
                    mainData[l as number]['parent'] = newData;
                }
            }
        }
    }
    /* eslint-disable valid-jsdoc */
    /**
     * This method is used to perform the print functionality in treemap.
     *
     * @param {string[] | string | Element} id - Specifies the element to print the treemap.
     * @returns {void}
     */
    public print(id?: string[] | string | Element): void {
        if (this.allowPrint && this.printModule) {
            this.printModule.print(this, id);
        }
    }
    /**
     * This method is used to perform the export functionality for the rendered treemap.
     *
     * @param {ExportType} type - Specifies the extension type of the exported document.
     * @param {string} fileName - Specifies file name for exporting the rendered TreeMap.
     * @param {PdfPageOrientation} orientation - Specifies the orientation of the PDF document.
     * @param {boolean} allowDownload - Specifies whether the exported file should be downloaded or not.
     * @returns {string} - Specifies the base64 string of the exported image which is returned when the allowDownload is set to false.
     */
    public export(type: ExportType, fileName: string, orientation?: PdfPageOrientation, allowDownload?: boolean): Promise<string> {
        if (isNullOrUndefined(allowDownload)) {
            allowDownload = true;
        }
        if (type === 'PDF' && this.allowPdfExport && this.pdfExportModule) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
            return new Promise((resolve: any, reject: any) => {
                resolve(this.pdfExportModule.export(this, type, fileName, orientation, allowDownload));
            });

        } else if (this.allowImageExport && (type !== 'PDF') && this.imageExportModule) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
            return new Promise((resolve: any, reject: any) => {
                resolve(this.imageExportModule.export(this, type, fileName, allowDownload));
            });
        }
        return null;
    }
    private processFlatJsonData(): void {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.dataSource = this.dataSource as any[];
        let groupPath: string;
        const orderNames: string[] = [];
        for (let i: number = 0; i < this.levels.length + 1; i++) {
            groupPath = this.levels[i as number] ? this.levels[i as number].groupPath : this.leafItemSettings.labelPath;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const level: any = {};
            level['level'] = i;
            level[groupPath as string] = [];
            this.treemapLevelData.levelsData.push(level);
            for (let j: number = 0; j < this.dataSource.length; j++) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const currentData: any = {}; let childName: string = '';
                if (!isNullOrUndefined(groupPath)) {
                    const name: string = this.dataSource[j as number][groupPath as string];
                    if (i !== 0) {
                        for (let k: number = 0; k <= i; k++) {
                            const childGroupPath: string = this.levels[k as number] ? this.levels[k as number].groupPath : groupPath;
                            childName += (this.dataSource[j as number][childGroupPath as string]) + ((k === i) ? '' : '#');
                        }
                    }
                    if (!(orderNames.length > 0 ? orderNames.indexOf(childName ?
                        childName : name) !== -1 : false)) {
                        currentData['name'] = name;
                        currentData['levelOrderName'] = ((childName) ? childName : name) + '';
                        currentData['groupIndex'] = i;
                        currentData['isDrilled'] = false;
                        currentData['groupName'] = groupPath;
                        currentData['data'] = this.dataSource[j as number];
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any, max-len
                        (<any[]>this.treemapLevelData.levelsData[(this.treemapLevelData.levelsData.length - 1) as number][groupPath as string]).push(currentData);
                        orderNames.push((childName) ? childName : name);
                    }
                }
            }
        }
    }

    /**
     * This method orders the treemap level data.
     *
     * @param {number} start - Specifies the start value of the treemap level.
     * @returns {void}
     * @private
     */
    public reOrderLevelData(start: number): void {
        let currentName: string;
        const currentPath: string = this.levels[start as number] ? this.levels[start as number].groupPath : this.leafItemSettings.labelPath;
        const prevPath: string = this.levels[start - 1].groupPath;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const currentData: any[] = this.treemapLevelData.levelsData[start as number][currentPath as string] as any[];
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const previousData: any[] = this.treemapLevelData.levelsData[start - 1][prevPath as string] as any[];
        for (let i: number = 0; i < currentData.length; i++) {
            currentName = currentData[i as number]['levelOrderName'] as string;
            for (let j: number = 0; j < previousData.length; j++) {
                previousData[j as number][currentPath as string] = isNullOrUndefined(previousData[j as number][currentPath as string]) ?
                    [] : previousData[j as number][currentPath as string];
                if (this.IsChildHierarchy(currentName.split('#'), (previousData[j as number]['levelOrderName'] as string).split('#'))) {
                    if (isNullOrUndefined(currentData[i as number]['parent'])) {
                        currentData[i as number]['parent'] = previousData[j as number];
                    }
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    (<any[]>previousData[j as number][currentPath as string]).push(currentData[i as number]);
                    break;
                }
            }
        }
        this.findTotalWeight(this.treemapLevelData.levelsData[this.treemapLevelData.levelsData.length - 1][currentPath as string], 'Child');
        this.treemapLevelData.levelsData.splice(start, 1);
        if ((start - 1) > 0) {
            this.reOrderLevelData(start - 1);
        }
    }

    private IsChildHierarchy (current: string[], previous: string[]): boolean {
        let isChild: boolean = false;
        for (let i: number = 0; i < previous.length; i++)
        {
            if (current.length < i || previous[i as number] !== current[i as number])
            {
                return false;
            }
            else
            {
                isChild = true;
            }
        }
        return isChild;
    }

    /**
     * This method finds the weight value of the treemap level.
     *
     * @param {any[]} processData - Specifies the treemap data.
     * @param {string} type - Specifies the type of the data.
     * @returns {void}
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public findTotalWeight(processData: any[], type: string): void {
        let totalWeight: number;
        let split: string[];
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let groupName: string; const groupObj: any = {};
        for (let i: number = 0; i < processData.length; i++) {
            totalWeight = 0;
            groupName = processData[i as number]['groupName'];
            split = (processData[i as number]['levelOrderName'] as string).split('#');
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            (<any[]>this.dataSource).forEach((data: any) => {
                if (isContainsData(split, processData[i as number]['levelOrderName'], data, this)) {
                    totalWeight += parseFloat(data[this.weightValuePath]);
                }
            });
            if (type === 'Parent') {
                groupObj[groupName as string] = processData;
                processData[i as number]['parent'] = groupObj;
            }
            processData[i as number]['weight'] = totalWeight;
        }
    }

    /**
     * To unbind event handlers for treemap.
     *
     * @returns {void}
     * @private
     */
    private unWireEVents(): void {
        EventHandler.remove(this.element, 'click', this.clickOnTreeMap);
        EventHandler.remove(this.element, 'dblclick', this.doubleClickOnTreeMap);
        EventHandler.remove(this.element, 'contextmenu', this.rightClickOnTreeMap);
        EventHandler.remove(this.element, Browser.touchStartEvent, this.mouseDownOnTreeMap);
        EventHandler.remove(this.element, Browser.touchMoveEvent, this.mouseMoveOnTreeMap);
        EventHandler.remove(this.element, Browser.touchEndEvent, this.mouseEndOnTreeMap);
        EventHandler.remove(this.element, 'pointerleave mouseleave', this.mouseLeaveOnTreeMap);
        EventHandler.remove(this.element, 'keydown', this.keyDownHandler);
        EventHandler.remove(this.element, 'keyup', this.keyUpHandler);
        EventHandler.remove(this.element, 'focusout', this.focusHandler);
        window.removeEventListener('resize', this.resizeEvent);
    }

    /**
     * To bind event handlers for treemap.
     *
     * @returns {void}
     */
    private wireEVents(): void {
        EventHandler.add(this.element, 'click', this.clickOnTreeMap, this);
        EventHandler.add(this.element, 'dblclick', this.doubleClickOnTreeMap, this);
        EventHandler.add(this.element, 'contextmenu', this.rightClickOnTreeMap, this);
        EventHandler.add(this.element, Browser.touchStartEvent, this.mouseDownOnTreeMap, this);
        EventHandler.add(this.element, Browser.touchMoveEvent, this.mouseMoveOnTreeMap, this);
        EventHandler.add(this.element, Browser.touchEndEvent, this.mouseEndOnTreeMap, this);
        EventHandler.add(this.element, 'pointerleave mouseleave', this.mouseLeaveOnTreeMap, this);
        EventHandler.add(this.element, 'keydown', this.keyDownHandler, this);
        EventHandler.add(this.element, 'keyup', this.keyUpHandler, this);
        EventHandler.add(this.element, 'focusout', this.focusHandler, this);
        this.resizeEvent = this.resizeOnTreeMap.bind(this);
        window.addEventListener('resize', this.resizeEvent);
    }

    /**
     * Method to set culture for maps
     *
     * @returns {void}
     */
    private setCulture(): void {
        this.intl = new Internationalization();
    }

    /**
     * To add tab index for treemap element
     *
     * @returns {void}
     */
    private addTabIndex(): void {
        this.element.setAttribute('aria-label', this.description || 'TreeMap Element');
        this.element.setAttribute('role', 'region');
        this.element.setAttribute('tabindex', this.tabIndex.toString());
    }

    /**
     * This method handles the window resize event on treemap.
     *
     * @param {Event} e - Specifies the pointer event.
     * @returns {void}
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    public resizeOnTreeMap(e: Event): void {
        if (!this.isDestroyed) {
            this.isResize = true;
            const args: IResizeEventArgs = {
                name: resize,
                cancel: false,
                previousSize: this.availableSize,
                currentSize: new Size(0, 0),
                treemap: this
            };
            if (this.resizeTo) {
                clearTimeout(this.resizeTo);
            }
            if (!isNullOrUndefined(this.element) && this.element.classList.contains('e-treemap')) {
                this.resizeTo = setTimeout(
                    (): void => {
                        this.unWireEVents();
                        this.createSvg();
                        this.refreshing = true;
                        this.wireEVents();
                        args.currentSize = this.availableSize;
                        // eslint-disable-next-line @typescript-eslint/no-unused-vars
                        this.trigger(resize, args, (observedArgs: IResizeEventArgs) => {
                            this.render();
                            this.refreshing = false;
                        });
                    },
                    500);
            }
        }
    }

    /**
     * This method handles the click event on the treemap.
     *
     * @param {PointerEvent} e - Specifies the mouse click event in the treemap.
     * @returns {void}
     * @private
     */
    public clickOnTreeMap(e: PointerEvent): void {
        const targetEle: Element = <Element>e.target;
        const targetId: string = targetEle.id;
        let eventArgs: IItemClickEventArgs;
        let itemIndex: number;
        const labelText : string = (targetEle as HTMLElement).innerHTML;
        const clickArgs: IClickEventArgs = { cancel: false, name: click, treemap: this, mouseEvent: e };
        this.trigger(click, clickArgs);
        if (targetId.indexOf('_Item_Index') > -1) {
            e.preventDefault();
            itemIndex = parseFloat(targetId.split('_Item_Index_')[1]);
            eventArgs = {
                cancel: false, name: itemClick, treemap: this, item: this.layout.renderItems[itemIndex as number], mouseEvent: e,
                groupIndex: this.layout.renderItems[itemIndex as number]['groupIndex'], groupName: this.layout.renderItems[itemIndex as number]['name'],
                text: labelText, contentItemTemplate : labelText
            };
            this.trigger(itemClick, eventArgs, (observedArgs: IItemClickEventArgs) => {
                if (observedArgs.text !== labelText || observedArgs.contentItemTemplate !== labelText) {
                    if (isNullOrUndefined(this.leafItemSettings.labelTemplate)) {
                        observedArgs.text = textFormatter(observedArgs.text, observedArgs['item']['data'], observedArgs.treemap);
                        (targetEle as HTMLElement).textContent = observedArgs.text;
                    } else {
                        setItemTemplateContent(targetId, targetEle, observedArgs.contentItemTemplate);
                    }
                }
            });
        }
        const end: number = new Date().getMilliseconds();
        let doubleTapTimer1: number;
        if (!isNullOrUndefined(this.doubleClick)) {
            if (!isNullOrUndefined(doubleTapTimer1) && end - doubleTapTimer1 < 500) {
                this.doubleClickOnTreeMap(e);
            }
            doubleTapTimer1 = end;
        }

    }

    /**
     * This method handles the double click event in the treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse click.
     * @returns {void}
     */
    public doubleClickOnTreeMap(e: PointerEvent): void {
        const doubleClickArgs: IDoubleClickEventArgs = { cancel: false, name: doubleClick, treemap: this, mouseEvent: e };
        this.trigger(doubleClick, doubleClickArgs);
        //this.notify('dblclick', e);
    }

    /**
     * This method handles the right click event in the treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse click.
     * @returns {void}
     * @private
     */
    public rightClickOnTreeMap(e: PointerEvent): void {
        const rightClickArgs: IRightClickEventArgs = { cancel: false, name: rightClick, treemap: this, mouseEvent: e };
        this.trigger(rightClick, rightClickArgs);
    }


    /**
     * This method handles the mouse down event in the treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse click.
     * @returns {void}
     * @private
     */
    public mouseDownOnTreeMap(e: PointerEvent): void {
        if ((<Element>e.target).id.indexOf('_Item_Index') > -1) {
            this.mouseDown = true;
        }
        if (e.type === 'touchstart' || e.type === 'mousedown') {
            this.removeFocus('none');
            e.preventDefault();
        }
        this.notify(Browser.touchStartEvent, e);
    }

    /**
     * This method handles the mouse move event in the treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse click.
     * @returns {void}
     * @private
     */
    public mouseMoveOnTreeMap(e: PointerEvent): void {
        const targetEle: Element = <Element>e.target;
        const targetId: string = targetEle.id;
        let eventArgs: IItemMoveEventArgs;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let item: any;
        const moveArgs: IMouseMoveEventArgs = { cancel: false, name: mouseMove, treemap: this, mouseEvent: e };
        this.trigger(mouseMove, moveArgs);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let childItems: any[];
        this.removeFocus('none');
        if (targetId.indexOf('_Item_Index') > -1) {
            item = this.layout.renderItems[parseFloat(targetId.split('_Item_Index_')[1])];
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            childItems = findChildren(item)['values'] as any[];
            this.element.style.cursor = (!item['isLeafItem'] && childItems && childItems.length > 0 && this.enableDrillDown) ?
                'pointer' : 'auto';
            eventArgs = { cancel: false, name: itemMove, treemap: this, item: item, mouseEvent: e };
            this.trigger(itemMove, eventArgs);
        } else {
            this.element.style.cursor = 'default';
        }
        this.notify(Browser.touchMoveEvent, e);
    }

    /**
     * This method calculates the selected treemap levels.
     *
     * @param {string} labelText - Specifies the label text.
     * @param {any} item - Specifies the treemap item.
     * @returns {any} - Returns label of the drilled level.
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public calculateSelectedTextLevels(labelText: string, item: any): any {
        //to find the levels by clicking the particular text both for drillDownView as true / false.
        let drillLevel: number; let k: string; let text: string;
        const levelLabels: string = item['levelOrderName'];
        const levelText: string[] = levelLabels.split('#');
        for (k of Object.keys(levelText)) {
            if (levelText[k as string] === labelText) {
                drillLevel = parseInt(k, 10);
                text = labelText;
            }
        }
        return { drillLevel: drillLevel, currentLevelLabel: text, levelText: levelText };
    }

    /**
     * This method calculates the previous level of child items in treemap.
     *
     * @param {any} drillLevelValues - Specifies the values of drill level.
     * @param {any} item - Specifies the treemap item.
     * @param {boolean} directLevel - Specifies the current level.
     * @returns {boolean} - check whether it is previous level or not.
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public calculatePreviousLevelChildItems(drillLevelValues: any, item: any, directLevel: boolean): boolean {
        //By clicking any child items drilldown to the particular level.
        //At the time store all the previous drilled level items in drilledItems
        // This condition satisfies while drilldown View is set as false and the text contains '[+]'
        let text: string; let p: number = 0; let levelItems: string; let text1: string;
        const drillTextLevel: number = this.layout.renderItems[0]['levelOrderName'].split('#').length;
        for (let h: number = 0; h < drillTextLevel; h++) {
            text1 = h === 0 ? drillLevelValues['levelText'][h as number] : text1 + '#' + drillLevelValues['levelText'][h as number];
        }
        p = drillTextLevel > 1 ? drillTextLevel : p;
        for (levelItems of Object['values'](this.layout.renderItems)) {
            const drillLevelText: string = levelItems['levelOrderName'].split('#');
            if (drillLevelText[0] === drillLevelValues['levelText'][0]) {
                text = p === 0 ? isNullOrUndefined(text1 as string) ? text1 : drillLevelValues['levelText'][p as number] :
                    directLevel ? text1 : text1 + '#' + drillLevelValues['levelText'][p as number];
                if (text === levelItems['levelOrderName']) {
                    this.drilledItems.push({ name: levelItems['levelOrderName'], data: levelItems });
                    p++;
                    directLevel = true;
                    if (p <= item['groupIndex']) {
                        text = text + '#' + drillLevelValues['levelText'][p as number];
                        text1 = text;
                    }
                }
            }
        }
        return directLevel;
    }

    /**
     * This method compares the selected labels with the drill down items.
     *
     * @param {any} drillLevelValues - Specifies the values of drill level.
     * @param {any} item - Specifies the treemap item.
     * @param {number} i - Specifies the treemap item.
     * @returns {any} - return the new drill down object.
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public compareSelectedLabelWithDrillDownItems(drillLevelValues: any, item: any, i: number): any {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let drillLevelChild: any; const newDrillItem: any = {};
        const b: number = drillLevelValues['drillLevel'] + 1;
        if (b === this.drilledItems[i as number]['data']['groupIndex']) {
            drillLevelChild = this.drilledItems[i as number]['data']['parent'];
            drillLevelChild['isDrilled'] = true;
            newDrillItem[drillLevelChild[this.drilledItems[i as number]['data']['groupName']]]
                = [drillLevelChild];
            // to remove all the items after matched drilled items
            this.drilledItems.splice(i, this.drilledItems.length);
        } else if (drillLevelValues['drillLevel'] === (this.drilledItems.length - 1)
            || drillLevelValues['drillLevel'] === item['groupIndex']) {
            newDrillItem[item['groupName']] = [item];
        }
        return newDrillItem;
    }

    /**
     * This method handles mouse end event in treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse.
     * @returns {void}
     * @private
     */
    public mouseEndOnTreeMap(e: PointerEvent | KeyboardEvent): void {
        const targetEle: Element = <Element>e.target; const targetId: string = targetEle.id; let totalRect: Rect;
        let startEvent: IDrillStartEventArgs; let endEvent: IDrillEndEventArgs; let directLevel: boolean = false;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let index: number; let newDrillItem: any = {}; let item: any; const process: boolean = true;
        const layoutID: string = this.element.id + '_TreeMap_' + this.layoutType + '_Layout'; let drillLevel: number;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const templateID: string = this.element.id + '_Label_Template_Group'; let drillLevelValues: any;
        if (targetId.indexOf('_Item_Index') > -1 && this.enableDrillDown && !this.drillMouseMove) {
            if (e.cancelable) {
                e.preventDefault();
            }
            index = parseFloat(targetId.split('_Item_Index_')[1]);
            item = this.layout.renderItems[index as number];
            const labelText: string = (targetEle as HTMLElement).textContent;
            if (this.enableBreadcrumb) {
                drillLevelValues = this.calculateSelectedTextLevels(labelText, item);
                drillLevel = drillLevelValues['drillLevel'];
                if (!this.drillDownView && labelText.search('[+]') !== -1) {
                    directLevel = this.calculatePreviousLevelChildItems(drillLevelValues, item, directLevel);
                }
            }
            if (this.levels.length !== 0 && !item['isLeafItem'] && findChildren(item)['values'] &&
                findChildren(item)['values'].length > 0) {
                if (this.drilledItems.length > 0) {
                    item = directLevel ? this.drilledItems[this.drilledItems.length - 1]['data'] : item;
                    for (let i: number = 0; i < this.drilledItems.length; i++) {
                        if (!isNullOrUndefined(drillLevel)) {  //Compare the selected text level with drilled items
                            const drillLength: number = this.drilledItems.length;
                            newDrillItem = this.compareSelectedLabelWithDrillDownItems(drillLevelValues, item, i);
                            if (drillLength !== this.drilledItems.length) {
                                i -= 1; break;
                            }
                        } //when clicking the levels drill back to the previous level process takes place
                        if (item['levelOrderName'] === this.drilledItems[i as number]['name'] && !directLevel && isNullOrUndefined(drillLevel)) {
                            if (item['groupIndex'] === 0 && item['parent'][item['groupName']] instanceof Array) {
                                item['isDrilled'] = !(item['isDrilled']);
                                if (!item['isDrilled']) {
                                    newDrillItem = item['parent'];
                                } else {
                                    newDrillItem[item['groupName']] = [item];
                                }
                            } else {
                                item['isDrilled'] = false; item['parent']['isDrilled'] = true; item = item['parent'];
                                newDrillItem[item['groupName']] = [item];
                            }
                            this.drilledItems.splice(i, 1);
                            i -= 1; break;
                        } else if (i === this.drilledItems.length - 1 && isNullOrUndefined(drillLevel)) {
                            item['isDrilled'] = true; // click the items move to next level.
                            newDrillItem[item['groupName']] = [item];
                        }
                    }
                } else {
                    item['isDrilled'] = true;
                    newDrillItem[item['groupName']] = [item];
                }
                startEvent = {
                    cancel: false, name: drillStart, treemap: this,
                    element: targetEle, groupIndex: this.enableBreadcrumb &&
                    this.drilledItems.length !== 0 && !isNullOrUndefined(drillLevel) ?
                        this.drilledItems[this.drilledItems.length - 1]['data']['groupIndex'] : item['groupIndex'],
                    groupName: this.enableBreadcrumb && this.drilledItems.length !== 0 && !isNullOrUndefined(drillLevel) ?
                        this.drilledItems[this.drilledItems.length - 1]['data']['name'] : item['name'],
                    rightClick: e.which === 3 ? true : false, childItems: null,  item: newDrillItem
                };
                this.trigger(drillStart, startEvent, (observedArgs: IDrillStartEventArgs) => {
                    this.currentLevel = item['isDrilled'] && isNullOrUndefined(drillLevel) ? item['groupIndex'] :
                        (!isNullOrUndefined(drillLevel) && this.enableBreadcrumb && item['isDrilled']) ? drillLevel : null;
                    if (!observedArgs.cancel) {
                        if (document.getElementById(layoutID)) {
                            const layerElementId: HTMLElement = document.getElementById(layoutID);
                            layerElementId.parentNode.removeChild(layerElementId);
                        }
                        totalRect = extend({}, this.areaRect, totalRect, true) as Rect;
                        if (this.legendSettings.visible && !isNullOrUndefined(this.treeMapLegendModule)) {
                            if (!isNullOrUndefined(newDrillItem)) {
                                this.treeMapLegendModule.legendGroup.textContent = ''; this.treeMapLegendModule.legendGroup = null;
                                this.treeMapLegendModule.widthIncrement = 0; this.treeMapLegendModule.heightIncrement = 0;
                                if (this.enableBreadcrumb && !isNullOrUndefined(drillLevel)) {
                                    this.drilledLegendItems = {
                                        name: this.drilledItems[this.drilledItems.length - 1]['data']['levelOrderName'],
                                        data: this.drilledItems[this.drilledItems.length - 1]['data']
                                    };
                                } else {
                                    this.drilledLegendItems = { name: item['levelOrderName'], data: item };
                                }
                                this.treeMapLegendModule.renderLegend();
                            }
                            totalRect = !isNullOrUndefined(this.totalRect) ? this.totalRect : totalRect;
                        }
                        if (document.getElementById(templateID)) {
                            const drillElementId: HTMLElement = document.getElementById(templateID);
                            drillElementId.parentNode.removeChild(drillElementId);
                        }
                        if (!isNullOrUndefined(observedArgs.childItems) && !observedArgs.cancel) {
                            this.layout.onDemandProcess(observedArgs.childItems);
                        } else {
                            this.layout.calculateLayoutItems(newDrillItem, totalRect);
                            this.layout.renderLayoutItems();
                        }
                    }
                });
                endEvent = { cancel: false, name: drillEnd, treemap: this, renderItems: this.layout.renderItems };
                this.trigger(drillEnd, endEvent);
                if (process) {
                    if (!directLevel && isNullOrUndefined(drillLevel)) {
                        this.drilledItems.push({ name: item['levelOrderName'], data: item });
                    }
                }
            }
        }
        this.mouseDown = false;
        this.notify(Browser.touchEndEvent, e);
    }

    /**
     * This method handles mouse leave event in treemap.
     *
     * @param {PointerEvent} e - Specifies the pointer event of mouse.
     * @return {void}
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    public mouseLeaveOnTreeMap(e: PointerEvent): void {
        if (this.treeMapTooltipModule) {
            this.treeMapTooltipModule.removeTooltip();
        }
        if (this.treeMapLegendModule) {
            this.treeMapLegendModule.removeInteractivePointer();
        }
        removeClassNames(document.getElementsByClassName('treeMapHighLight'), 'treeMapHighLight', this);
        if (this.treeMapHighlightModule) {
            removeLegend(this.treeMapHighlightModule.shapeHighlightCollection, this);
            this.treeMapHighlightModule.highLightId = '';
        }
        this.removeFocus('');
    }

    /**
     * This method is used to perform operations when keyboard up on TreeMap.
     *
     * @param {KeyboardEvent} event - Specifies the keyboard event on TreeMap.
     * @returns {void}
     * @private
     */
    public keyUpHandler(event: KeyboardEvent): void {
        if (event.code !== 'Tab') {
            return;
        }
        const id: string = event.target['id'];
        const targetEle: Element = document.getElementById(id);
        if ((id.indexOf('Item_Index') > -1 || id.indexOf('Legend') > -1)) {
            if (this.treeMapTooltipModule) {
                this.treeMapTooltipModule.removeTooltip();
            }
            if (this.treeMapHighlightModule && this.highlightSettings.enable) {
                (targetEle as HTMLElement).style.setProperty('outline', 'none');
                (targetEle as HTMLElement).classList.add('keyboard-focused');
                const highlightElement: Element = id.indexOf('_Legend_') > -1 && this.legendSettings.mode === 'Default' ?
                    targetEle.children[0] : targetEle;
                this.treeMapHighlightModule.highlightOnMouseMove(highlightElement);
            } else {
                this.removeFocus('');
                (targetEle as HTMLElement).style.outline = '2px solid black';
                (targetEle as HTMLElement).classList.add('keyboard-focused');
            }
        } else {
            removeClassNames(document.getElementsByClassName('treeMapHighLight'), 'treeMapHighLight', this);
            if (this.treeMapHighlightModule) {
                removeLegend(this.treeMapHighlightModule.shapeHighlightCollection, this);
            }
            this.removeFocus('none');
        }
    }

    /**
     * This method is used to perform operations when keyboard down on treemap.
     *
     * @param {KeyboardEvent} event - Specifies the keyboard event on treemap.
     * @returns {void}
     * @private
     */
    public keyDownHandler(event: KeyboardEvent): void {
        const id: string = event.target['id'];
        const targetEle: Element = document.getElementById(id);
        if (event.code === 'Enter') {
            if (this.selectionSettings.enable && (id.indexOf('Item_Index') > -1 || id.indexOf('Legend') > -1)) {
                const selectionElement: Element = id.indexOf('_Legend_') > -1 && this.legendSettings.mode === 'Default' ?
                    targetEle.children[0] : targetEle;
                this.treeMapSelectionModule.selectionOnMouseDown(selectionElement);
            }
            if (this.enableDrillDown && targetEle.childElementCount > 0) {
                this.mouseEndOnTreeMap(event);
            }
        }
    }

    /**
     * This method is used to perform operations when focus out on treemap.
     *
     * @param {KeyboardEvent} event - Specifies the keyboard event on treemap.
     * @returns {void}
     * @private
     */
    public focusHandler(event: FocusEvent): void {
        const id: string = event.target['id'];
        if (!this.element.contains(event.relatedTarget as Node) ||
            (id.indexOf('Item_Index') > -1 && (event.relatedTarget as HTMLElement).id.indexOf('Legend') > -1) ||
            (id.indexOf('Legend') > -1 && (event.relatedTarget as HTMLElement).id.indexOf('Item_Index') > -1)) {
            if (this.treeMapHighlightModule && this.highlightSettings.enable) {
                removeClassNames(document.getElementsByClassName('treeMapHighLight'), 'treeMapHighLight', this);
                removeLegend(this.treeMapHighlightModule.legendHighlightCollection, this);
                this.treeMapHighlightModule.highLightId = '';
            } else {
                this.removeFocus('none');
            }
        }
    }

    private removeFocus(outline: string): void {
        const highlightedElement: Element = document.querySelector('.keyboard-focused');
        if (highlightedElement) {
            (highlightedElement as HTMLElement).style.outline = outline;
            highlightedElement.classList.remove('keyboard-focused');
        }
    }

    /**
     * This method is used to select or remove the selection of treemap item based on the provided selection settings.
     *
     * @param {string[]} levelOrder - Specifies the order of the level.
     * @param {boolean} isSelected - Specifies whether the treemap item should be selected or the selection should be removed.
     * @return {void}
     */
    public selectItem(levelOrder: string[], isSelected ?: boolean): void {
        if (isNullOrUndefined(isSelected)) {
            isSelected = true;
        }
        let levelOrderName: string = '';
        for (let i: number = 0; i < levelOrder.length; i++) {
            if (i !== levelOrder.length - 1) {
                levelOrderName += levelOrder[i as number] + '#';
            } else {
                levelOrderName += levelOrder[i as number];
            }
        }
        if (this.treeMapSelectionModule && this.selectionSettings.enable) {
            this.treeMapSelectionModule.selectTreemapItem(levelOrderName, isSelected);
        }
    }


    /**
     * To provide the array of modules needed for maps rendering
     *
     * @returns {ModuleDeclaration[]} Returns the modules
     * @private
     */
    public requiredModules(): ModuleDeclaration[] {
        const modules: ModuleDeclaration[] = [];
        if (this.tooltipSettings.visible) {
            modules.push({
                member: 'treeMapTooltip',
                args: [this],
                name: 'TreeMapTooltip'
            });
        }
        if (this.highlightSettings.enable) {
            modules.push({
                member: 'treeMapHighlight',
                args: [this],
                name: 'TreeMapHighlight'
            });
        }
        if (this.selectionSettings.enable) {
            modules.push({
                member: 'treeMapSelection',
                args: [this],
                name: 'TreeMapSelection'
            });
        }
        if (this.legendSettings.visible) {
            modules.push({
                member: 'treeMapLegend',
                args: [this],
                name: 'TreeMapLegend'
            });
        }
        if (this.allowPrint) {
            modules.push({
                member: 'Print',
                args: [this, Print],
                name: 'Print'
            });
        }
        if (this.allowImageExport) {
            modules.push({
                member: 'ImageExport',
                args: [this, ImageExport],
                name: 'ImageExport'
            });
        }
        if (this.allowPdfExport) {
            modules.push({
                member: 'PdfExport',
                args: [this, PdfExport],
                name: 'PdfExport'
            });
        }
        return modules;
    }

    /**
     * Called internally if any of the property value changed.
     *
     * @param {TreeMapModel} newProp - Specifies the new property
     * @param {TreeMapModel} oldProp - Specifies the old property
     * @returns {void}
     * @private
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    public onPropertyChanged(newProp: TreeMapModel, oldProp: TreeMapModel): void {
        if (!this.isDestroyed) {
            let render: boolean = false;
            for (const prop of Object.keys(newProp)) {
                switch (prop) {
                case 'background':
                    this.renderBorder();
                    break;
                case 'height':
                case 'width':
                case 'layoutType':
                case 'levels':
                case 'drillDownView':
                case 'renderDirection':
                case 'leafItemSettings':
                case 'legendSettings':
                case 'dataSource':
                    render = true;
                    break;
                }
            }
            if (render) {
                this.createSvg();
                this.renderElements();
            }
        }
    }

    /**
     * Gets component name.
     *
     * @returns {string} - return the treemap instance.
     * @private
     */
    public getModuleName(): string {
        return 'treemap';
    }

    /**
     * This method destroys the treemap. This method removes the events associated with the treemap and disposes the objects created for rendering and updating the treemap.
     */
    public destroy(): void {
        this.unWireEVents();
        removeElement('treeMapMeasureText');
        this.drilledItems = [];
        this.levelSelection = [];
        this.legendId = [];
        this.removeSvg();
        super.destroy();
        this.areaRect = null;
        this.themeStyle = null;
        this.totalRect = null;
        this.drilledLegendItems = null;
        this.doubleTapTimer = null;
        this.treemapLevelData = null;
        this.resizeEvent = null;
        this.availableSize = null;
        this.intl = null;
        if (!isNullOrUndefined(this.layout)) {
            this.layout.destroy();
        }
        this.layout = null;
        this.renderer = null;
    }

    private removeSvg(): void {
        removeElement(this.element.id + '_Secondary_Element');
        if (this.svgObject) {
            while (this.svgObject.childNodes.length > 0) {
                this.svgObject.removeChild(this.svgObject.firstChild);
            }
            if (!this.svgObject.hasChildNodes() && this.svgObject.parentNode) {
                remove(this.svgObject);
            }
        }
    }

    /**
     * Get the properties to be maintained in the persisted state.
     *
     * @returns {string} - Returns the string value.
     * @private
     */
    public getPersistData(): string {
        return '';
    }
}

/**
 * @private
 */
export class LevelsData {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    levelsData : any[];
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    defaultLevelsData : any[];
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    hierarchyData : any[];
}
