import { printTree } from 'tree-dump';
import { AvlMap } from 'sonic-forest/lib/avl/AvlMap';
import { InputController } from './InputController';
import { CursorController } from './CursorController';
import { RichTextController } from './RichTextController';
import { KeyController } from './KeyController';
import { CompositionController } from './CompositionController';
import { AnnalsController } from './annals/AnnalsController';
import { ElementAttr } from '../constants';
import { Anchor } from '../../../json-crdt-extensions/peritext/rga/constants';
import { compare } from '../../../json-crdt-patch';
import { UiHandle } from '../../../json-crdt-extensions/peritext/events/defaults/ui/UiHandle';
export class DomController {
    events;
    log;
    txt;
    et;
    keys;
    comp;
    input;
    cursor;
    richText;
    annals;
    /**
     * Index of block HTML <div> elements keyed by the ID (timestamp) of the split
     * boundary that starts that block element.
     */
    blocks = new AvlMap(compare);
    /**
     * Index of inline HTML <span> elements keyed by the slice start {@link Point}.
     */
    inlines = new AvlMap((a, b) => a.cmpSpatial(b));
    constructor(events, log) {
        this.events = events;
        this.log = log;
        const { txt } = events;
        this.txt = txt;
        this.et = events.et;
        this.keys = new KeyController(this);
        this.comp = new CompositionController(this);
        this.input = new InputController(this);
        this.cursor = new CursorController(this);
        this.richText = new RichTextController(this);
        this.annals = new AnnalsController(this);
        const uiHandle = new 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');
    }
    /** -------------------------------------------------- {@link UiLifeCycles} */
    /**
     * Must be set before calling {@link start}.
     */
    el;
    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 ??= 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[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[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, Anchor.Before);
        const end = txt.point(id, Anchor.After);
        const charRange = txt.range(start, end);
        const span = this.findSpanContaining(charRange);
        if (!span)
            return;
        const inline = span[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' +
            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),
            ]));
    }
}
//# sourceMappingURL=DomController.js.map