// Copyright (c) Mito
// Distributed under the terms of the Modified BSD License.

/*
    Contains all the selectors used across Mito frontend tests. 
*/

import { Selector} from 'testcafe';
import { testUser } from './roles';
import { modalAdvanceButtonSelector } from './allModals';
import { 
    getCellSelector, 
    selectKernelButton,
    fileTab,
    fileTabNew,
    fileTabNewNotebook,
    getActiveElement,
    addFilterButton,
    filterConditionSelector,
    filterConditionOptionSelector,
    filterValueInputSelector,
    operatorSelector,
    operatorOptionSelector,
    groupButton,
    rowContainerSelector,
    columnContainerSelector,
    deleteAggregationSelector,
    groupModalAddValueSelector,
    valueMappingKey,
    valueMappingKeyOptions,
    valueMappingValue,
    valueMappingValueOptions,
    ascendingSortSelector,
    descendingSortSelector,
    getTabSelector
} from './selectors';

import {
    getColumnHeaderFilterSelector
} from './columnHelpers'
import { exception } from 'console';

// A string you can pass to t.pressKeys that will delete all values from the currently selecte input
export const DELETE_PRESS_KEYS_STRING = 'ctrl+a delete'


export type FilterType = {
    filterCondition: string,
    filterValue?: string 
}

/*
    Applies the given filters to a column. Notably, deletes all other filters that may be on 
    the column at that time to apply these filters!
*/
export async function applyFiltersToColumn(
        t: TestController, 
        columnHeader: string, 
        filters: FilterType[],
        operator?: 'Or' | 'And'
    ): Promise<void> {

    // Open the filter modal
    await t.click(getColumnHeaderFilterSelector(columnHeader))

    const filterTableSelector = Selector('div.filter-modal-centering-container')
        .find('table')

    const numFilters = await filterTableSelector.child('tr').count;

    // Delete all existing filters
    for (let i = 0; i < numFilters - 1; i++) {
        const deleteFilterSelector = Selector('rect').withAttribute('width', '13')
        await t.click(deleteFilterSelector)
    }

    for (let i = 0; i < filters.length; i++) {
        await t.click(addFilterButton)

        const filterCondition = filters[i].filterCondition;
        const filterValue = filters[i].filterValue;

        await t
            .click(filterConditionSelector.nth(i))
            .click(filterConditionOptionSelector.withExactText(filterCondition).nth(i))
            

        if (filterValue !== undefined) {
            await t
                .click(filterValueInputSelector.nth(i))
                .pressKey(DELETE_PRESS_KEYS_STRING)
                .typeText(filterValueInputSelector.nth(i), filterValue)
        }
    }

    // If there is an operator, and it makes sense to apply it, we apply it
    if (operator !== undefined) {
        await t
            .click(operatorSelector)
            .click(operatorOptionSelector.withExactText(operator));
    }


    await t.click(modalAdvanceButtonSelector)
}


/*
    Does a pivot with the passed params
*/
export async function doGroup(t: TestController, rows: string[], columns: string[], values: {columnHeader: string, aggfunc: string}[]): Promise<void> {
    
    // Open the group modal
    await t.click(groupButton)

    // Select rows
    for (let i = 0; i < rows.length; i++) {
        await t.click(rowContainerSelector.withAttribute('name', rows[i]));
    }

    // And the columns
    for (let i = 0; i < columns.length; i++) {
        await t.click(columnContainerSelector.withAttribute('name', columns[i]));
    }

    // Delete the existing aggregation value
    await t.click(deleteAggregationSelector);

    // And then add aggregation values placeholders
    for (let i = 0; i < values.length; i++) {
        await t.click(groupModalAddValueSelector);
    }

    // Finially, add the aggregation values
    for (let i = 0; i < values.length; i++) {
        const columnHeader = values[i].columnHeader
        const aggfunc = values[i].aggfunc;

        await t
            .click(valueMappingKey.nth(i))
            .click(valueMappingKeyOptions.withExactText(columnHeader).nth(i))
        await t
            .click(valueMappingValue.nth(i))
            .click(valueMappingValueOptions.withExactText(aggfunc).nth(i))
    }

    await t.click(modalAdvanceButtonSelector)
}

