import {Page} from '@playwright/test';

import {ChartkitMenuDialogsQA, ChartKitTableQa, ChartQa, MenuItemsQA} from '../../../src/shared';
import {slct, waitForCondition} from '../../utils';
import {readDownload} from '../../utils/playwright/utils';
import {COMMON_CHARTKIT_SELECTORS} from '../constants/chartkit';

export enum DOMNamedAttributes {
    StrokeDashArray = 'stroke-dasharray',
    Stroke = 'stroke',
}

export const CHARTKIT_MENU_ITEMS_SELECTORS = {
    menuCommentsQA: 'comments',
    menuShowCommentsQA: 'showComments',
    menuHideCommentsQA: 'hideComments',
    inspectorQA: 'inspector',
    shareQA: 'getLink',
    exportQA: 'export',
    alertsQA: 'alerts',
};

export default class ChartKit {
    geopointSelector = '.chartkit-yandex-map-chips';
    tooltipSelector = '.chartkit-tooltip';
    yMapSelector = '.chartkit-ymap';
    legendItemSelector = COMMON_CHARTKIT_SELECTORS.chartLegendItem;
    chartMenuSwitcher = slct('chart-dropdown-switcher');
    chartMenuList = slct('chart-dropdown-menu');
    commentsSelector = '.highcharts-comment';
    alertLimitSelector = '.highcharts-plot-band';
    xAxisLabel = '.highcharts-xaxis-labels text, .gcharts-x-axis__label';
    chartTitle = ['.highcharts-title', slct('g-charts-title')].join();
    navigator = '.highcharts-navigator, .gcharts-range-slider';
    metricItemSelector = ['.chartkit-indicator__item', slct(ChartQa.Chart)].join();
    metricItemValueSelector = '.chartkit-indicator__item-value,.markup-metric-value';
    metricItemTitleSelector = '.chartkit-indicator__item-title,.markup-metric-title';
    private drillArrowsSelector = '.chartkit-drill__drill-action';
    private paginatorSelector = '.chartkit-table-paginator';
    private tableHeadCellSelector = '.data-table__head-cell';
    private layerLegendSelector = '.chartkit .chartkit-ymap-legend-layer';
    private labelsSelector =
        '.highcharts-data-labels .highcharts-data-label, .gcharts-line__label, .gcharts-bar-x__label';
    private chartkitSeriesRect =
        '.chartkit-highcharts rect.highcharts-point, .gcharts-bar-x__segment';

    private page: Page;

    constructor(page: Page) {
        this.page = page;
    }

    async openChartMenu() {
        await this.page.hover(slct(ChartkitMenuDialogsQA.chartWidget));
        await this.page.waitForSelector(this.chartMenuSwitcher);
        await this.page.locator(this.chartMenuSwitcher).click();
    }

    async waitForItemInMenu(qaName: string) {
        await this.page.waitForSelector(`${this.chartMenuList} [data-qa="${qaName}"]`);
    }

    async openChartMenuAndClickItem(menuItemSelector: string) {
        await waitForCondition(async () => {
            await this.openChartMenu();

            try {
                await this.page.click(menuItemSelector, {timeout: 2000});
                return true;
            } catch {
                return false;
            }
        });
    }

    async clickMenuItem(qaSelector: string) {
        await this.page.locator(slct(ChartkitMenuDialogsQA.chartWidget)).hover();
        await this.page.waitForSelector(this.chartMenuSwitcher);
        await this.openChartMenu();
        await this.waitForItemInMenu(qaSelector);
        await this.page.click(`[data-qa="${qaSelector}"]`);
    }

    async getComments() {
        // while in the tests we take into account only comments-lines, for areas and so on, the logic may be different
        const locator = this.page
            .locator(`path${this.commentsSelector}`)
            .or(this.page.locator(slct(ChartQa.CommentLabel)));

        return await locator.all();
    }

