
import BusinessObjects from "../lib/business_objects/business_objects.js";
import Events from "../lib/events.js";
import LocalStorage from "../lib/local_storage.js";
import Validation from "../lib/validation.js";
import BasePage from "./base.js";
import API from "../api.js";
import DOM from "../dom.js";
import Utils from "../lib/utils.js";

import OverlayConfirm from "../components/common/temporary/overlay_confirm.js";
import OverlayError from "../components/common/temporary/overlay_error.js";

export default class TableBasePage extends BasePage {
    // static hash
    // static attrIdRowObject
    // callSaveTableContent

    constructor(router) {
        super(router);
        this.cursorYInitial = null;
        this.rowInitial = null;
        this.placeholder = null;
        this.dragSrcEl = null;
        this.dragSrcRow = null;

        this.hookupTableCellDdls = this.hookupTableCellDdls.bind(this);
    }
    
    initialize(isPopState = false) {
        throw new Error("Must implement initialize() method.");
    }
    sharedInitialize(isPopState = false, isSinglePageApp = false) {
        if (!isPopState) {
            super.sharedInitialize();
            this.hookupFilters();
            this.hookupButtonsSaveCancel();
            this.hookupTableMain();
            OverlayConfirm.hookup(() => {
                if (isSinglePageApp) {
                    this.saveRecordsTableDirtySinglePageApp();
                }
                else {
                    this.saveRecordsTableDirty();
                }
            });
        } else {
            let dataPage = this.getLocalStoragePage();
            let filters = dataPage[flagFormFilters];
            let formFilters = TableBasePage.getFormFilters();
            let filtersDefault = DOM.convertForm2JSON(formFilters);
            if (!Validation.areEqualDicts(filters, filtersDefault)) {
                this.callFilterTableContent();
            }
        }
    }
    hookupFilters() {
        if (this.constructor === TableBasePage) {
            throw new Error("Subclass of TableBasePage must implement method hookupFilters().");
        }
    }
    sharedHookupFilters() {
        this.hookupButtonApplyFilters();
        this.hookupSearchTextFilter();
    }
    hookupFilterActive() {
        let filterSelector = idFormFilters + ' #' + flagActiveOnly;
        let filterActiveOld = document.querySelector(filterSelector);
        filterActiveOld.removeAttribute('id');
        let parentDiv = filterActiveOld.parentElement;
        let isChecked = (DOM.getElementAttributeValuePrevious(parentDiv) == "True");
        let filterActiveNew = document.querySelector(idFormFilters + ' div.' + flagActiveOnly + '.' + flagContainerInput + ' svg.' + flagActiveOnly);
        filterActiveNew.setAttribute('id', flagActiveOnly);
        if (isChecked) filterActiveNew.classList.add(flagIsChecked);

        Events.hookupEventHandler("click", filterSelector, (event, filterActive) => {
            Utils.consoleLogIfNotProductionEnvironment({ filterActive });
            Utils.consoleLogIfNotProductionEnvironment({ [filterActive.tagName]: filterActive.tagName });
            let svgElement = (filterActive.tagName.toUpperCase() == 'SVG') ? filterActive : filterActive.parentElement;
            let wasChecked = svgElement.classList.contains(flagIsChecked);
            if (wasChecked) {
                svgElement.classList.remove(flagIsChecked);
            }
            else {
                svgElement.classList.add(flagIsChecked);
            }
            return this.handleChangeFilter(event, filterActive);
        });
        let filter = document.querySelector(filterSelector);
        let filterValuePrevious = DOM.getElementValueCurrent(filter);
        filter.setAttribute(attrValueCurrent, filterValuePrevious);
        filter.setAttribute(attrValuePrevious, filterValuePrevious);
    }
    hookupFilter(filterFlag, handler = (event, filter) => { return this.handleChangeFilter(event, filter); }) {
        let filterSelector = idFormFilters + ' #' + filterFlag;
        Events.hookupEventHandler("change", filterSelector, handler);
        let filter = document.querySelector(filterSelector);
        let filterValuePrevious = DOM.getElementValueCurrent(filter);
        filter.setAttribute(attrValueCurrent, filterValuePrevious);
        filter.setAttribute(attrValuePrevious, filterValuePrevious);
    }
    handleChangeFilter(event, filter) {
        let isDirtyFilter = DOM.updateAndCheckIsElementDirty(filter);
        let formFilters = TableBasePage.getFormFilters();
        let areDirtyFilters = isDirtyFilter || DOM.hasDirtyChildrenContainer(formFilters);
        let tbody = document.querySelector(idTableMain + ' tbody');
        let rows = tbody.querySelectorAll(':scope > tr');
        rows.forEach((row) => {
            if (areDirtyFilters && !row.classList.contains(flagIsCollapsed)) row.classList.add(flagIsCollapsed);
            if (!areDirtyFilters && row.classList.contains(flagIsCollapsed)) {
                row.classList.remove(flagIsCollapsed);
                let dirtyInputs = row.querySelectorAll('input.' + flagDirty);
                dirtyInputs.forEach((dirtyInput) => {
                    dirtyInput.value = DOM.getElementAttributeValueCurrent(dirtyInput);
                });
            }
        });
        if (areDirtyFilters) {
            /*
            tbody.querySelectorAll('tr').forEach((tr) => { 
                if (!DOM.hasDirtyChildrenContainer(tr)) tr.remove(); 
            });
            */
            tbody.innerHTML = '<div>Press "Apply Filters" to refresh the table.</div>' + tbody.innerHTML;
            if (!tbody.classList.contains(flagIsCollapsed)) tbody.classList.add(flagIsCollapsed);
        }
        else {
            let isDirtyLabel = tbody.querySelector(":scope > div");
            if (isDirtyLabel != null) isDirtyLabel.remove();
            if (tbody.classList.contains(flagIsCollapsed)) tbody.classList.remove(flagIsCollapsed);
            let initialisedElements = tbody.querySelectorAll('.' + flagInitialised);
            initialisedElements.forEach((initialisedElement) => {
                initialisedElement.classList.remove(flagInitialised);
            });
            this.hookupTableMain();
        }
        this.updateAndToggleShowButtonsSaveCancel();
    }
    hookupFilterIsNotEmpty() {
        this.hookupFilter(flagIsNotEmpty);
    }
    hookupButtonApplyFilters() {
        Events.hookupEventHandler("click", idButtonApplyFilters, (event, button) => {
            event.stopPropagation();
            this.callFilterTableContent();
        });
    }
    hookupSearchTextFilter() {
        this.hookupFilter(flagSearch);
    }
    hookupFilterCommandCategory() {
        this.hookupFilter(attrIdCommandCategory, (event, filterCommandCategory) => {
            this.handleChangeFilter();
            let isDirtyFilter = filterCommandCategory.classList.contains(flagDirty);
            let idCommandCategory = DOM.getElementValueCurrent(filterCommandCategory);
            console.log("filter commands unsorted");
            console.log(Utils.getListFromDict(filterCommands));
            let commandsInCategory = Utils.getListFromDict(filterCommands).filter(command => command[attrIdCommandCategory] == idCommandCategory);
            let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName]));
            let filterCommand = document.querySelector(idFormFilters + ' .' + flagCommand);
            let idCommandPrevious = DOM.getElementAttributeValuePrevious(filterCommand);
            filterCommand.innerHTML = '';
            let optionJson, option;
            option = DOM.createOption(null);
            filterCommand.appendChild(option);
            sortedCommands.forEach((command) => {
                optionJson = BusinessObjects.getOptionJsonFromObjectJson(command, idCommandPrevious);
                option = DOM.createOption(optionJson);
                filterCommand.appendChild(option);
            });
            filterCommand.dispatchEvent(new Event('change'));
            return isDirtyFilter;
        });
    }
    hookupFilterCommand() {
        this.hookupFilter(attrIdCommand);
    }
    hookupFilterLocation() {
        this.hookupFilter(attrIdLocation);
    }
    /*
    getAndLoadFilteredTableContent = () => {
        this.callFilterTableContent()
            .catch(error => console.error('Error:', error));
    }
    */
    static getFormFilters() {
        return document.querySelector(idFormFilters);
    }
    callFilterTableContent() {
        let formFilters = TableBasePage.getFormFilters();
        let filtersJson = DOM.convertForm2JSON(formFilters);
        Utils.consoleLogIfNotProductionEnvironment("callFilterTableContent");
        Utils.consoleLogIfNotProductionEnvironment("formFilters");
        Utils.consoleLogIfNotProductionEnvironment(formFilters);
        Utils.consoleLogIfNotProductionEnvironment("filtersJson");
        Utils.consoleLogIfNotProductionEnvironment(filtersJson);
        this.leave();
        API.goToHash(this.constructor.hash, filtersJson);
    }
    callbackLoadTableContent(response) {
        let table = TableBasePage.getTableMain();
        let bodyTable = table.querySelector('tbody');
        bodyTable.querySelectorAll('tr').forEach(function(row) { row.remove(); });
        let rowsJson = response.data[flagRows];
        if (!Validation.isEmpty(rowsJson) && rowsJson.every(row => row.hasOwnProperty('display_order'))) {
            rowsJson = rowsJson.sort((a, b) => a.display_order - b.display_order);
        }
        rowsJson.forEach(this.loadRowTable.bind(this));
        this.hookupTableMain();
    }
    static getTableMain() {
        return document.querySelector(idTableMain);
    }
    loadRowTable(rowJson) {
        throw new Error("Subclass of TableBasePage must implement method loadRowTable().");
    }
    getAndLoadFilteredTableContentSinglePageApp() {
        this.callFilterTableContent()
            .then(data => {
                Utils.consoleLogIfNotProductionEnvironment('Table data received:', data);
                this.callbackLoadTableContent(data);
            })
            .catch(error => console.error('Error:', error));
    }
    hookupButtonsSaveCancel() {
        this.hookupButtonSave();
        this.hookupButtonCancel();
        this.toggleShowButtonsSaveCancel(false);
    }
    saveRecordsTableDirty() {
        let records = this.getTableRecords(true);
        if (records.length == 0) {
            OverlayError.show('No records to save');
            return;
        }
        let formElement = TableBasePage.getFormFilters();
        let comment = DOM.getElementValueCurrent(document.querySelector(idTextareaConfirm));
        this.callSaveTableContent(records, formElement, comment)
            .then(data => {
                if (data[flagStatus] == flagSuccess) {
                    if (_verbose) { 
                        Utils.consoleLogIfNotProductionEnvironment('Records saved!');
                        Utils.consoleLogIfNotProductionEnvironment('Data received:', data);
                    }
                    this.callFilterTableContent();
                }
                else {
                    Utils.consoleLogIfNotProductionEnvironment("error: ", data[flagMessage]);
                    OverlayError.show(data[flagMessage]);
                }
            })
            .catch(error => console.error('Error:', error));
    }
    getTableRecords(dirtyOnly = false) {
        let records = [];
        let record;
        document.querySelectorAll(idTableMain + ' > tbody > tr').forEach((row) => {
            if (dirtyOnly && !DOM.hasDirtyChildrenContainer(row)) return;
            record = this.getJsonRow(row);
            records.push(record);
        });
        return records;
    }
    getJsonRow(row) {
        throw new Error("Subclass of TableBasePage must implement method getJsonRow().");
    }
    saveRecordsTableDirtySinglePageApp() {
        let records = this.getTableRecords(true);
        if (records.length == 0) {
            OverlayError.show('No records to save');
            return;
        }
        let formElement = TableBasePage.getFormFilters();
        let comment = DOM.getElementValueCurrent(document.querySelector(idTextareaConfirm));
        this.callSaveTableContent(records, formElement, comment)
            .then(data => {
                if (data[flagStatus] == flagSuccess) {
                    if (_verbose) { 
                        Utils.consoleLogIfNotProductionEnvironment('Records saved!');
                        Utils.consoleLogIfNotProductionEnvironment('Data received:', data);
                    }
                    this.callbackLoadTableContent(data);
                }
                else {
                    Utils.consoleLogIfNotProductionEnvironment("error: ", data[flagMessage]);
                    OverlayError.show(data[flagMessage]);
                }
            })
            .catch(error => console.error('Error:', error));
    }
    hookupButtonCancel() {
        Events.initialiseEventHandler('.' + flagContainer + '.' + flagSave + '.' + flagCancel + ' button.' + flagCancel, flagInitialised, (button) => {
            button.addEventListener("click", (event) => {
                event.stopPropagation();
                button = event.target;
                if (button.classList.contains(flagIsCollapsed)) return;
                this.callFilterTableContent();
            });
            button.classList.add(flagIsCollapsed);
        });
    }
    handleClickAddRowTable(event, button) {
        event.stopPropagation();
        _rowBlank.setAttribute(this.constructor.attrIdRowObject, -1 - _rowBlank.getAttribute(this.constructor.attrIdRowObject));
        let tbody = document.querySelector(idTableMain + ' tbody');
        if (tbody.classList.contains(flagIsCollapsed)) return;
        let row = _rowBlank.cloneNode(true);
        row.classList.remove(flagInitialised);
        row.querySelectorAll('.' + flagInitialised).forEach(function(element) {
            element.classList.remove(flagInitialised);
        });
        let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length;
        row.setAttribute(this.constructor.attrIdRowObject, -1 - countRows);
        this.initialiseRowNew(tbody, row);
        tbody.prepend(row);
        tbody.scrollTop = 0;
        this.hookupTableMain();
        this.postInitialiseRowNewCallback(tbody);
    }
    initialiseRowNew(tbody, row) {
        if (this.constructor === TableBasePage) {
            throw new Error("Subclass of TableBasePage must implement method initialiseRowNew().");
        }
        // row.classList.remove(flagRowNew);
    }
    hookupTableMain() {
        if (this.constructor === TableBasePage) {
            throw new Error("Must implement hookupTableMain() method.");
        }
        Events.initialiseEventHandler(idTableMain, flagInitialised, (table) => {
            this.cacheRowBlank();
        });
    }
    cacheRowBlank() {
        let selectorRowNew = idTableMain + ' tbody tr.' + flagRowNew;
        let rowBlankTemp = document.querySelector(selectorRowNew);
        if (rowBlankTemp == null) return;
        Utils.consoleLogIfNotProductionEnvironment("row blank temp: ", rowBlankTemp);
        let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length;
        _rowBlank = rowBlankTemp.cloneNode(true);
        document.querySelectorAll(selectorRowNew).forEach(function(row) {
            row.remove();
        });
        _rowBlank.setAttribute(this.constructor.attrIdRowObject, -1 - countRows);
    }
    postInitialiseRowNewCallback(tbody) {
        if (this.constructor === TableBasePage) {
            throw new Error("Subclass of TableBasePage must implement method postInitialiseRowNewCallback(tbody).");
        }
    }
    initialiseSliderDisplayOrderRowNew(tbody, row) {
        // let tdSelector = ':scope > tr > td.' + flagDisplayOrder;
        // let tbody = document.querySelector('table' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > tbody');
        let slidersDisplayOrder = tbody.querySelectorAll(':scope > tr > td.' + flagDisplayOrder + ' input.' + flagSlider);
        let maxDisplayOrder = 0;
        slidersDisplayOrder.forEach((slider) => {
            maxDisplayOrder = Math.max(maxDisplayOrder, parseFloat(DOM.getElementValueCurrent(slider)));
        });
        let sliderDisplayOrder = row.querySelector('td.' + flagDisplayOrder + ' .' + flagSlider);
        DOM.setElementValuesCurrentAndPrevious(sliderDisplayOrder, maxDisplayOrder + 1);
    }
    hookupSlidersDisplayOrderTable() {
        let selectorDisplayOrder = idTableMain + ' tbody tr td.' + flagDisplayOrder + ' input.' + flagSlider + '.' + flagDisplayOrder;
        this.hookupChangeHandlerTableCells(selectorDisplayOrder);
    }
    hookupChangeHandlerTableCells(inputSelector, handler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        Events.initialiseEventHandler(inputSelector, flagInitialised, (input) => {
            input.addEventListener("change", (event) => {
                handler(event, input);
            });
            handler(null, input);
        });
    }
    handleChangeNestedElementCellTable(event, element) {
        let wasDirtyParentRows = this.getAllIsDirtyRowsInParentTree(element);
        let wasDirtyElement = element.classList.contains(flagDirty);
        let isDirtyElement = DOM.updateAndCheckIsElementDirty(element);
        // Utils.consoleLogIfNotProductionEnvironment({isDirtyElement, wasDirtyElement, wasDirtyParentRows});
        // let td = DOM.getCellFromElement(element);
        // DOM.setElementAttributeValueCurrent(td, DOM.getElementAttributeValueCurrent(element));
        if (isDirtyElement != wasDirtyElement) {
            // DOM.handleDirtyElement(td, isDirtyElement);
            this.updateAndToggleShowButtonsSaveCancel();
            this.cascadeChangedIsDirtyNestedElementCellTable(element, isDirtyElement, wasDirtyParentRows);
        }
    }
    getAllIsDirtyRowsInParentTree(element) {
        let rows = [];
        let parent = element;
        let isDirty;
        while (parent) {
            if (parent.tagName.toUpperCase() == 'TR') {
                isDirty = parent.classList.contains(flagDirty)
                rows.push(isDirty);
            }
            parent = parent.parentElement;
        }
        return rows;
    }
    cascadeChangedIsDirtyNestedElementCellTable(element, isDirtyElement, wasDirtyParentRows) {
        if (Validation.isEmpty(wasDirtyParentRows)) return;
        let tr = DOM.getRowFromElement(element);
        let isDirtyRow = isDirtyElement || DOM.hasDirtyChildrenContainer(tr);
        let wasDirtyRow = wasDirtyParentRows.shift();
        Utils.consoleLogIfNotProductionEnvironment({isDirtyRow, wasDirtyRow});
        if (isDirtyRow != wasDirtyRow) {
            DOM.handleDirtyElement(tr, isDirtyRow);
            this.updateAndToggleShowButtonsSaveCancel();
            this.cascadeChangedIsDirtyNestedElementCellTable(tr.parentElement, isDirtyRow, wasDirtyParentRows);
        }
    }
    hookupChangeHandlerTableCellsWhenNotCollapsed(inputSelector, handler = (event, element) => {
        if (!element.classList.contains(flagIsCollapsed)) this.handleChangeNestedElementCellTable(event, element);
    }) {
        Events.hookupEventHandler("change", inputSelector, handler);
    }
    hookupFieldsCodeTable() {
        this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagCode + ' > .' + flagCode);
    }
    hookupFieldsNameTable() {
        this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagName + ' > .' + flagName);
    }
    hookupFieldsDescriptionTable() {
        this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagDescription + ' > .' + flagDescription);
    }
    hookupFieldsNotesTable() {
        this.hookupChangeHandlerTableCells(idTableMain + ' > tbody > tr > td.' + flagNotes + ' > .' + flagNotes);
    }
    hookupFieldsActive(flagTable = '', handleClickRowNew = (event, element) => { this.handleClickAddRowTable(event, element); }) {
        let selectorButton = 'table.table-main' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > tbody > tr > td.' + flagActive + ' .' + flagButton + '.' + flagActive;
        let selectorButtonDelete = selectorButton + '.' + flagDelete;
        let selectorButtonUndelete = selectorButton + ':not(.' + flagDelete + ')';
        Utils.consoleLogIfNotProductionEnvironment("hookupFieldsActive: ", selectorButtonDelete, selectorButtonUndelete);
        this.hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete);
        this.hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete);
        Events.hookupEventHandler(
            "click"
            , 'table.table-main' + (Validation.isEmpty(flagTable) ? '' : '.' + flagTable) + ' > thead > tr > th.' + flagActive + ' .' + flagButton + '.' + flagActive
            , (event, button) => { handleClickRowNew(event, button); }
        );
    }
    hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        Events.hookupEventHandler("click", selectorButtonDelete, (event, element) => {
            this.handleClickButtonRowDelete(event, element, selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); });
        });
    }
    handleClickButtonRowDelete(event, element, selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        if (element.tagName.toUpperCase() != 'SVG') element = element.parentElement;
        let valuePrevious = DOM.getElementAttributeValuePrevious(element);
        let wasDirty = element.classList.contains(flagDirty);
        let row = DOM.getRowFromElement(element);
        if (row.classList.contains(flagRowNew) && !DOM.hasDirtyChildrenContainer(row)) {
            row.parentNode.removeChild(row);
        }
        else {
            let buttonAddTemplate = document.querySelector(idContainerTemplateElements + ' .' + flagButton + '.' + flagActive + '.' + flagAdd);
            let buttonAdd = buttonAddTemplate.cloneNode(true);
            DOM.setElementAttributeValuePrevious(buttonAdd, valuePrevious);
            DOM.setElementAttributeValueCurrent(buttonAdd, false);
            if (wasDirty) buttonAdd.classList.add(flagDirty);
            element.replaceWith(buttonAdd);
            changeHandler(null, buttonAdd);
            this.hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); });
        }
        this.updateAndToggleShowButtonsSaveCancel();
    }
    hookupButtonsRowUndelete(selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        Events.hookupEventHandler("click", selectorButtonUndelete, (event, element) => {
            this.handleClickButtonRowUndelete(event, element, selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); });
        });
    }
    handleClickButtonRowUndelete(event, element, selectorButtonDelete, selectorButtonUndelete, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        if (element.tagName.toUpperCase() != 'SVG') element = element.parentElement;
        let valuePrevious = DOM.getElementAttributeValuePrevious(element);
        let wasDirty = DOM.isElementDirty(element);
        let buttonDeleteTemplate = document.querySelector(idContainerTemplateElements + ' .' + flagButton + '.' + flagActive + '.' + flagDelete);
        let buttonDelete = buttonDeleteTemplate.cloneNode(true);
        DOM.setElementAttributeValuePrevious(buttonDelete, valuePrevious);
        DOM.setElementAttributeValueCurrent(buttonDelete, true);
        if (wasDirty) buttonDelete.classList.add(flagDirty);
        element.replaceWith(buttonDelete);
        changeHandler(null, buttonDelete);
        this.hookupButtonsRowDelete(selectorButtonDelete, selectorButtonUndelete, (changeEvent, changeElement) => { changeHandler(changeEvent, changeElement); });
        this.updateAndToggleShowButtonsSaveCancel();
    }
    hookupTdsAccessLevel() {
        this.hookupTableCellDdlPreviews(flagAccessLevel, Utils.getListFromDict(accessLevels));
    }
    hookupTableCellDdlPreviews(
        fieldFlag
        , optionList
        , cellSelector = null
        , ddlHookup = (ddlSelector) => { this.hookupTableCellDdls(ddlSelector); }
        , changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }
    ) {
        if (cellSelector == null) cellSelector = idTableMain + ' > tbody > tr > td.' + fieldFlag;
        Events.hookupEventHandler("click", cellSelector + ' div.' + fieldFlag, (event, div) => {
            this.handleClickTableCellDdlPreview(
                event
                , div
                , fieldFlag
                , optionList
                , cellSelector
                , (ddlSelector) => { ddlHookup(
                    ddlSelector
                    , (event, element) => { changeHandler(event, element); }
                ); }
            );
        });
        ddlHookup(cellSelector + ' select.' + fieldFlag);
    }
    hookupTableCellDdls(ddlSelector, changeHandler = (event, element) => { this.handleChangeNestedElementCellTable(event, element); }) {
        this.hookupChangeHandlerTableCells(ddlSelector, (event, element) => { changeHandler(event, element); });
    }
    handleClickTableCellDdlPreview(event, div, fieldFlag, optionObjectList, cellSelector = null, ddlHookup = (cellSelector) => { this.hookupTableCellDdls(cellSelector); }) {
        if (Validation.isEmpty(cellSelector)) cellSelector = idTableMain + ' > tbody > tr > td.' + fieldFlag;
        let idSelected = DOM.getElementAttributeValueCurrent(div);
        let td = DOM.getCellFromElement(div);
        td.innerHTML = '';
        let ddl = document.createElement('select');
        ddl.classList.add(fieldFlag);
        DOM.setElementValuesCurrentAndPrevious(ddl, idSelected);
        let optionJson, option;
        if (_verbose) { 
            Utils.consoleLogIfNotProductionEnvironment("click table cell ddl preview");
            Utils.consoleLogIfNotProductionEnvironment({optionObjectList, cellSelector});
        }
        option = DOM.createOption(null);
        ddl.appendChild(option);
        optionObjectList.forEach((optionObjectJson) => {
            optionJson = BusinessObjects.getOptionJsonFromObjectJson(optionObjectJson, idSelected);
            option = DOM.createOption(optionJson);
            ddl.appendChild(option);
        });
        td.appendChild(ddl);
        let ddlSelector = cellSelector + ' select.' + fieldFlag;
        ddlHookup(ddlSelector);
    }
    /*
    hookupTableCellDDlPreviewsWhenNotCollapsed(cellSelector, optionList, ddlHookup = (event, element) => { this.hookupTableCellDdls(event, element); }) {
        Events.hookupEventHandler("click", cellSelector + ' div', (event, div) => {
            this.handleClickTableCellDdlPreview(event, div, optionList, cellSelector, (event, element) => { ddlHookup(event, element); });
        });
    }
    */
    toggleColumnCollapsed(flagColumn, isCollapsed) {
        this.toggleColumnHasClassnameFlag(flagColumn, isCollapsed, flagIsCollapsed);
    }
    toggleColumnHeaderCollapsed(flagColumn, isCollapsed) {
        this.toggleColumnHasClassnameFlag(flagColumn, isCollapsed, flagIsCollapsed);
    }

    hookupFieldsCommandCategory(idTable = null) {
        if (idTable == null) idTable = idTableMain;
        this.hookupTableCellDdlPreviews(
            flagCommandCategory
            , Utils.getListFromDict(filterCommandCategories).sort((a, b) => a[flagName].localeCompare(b[flagName]))
            , idTable + ' > tbody > tr > td.' + flagCommandCategory // + ' .' + flagCommandCategory
            , (cellSelector) => { this.hookupCommandCategoryDdls(cellSelector); }
        );
    }
    hookupCommandCategoryDdls(ddlSelector) {
        this.hookupChangeHandlerTableCells(ddlSelector, (event, element) => { this.handleChangeCommandCategoryDdl(event, element); });
    }
    handleChangeCommandCategoryDdl(event, ddlCategory) {
        let row = DOM.getRowFromElement(ddlCategory);
        let idCommandCategoryRowOld = this.getIdCommandCategoryRow(row); // DOM.getElementAttributeValueCurrent(ddlCategory);
        this.handleChangeNestedElementCellTable(event, ddlCategory);
        let idCommandCategoryRowNew = this.getIdCommandCategoryRow(row); // DOM.getElementAttributeValueCurrent(ddlCategory);
        if (
            idCommandCategoryRowOld == idCommandCategoryRowNew
            || idCommandCategoryRowNew == 0
        ) return;
        console.log({ idCommandCategoryRowNew, idCommandCategoryRowOld });
        let idCommandCategoryFilter = this.getIdCommandCategoryFilter();
        let tdCommand = row.querySelector('td.' + flagCommand);
        tdCommand.dispatchEvent(new Event('click'));
        let ddlCommand = row.querySelector('td.' + flagCommand + ' select.' + flagCommand);
        ddlCommand.innerHTML = '';
        ddlCommand.appendChild(DOM.createOption(null));
        let optionJson, option;
        let commandsInCategory = Utils.getListFromDict(filterCommands).filter(command => 
            (
                command[attrIdCommandCategory] == idCommandCategoryRowNew
                || idCommandCategoryRowNew == 0
            )
            && (
                   command[attrIdCommandCategory] == idCommandCategoryFilter
                || idCommandCategoryFilter == 0
            )
        );
        let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName]));
        sortedCommands.forEach((command) => {
            optionJson = BusinessObjects.getOptionJsonFromObjectJson(command);
            option = DOM.createOption(optionJson);
            ddlCommand.appendChild(option);
        });
        this.handleChangeNestedElementCellTable(event, ddlCommand);
    }
    hookupFieldsCommand(idTable = null) {
        if (idTable == null) idTable = idTableMain;
        Events.hookupEventHandler("click", idTable + ' > tbody > tr > td.' + flagCommand + ' div.' + flagCommand, (event, div) => {
            Utils.consoleLogIfNotProductionEnvironment(div);
            let parentTr = DOM.getRowFromElement(div);
            Utils.consoleLogIfNotProductionEnvironment({ div, parentTr });
            let tdCommandCategory = parentTr.querySelector('td.' + flagCommandCategory);
            let idCommandCategoryRow = this.getIdCommandCategoryRow(parentTr); // DOM.getElementAttributeValueCurrent(tdCommandCategory);
            let idCommandCategoryFilter = this.getIdCommandCategoryFilter();
            let filterCommandList = Utils.getListFromDict(filterCommands);
            let commandsInCategory = filterCommandList.filter(command => 
                (
                    command[attrIdCommandCategory] == idCommandCategoryRow
                    || idCommandCategoryRow == 0
                )
                && (
                       command[attrIdCommandCategory] == idCommandCategoryFilter
                    || idCommandCategoryFilter == 0
                )
            );
            let sortedCommands = commandsInCategory.sort((a, b) => a[flagName].localeCompare(b[flagName]));
            Utils.consoleLogIfNotProductionEnvironment({ tdCommandCategory, idCommandCategoryRow, idCommandCategoryFilter, filterCommandList, commandsInCategory });
            Utils.consoleLogIfNotProductionEnvironment(filterCommandList);
            this.handleClickTableCellDdlPreview(
                event
                , div
                , flagCommand // fieldFlag
                , sortedCommands // optionList
                , idTable + ' > tbody > tr > td.' + flagCommand // cellSelector
                , (cellSelector) => { this.hookupTableCellDdls(
                    cellSelector
                    , (event, element) => { this.handleChangeCommandDdl(event, element); }
                ); }
            );
        });
        this.hookupTableCellDdls(
            idTable + ' > tbody > tr > td.' + flagCommand + ' select.' + flagCommand
            , (event, element) => { this.handleChangeCommandDdl(event, element); }
        );
    }
    handleChangeCommandDdl(event, ddlCommand) {
        // console.log("handle change command ddl");
        let row = DOM.getRowFromElement(ddlCommand);
        this.handleChangeNestedElementCellTable(event, ddlCommand);
        let idCommandCategoryRowOld = this.getIdCommandCategoryRow(row);
        let idCommandNew = this.getIdCommandRow(row);
        let commandNew = filterCommands[idCommandNew];
        // console.log({ idCommandCategoryRowOld, commandNew });
        if (commandNew == null || idCommandCategoryRowOld == commandNew[attrIdCommandCategory]) return;
        let divCommandCategory = row.querySelector('td.' + flagCommandCategory + ' div');
        if (divCommandCategory) divCommandCategory.dispatchEvent(new Event('click'));
        let ddlCommandCategory = row.querySelector('td.' + flagCommandCategory + ' select.' + flagCommandCategory);
        DOM.setElementValueCurrent(ddlCommandCategory, commandNew[attrIdCommandCategory]);
        // console.log({ ddlCommandCategory, commandNew });
        this.handleChangeNestedElementCellTable(event, ddlCommandCategory);
    }
    getIdCommandCategoryRow(tr) {
        let elementCommandCategory = tr.querySelector('td.' + flagCommandCategory + ' .' + flagCommandCategory);
        return DOM.getElementAttributeValueCurrent(elementCommandCategory);
    }
    getIdCommandCategoryFilter() {
        let formFilters = TableBasePage.getFormFilters();
        let idCommandCategory = 0;
        if (formFilters == null) return idCommandCategory;
        let commandCategoryFilter = formFilters.querySelector('#' + attrIdCommandCategory);
        let commandFilter = formFilters.querySelector('#' + attrIdCommand);
        let valueCurrentCommandCategoryFilter = DOM.getElementAttributeValueCurrent(commandCategoryFilter);
        Utils.consoleLogIfNotProductionEnvironment({ valueCurrentCommandCategoryFilter });
        if (valueCurrentCommandCategoryFilter == "") {
            let valueCurrentCommandFilter = DOM.getElementAttributeValueCurrent(commandFilter);
            Utils.consoleLogIfNotProductionEnvironment({ valueCurrentCommandFilter });
            if (valueCurrentCommandFilter != "") {
                let command = filterCommands[valueCurrentCommandFilter];
                idCommandCategory = command[attrIdCommandCategory];
            }
        } else {
            idCommandCategory = parseInt(valueCurrentCommandCategoryFilter);
        }
        return idCommandCategory;
    }
    getHasCommandCategoryFilter() {
        let idCommandCategoryFilter = this.getIdCommandCategoryFilter();
        return !(Validation.isEmpty(idCommandCategoryFilter) || idCommandCategoryFilter == 0);
    }
    getIdCommandRow(tr) {
        let elementCommand = tr.querySelector('td.' + flagCommand + ' .' + flagCommand);
        return DOM.getElementAttributeValueCurrent(elementCommand);
    }
    getIdCommandFilter() {
        let formFilters = TableBasePage.getFormFilters();
        let commandFilter = formFilters.querySelector('#' + attrIdCommand);
        let valueCurrentCommandFilter = DOM.getElementAttributeValueCurrent(commandFilter);
        let idCommand = parseInt(valueCurrentCommandFilter);
        return idCommand;
    }
    getHasCommandFilter() {
        let idCommandFilter = this.getIdCommandFilter();
        return !(Validation.isEmpty(idCommandFilter) || idCommandFilter == 0);
    }
    /*
    createTdActive(isActive) {
        let tdActive = document.createElement("td");
        tdActive.classList.add(flagActive);
        let buttonActive = document.createElement("button");
        buttonActive.classList.add(flagActive);
        buttonActive.classList.add(isActive ? flagDelete : flagAdd);
        buttonActive.textContent = isActive ? 'x' : '+';
        DOM.setElementAttributesValuesCurrentAndPrevious(buttonActive, isActive);
        tdActive.appendChild(buttonActive);
        return tdActive;
    }
    */
    leave() {
        if (this.constructor === TableBasePage) {
            throw new Error("Must implement leave() method.");
        }
        super.leave();
        let formFilters = TableBasePage.getFormFilters();
        let dataPage = {};
        dataPage[flagFormFilters] = DOM.convertForm2JSON(formFilters);
        this.setLocalStoragePage(dataPage);
    }

    toggleColumnHasClassnameFlag(columnFlag, isRequiredFlag, classnameFlag) {
        let table = TableBasePage.getTableMain();
        let columnTh = table.querySelector('th.' + columnFlag);
        let columnThHasFlag = columnTh.classList.contains(classnameFlag);
        if (isRequiredFlag == columnThHasFlag) return;
        DOM.toggleElementHasClassnameFlag(columnTh, isRequiredFlag, classnameFlag);
    }
    toggleColumnHeaderHasClassnameFlag(columnFlag, isRequiredFlag, classnameFlag) {
        let table = TableBasePage.getTableMain();
        let columnTh = table.querySelector('th.' + columnFlag);
        DOM.toggleElementHasClassnameFlag(columnTh, isRequiredFlag, classnameFlag);
    }

    updateAndToggleShowButtonsSaveCancel() {
        // let pageBody = document.querySelector(idPageBody);
        let isDirty = DOM.hasDirtyChildrenContainer(pageBody);

        let buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel;
        let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave);
        let areVisibleSaveCancelButtons = !buttonSave.classList.contains(flagIsCollapsed);
        
        console.log({ pageBody, isDirty, areVisibleSaveCancelButtons });

        this.toggleShowButtonsSaveCancel(isDirty || areVisibleSaveCancelButtons);
    }
}
