import { isObject, merge } from "@visactor/vutils";

import { Generator } from "../../common/generator";

import { createGroup, createLine, createRect, createRichText, getRichTextBounds, RichText } from "../../graphic";

import { EditModule, findConfigIndexByCursorIdx, getDefaultCharacterConfig } from "./edit-module";

import { application } from "../../application";

import { getWordStartEndIdx } from "../../graphic/richtext/utils";

class Selection {
    constructor(selectionStartCursorIdx, curCursorIdx, rt) {
        this.curCursorIdx = curCursorIdx, this.selectionStartCursorIdx = selectionStartCursorIdx, 
        this.rt = rt;
    }
    isEmpty() {
        return this.selectionStartCursorIdx === this.curCursorIdx;
    }
    getSelectionPureText() {
        const minCursorIdx = Math.min(this.selectionStartCursorIdx, this.curCursorIdx), maxCursorIdx = Math.max(this.selectionStartCursorIdx, this.curCursorIdx);
        if (minCursorIdx === maxCursorIdx) return "";
        const config = this.rt.attribute.textConfig, startIdx = findConfigIndexByCursorIdx(config, Math.ceil(minCursorIdx)), endIdx = findConfigIndexByCursorIdx(config, Math.floor(maxCursorIdx));
        let str = "";
        for (let i = startIdx; i <= endIdx; i++) str += config[i].text;
        return str;
    }
    hasFormat(key) {
        return null != this.getFormat(key);
    }
    _getFormat(key, cursorIdx) {
        var _a;
        if (!this.rt) return null;
        let idx = Math.round(cursorIdx);
        const config = this.rt.attribute.textConfig;
        if (!config.length) return null;
        for (let i = 0; i < config.length; i++) if ("\n" !== config[i].text && (idx--, idx < 0)) return config[i][key];
        return null !== (_a = config[Math.min(idx, config.length - 1)][key]) && void 0 !== _a ? _a : this.rt.attribute[key];
    }
    getFormat(key, supportOutAttr = !1) {
        return this.getAllFormat(key, supportOutAttr)[0];
    }
    getAllFormat(key, supportOutAttr = !1) {
        var _a, _b, _c, _d;
        const valSet = new Set, minCursorIdx = Math.min(this.selectionStartCursorIdx, this.curCursorIdx), maxCursorIdx = Math.max(this.selectionStartCursorIdx, this.curCursorIdx);
        if (minCursorIdx === maxCursorIdx) return supportOutAttr ? [ null !== (_a = this._getFormat(key, minCursorIdx)) && void 0 !== _a ? _a : (null === (_b = this.rt) || void 0 === _b ? void 0 : _b.attribute)[key] ] : [ this._getFormat(key, minCursorIdx) ];
        for (let i = Math.ceil(minCursorIdx); i <= Math.floor(maxCursorIdx); i++) {
            const val = supportOutAttr ? null !== (_c = this._getFormat(key, i)) && void 0 !== _c ? _c : (null === (_d = this.rt) || void 0 === _d ? void 0 : _d.attribute)[key] : this._getFormat(key, i);
            val && valSet.add(val);
        }
        return Array.from(valSet.values());
    }
}

export const FORMAT_TEXT_COMMAND = "FORMAT_TEXT_COMMAND";

export const FORMAT_ALL_TEXT_COMMAND = "FORMAT_ALL_TEXT_COMMAND";

export const FORMAT_ELEMENT_COMMAND = "FORMAT_ELEMENT_COMMAND";

