"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DomController = void 0;
const tree_dump_1 = require("tree-dump");
const AvlMap_1 = require("sonic-forest/lib/avl/AvlMap");
const InputController_1 = require("./InputController");
const CursorController_1 = require("./CursorController");
const RichTextController_1 = require("./RichTextController");
const KeyController_1 = require("./KeyController");
const CompositionController_1 = require("./CompositionController");
const AnnalsController_1 = require("./annals/AnnalsController");
const constants_1 = require("../constants");
const constants_2 = require("../../../json-crdt-extensions/peritext/rga/constants");
const json_crdt_patch_1 = require("../../../json-crdt-patch");
const UiHandle_1 = require("../../../json-crdt-extensions/peritext/events/defaults/ui/UiHandle");
class DomController {
    constructor(events, log) {
        this.events = events;
        this.log = log;
        /**
         * Index of block HTML <div> elements keyed by the ID (timestamp) of the split
         * boundary that starts that block element.
         */
        this.blocks = new AvlMap_1.AvlMap(json_crdt_patch_1.compare);
        /**
         * Index of inline HTML <span> elements keyed by the slice start {@link Point}.
         */
        this.inlines = new AvlMap_1.AvlMap((a, b) => a.cmpSpatial(b));
        const { txt } = events;
        this.txt = txt;
        this.et = events.et;
        this.keys = new KeyController_1.KeyController(this);
        this.comp = new CompositionController_1.CompositionController(this);
        this.input = new InputController_1.InputController(this);
        this.cursor = new CursorController_1.CursorController(this);
        this.richText = new RichTextController_1.RichTextController(this);
        this.annals = new AnnalsController_1.AnnalsController(this);
        const uiHandle = new UiHandle_1.UiHandle(txt, this);
        events.ui = uiHandle;
        events.undo = this.annals;
    }
    isEditable(el) {
        if (!el.isContentEditable)
            return false;
        const computed = getComputedStyle(el);
        return (computed.getPropertyValue('--jsonjoy-peritext-id') === this.et.id + '' &&
            computed.getPropertyValue('--jsonjoy-peritext-editable') === 'yes');
    }
    start() {
        const { et, el } = this;
        el.contentEditable = 'true';
        const style = el.style;
        style.setProperty('--jsonjoy-peritext-id', et.id + '');
        style.setProperty('--jsonjoy-peritext-editable', 'yes');
        const stopKeys = this.keys.start();
        const stopComp = this.comp.start();
        const stopInput = this.input.start();
        const stopCursor = this.cursor.start();
        const stopRichText = this.richText.start();
        const stopAnnals = this.annals.start();
        return () => {
            el.contentEditable = 'false';
            stopKeys();
            stopComp();
            stopInput();
            stopCursor();
            stopRichText();
            stopAnnals();
        };
    }
    /** ------------------------------------------------- {@link PeritextUiApi} */
    focus() {
        this.el.focus();
    }
    getSpans(blockInnerId) {
        let el;
        if (blockInnerId) {
            const txt = this.txt;
            const marker = txt.overlay.getOrNextLowerMarker(blockInnerId);
            const markerId = marker?.id ?? txt.str.id;
            el = this.blocks.get(markerId);
        }
        el ?? (el = this.el);
        return el.querySelectorAll('.jsonjoy-peritext-inline');
    }
    findSpanContaining(char) {
        const start = char.start;
        const overlayPoint = this.txt.overlay.getOrNextLower(start);
        if (overlayPoint) {
            const span = this.inlines.get(overlayPoint);
            if (span) {
                const inline = span[constants_1.ElementAttr.InlineOffset];
                if (inline) {
                    const contains = inline.contains(char);
                    if (contains)
                        return span;
                }
            }
        }
        const spans = this.getSpans(start);
        const length = spans.length;
        for (let i = 0; i < length; i++) {
            const span = spans[i];
            const inline = span[constants_1.ElementAttr.InlineOffset];
            if (inline) {
                const contains = inline.contains(char);
                if (contains)
                    return span;
            }
        }
        return;
    }
    getCharRect(char) {
        const txt = this.events.txt;
        const id = typeof char === 'number' ? txt.str.find(char) : char;
        if (!id)
            return;
        const start = txt.point(id, constants_2.Anchor.Before);
        const end = txt.point(id, constants_2.Anchor.After);
        const charRange = txt.range(start, end);
        const span = this.findSpanContaining(charRange);
        if (!span)
            return;
        const inline = span[constants_1.ElementAttr.InlineOffset];
        if (!inline)
            return;
        const textNode = span.firstChild;
        if (!textNode)
            return;
        const range = document.createRange();
        range.selectNode(textNode);
        const offset = Math.max(0, Math.min(textNode.length - 1, charRange.start.viewPos() - inline.start.viewPos()));
        range.setStart(textNode, offset);
        range.setEnd(textNode, offset + 1);
        const rects = range.getClientRects();
        return rects[0];
    }
    caretRect() {
        return document.getElementById(this.cursor.caretId)?.getBoundingClientRect?.();
    }
    /** ----------------------------------------------------- {@link Printable} */
    toString(tab) {
        return ('DOM' +
            (0, tree_dump_1.printTree)(tab, [
                (tab) => 'blocks: ' + this.blocks.size(),
                (tab) => 'inlines: ' + this.inlines.size(),
                (tab) => this.cursor.toString(tab),
                (tab) => this.keys.toString(tab),
                (tab) => this.comp.toString(tab),
                (tab) => this.annals.toString(tab),
            ]));
    }
}
exports.DomController = DomController;
//# sourceMappingURL=DomController.js.map