/**
 * DevExtreme (esm/ui/html_editor/modules/tableResizing.js)
 * Version: 21.1.4 (build 21182-0312)
 * Build date: Thu Jul 01 2021
 *
 * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import $ from "../../../core/renderer";
import eventsEngine from "../../../events/core/events_engine";
import {
    isDefined
} from "../../../core/utils/type";
import {
    addNamespace
} from "../../../events/utils/index";
import _windowResizeCallbacks from "../../../core/utils/resize_callbacks";
import {
    move
} from "../../../animation/translator";
import {
    getBoundingRect
} from "../../../core/utils/position";
import BaseModule from "./base";
import Draggable from "../../draggable";
import {
    each
} from "../../../core/utils/iterator";
import {
    getWindow,
    hasWindow
} from "../../../core/utils/window";
import {
    extend
} from "../../../core/utils/extend";
var DX_COLUMN_RESIZE_FRAME_CLASS = "dx-table-resize-frame";
var DX_COLUMN_RESIZER_CLASS = "dx-htmleditor-column-resizer";
var DX_ROW_RESIZER_CLASS = "dx-htmleditor-row-resizer";
var DEFAULT_MIN_COLUMN_WIDTH = 40;
var DEFAULT_MIN_ROW_HEIGHT = DEFAULT_MIN_COLUMN_WIDTH / 2;
var DRAGGABLE_ELEMENT_OFFSET = 2;
var MODULE_NAMESPACE = "dxHtmlTableResizingModule";
var POINTERDOWN_EVENT = addNamespace("dxpointerdown", MODULE_NAMESPACE);
var SCROLL_EVENT = addNamespace("scroll", MODULE_NAMESPACE);
export default class TableResizingModule extends BaseModule {
    constructor(quill, options) {
        var _options$minColumnWid, _options$minRowHeight;
        super(quill, options);
        this.enabled = !!options.enabled;
        this._tableResizeFrames = [];
        this._minColumnWidth = null !== (_options$minColumnWid = options.minColumnWidth) && void 0 !== _options$minColumnWid ? _options$minColumnWid : DEFAULT_MIN_COLUMN_WIDTH;
        this._minRowHeight = null !== (_options$minRowHeight = options.minRowHeight) && void 0 !== _options$minRowHeight ? _options$minRowHeight : DEFAULT_MIN_ROW_HEIGHT;
        this._quillContainer = this.editorInstance._getQuillContainer();
        this._tableData = [];
        if (this.enabled) {
            this.editorInstance.addContentInitializedCallback(() => {
                var $tables = this._findTables();
                if ($tables.length) {
                    this._fixTablesWidths($tables);
                    this._createResizeFrames($tables);
                    this._updateFramesPositions();
                    this._updateFramesSeparators()
                }
                this._attachEvents()
            });
            this.addCleanCallback(this.clean.bind(this));
            this._resizeHandler = _windowResizeCallbacks.add(this._resizeHandler.bind(this))
        }
    }
    _attachEvents() {
        eventsEngine.on(this.editorInstance._getContent(), SCROLL_EVENT, this._updateFramesPositions.bind(this));
        this.quill.on("text-change", this._getQuillTextChangeHandler())
    }
    _detachEvents() {
        eventsEngine.off(this.editorInstance._getContent(), MODULE_NAMESPACE);
        this.quill.off("text-change", this._quillTextChangeHandler)
    }
    _getQuillTextChangeHandler(delta, oldContent, source) {
        return (delta, oldContent, source) => {
            if (this._isTableChanging()) {
                var $tables = this._findTables();
                this._removeResizeFrames();
                if ("api" === source) {
                    this._fixTablesWidths($tables)
                }
                this._updateTablesColumnsWidth($tables);
                this._createResizeFrames($tables);
                this._updateFramesPositions();
                this._updateFramesSeparators()
            } else {
                this._updateFramesPositions()
            }
        }
    }
    _getFrameForTable($table) {
        var _this$_framesForTable;
        return null === (_this$_framesForTable = this._framesForTables) || void 0 === _this$_framesForTable ? void 0 : _this$_framesForTable.get($table.get(0))
    }
    _resizeHandler() {
        this._windowResizeTimeout = setTimeout(() => {
            var $tables = this._findTables();
            each($tables, (index, table) => {
                var $table = $(table);
                var frame = this._tableResizeFrames[index];
                var actualTableWidth = $table.outerWidth();
                var lastTableWidth = this._tableLastWidth(frame);
                if (Math.abs(actualTableWidth - lastTableWidth) > 1) {
                    this._tableLastWidth(frame, actualTableWidth);
                    this._updateColumnsWidth($table, index)
                }
            });
            this._updateFramesPositions();
            this._updateFramesSeparators()
        })
    }
    _findTables() {
        return $(this._quillContainer).find("table")
    }
    _getWidthAttrValue($element) {
        var attrValue = $element.attr("width");
        return attrValue ? parseInt(attrValue) : void 0
    }
    _tableLastWidth(frame, newValue) {
        if (isDefined(newValue)) {
            frame.lastWidth = newValue
        } else {
            return null === frame || void 0 === frame ? void 0 : frame.lastWidth
        }
    }
    _fixTablesWidths($tables) {
        each($tables, (index, table) => {
            var $table = $(table);
            var $columnElements = this._getTableDeterminantElements($table, "horizontal");
            if (!this._tableResizeFrames[index]) {
                this._tableResizeFrames[index] = {
                    lastWidth: void 0
                }
            }
            var frame = this._getFrameForTable($table);
            if (!frame) {
                this._tableResizeFrames.push({
                    $table: $table
                })
            }
            if ($columnElements.eq(0).attr("width")) {
                var _this$_tableLastWidth;
                var columnsSum = 0;
                each($columnElements, (_, element) => {
                    var $element = $(element);
                    var columnWidth = $element.attr("width") ? this._getWidthAttrValue($element) : $element.outerWidth();
                    columnsSum += columnWidth
                });
                $table.css("width", "auto");
                var tableWidth = null !== (_this$_tableLastWidth = this._tableLastWidth(frame)) && void 0 !== _this$_tableLastWidth ? _this$_tableLastWidth : $table.outerWidth();
                if (frame) {
                    this._tableLastWidth(frame, Math.max(columnsSum, tableWidth))
                }
            }
        })
    }
    _createResizeFrames($tables) {
        this._framesForTables = new Map;
        $tables.each((index, table) => {
            var _this$_tableResizeFra;
            var $table = $(table);
            var $lastTable = null === (_this$_tableResizeFra = this._tableResizeFrames[index]) || void 0 === _this$_tableResizeFra ? void 0 : _this$_tableResizeFra.$table;
            var $tableLastWidth = this._tableResizeFrames[index].lastWidth;
            this._tableResizeFrames[index] = {
                $frame: this._createTableResizeFrame(table),
                $table: $table,
                index: index,
                lastWidth: $lastTable && table === $lastTable.get(0) ? $tableLastWidth : void 0,
                columnsCount: this._getTableDeterminantElements($table, "horizontal").length,
                rowsCount: this._getTableDeterminantElements($table, "vertical").length
            };
            this._framesForTables.set(table, this._tableResizeFrames[index])
        });
        this._tableResizeFrames.length = $tables.length
    }
    _isTableChanging() {
        var $tables = this._findTables();
        var result = false;
        if ($tables.length !== this._tableResizeFrames.length) {
            result = true
        } else {
            each($tables, (index, table) => {
                var $table = $(table);
                var frame = this._tableResizeFrames[index];
                var isColumnsCountChanged = (null === frame || void 0 === frame ? void 0 : frame.columnsCount) !== this._getTableDeterminantElements($table, "horizontal").length;
                var isRowCountChanged = (null === frame || void 0 === frame ? void 0 : frame.rowsCount) !== this._getTableDeterminantElements($table, "vertical").length;
                if (isColumnsCountChanged || isRowCountChanged) {
                    result = true;
                    return false
                }
            })
        }
        return result
    }
    _removeResizeFrames(clearArray) {
        var _this$_framesForTable2;
        each(this._tableResizeFrames, (index, resizeFrame) => {
            if (resizeFrame.$frame) {
                var _resizeFrame$$frame;
                var resizerElementsSelector = ".".concat(DX_COLUMN_RESIZER_CLASS, ", .").concat(DX_ROW_RESIZER_CLASS);
                this._detachSeparatorEvents(null === (_resizeFrame$$frame = resizeFrame.$frame) || void 0 === _resizeFrame$$frame ? void 0 : _resizeFrame$$frame.find(resizerElementsSelector));
                resizeFrame.$frame.remove()
            }
        });
        null === (_this$_framesForTable2 = this._framesForTables) || void 0 === _this$_framesForTable2 ? void 0 : _this$_framesForTable2.clear();
        if (clearArray) {
            this._tableResizeFrames = []
        }
    }
    _detachSeparatorEvents($lineSeparators) {
        $lineSeparators.each((i, $lineSeparator) => {
            eventsEngine.off($lineSeparator, POINTERDOWN_EVENT)
        })
    }
    _createTableResizeFrame() {
        return $("<div>").addClass(DX_COLUMN_RESIZE_FRAME_CLASS).appendTo(this._quillContainer)
    }
    _updateFramesPositions() {
        each(this._tableResizeFrames, (index, tableResizeFrame) => {
            this._updateFramePosition(tableResizeFrame.$table, tableResizeFrame.$frame)
        })
    }
    _updateFramePosition($table, $frame) {
        var {
            height: height,
            width: width,
            top: targetTop,
            left: targetLeft
        } = getBoundingRect($table.get(0));
        var {
            top: containerTop,
            left: containerLeft
        } = getBoundingRect(this.quill.root);
        $frame.css({
            height: height,
            width: width,
            top: targetTop - containerTop,
            left: targetLeft - containerLeft
        });
        move($frame, {
            left: 0,
            top: 0
        })
    }
    _updateFramesSeparators() {
        each(this._tableResizeFrames, (index, frame) => {
            this._updateFrameSeparators(frame, "vertical");
            this._updateFrameSeparators(frame, "horizontal")
        })
    }
    _isDraggable($element) {
        return $element.hasClass("dx-draggable") && $element.is(":visible")
    }
    _removeDraggable($currentLineSeparator, lineResizerClass) {
        if (this._isDraggable($currentLineSeparator)) {
            var draggable = $($currentLineSeparator).dxDraggable("instance");
            draggable.dispose();
            $($currentLineSeparator).addClass(lineResizerClass)
        }
    }
    _getDirectionInfo(direction) {
        if ("vertical" === direction) {
            return {
                lineResizerClass: DX_ROW_RESIZER_CLASS,
                sizeFunction: "outerHeight",
                positionCoordinate: "top",
                positionStyleProperty: "height",
                positionCoordinateName: "y"
            }
        } else {
            return {
                lineResizerClass: DX_COLUMN_RESIZER_CLASS,
                sizeFunction: "outerWidth",
                positionCoordinate: this.editorInstance.option("rtlEnabled") ? "right" : "left",
                positionStyleProperty: "width",
                positionCoordinateName: "x"
            }
        }
    }
    _getSize($element, directionInfo) {
        return $element[directionInfo.sizeFunction]()
    }
    _updateFrameSeparators(frame, direction) {
        var $determinantElements = this._getTableDeterminantElements(frame.$table, direction);
        var determinantElementsCount = $determinantElements.length;
        var determinantElementsSeparatorsCount = determinantElementsCount - 1;
        var directionInfo = this._getDirectionInfo(direction);
        var lineSeparators = frame.$frame.find(".".concat(directionInfo.lineResizerClass));
        var styleOptions = {
            transform: "none"
        };
        var currentPosition = 0;
        for (var i = 0; i <= determinantElementsSeparatorsCount; i++) {
            currentPosition += this._getSize($determinantElements.eq(i), directionInfo);
            if (!isDefined(lineSeparators[i])) {
                lineSeparators[i] = $("<div>").addClass(directionInfo.lineResizerClass).appendTo(frame.$frame).get(0)
            }
            var $currentLineSeparator = $(lineSeparators[i]);
            this._removeDraggable($currentLineSeparator, directionInfo.lineResizerClass);
            styleOptions[directionInfo.positionCoordinate] = currentPosition - DRAGGABLE_ELEMENT_OFFSET;
            $($currentLineSeparator).css(styleOptions);
            var attachSeparatorData = {
                lineSeparator: lineSeparators[i],
                index: i,
                $determinantElements: $determinantElements,
                frame: frame,
                direction: direction
            };
            this._attachColumnSeparatorEvents(attachSeparatorData)
        }
    }
    _getTableDeterminantElements($table, direction) {
        if ("vertical" === direction) {
            return $table.find("td:first-child")
        } else {
            var $theadElements = $table.find("th");
            if ($theadElements.length) {
                return $theadElements
            } else {
                return $table.find("tr").eq(0).find("td")
            }
        }
    }
    _attachColumnSeparatorEvents(options) {
        eventsEngine.on(options.lineSeparator, POINTERDOWN_EVENT, () => {
            this._createDraggableElement(options)
        })
    }
    _dragStartHandler(_ref) {
        var {
            $determinantElements: $determinantElements,
            index: index,
            frame: frame,
            direction: direction
        } = _ref;
        var directionInfo = this._getDirectionInfo(direction);
        this._fixColumnsWidth(frame.$table);
        this._startLineSize = parseInt(this._getSize($($determinantElements[index]), directionInfo));
        this._nextLineSize = 0;
        if ($determinantElements[index + 1]) {
            this._nextLineSize = parseInt(this._getSize($($determinantElements[index + 1]), directionInfo))
        } else if ("horizontal" === direction) {
            frame.$table.css("width", "auto")
        }
    }
    _shouldRevertOffset(direction) {
        return "horizontal" === direction && this.editorInstance.option("rtlEnabled")
    }
    _getLineElements($table, index) {
        return $table.find("td:nth-child(".concat(1 + index, ")"))
    }
    _dragMoveHandler(event, _ref2) {
        var {
            $determinantElements: $determinantElements,
            index: index,
            frame: frame,
            direction: direction
        } = _ref2;
        var directionInfo = this._getDirectionInfo(direction);
        var eventOffset = event.offset[directionInfo.positionCoordinateName];
        if (this._shouldRevertOffset(direction)) {
            eventOffset = -eventOffset
        }
        var currentLineNewSize = this._startLineSize + eventOffset;
        if ("horizontal" === direction) {
            var nextColumnNewSize = this._nextLineSize && this._nextLineSize - eventOffset;
            var isCurrentColumnHasEnoughPlace = currentLineNewSize >= this._minColumnWidth;
            var isNextColumnHasEnoughPlace = !this._nextLineSize || nextColumnNewSize >= this._minColumnWidth;
            var $lineElements = this._getLineElements(frame.$table, index);
            var $nextLineElements = this._getLineElements(frame.$table, index + 1);
            if (isCurrentColumnHasEnoughPlace && isNextColumnHasEnoughPlace) {
                $lineElements.each((i, element) => {
                    $(element).attr(directionInfo.positionStyleProperty, currentLineNewSize + "px")
                });
                if (this._nextLineSize) {
                    $nextLineElements.each((i, element) => {
                        $(element).attr(directionInfo.positionStyleProperty, nextColumnNewSize + "px")
                    })
                }
            }
        } else {
            var newHeight = Math.max(currentLineNewSize, this._minRowHeight);
            $determinantElements.eq(index).attr(directionInfo.positionStyleProperty, newHeight + "px")
        }
        this._updateFramePosition(frame.$table, frame.$frame)
    }
    _isLastColumnResizing(_ref3) {
        var {
            $determinantElements: $determinantElements,
            index: index
        } = _ref3;
        return !isDefined($determinantElements[index + 1])
    }
    _getBoundaryConfig(options) {
        var result = {};
        if ("vertical" === options.direction) {
            result.boundary = options.frame.$table;
            result.boundOffset = {
                bottom: hasWindow() ? -$(getWindow()).height() : -$(this._quillContainer).outerHeight(),
                top: 0,
                left: 0,
                right: 0
            }
        } else if (!this._isLastColumnResizing(options)) {
            result.boundary = options.frame.$table
        } else {
            result.boundary = $(this._quillContainer)
        }
        return result
    }
    _createDraggableElement(options) {
        var boundaryConfig = this._getBoundaryConfig(options);
        var config = {
            contentTemplate: null,
            allowMoveByClick: false,
            dragDirection: options.direction,
            onDragMove: _ref4 => {
                var {
                    component: component,
                    event: event
                } = _ref4;
                this._dragMoveHandler(event, options)
            },
            onDragStart: () => {
                this._dragStartHandler(options)
            },
            onDragEnd: () => {
                this._tableLastWidth(options.frame, options.frame.$table.outerWidth());
                this._updateFramesPositions();
                this._updateFramesSeparators()
            }
        };
        extend(config, boundaryConfig);
        this._currentDraggableElement = this.editorInstance._createComponent(options.lineSeparator, Draggable, config)
    }
    _fixColumnsWidth($table) {
        var determinantElements = this._getTableDeterminantElements($table);
        each(determinantElements, (index, element) => {
            var columnWidth = $(element).outerWidth();
            $(element).attr("width", Math.max(columnWidth, DEFAULT_MIN_COLUMN_WIDTH) + "px")
        })
    }
    _updateColumnsWidth($table, frameIndex) {
        var determinantElements = this._getTableDeterminantElements($table);
        var frame = this._tableResizeFrames[frameIndex];
        if (!frame) {
            this._tableResizeFrames[frameIndex] = {}
        }
        frame = this._tableResizeFrames[frameIndex];
        var tableWidth = this._tableLastWidth(frame) || $table.outerWidth();
        var columnsWidths = [];
        var columnSum = 0;
        var ratio;
        each(determinantElements, (index, element) => {
            var $element = $(element);
            var columnWidth = this._getWidthAttrValue($element) || $element.outerWidth();
            columnsWidths[index] = Math.max(columnWidth, this._minColumnWidth);
            columnSum += columnsWidths[index]
        });
        var minWidthForColumns = determinantElements.length * this._minColumnWidth;
        if (columnSum > minWidthForColumns) {
            ratio = (tableWidth - minWidthForColumns) / (columnSum - minWidthForColumns)
        } else {
            ratio = -1
        }
        this._tableLastWidth(frame, ratio > 0 ? tableWidth : minWidthForColumns);
        each(determinantElements, index => {
            var $lineElements = this._getLineElements($table, index);
            var resultWidth;
            if (ratio > 0) {
                resultWidth = this._minColumnWidth + Math.round((columnsWidths[index] - this._minColumnWidth) * ratio)
            } else {
                resultWidth = this._minColumnWidth
            }
            $lineElements.each((i, element) => {
                $(element).attr("width", resultWidth + "px")
            })
        })
    }
    _updateTablesColumnsWidth($tables) {
        each($tables, (index, table) => {
            this._updateColumnsWidth($(table), index)
        })
    }
    clean() {
        this._removeResizeFrames(true);
        this._detachEvents();
        _windowResizeCallbacks.remove(this._resizeHandler);
        clearTimeout(this._windowResizeTimeout);
        this._resizeHandler = void 0;
        clearTimeout(this._attachResizerTimeout)
    }
}