    async openScreenshotDialog() {
        await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_SCREENSHOT));
    }

    async drillDown() {
        await this.page.waitForSelector(this.drillArrowsSelector);
        const [_leftArrow, rightArrow] = await this.page.$$(this.drillArrowsSelector);
        await rightArrow.click();
    }

    async drillUp() {
        await this.page.waitForSelector(this.drillArrowsSelector);
        const [leftArrow] = await this.page.$$(this.drillArrowsSelector);
        await leftArrow.click();
    }

    async waitForErrorTitle(errorQa: string) {
        await this.page.waitForSelector(slct(errorQa));
    }

    getTableLocator() {
        return this.page
            .locator('.chartkit-table')
            .or(this.page.locator(slct(ChartKitTableQa.Widget)));
    }

    async waitForSuccessfulRender() {
        const locator = this.page
            .locator(COMMON_CHARTKIT_SELECTORS.chart)
            .or(this.getTableLocator());
        await locator.waitFor();
    }

    async openExportMenuAndClickSubItem(subItemSelector: string) {
        await this.openChartMenu();
        await this.page.hover(slct(MenuItemsQA.EXPORT));

        const subItem = await this.page.waitForSelector(subItemSelector);
        await subItem.click();
    }

    async openCsvExportDialog() {
        await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_CSV));
        return this.page.waitForSelector(slct(ChartkitMenuDialogsQA.chartMenuExportCsvDialog));
    }

    async exportCsv(options?: {fractionDelimiter?: string}) {
        const downloadPromise = this.page.waitForEvent('download');

        const dialog = await this.openCsvExportDialog();

        if (options?.fractionDelimiter) {
            const selectFloatSelector = await dialog.waitForSelector(
                slct(ChartkitMenuDialogsQA.chartMenuExportCsvSelectFloat),
            );
            await selectFloatSelector.click();
            await this.page.locator(slct(options.fractionDelimiter)).click();
        }

        await this.page.click(slct(ChartkitMenuDialogsQA.chartMenuExportModalApplyBtn));

        const download = await downloadPromise;
        const content = await readDownload(download);
        await download.delete();

        return content?.toString('utf8');
    }

    async exportMarkdown() {
        await this.openExportMenuAndClickSubItem(slct(MenuItemsQA.EXPORT_MARKDOWN));

        return await this.page.evaluate(async () => {
            return await navigator.clipboard.readText();
        });
    }

    async openInNewTab() {
        const newTabPagePromise: Promise<Page> = new Promise((resolve) =>
            this.page.context().on('page', resolve),
        );
        const selector = slct(MenuItemsQA.NEW_WINDOW);
        await this.openChartMenuAndClickItem(selector);

        return newTabPagePromise;
    }

    async saveAsPicture() {
        const newTabPagePromise: Promise<Page> = new Promise((resolve) =>
            this.page.context().on('page', resolve),
        );
        await this.openScreenshotDialog();

        await this.page.click(slct('download-screenshot-modal-download-btn'));

        return newTabPagePromise;
    }

    async waitForPaginationExist() {
        await this.page.waitForSelector(this.paginatorSelector);
    }

    async waitForPaginationNotExist() {
        await this.page.waitForSelector(this.paginatorSelector, {state: 'detached'});
    }

    async getTableRowsCount() {
        return await this.getTableLocator().locator('tbody tr').count();
    }

    getLayerLegend() {
        return this.page.$(this.layerLegendSelector);
    }

    async getHeadRowsHtml() {
        const headerRows = this.getTableLocator().locator('thead').first().locator('tr');
        const headCellContent = this.page
            .locator(this.tableHeadCellSelector)
            .or(this.page.locator(slct(ChartKitTableQa.HeadCellContent)));
        await headerRows.first().waitFor();

        const rows = await headerRows.all();
        return await Promise.all(
            rows.map(async (row) =>
                Promise.all(
                    (await row.locator(headCellContent).all()).map((cell) => cell.innerHTML()),
                ),
            ),
        );
    }

    async getHeadRowsTexts() {
        const headerRows = this.getTableLocator().locator('thead').first().locator('tr');
        await headerRows.first().waitFor();

        const rows = await headerRows.all();
        return await Promise.all(rows.map((row) => row.locator('th').allTextContents()));
    }

    async getRowsTexts() {
        const rowLocator = this.getTableLocator().locator('tbody tr');
        await rowLocator.first().waitFor();

        const rows = await rowLocator.all();
        const content = await Promise.all(rows.map((row) => row.locator('td').allTextContents()));

        return content.map((row) => {
            return row.map((rowValue) => rowValue.replace(/\u00a0/g, ' '));
        });
    }

    async waitUntilLoaderExists() {
        await waitForCondition(async () => {
            return !(await this.page.$('.chartkit-loader.chartkit-loader_veil'));
        }).catch(() => {
            throw new Error("Loader didn't disappear");
        });
    }

    async getAttributeFromLines(attributeName: DOMNamedAttributes) {
        let lines: string[] = [];
        await waitForCondition(async () => {
            lines = await this.page.evaluate(
                ({attribute}: {attribute: DOMNamedAttributes}) => {
                    const lineNodes = document.querySelectorAll(
                        '.highcharts-series .highcharts-graph, .gcharts-line path',
                    );
                    return Array.from(lineNodes)
                        .map((lineEl) => lineEl.attributes.getNamedItem(attribute)?.value)
                        .filter(Boolean) as string[];
                },
                {attribute: attributeName} as {attribute: DOMNamedAttributes},
            );
            return lines.length;
        });
        return lines;
    }

    async navigateToNextTablePage(times: number) {
        for (let i = 0; i < times; i++) {
            await this.page.click(slct('chartkit-table-paginator-next-btn'));
        }
    }

    async getSeriesClientRect() {
        const locator = this.page.locator(this.chartkitSeriesRect);
        return await locator.evaluateAll((elements) =>
            elements.map((el) => el.getBoundingClientRect()),
        );
    }

    async getLabelsClientRect() {
        const locator = this.page.locator(this.labelsSelector);
        return await locator.evaluateAll((elements) =>
            elements.map((el) => el.getBoundingClientRect()),
        );
    }

    async getLabelsTexts() {
        await this.page.waitForSelector(this.labelsSelector);

        const locator = this.page.locator(this.labelsSelector);

        await locator.first().waitFor();
        const labels = await locator.allTextContents();

        return labels.map((rowValue) => rowValue.replace(/\u00a0/g, ' '));
    }
}
