import { printTree } from 'tree-dump/lib/printTree';
import { CONST, updateJson, updateNum } from '../../../json-hash/hash';
import { MarkerOverlayPoint } from '../overlay/MarkerOverlayPoint';
import { UndEndIterator } from '../../../util/iterator';
import { Inline } from './Inline';
import { formatStep, getTag } from '../slice/util';
import { Range } from '../rga/Range';
import { toLine } from 'pojo-dump/lib/toLine';
export class Block extends Range {
    txt;
    path;
    marker;
    start;
    end;
    parent = null;
    children = [];
    constructor(txt, path, marker, start, end) {
        super(txt.str, start, end);
        this.txt = txt;
        this.path = path;
        this.marker = marker;
        this.start = start;
        this.end = end;
    }
    /**
     * @returns Stable unique identifier within a list of blocks. Used for React
     * or other rendering library keys.
     */
    key() {
        if (!this.marker)
            return this.tag();
        const id = this.marker.id;
        return id.sid.toString(36) + id.time.toString(36);
    }
    tag() {
        return getTag(this.path);
    }
    attr() {
        const path = this.path;
        const length = path.length;
        if (length === 0)
            return;
        const tagStep = path[length - 1];
        if (!Array.isArray(tagStep))
            return;
        return tagStep[2];
    }
    isLeaf() {
        return false;
    }
    /**
     * Iterate through all overlay points of this block, until the next marker
     * (regardless if that marker is a child or not).
     */
    points0(withMarker = false) {
        const txt = this.txt;
        const overlay = txt.overlay;
        const iterator = overlay.points0(this.marker);
        let closed = false;
        return () => {
            if (withMarker) {
                withMarker = false;
                return this.marker ?? overlay.START;
            }
            if (closed)
                return;
            const point = iterator();
            if (!point)
                return;
            if (point instanceof MarkerOverlayPoint) {
                closed = true;
                return;
            }
            return point;
        };
    }
    points(withMarker) {
        return new UndEndIterator(this.points0(withMarker));
    }
    tuples0() {
        const overlay = this.txt.overlay;
        const marker = this.marker;
        const iterator = overlay.tuples0(marker);
        let closed = false;
        return () => {
            if (closed)
                return;
            let pair = iterator();
            while (!marker && pair && pair[1] && pair[1].cmpSpatial(this.start) < 0)
                pair = iterator();
            if (!pair)
                return (closed = true), void 0;
            if (!pair[1] || pair[1] instanceof MarkerOverlayPoint)
                closed = true;
            return pair;
        };
    }
    /**
     * @todo Consider moving inline-related methods to {@link LeafBlock}.
     */
    texts0() {
        const txt = this.txt;
        const overlay = txt.overlay;
        const iterator = this.tuples0();
        const start = this.start;
        const end = this.end;
        const startIsMarker = overlay.isMarker(start.id);
        const endIsMarker = overlay.isMarker(end.id);
        let isFirst = true;
        let next = iterator();
        let closed = false;
        const newIterator = () => {
            if (closed)
                return;
            const pair = next;
            next = iterator();
            if (!pair)
                return;
            const [overlayPoint1, overlayPoint2] = pair;
            let point1 = overlayPoint1;
            let point2 = overlayPoint2;
            if (isFirst) {
                isFirst = false;
                if (start.cmpSpatial(overlayPoint1) > 0)
                    point1 = start;
                if (startIsMarker) {
                    point1 = point1.clone();
                    point1.step(1);
                    // Skip condition when inline annotations tarts immediately at th
                    // beginning of the block.
                    if (point1.cmp(point2) === 0)
                        return newIterator();
                }
            }
            if (!endIsMarker && end.cmpSpatial(overlayPoint2) < 0) {
                closed = true;
                point2 = end;
            }
            return new Inline(txt, overlayPoint1, overlayPoint2, point1, point2);
        };
        return newIterator;
    }
    /**
     * @todo Consider moving inline-related methods to {@link LeafBlock}.
     */
    texts() {
        return new UndEndIterator(this.texts0());
    }
    text() {
        let str = '';
        const children = this.children;
        const length = children.length;
        for (let i = 0; i < length; i++)
            str += children[i].text();
        return str;
    }
    // ------------------------------------------------------------------- export
    toJson() {
        const data = this.attr();
        const attr = data !== void 0 ? { data } : null;
        const node = [this.tag(), attr];
        const children = this.children;
        const length = children.length;
        for (let i = 0; i < length; i++)
            node.push(children[i].toJson());
        return node;
    }
    // ----------------------------------------------------------------- Stateful
    hash = 0;
    refresh() {
        const { path, children } = this;
        let state = CONST.START_STATE;
        state = updateJson(state, path);
        const marker = this.marker;
        if (marker) {
            state = updateNum(state, marker.marker.refresh());
            state = updateNum(state, marker.textHash);
        }
        else {
            state = updateNum(state, this.txt.overlay.leadingTextHash);
        }
        for (let i = 0; i < children.length; i++)
            state = updateNum(state, children[i].refresh());
        return (this.hash = state);
    }
    // ---------------------------------------------------------------- Printable
    toStringName() {
        return 'Block';
    }
    toStringHeader() {
        const hash = `#${this.hash.toString(36).slice(-4)}`;
        const tag = this.path.map((step) => formatStep(step)).join('.');
        const data = toLine(this.attr());
        const header = `${super.toString('', true)} ${hash} ${tag} ${data}`;
        return header;
    }
    toString(tab = '') {
        const header = this.toStringHeader();
        const hasChildren = !!this.children.length;
        return (header +
            printTree(tab, [
                this.marker ? (tab) => this.marker.toString(tab) : null,
                this.marker && hasChildren ? () => '' : null,
                hasChildren
                    ? (tab) => 'children' +
                        printTree(tab, this.children.map((child, i) => (tab) => `${i + 1}. ` + child.toString(tab + '  ' + ' '.repeat(String(i + 1).length))))
                    : null,
            ]));
    }
}
//# sourceMappingURL=Block.js.map