/*
    Sorts a column, yo.
*/
export async function sortColumn(t: TestController, columnHeader: string, direction: 'ascending' | 'descending'): Promise<void> {
    
    // Open the filter/sort modal
    await t.click(getColumnHeaderFilterSelector(columnHeader));

    if (direction === 'ascending') {
        await t.click(ascendingSortSelector);
    } else {
        await t.click(descendingSortSelector);
    }

    await t.click(modalAdvanceButtonSelector)
}


/*
    Helper function for checking mulitple tabs in one go!
*/
export async function checkSheets(t: TestController, sheets: Record<string, Record<string, string[]>>): Promise<void> {
    for (const tabName in sheets) {
        await checkSheet(t, sheets[tabName], tabName);
    }
}


/*
    Helper function for checking that a given sheet has the passed data
*/
export async function checkSheet(t: TestController, dataFrame: Record<string, string[]>, tabName?: string): Promise<void> {
    if (tabName !== undefined) {
        await t.click(getTabSelector(tabName));
    }

    for (const key in dataFrame) {
        await checkColumn(t, key, dataFrame[key])
    }
}


/*
    Helper function for checking that a given column has exactly columnValues inside
    of it, and nothing else. 
*/
export async function checkColumn(t: TestController, columnHeader: string, columnValues: string[]): Promise<void> {

    for (let i = 0; i < columnValues.length; i++) {
        // There is a warning generated by this line that is a testcafe bug fixed here: https://github.com/DevExpress/testcafe/issues/5389
        await t.expect(getCellSelector(columnHeader, i.toString()).innerText).eql(columnValues[i])
    }

    // And we check there are no other values
    await t.expect(getCellSelector(columnHeader, columnValues.length.toString()).exists).notOk();
}

export async function createNotebookRunCell(t: TestController, logIn: boolean, cellText: string): Promise<void> {
    // Creates a new notebook, and inserts the given cellText into that notebook

    // Log in
    if (logIn) {
        await t.useRole(testUser)
    }

    // First, we check if there is currently a select kernel modal (which there is sometimes
    // when a notebook fails to be deleted, for some reason. If there is, we click it, as many
    // times as it pops up
    try {
        // Keep clicking kernel buttons, as long as they are there!
        while (!(await selectKernelButton.exists)) {
            await t.click(selectKernelButton)
        }
    } catch (e) {
        // We don't care if this fails... it only needs to happen it when does...
    }

    // Then, we delete all the notebooks there are
    await deleteAllNotebooks(t);


    await t
        // Then, we make a new notebook
        .click(fileTab)
        .hover(fileTabNew)
        .click(fileTabNewNotebook)

    // Then, we try to select a kernel (this popup only appears sometimes), 
    // which is why we don't necessarily _need_ to click it

    try {
        await t.click(selectKernelButton)
    } catch (e) {
        console.log("No need to select kernel.")
    }

    await t
        // Then, we create a mito notebook
        .typeText(
            // By default, the first cell is focused on
            getActiveElement(), 
            cellText
        )
        .pressKey('shift+enter')
}

export async function deleteAllNotebooks(t: TestController, failOnFailure?: boolean): Promise<void> {
    const notebookCount = await Selector('ul.jp-DirListing-content').child('li').count

    if (notebookCount === 0) {
        return;
    }
    
    try {
        // First, click the first notebook
        const firstNotebook = Selector('ul.jp-DirListing-content').child('li').nth(0)
        await t.click(firstNotebook)

        // Then, shift click the last notebook
        const last = Selector('ul.jp-DirListing-content').child('li').nth(notebookCount - 1)
        await t.click(last, {modifiers: {shift: true}})

        // Then, delete them all!
        const deleteModalButton = Selector('div.jp-Dialog-buttonLabel')
            .withExactText('Delete')

        await t
            .rightClick(firstNotebook)
            .pressKey('d')
            .click(deleteModalButton)
    } catch (e) {
        if (failOnFailure) {
            throw exception('Failed to delete notebooks!');
        }
        console.log("Failed deleting all notebooks")
    }
}

export async function deleteCurrentNotebook(t: TestController): Promise<void> {
    const notebook = Selector('ul.jp-DirListing-content')
        .child('li')
        .nth(0)

    const deleteModalButton = Selector('div.jp-Dialog-buttonLabel')
        .withExactText('Delete')
    
    await t
        .rightClick(notebook)
        .pressKey('d')
        .click(deleteModalButton)
}
