import { Anchor } from '../../../../json-crdt-extensions/peritext/rga/constants';
import { placeCursor } from './annals';
import { Cursor } from '../../../../json-crdt-extensions/peritext/editor/Cursor';
import { CursorAnchor } from '../../../../json-crdt-extensions/peritext';
import { PersistedSlice } from '../../../../json-crdt-extensions/peritext/slice/PersistedSlice';
import { MarkerSlice } from '../../slice/MarkerSlice';
const toText = (buf) => new TextDecoder().decode(buf);
const getEdge = (start, end, anchor, edge) => edge === 'start'
    ? start
    : edge === 'end'
        ? end
        : edge === 'focus'
            ? anchor === CursorAnchor.Start
                ? end
                : start
            : anchor === CursorAnchor.Start
                ? start
                : end;
/**
 * Implementation of default handlers for Peritext events, such as "insert",
 * "delete", "cursor", etc. These implementations are used by the
 * {@link PeritextEventTarget} to provide default behavior for each event type.
 * If `event.preventDefault()` is called on a Peritext event, the default handler
 * will not be executed.
 */
export class PeritextEventDefaults {
    txt;
    et;
    opts;
    undo;
    ui;
    editorUi = {
        eol: (point, steps) => {
            const ui = this.ui;
            if (!ui)
                return;
            const res = ui.getLineEnd(point, steps > 0);
            return res ? res[0] : void 0;
        },
        vert: (point, steps) => {
            const ui = this.ui;
            if (!ui)
                return;
            const pos = ui.pointX(point);
            if (!pos)
                return;
            const currLine = ui.getLineInfo(point);
            if (!currLine)
                return;
            const x = pos[0];
            const iterations = Math.abs(steps);
            let nextPoint = point;
            for (let i = 0; i < iterations; i++) {
                const nextLine = steps > 0 ? ui.getNextLineInfo(currLine) : ui.getNextLineInfo(currLine, -1);
                if (!nextLine)
                    break;
                nextPoint = ui.findPointAtX(x, nextLine);
                if (!nextPoint)
                    break;
                if (point.anchor === Anchor.Before)
                    nextPoint.refBefore();
                else
                    nextPoint.refAfter();
            }
            return nextPoint;
        },
    };
    constructor(txt, et, opts = {}) {
        this.txt = txt;
        this.et = et;
        this.opts = opts;
    }
    getSelSet({ at }) {
        const { editor } = this.txt;
        return at ? [editor.sel2range(at)[0]] : [...editor.cursors()];
    }
    moveRange(start, end, anchor, move) {
        if (!move)
            return [start, end, anchor];
        const { txt, editorUi } = this;
        const start0 = start;
        for (const [edge, to, len, collapse] of move) {
            const point = getEdge(start, end, anchor, edge);
            const point2 = typeof to === 'string'
                ? len
                    ? txt.editor.skip(point, len, to ?? 'char', editorUi)
                    : point.clone()
                : txt.editor.pos2point(to);
            if (point === start)
                start = point2;
            else
                end = point2;
            if (collapse) {
                if (to !== 'point')
                    point2.refAfter();
                if (point === start0)
                    end = point2.clone();
                else
                    start = point2.clone();
            }
        }
        if (start.cmpSpatial(end) > 0) {
            const tmp = start;
            start = end;
            end = tmp;
            anchor = anchor === CursorAnchor.Start ? CursorAnchor.End : CursorAnchor.Start;
        }
        return [start, end, anchor];
    }
    moveSelSet(set, { move }) {
        if (!move)
            return;
        for (const selection of set) {
            const [start, end, anchor] = this.moveRange(selection.start, selection.end, selection instanceof Cursor ? selection.anchorSide : CursorAnchor.End, move);
            if (selection instanceof Cursor)
                selection.set(start, end, anchor);
            else
                selection.set(start, end);
        }
    }
    change = (event) => { };
    insert = ({ detail }) => {
        const { move, text } = detail;
        const set = [...this.getSelSet(detail)];
        if (move)
            this.moveSelSet(set, detail);
        this.txt.editor.insert(text, set);
        this.undo?.capture();
    };
    delete = ({ detail }) => {
        const { move, add, at } = detail;
        const set = [...this.getSelSet(detail)];
        const editor = this.txt.editor;
        let deleted = false;
        for (const range of set) {
            if (range.length()) {
                deleted = true;
                editor.delRange(range);
                const start = range.start;
                start.refAfter();
                range.set(start);
            }
        }
        if (deleted) {
            this.undo?.capture();
            return;
        }
        if (move)
            this.moveSelSet(set, detail);
        for (const range of set) {
            editor.delRange(range);
            range.collapseToStart();
            const start = range.start;
            start.refAfter();
            range.set(start);
        }
        if (add && at)
            editor.cursor.setRange(set[0]);
        this.undo?.capture();
    };
    cursor = ({ detail }) => {
        const { at, move, add, flip, clear } = detail;
        if (clear === true) {
            this.txt.editor.delCursors();
            return;
        }
        if (at === void 0) {
            const selection = this.getSelSet(detail);
            this.moveSelSet(selection, detail);
            // Collapse cursors if there are no visible characters between edges.
            // (Only for relative focus edge moves.)
            if (move && move.length === 1 && move[0][0] === 'focus')
                for (const range of selection)
                    if (range.length() === 0)
                        range.collapseToStart();
            // Swap anchor and focus edges.
            if (flip)
                for (const range of selection)
                    if (range instanceof Cursor)
                        range.anchorSide = range.anchorSide === CursorAnchor.Start ? CursorAnchor.End : CursorAnchor.Start;
        }
        else {
            const { txt } = this;
            const { editor } = txt;
            const [range, anchor0] = editor.sel2range(at);
            const [start, end, anchor] = this.moveRange(range.start, range.end, anchor0, move);
            if (add)
                editor.addCursor(txt.range(start, end), anchor);
            else
                editor.cursor.set(start, end, anchor);
        }
    };
    format = ({ detail }) => {
        const selection = [...this.getSelSet(detail)];
        this.moveSelSet(selection, detail);
        const { action, type: tag, store = 'saved' } = detail;
        const editor = this.txt.editor;
        const slices = store === 'saved' ? editor.saved : store === 'extra' ? editor.extra : editor.local;
        switch (action) {
            case 'ins':
            case 'tog': {
                const { stack = 'one', data } = detail;
                if (tag === undefined)
                    throw new Error('TYPE_REQUIRED');
                switch (stack) {
                    case 'many': {
                        slices.insStack(tag, data, selection);
                        break;
                    }
                    case 'one': {
                        if (action === 'ins')
                            slices.insOne(tag, data, selection);
                        else
                            editor.toggleExclFmt(tag, data, slices, selection);
                        break;
                    }
                    case 'erase': {
                        slices.insOne(tag, data, selection);
                        break;
                    }
                }
                break;
            }
            case 'del': {
                const { slice } = detail;
                if (!tag && slice) {
                    const persistedSlice = slice instanceof PersistedSlice ? slice : this.txt.getSlice(slice);
                    if (persistedSlice instanceof PersistedSlice && !persistedSlice.isSplit()) {
                        persistedSlice.del();
                    }
                }
                else {
                    editor.clearFormatting(slices, selection);
                }
                break;
            }
            case 'erase': {
                if (tag === undefined)
                    editor.eraseFormatting(slices, selection);
                else
                    slices.insErase(tag, detail.data, selection);
                break;
            }
            case 'upd':
            case 'set': {
                const { slice } = detail;
                if (slice) {
                    const persistedSlice = slice instanceof PersistedSlice ? slice : this.txt.getSlice(slice);
                    if (persistedSlice instanceof PersistedSlice) {
                        if (action === 'set')
                            persistedSlice.setData(detail.data);
                        else
                            persistedSlice.mergeData(detail.data);
                    }
                }
                break;
            }
        }
        this.undo?.capture();
    };
    marker = ({ detail }) => {
        const selection = [...this.getSelSet(detail)];
        this.moveSelSet(selection, detail);
        const { action, type, data } = detail;
        const editor = this.txt.editor;
        switch (action) {
            case 'ins': {
                editor.split(type, data, selection);
                break;
            }
            case 'del': {
                const { slice } = detail;
                if (slice) {
                    const persistedSlice = slice instanceof PersistedSlice ? slice : this.txt.getSlice(slice);
                    if (persistedSlice instanceof MarkerSlice)
                        persistedSlice.del();
                }
                else {
                    editor.delMarker(selection);
                }
                break;
            }
            case 'upd': {
                const { target, ops } = detail;
                if (target && ops) {
                    editor.updMarker(target, ops, selection);
                }
                break;
            }
        }
        this.undo?.capture();
    };
    buffer = async ({ detail }) => {
        const selection = [...this.getSelSet(detail)];
        this.moveSelSet(selection, detail);
        const opts = this.opts;
        const clipboard = opts.clipboard;
        if (!clipboard)
            return;
        const { action, format } = detail;
        const txt = this.txt;
        const editor = txt.editor;
        const range = selection[0] ?? txt.rangeAll();
        if (!range)
            return;
        switch (action) {
            case 'cut':
            case 'copy': {
                const copyStyle = () => {
                    if (!range)
                        return;
                    if (range.length() < 1) {
                        range.end.step(1);
                        if (range.length() < 1)
                            range.start.step(-1);
                    }
                    const data = opts.transfer?.toFormat?.(range);
                    clipboard.write(data)?.catch((err) => console.error(err));
                };
                switch (format) {
                    case 'text': {
                        const text = range.text();
                        clipboard.writeText(text)?.catch((err) => console.error(err));
                        if (action === 'cut')
                            editor.collapseCursor(range);
                        break;
                    }
                    case 'style': {
                        copyStyle();
                        break;
                    }
                    case 'html':
                    case 'hast':
                    case 'json':
                    case 'jsonml':
                    case 'mdast':
                    case 'md':
                    case 'fragment': {
                        const transfer = opts.transfer;
                        if (!transfer)
                            return;
                        let text = '';
                        switch (format) {
                            case 'html':
                                text = transfer.toHtml(range);
                                break;
                            case 'hast':
                                text = JSON.stringify(transfer.toHast(range), null, 2);
                                break;
                            case 'jsonml':
                                text = JSON.stringify(transfer.toJson(range), null, 2);
                                break;
                            case 'json':
                                text = JSON.stringify(transfer.toView(range), null, 2);
                                break;
                            case 'mdast':
                                text = JSON.stringify(transfer.toMdast(range), null, 2);
                                break;
                            case 'md':
                                text = transfer.toMarkdown(range);
                                break;
                            case 'fragment':
                                text = transfer.toFragment(range) + '';
                                break;
                        }
                        clipboard.writeText(text)?.catch((err) => console.error(err));
                        if (action === 'cut')
                            editor.collapseCursor(range);
                        break;
                    }
                    default: {
                        // `auto'
                        const transfer = opts.transfer;
                        if (!transfer)
                            return;
                        if (range.length() < 1) {
                            copyStyle();
                        }
                        else {
                            const data = transfer.toClipboard(range);
                            clipboard.write(data)?.catch((err) => console.error(err));
                            if (action === 'cut')
                                editor.collapseCursor(range);
                        }
                    }
                }
                break;
            }
            case 'paste': {
                switch (format) {
                    case 'text': {
                        const data = await clipboard.read(['text/plain', 'text/html']);
                        let buffer;
                        if ((buffer = data['text/plain'])) {
                            const text = toText(buffer);
                            this.et.insert(text);
                        }
                        else if ((buffer = data['text/html'])) {
                            const html = toText(buffer);
                            const text = opts.transfer?.textFromHtml?.(html) ?? html;
                            this.et.insert(text);
                        }
                        break;
                    }
                    case 'style': {
                        const transfer = opts.transfer;
                        if (transfer) {
                            const { html } = detail.data || (await clipboard.readData());
                            if (html) {
                                transfer.fromStyle(range, html);
                                this.et.change();
                            }
                        }
                        break;
                    }
                    case 'html':
                    case 'hast':
                    case 'json':
                    case 'jsonml':
                    case 'mdast':
                    case 'md': {
                        const data = detail.data;
                        const transfer = opts.transfer;
                        if (!transfer)
                            return;
                        let text = data?.text || '';
                        if (!text) {
                            const clipboardData = await clipboard.read(['text/plain']);
                            const buffer = clipboardData['text/plain'];
                            if (buffer)
                                text = toText(buffer);
                        }
                        if (!range.isCollapsed())
                            editor.delRange(range);
                        range.collapseToStart();
                        const start = range.start;
                        const pos = start.viewPos();
                        let _inserted = 0;
                        switch (format) {
                            case 'html': {
                                _inserted = transfer.fromHtml(pos, text);
                                break;
                            }
                            case 'hast': {
                                const json = JSON.parse(text);
                                _inserted = transfer.fromHast(pos, json);
                                break;
                            }
                            case 'jsonml': {
                                const json = JSON.parse(text);
                                _inserted = transfer.fromJson(pos, json);
                                break;
                            }
                            case 'json': {
                                const json = JSON.parse(text);
                                _inserted = transfer.fromView(pos, json);
                                break;
                            }
                            case 'mdast': {
                                const json = JSON.parse(text);
                                _inserted = transfer.fromMdast(pos, json);
                                break;
                            }
                            case 'md': {
                                _inserted = transfer.fromMarkdown(pos, text);
                                break;
                            }
                        }
                        // if (inserted) this.et.move(inserted, 'char');
                        this.et.change();
                        break;
                    }
                    default: {
                        // 'auto'
                        let data = detail.data;
                        const transfer = opts.transfer;
                        if (!transfer) {
                            let text = data?.text || '';
                            if (!text) {
                                const clipboardData = await clipboard.read(['text/plain']);
                                const buffer = clipboardData['text/plain'];
                                if (buffer)
                                    text = toText(buffer);
                            }
                            if (text)
                                this.et.insert(text);
                            return;
                        }
                        if (!data)
                            data = await clipboard.readData();
                        const inserted = transfer.fromClipboard(range, data);
                        if (inserted && editor.cursorCard() === 1)
                            this.et.move([
                                ['start', 'char', inserted],
                                ['end', 'char', inserted],
                            ]);
                        this.et.change();
                    }
                }
                break;
            }
        }
        this.undo?.capture();
    };
    annals = (event) => {
        const { batch } = event.detail;
        this.txt.model.applyBatch(batch);
        const txt = this.txt;
        const cursor = placeCursor(txt, batch);
        if (cursor)
            txt.editor.cursor.setRange(cursor);
    };
}
//# sourceMappingURL=PeritextEventDefaults.js.map