export class RichTextEditPlugin {
    static tryUpdateRichtext(richtext) {
        const cache = richtext.getFrameCache();
        if (!RichText.AllSingleCharacter(cache)) {
            const tc = RichText.TransformTextConfig2SingleCharacter(richtext.attribute.textConfig);
            richtext.setAttributes({
                textConfig: tc
            }), richtext.doUpdateFrameCache(tc);
        }
    }
    static CreateSelection(rt) {
        if (!rt) return null;
        const {textConfig: textConfig = []} = rt.attribute;
        return new Selection(0, textConfig.length - 1, rt);
    }
    constructor() {
        this.name = "RichTextEditPlugin", this.activeEvent = "onRegister", this._uid = Generator.GenAutoIncrementId(), 
        this.key = this.name + this._uid, this.editing = !1, this.focusing = !1, this.pointerDown = !1, 
        this.formatTextCommandCb = (payload, p) => {
            const rt = p.currRt;
            if (!rt) return;
            const selectionData = p.getSelection();
            if (!selectionData) return;
            const {selectionStartCursorIdx: selectionStartCursorIdx, curCursorIdx: curCursorIdx} = selectionData, minCursorIdx = Math.min(selectionStartCursorIdx, curCursorIdx), maxCursorIdx = Math.max(selectionStartCursorIdx, curCursorIdx), minConfigIdx = findConfigIndexByCursorIdx(rt.attribute.textConfig, minCursorIdx), maxConfigIdx = findConfigIndexByCursorIdx(rt.attribute.textConfig, maxCursorIdx), config = rt.attribute.textConfig.slice(minConfigIdx, maxConfigIdx);
            this._formatTextCommand(payload, config, rt);
        }, this.formatAllTextCommandCb = (payload, p) => {
            const rt = p.currRt;
            if (!rt) return;
            const config = rt.attribute.textConfig;
            this._formatTextCommand(payload, config, rt);
        }, this.handleKeyDown = e => {
            this.currRt && this.editing && (this.copyToClipboard(e) || this.fullSelectionKeyHandler(e) || this.directKeyHandler(e));
        }, this.handleInput = (text, isComposing, cursorIdx, rt) => {
            this.currRt && (this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.hideSelection(), 
            this.updateCbs.forEach((cb => cb("input", this))));
        }, this.handleChange = (text, isComposing, cursorIdx, rt) => {
            if (!this.currRt) return;
            this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.curCursorIdx = cursorIdx, 
            this.selectionStartCursorIdx = cursorIdx;
            const p = this.computedCursorPosByCursorIdx(cursorIdx, rt);
            this.setCursorAndTextArea(p.x, p.y1, p.y2, rt), this.hideSelection(), this.updateCbs.forEach((cb => cb("change", this)));
        }, this.handleFocusIn = () => {
            throw new Error("不会走到这里 handleFocusIn");
        }, this.handleFocusOut = () => {
            throw new Error("不会走到这里 handleFocusOut");
        }, this.handleMove = e => {
            this.currRt && !this.currRt.attribute.editable && this.deFocus(!0), this.isEditableRichtext(e) ? (this.handleEnter(), 
            e.target.once("pointerleave", this.handleLeave, {
                capture: !0
            }), this.tryShowSelection(e, !1)) : this.handleLeave();
        }, this.handleEnter = () => {
            this.editing = !0, this.pluginService.stage.setCursor("text");
        }, this.handleLeave = () => {
            this.editing = !1, this.pluginService.stage.setCursor("default");
        }, this.handlePointerDown = e => {
            this.editing && this.isEditableRichtext(e) ? this.onFocus(e) : this.deFocus(!0), 
            this.triggerRender(), this.pointerDown = !0, this.updateCbs.forEach((cb => cb(this.editing ? "onfocus" : "defocus", this)));
        }, this.handlePointerUp = e => {
            this.pointerDown = !1;
        }, this.handleDBLClick = e => {
            this.editing && this.tryShowSelection(e, !0);
        }, this.commandCbs = new Map, this.commandCbs.set(FORMAT_TEXT_COMMAND, [ this.formatTextCommandCb ]), 
        this.commandCbs.set(FORMAT_ALL_TEXT_COMMAND, [ this.formatAllTextCommandCb ]), this.updateCbs = [], 
        this.deltaX = 0, this.deltaY = 0;
    }
    _formatTextCommand(payload, config, rt) {
        "bold" === payload ? config.forEach((item => item.fontWeight = "bold")) : "italic" === payload ? config.forEach((item => item.fontStyle = "italic")) : "underline" === payload ? config.forEach((item => item.underline = !0)) : "lineThrough" === payload ? config.forEach((item => item.lineThrough = !0)) : isObject(payload) && config.forEach((item => merge(item, payload))), 
        rt.setAttributes(rt.attribute);
        const cache = rt.getFrameCache();
        cache && (this.selectionRangeByCursorIdx(this.selectionStartCursorIdx, this.curCursorIdx, cache), 
        this.tryShowInputBounds());
    }
    dispatchCommand(command, payload) {
        const cbs = this.commandCbs.get(command);
        cbs && cbs.forEach((cb => cb(payload, this))), this.updateCbs.forEach((cb => cb("dispatch", this)));
    }
    registerCommand(command, cb) {
        (this.commandCbs.get(command) || []).push(cb);
    }
    removeCommand(command, cb) {
        const cbs = this.commandCbs.get(command) || [], idx = cbs.indexOf(cb);
        idx > -1 && cbs.splice(idx, 1);
    }
    registerUpdateListener(cb) {
        (this.updateCbs || []).push(cb);
    }
    removeUpdateListener(cb) {
        const cbs = this.updateCbs || [], idx = cbs.indexOf(cb);
        idx > -1 && cbs.splice(idx, 1);
    }
    activate(context) {
        this.pluginService = context, this.editModule = new EditModule, context.stage.on("pointermove", this.handleMove, {
            capture: !0
        }), context.stage.on("pointerdown", this.handlePointerDown, {
            capture: !0
        }), context.stage.on("pointerup", this.handlePointerUp, {
            capture: !0
        }), context.stage.on("pointerleave", this.handlePointerUp, {
            capture: !0
        }), context.stage.on("dblclick", this.handleDBLClick, {
            capture: !0
        }), application.global.addEventListener("keydown", this.handleKeyDown), this.editModule.onInput(this.handleInput), 
        this.editModule.onChange(this.handleChange), this.editModule.onFocusOut(this.handleFocusOut), 
        this.timeline = this.createTimeline && this.createTimeline(), this.ticker = this.createTicker && this.createTicker(context.stage);
    }
    copyToClipboard(e) {
        if (application.global.isMacOS() && e.metaKey && "c" === e.key || !application.global.isMacOS() && e.ctrlKey && "c" === e.key) {
            const text = this.getSelection().getSelectionPureText();
            return application.global.copyToClipBoard(text), e.preventDefault(), !0;
        }
        return !1;
    }
    selectionRange(startIdx, endIdx) {
        const currRt = this.currRt;
        if (!currRt) return;
        const cache = currRt.getFrameCache();
        if (!cache) return;
        const {lines: lines} = cache, totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
        startIdx > endIdx && ([startIdx, endIdx] = [ endIdx, startIdx ]), startIdx = Math.min(Math.max(startIdx, -.1), totalCursorCount + .1), 
        endIdx = Math.min(Math.max(endIdx, -.1), totalCursorCount + .1), this.selectionRangeByCursorIdx(startIdx, endIdx, cache);
    }
    selectionRangeByCursorIdx(startCursorIdx, endCursorIdx, cache) {
        this.curCursorIdx = endCursorIdx, this.selectionStartCursorIdx = startCursorIdx;
        const {x: x, y1: y1, y2: y2} = this.computedCursorPosByCursorIdx(this.selectionStartCursorIdx, this.currRt);
        this.startCursorPos = {
            x: x,
            y: (y1 + y2) / 2
        };
        const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt);
        this.setCursorAndTextArea(pos.x, pos.y1, pos.y2, this.currRt), this._tryShowSelection(pos, cache);
    }
    fullSelection() {
        const currRt = this.currRt;
        if (!currRt) return;
        const cache = currRt.getFrameCache();
        if (!cache) return;
        const {lines: lines} = cache;
        if (!lines.length || !lines[0].paragraphs.length) return;
        const totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
        this.selectionRange(-.1, totalCursorCount + .1);
    }
    fullSelectionKeyHandler(e) {
        return !!(application.global.isMacOS() && e.metaKey && "a" === e.key || !application.global.isMacOS() && e.ctrlKey && "a" === e.key) && (this.fullSelection(), 
        e.preventDefault(), !0);
    }
    directKeyHandler(e) {
        if ("ArrowUp" !== e.key && "ArrowDown" !== e.key && "ArrowLeft" !== e.key && "ArrowRight" !== e.key) return !1;
        const cache = this.currRt.getFrameCache();
        if (!cache) return !1;
        let x = 0, y = 0;
        "ArrowUp" === e.key ? y = -1 : "ArrowDown" === e.key ? y = 1 : "ArrowLeft" === e.key ? x = -1 : "ArrowRight" === e.key && (x = 1);
        const {lineInfo: lineInfo, columnInfo: columnInfo} = this.getColumnByIndex(cache, Math.round(this.curCursorIdx)), {lines: lines} = cache, totalCursorCount = lines.reduce(((total, line) => total + line.paragraphs.length), 0) - 1;
        if (x) {
            x > 0 && columnInfo === lineInfo.paragraphs[lineInfo.paragraphs.length - 2] && this.curCursorIdx < Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx + .2 : x > 0 && columnInfo === lineInfo.paragraphs[lineInfo.paragraphs.length - 1] && this.curCursorIdx > Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx + 1 - .2 : x < 0 && columnInfo === lineInfo.paragraphs[0] && this.curCursorIdx > Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx - .2 : x < 0 && columnInfo === lineInfo.paragraphs[0] && this.curCursorIdx < Math.round(this.curCursorIdx) ? this.curCursorIdx = this.curCursorIdx - 1 + .2 : this.curCursorIdx += x, 
            this.curCursorIdx < -.1 ? this.curCursorIdx = -.1 : this.curCursorIdx > totalCursorCount + .1 && (this.curCursorIdx = totalCursorCount + .1), 
            this.selectionStartCursorIdx = this.curCursorIdx;
            const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt);
            this.setCursorAndTextArea(pos.x, pos.y1, pos.y2, this.currRt), this.hideSelection();
        }
        if (y) {
            if (y > 0 && lineInfo === cache.lines[cache.lines.length - 1]) return;
            if (y < 0 && lineInfo === cache.lines[0]) return;
            const lineIdx = cache.lines.findIndex((item => item === lineInfo)) + y;
            if (lineIdx < 0 || lineIdx >= cache.lines.length) return;
            const pos = this.computedCursorPosByCursorIdx(this.curCursorIdx, this.currRt), posX = pos.x;
            let posY = (pos.y1 + pos.y2) / 2;
            posY += y * lineInfo.height;
            const nextLineInfo = cache.lines[lineIdx], {columnInfo: columnInfo, delta: delta} = this.getColumnAndIndexByLinePoint(nextLineInfo, {
                x: posX,
                y: posY
            });
            if (!columnInfo) return;
            let cursorIdx = this.getColumnIndex(cache, columnInfo) + delta;
            const data = this.computedCursorPosByCursorIdx(cursorIdx, this.currRt);
            cursorIdx < -.1 ? cursorIdx = -.1 : cursorIdx > totalCursorCount + .1 && (cursorIdx = totalCursorCount + .1), 
            this.curCursorIdx = cursorIdx, this.selectionStartCursorIdx = cursorIdx, this.setCursorAndTextArea(data.x, data.y1, data.y2, this.currRt);
        }
        return !0;
    }
    tryShowShadowPlaceholder() {
        if (!this.currRt) return;
        const shadowRoot = this.currRt.shadowRoot;
        if (shadowRoot) {
            const placeholder = shadowRoot.getElementsByType("richtext")[0];
            placeholder && shadowRoot.removeChild(placeholder);
        }
        const {textConfig: textConfig, editOptions: editOptions = {}} = this.currRt.attribute;
        if (textConfig && textConfig.length) return;
        if (!editOptions || !editOptions.placeholder) return;
        const {placeholder: placeholder, placeholderColor: placeholderColor = "rgba(0, 0, 0, 0.6)", placeholderFontFamily: placeholderFontFamily, placeholderFontSize: placeholderFontSize} = editOptions, shadow = this.getShadow(this.currRt), textConfigItem = Object.assign(Object.assign({}, getDefaultCharacterConfig(this.currRt.attribute)), {
            text: placeholder
        });
        placeholderColor && (textConfigItem.fill = placeholderColor), placeholderFontFamily && (textConfigItem.fontFamily = placeholderFontFamily), 
        placeholderFontSize && (textConfigItem.fontSize = placeholderFontSize), this.shadowPlaceHolder = createRichText(Object.assign(Object.assign({}, this.currRt.attribute), {
            x: 0,
            y: 0,
            dx: -this.deltaX,
            dy: -this.deltaY,
            angle: 0,
            textConfig: [ textConfigItem ]
        })), shadow.add(this.shadowPlaceHolder);
    }
    getRichTextAABBBounds(rt) {
        const {attribute: attribute} = rt;
        return attribute.textConfig.length ? rt.AABBBounds : getRichTextBounds(Object.assign(Object.assign({}, this.shadowPlaceHolder.attribute), {
            x: attribute.x,
            y: attribute.y,
            textAlign: attribute.textAlign,
            boundsMode: "accurate"
        }));
    }
    tryShowInputBounds() {
        if (!this.currRt || !this.focusing) return;
        const {editOptions: editOptions = {}} = this.currRt.attribute, {boundsStrokeWhenInput: boundsStrokeWhenInput} = editOptions;
        if (!editOptions || !boundsStrokeWhenInput) return;
        this.offsetShadowRoot();
        const b = this.getRichTextAABBBounds(this.currRt), height = b.height(), width = b.width();
        this.shadowBounds = this.shadowBounds || createRect({}), this.shadowBounds.setAttributes({
            x: 0,
            y: 0,
            width: width,
            height: height,
            fill: !1,
            stroke: boundsStrokeWhenInput,
            lineWidth: 1,
            zIndex: -1
        });
        const shadow = this.getShadow(this.currRt);
        this.addEditLineOrBgOrBounds(this.shadowBounds, shadow), this.offsetLineBgAndShadowBounds();
    }
    trySyncPlaceholderToTextConfig() {
        if (!this.currRt) return;
        const {textConfig: textConfig, editOptions: editOptions} = this.currRt.attribute;
        if (textConfig && textConfig.length) return;
        if (!(editOptions && editOptions.placeholder && editOptions.syncPlaceholderToTextConfig)) return;
        const {placeholder: placeholder} = editOptions;
        this.currRt.setAttributes({
            textConfig: [ Object.assign({
                text: placeholder
            }, getDefaultCharacterConfig(this.currRt.attribute)) ]
        });
    }
    deactivate(context) {
        context.stage.off("pointermove", this.handleMove, {
            capture: !0
        }), context.stage.off("pointerdown", this.handlePointerDown, {
            capture: !0
        }), context.stage.off("pointerup", this.handlePointerUp, {
            capture: !0
        }), context.stage.off("pointerleave", this.handlePointerUp, {
            capture: !0
        }), context.stage.off("dblclick", this.handleDBLClick, {
            capture: !0
        }), application.global.addEventListener("keydown", this.handleKeyDown);
    }
    stopPropagation(e) {
        e.stopPropagation();
    }
    addEditLineOrBgOrBounds(graphic, shadowRoot) {
        let group = shadowRoot.getElementById("emptyBoundsContainer");
        group || (group = createGroup({
            x: 0,
            y: 0,
            width: 0,
            height: 0,
            boundsMode: "empty"
        }), group.id = "emptyBoundsContainer", shadowRoot.add(group)), group.add(graphic);
    }
    removeEditLineOrBgOrBounds(graphic, shadowRoot) {
        const group = shadowRoot.getElementById("emptyBoundsContainer");
        group && group.removeChild(graphic);
    }
    onFocus(e, data) {
        this.updateCbs && this.updateCbs.forEach((cb => cb("beforeOnfocus", this))), this.deFocus(!1), 
        this.focusing = !0, this.editing = !0;
        const target = e.target;
        if (!target || "richtext" !== target.type) return;
        this.currRt = target, RichTextEditPlugin.tryUpdateRichtext(target);
        const shadowRoot = this.getShadow(target), cache = target.getFrameCache();
        if (!cache) return;
        const {editOptions: editOptions = {}} = this.currRt.attribute;
        if (editOptions.stopPropagation && target.addEventListener("*", this.stopPropagation), 
        this.offsetShadowRoot(target), !this.editLine) {
            const line = createLine({
                x: 0,
                y: 0,
                lineWidth: 1,
                stroke: "black"
            });
            this.addAnimateToLine(line), this.editLine = line, this.ticker && this.ticker.start(!0);
            const g = createGroup({
                x: 0,
                y: 0,
                width: 0,
                height: 0
            });
            this.editBg = g, this.addEditLineOrBgOrBounds(this.editLine, shadowRoot), this.addEditLineOrBgOrBounds(this.editBg, shadowRoot);
        }
        if (data = data || this.computedCursorPosByEvent(e, cache)) {
            const {x: x, y1: y1, y2: y2, cursorIndex: cursorIndex} = data;
            this.startCursorPos = {
                x: x,
                y: (y1 + y2) / 2
            }, this.curCursorIdx = cursorIndex, this.selectionStartCursorIdx = cursorIndex, 
            this.setCursorAndTextArea(x, y1, y2, target);
        } else {
            const x = 0, y1 = 0, y2 = getRichTextBounds(Object.assign(Object.assign({}, target.attribute), {
                textConfig: [ {
                    text: "a"
                } ]
            })).height();
            this.startCursorPos = {
                x: x,
                y: (y1 + y2) / 2
            }, this.curCursorIdx = -.1, this.selectionStartCursorIdx = -.1, this.setCursorAndTextArea(x, y1, y2, target);
        }
        this.tryShowShadowPlaceholder(), this.tryShowInputBounds(), this.currRt.addUpdateBoundTag();
    }
    offsetShadowRoot(rt) {
        if (!(rt = rt || this.currRt)) return;
        const shadowRoot = this.getShadow(rt);
        if (!shadowRoot) return;
        const cache = rt.getFrameCache();
        cache && (this.computeGlobalDelta(cache), shadowRoot.setAttributes({
            shadowRootIdx: 1,
            pickable: !1,
            x: this.deltaX,
            y: this.deltaY
        }), this.shadowPlaceHolder && this.shadowPlaceHolder.setAttributes({
            dx: -this.deltaX,
            dy: -this.deltaY
        }));
    }
    offsetLineBgAndShadowBounds() {
        const rt = this.currRt, {textBaseline: textBaseline} = rt.attribute;
        let b, dy = 0, attr = rt.attribute;
        "middle" !== textBaseline && "bottom" !== textBaseline || (attr.textConfig.length || (attr = Object.assign(Object.assign({}, attr), {
            textConfig: [ {
                text: "a"
            } ]
        })), b = getRichTextBounds(attr)), "middle" === textBaseline ? dy = -b.height() / 2 : "bottom" === textBaseline && (dy = -b.height()), 
        this.editLine && this.editLine.setAttributes({
            dy: dy
        }), this.editBg && this.editBg.setAttributes({
            dy: dy
        }), this.shadowBounds && this.shadowBounds.setAttributes({
            dy: dy
        });
    }
    deFocus(trulyDeFocus = !1) {
        this.editing = !1, this.updateCbs && this.updateCbs.forEach((cb => cb("beforeDefocus", this, {
            trulyDeFocus: trulyDeFocus
        })));
        const currRt = this.currRt;
        if (!currRt) return;
        const {editOptions: editOptions = {}} = currRt.attribute;
        editOptions.stopPropagation && currRt.removeEventListener("*", this.stopPropagation), 
        trulyDeFocus && (this.trySyncPlaceholderToTextConfig(), currRt.detachShadow()), 
        this.currRt = null, this.editModule.currRt = null;
        const shadowRoot = this.getShadow(currRt);
        this.editLine && (this.removeEditLineOrBgOrBounds(this.editLine, shadowRoot), this.editLine.release(), 
        this.editLine = null, this.removeEditLineOrBgOrBounds(this.editBg, shadowRoot), 
        this.editBg.release(), this.editBg = null), trulyDeFocus && (this.shadowBounds && (this.removeEditLineOrBgOrBounds(this.shadowBounds, shadowRoot), 
        this.shadowBounds.release(), this.shadowBounds = null), this.shadowPlaceHolder && (this.shadowPlaceHolder.parent && this.shadowPlaceHolder.parent.removeChild(this.shadowPlaceHolder), 
        this.shadowPlaceHolder.release(), this.shadowPlaceHolder = null)), this.focusing = !1, 
        currRt.removeEventListener("pointerleave", this.handleLeave);
    }
    addAnimateToLine(line) {
        if (!line.animate) return;
        line.setAttributes({
            opacity: 1
        }), line.animates && line.animates.forEach((animate => {
            animate.stop(), animate.release();
        }));
        line.animate({
            timeline: this.timeline
        }).to({
            opacity: 1
        }, 10, "linear").wait(700).to({
            opacity: 0
        }, 10, "linear").wait(700).loop(1 / 0);
    }
    tryShowSelection(e, dblclick) {
        const cache = e.target.getFrameCache();
        if (cache && this.editBg && this.startCursorPos) if (dblclick) {
            const currCursorData = this.computedCursorPosByEvent(e, cache);
            if (!currCursorData) return;
            const lineInfo = currCursorData.lineInfo, columnIndex = lineInfo.paragraphs.findIndex((item => item === currCursorData.columnInfo));
            if (columnIndex < 0) return;
            const str = lineInfo.paragraphs.reduce(((str, item) => str + item.text), "");
            let idx = 0;
            for (let i = 0; i < cache.lines.length; i++) {
                const line = cache.lines[i];
                if (line === lineInfo) break;
                idx += line.paragraphs.length;
            }
            const {startIdx: startIdx, endIdx: endIdx} = getWordStartEndIdx(str, columnIndex);
            this.selectionRange(idx + startIdx - .1, idx + endIdx - .1);
        } else if (this.pointerDown) {
            const currCursorData = this.computedCursorPosByEvent(e, cache);
            if (!currCursorData) return;
            this.curCursorIdx = currCursorData.cursorIndex, this._tryShowSelection(currCursorData, cache);
        }
    }
    _tryShowSelection(currCursorData, cache) {
        let startCursorPos = this.startCursorPos, endCursorPos = {
            x: currCursorData.x,
            y: (currCursorData.y1 + currCursorData.y2) / 2
        }, line0Info = this.getLineByPoint(cache, startCursorPos), line1Info = this.getLineByPoint(cache, endCursorPos);
        if ((startCursorPos.y > endCursorPos.y || startCursorPos.y === endCursorPos.y && startCursorPos.x > endCursorPos.x) && ([startCursorPos, endCursorPos] = [ endCursorPos, startCursorPos ], 
        [line1Info, line0Info] = [ line0Info, line1Info ]), this.hideSelection(), line0Info === line1Info) this.editBg.setAttributes({
            x: startCursorPos.x,
            y: line0Info.top,
            width: endCursorPos.x - startCursorPos.x,
            height: line0Info.height,
            fill: "#336df4",
            fillOpacity: .2
        }); else {
            this.editBg.setAttributes({
                x: 0,
                y: line0Info.top,
                width: 0,
                height: 0
            });
            const startIdx = cache.lines.findIndex((item => item === line0Info)), endIdx = cache.lines.findIndex((item => item === line1Info));
            let y = 0;
            for (let i = startIdx; i <= endIdx; i++) {
                const line = cache.lines[i];
                if (i === startIdx) {
                    const p = line.paragraphs[line.paragraphs.length - 1];
                    this.editBg.add(createRect({
                        x: startCursorPos.x,
                        y: y,
                        width: p.left + p.width - startCursorPos.x,
                        height: line.height,
                        fill: "#336df4",
                        fillOpacity: .2
                    }));
                } else if (i === endIdx) {
                    const p = line.paragraphs[0];
                    this.editBg.add(createRect({
                        x: p.left,
                        y: y,
                        width: endCursorPos.x - p.left,
                        height: line.height,
                        fill: "#336df4",
                        fillOpacity: .2
                    }));
                } else {
                    const p0 = line.paragraphs[0], p1 = line.paragraphs[line.paragraphs.length - 1];
                    this.editBg.add(createRect({
                        x: p0.left,
                        y: y,
                        width: p1.left + p1.width - p0.left,
                        height: line.height,
                        fill: "#336df4",
                        fillOpacity: .2
                    }));
                }
                y += line.height;
            }
        }
        this.setCursorAndTextArea(currCursorData.x, currCursorData.y1, currCursorData.y2, this.currRt), 
        this.triggerRender(), this.updateCbs.forEach((cb => cb("selection", this)));
    }
    hideSelection() {
        this.editBg && (this.editBg.removeAllChild(), this.editBg.setAttributes({
            fill: "transparent"
        }));
    }
    getShadow(rt) {
        const sr = rt.shadowRoot || rt.attachShadow();
        return sr.setAttributes({
            width: 0,
            height: 0
        }), sr;
    }
    getLineByPoint(cache, p1) {
        let lineInfo = cache.lines[0];
        for (let i = 0; i < cache.lines.length && !(lineInfo.top <= p1.y && lineInfo.top + lineInfo.height >= p1.y); i++) lineInfo = cache.lines[i + 1];
        return lineInfo;
    }
    getColumnAndIndexByLinePoint(lineInfo, p1) {
        let columnInfo = lineInfo.paragraphs[0], delta = 0;
        if (lineInfo.paragraphs.length) {
            const start = lineInfo.paragraphs[0], end = lineInfo.paragraphs[lineInfo.paragraphs.length - 1];
            p1.x <= start.left ? (delta = -.1, columnInfo = start) : p1.x >= end.left + end.width && (delta = .1, 
            columnInfo = end);
        }
        if (!delta) for (let i = 0; i < lineInfo.paragraphs.length; i++) if (columnInfo = lineInfo.paragraphs[i], 
        columnInfo.left <= p1.x && columnInfo.left + columnInfo.width >= p1.x) {
            delta = p1.x > columnInfo.left + columnInfo.width / 2 ? .1 : -.1;
            break;
        }
        return {
            columnInfo: columnInfo,
            delta: delta
        };
    }
    getColumnIndex(cache, cInfo) {
        let inputIndex = -1;
        for (let i = 0; i < cache.lines.length; i++) {
            const line = cache.lines[i];
            for (let j = 0; j < line.paragraphs.length; j++) if (inputIndex++, cInfo === line.paragraphs[j]) return inputIndex;
        }
        return -1;
    }
    isRichtext(e) {
        return !(!e.target || "richtext" !== e.target.type);
    }
    isEditableRichtext(e) {
        return this.isRichtext(e) && !!e.target.attribute.editable;
    }
    triggerRender() {
        this.pluginService.stage.renderNextFrame();
    }
    computeGlobalDelta(cache) {
        this.deltaX = 0, this.deltaY = 0, 0 === cache.lines.length && this.shadowPlaceHolder && (cache = this.shadowPlaceHolder.getFrameCache());
        const height = cache.height, actualHeight = cache.actualHeight, width = cache.lines.reduce(((w, item) => Math.max(w, item.actualWidth)), 0);
        "center" === cache.globalAlign ? this.deltaX = -width / 2 : "right" === cache.globalAlign && (this.deltaX = -width), 
        "middle" === cache.verticalDirection ? this.deltaY = height / 2 - actualHeight / 2 : "bottom" === cache.verticalDirection && (this.deltaY = height - actualHeight);
    }
    getEventPosition(e) {
        const p = this.pluginService.stage.eventPointTransform(e), p1 = {
            x: 0,
            y: 0
        };
        e.target.globalTransMatrix.transformPoint(p, p1), p1.x -= this.deltaX, p1.y -= this.deltaY;
        const rt = this.currRt, {textBaseline: textBaseline} = rt.attribute;
        let dy = 0;
        if ("middle" === textBaseline) {
            dy = getRichTextBounds(rt.attribute).height() / 2;
        } else if ("bottom" === textBaseline) {
            dy = getRichTextBounds(rt.attribute).height();
        }
        return p1.y += dy, p1;
    }
    setCursorAndTextArea(x, y1, y2, rt) {
        this.editLine.setAttributes({
            points: [ {
                x: x,
                y: y1
            }, {
                x: x,
                y: y2
            } ]
        }), this.addAnimateToLine(this.editLine);
        const out = {
            x: 0,
            y: 0
        };
        rt.globalTransMatrix.getInverse().transformPoint({
            x: x,
            y: y1
        }, out);
        const {left: left, top: top} = this.pluginService.stage.window.getBoundingClientRect();
        out.x += left, out.y += top, this.offsetLineBgAndShadowBounds(), this.offsetShadowRoot(), 
        this.editModule.moveTo(out.x, out.y, rt, this.curCursorIdx, this.selectionStartCursorIdx);
    }
    computedCursorPosByEvent(e, cache) {
        const p1 = this.getEventPosition(e), lineInfo = this.getLineByPoint(cache, p1);
        if (!lineInfo) return;
        const {columnInfo: columnInfo, delta: delta} = this.getColumnAndIndexByLinePoint(lineInfo, p1);
        if (!columnInfo) return;
        const y1 = lineInfo.top, y2 = lineInfo.top + lineInfo.height;
        let cursorIndex = this.getColumnIndex(cache, columnInfo);
        cursorIndex += delta;
        return {
            x: columnInfo.left + (delta > 0 ? columnInfo.width : 0),
            y1: y1,
            y2: y2,
            cursorIndex: cursorIndex,
            lineInfo: lineInfo,
            columnInfo: columnInfo
        };
    }
    computedCursorPosByCursorIdx(cursorIdx, rt) {
        var _a, _b, _c;
        const idx = Math.round(cursorIdx), leftRight = cursorIdx - idx, cache = rt.getFrameCache(), column = this.getColumnByIndex(cache, idx), height = null !== (_a = rt.attribute.fontSize) && void 0 !== _a ? _a : null === (_c = null === (_b = rt.attribute.textConfig) || void 0 === _b ? void 0 : _b[0]) || void 0 === _c ? void 0 : _c.fontSize;
        if (!column) {
            if (!cache.lines.length) {
                return {
                    x: 0,
                    y1: 0,
                    y2: getRichTextBounds(Object.assign(Object.assign({}, rt.attribute), {
                        textConfig: [ {
                            text: "a"
                        } ]
                    })).height()
                };
            }
            return {
                x: 0,
                y1: 0,
                y2: height
            };
        }
        const {lineInfo: lineInfo, columnInfo: columnInfo} = column, y1 = lineInfo.top, y2 = lineInfo.top + lineInfo.height;
        return {
            x: columnInfo.left + (leftRight < 0 ? 0 : columnInfo.width),
            y1: y1,
            y2: y2,
            lineInfo: lineInfo,
            columnInfo: columnInfo
        };
    }
    getColumnByIndex(cache, index) {
        for (let i = 0, inputIndex = 0; i < cache.lines.length; i++) {
            const lineInfo = cache.lines[i];
            for (let j = 0; j < lineInfo.paragraphs.length; j++) {
                const columnInfo = lineInfo.paragraphs[j];
                if (inputIndex === index) return {
                    lineInfo: lineInfo,
                    columnInfo: columnInfo
                };
                inputIndex++;
            }
        }
        return null;
    }
    release() {
        this.deactivate(this.pluginService), this.editModule.release();
    }
    getSelection(defaultAll = !1) {
        return this.currRt ? null != this.selectionStartCursorIdx && null != this.curCursorIdx ? new Selection(this.selectionStartCursorIdx, this.curCursorIdx, this.currRt) : defaultAll ? RichTextEditPlugin.CreateSelection(this.currRt) : null : null;
    }
    forceFocus(params) {
        const {target: target, e: e, cursorIndex: cursorIndex} = params;
        target && (this.currRt = target, e ? this._forceFocusByEvent(e) : this._forceFocusByCursorIndex(null != cursorIndex ? cursorIndex : -.1));
    }
    _forceFocusByEvent(e) {
        this.handleEnter(), this.handlePointerDown(e), this.handlePointerUp(e);
    }
    _forceFocusByCursorIndex(cursorIndex) {
        const richtext = this.currRt;
        if (!richtext) return;
        let x = 0, y1 = 0, y2 = 2, lineInfo = null, columnInfo = null;
        const data = this.computedCursorPosByCursorIdx(cursorIndex, richtext);
        x = data.x, y1 = data.y1, y2 = data.y2, lineInfo = data.lineInfo, columnInfo = data.columnInfo, 
        this.onFocus({
            target: this.currRt
        }, {
            x: x,
            y1: y1,
            y2: y2,
            cursorIndex: cursorIndex,
            lineInfo: lineInfo,
            columnInfo: columnInfo
        });
    }
}
//# sourceMappingURL=richtext-edit-plugin.js.map
