import { assertExists } from '@blockeditor/global/utils';
import {} from '@blockeditor/store';
import { Text } from '@blockeditor/store';
import { isInsideBlockByFlavour, matchFlavours, } from '../../../_common/utils/model.js';
import { getDocTitleByEditorHost, getInlineEditorByModel, getNextBlock, getPreviousBlock, } from '../../../_common/utils/query.js';
import { asyncFocusRichText, asyncSetInlineRange, focusBlockByModel, focusTitle, } from '../../../_common/utils/selection.js';
/**
 * Whether the block supports rendering its children.
 */
function supportsChildren(model) {
    if (matchFlavours(model, [
        // 'affine:database',
        'affine:image',
        'affine:divider',
        'affine:code',
    ])) {
        return false;
    }
    if (matchFlavours(model, ['affine:paragraph']) &&
        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'quote'].includes(model.type ?? '')) {
        return false;
    }
    return true;
}
export function handleBlockEndEnter(editorHost, model) {
    const page = model.page;
    const parent = page.getParent(model);
    const nextSibling = page.getNextSibling(model);
    if (!parent) {
        return;
    }
    const getProps = () => {
        const shouldInheritFlavour = matchFlavours(model, ['affine:list']);
        if (shouldInheritFlavour) {
            return [model.flavour, { type: model.type }];
        }
        return ['affine:paragraph', { type: 'text' }];
    };
    const [flavour, blockProps] = getProps();
    if (isInsideBlockByFlavour(page, model, 'affine:database')) {
        page.captureSync();
        const index = parent.children.findIndex(child => child.id === model.id);
        let newParent = parent;
        let newBlockIndex = index + 1;
        const childrenLength = parent.children.length;
        if (index === childrenLength - 1 && model.text?.yText.length === 0) {
            if (childrenLength !== 1) {
                page.deleteBlock(model);
            }
            const nextModel = page.getNextSibling(newParent);
            if (nextModel && matchFlavours(nextModel, ['affine:paragraph'])) {
                asyncFocusRichText(editorHost, page, nextModel.id, {
                    index: nextModel.text.yText.length,
                    length: 0,
                })?.catch(console.error);
                return;
            }
            const prevParent = page.getParent(parent);
            if (!prevParent)
                return;
            const prevIndex = prevParent.children.findIndex(child => child.id === parent.id);
            newParent = prevParent;
            newBlockIndex = prevIndex + 1;
        }
        const id = page.addBlock(flavour, blockProps, newParent, newBlockIndex);
        asyncFocusRichText(editorHost, page, id)?.catch(console.error);
        return;
    }
    const index = parent.children.indexOf(model);
    if (index === -1) {
        return;
    }
    // make adding text block by enter a standalone operation
    page.captureSync();
    // before press enter:
    // aaa|
    //   bbb
    //
    // after press enter:
    // aaa
    // |
    //   bbb
    const id = page.addBlock(flavour, blockProps, parent, index + 1);
    const newModel = page.getBlockById(id);
    assertExists(newModel);
    page.moveBlocks(model.children, newModel);
    // 4. If the target block is a numbered list, update the prefix of next siblings
    if (matchFlavours(model, ['affine:list']) && model.type === 'numbered') {
        let next = nextSibling;
        while (next &&
            matchFlavours(next, ['affine:list']) &&
            model.type === 'numbered') {
            page.updateBlock(next, {});
            next = page.getNextSibling(next);
        }
    }
    asyncFocusRichText(editorHost, page, id)?.catch(console.error);
}
export function handleBlockSplit(editorHost, model, splitIndex, splitLength) {
    if (!(model.text instanceof Text))
        return;
    // On press enter, it may convert symbols from yjs ContentString
    // to yjs ContentFormat. Once it happens, the converted symbol will
    // be deleted and not counted as model.text.yText.length.
    // Example: "`a`[enter]" -> yText[<ContentFormat: Code>, "a", <ContentFormat: Code>]
    // In this case, we should not split the block.
    if (model.text.yText.length < splitIndex + splitLength)
        return;
    const page = model.page;
    const parent = page.getParent(model);
    if (!parent)
        return;
    page.captureSync();
    const right = model.text.split(splitIndex, splitLength);
    const newParent = parent;
    const newBlockIndex = newParent.children.indexOf(model) + 1;
    const children = [...model.children];
    page.updateBlock(model, { children: [] });
    const id = page.addBlock(model.flavour, {
        text: right,
        type: model.type,
        children,
    }, newParent, newBlockIndex);
    return asyncFocusRichText(editorHost, page, id);
}
/**
 * @example
 * before unindent:
 * - aaa
 *   - bbb
 * - ccc|
 *   - ddd
 *   - eee
 *
 * after unindent:
 * - aaa
 *   - bbb
 *   - ccc|
 *     - ddd
 *     - eee
 */
export function handleIndent(editorHost, model, offset = 0) {
    const page = model.page;
    const previousSibling = page.getPreviousSibling(model);
    if (!previousSibling || !supportsChildren(previousSibling)) {
        // Bottom, can not indent, do nothing
        return;
    }
    const nextSiblings = page.getNextSiblings(model);
    page.captureSync();
    page.moveBlocks([model], previousSibling);
    // update list prefix
    if (matchFlavours(model, ['affine:list']) && model.type === 'numbered') {
        page.updateBlock(model, {});
    }
    nextSiblings
        .filter(sibling => matchFlavours(sibling, ['affine:list']) && sibling.type === 'numbered')
        .forEach(sibling => {
        page.updateBlock(sibling, {});
    });
    // update collapsed state
    if (matchFlavours(previousSibling, ['affine:list']) &&
        previousSibling.collapsed) {
        page.updateBlock(previousSibling, {
            collapsed: false,
        });
    }
    asyncSetInlineRange(editorHost, model, { index: offset, length: 0 }).catch(console.error);
}
export function handleMultiBlockIndent(editorHost, models) {
    if (!models.length)
        return;
    const page = models[0].page;
    // Find the first model that can be indented
    let firstIndentIndex = -1;
    let previousSibling = null;
    for (let i = 0; i < models.length; i++) {
        previousSibling = page.getPreviousSibling(models[i]);
        if (previousSibling && supportsChildren(previousSibling)) {
            firstIndentIndex = i;
            break;
        }
    }
    // No model can be indented
    if (firstIndentIndex === -1)
        return;
    page.captureSync();
    // Models waiting to be indented
    const indentModels = models.slice(firstIndentIndex);
    indentModels.forEach(model => {
        const parent = page.getParent(model);
        assertExists(parent);
        // Only indent the model which parent is not in the `indentModels`
        // When parent is in the `indentModels`, it means the parent has been indented
        // And the model should be indented with its parent
        if (!indentModels.includes(parent)) {
            handleIndent(editorHost, model);
        }
    });
}
/**
 * @example
 * before unindent:
 * - aaa
 *   - bbb
 *   - ccc|
 *     - ddd
 *   - eee
 *
 * after unindent:
 * - aaa
 *   - bbb
 * - ccc|
 *   - ddd
 *   - eee
 */
export function handleUnindent(editorHost, model, offset = 0) {
    const page = model.page;
    const parent = page.getParent(model);
    if (!parent || parent.role !== 'content') {
        // Top most, can not unindent, do nothing
        return;
    }
    const grandParent = page.getParent(parent);
    if (!grandParent)
        return;
    page.captureSync();
    const nextSiblings = page.getNextSiblings(model);
    const parentNextSiblings = page.getNextSiblings(parent);
    page.moveBlocks(nextSiblings, model);
    page.moveBlocks([model], grandParent, parent, false);
    // update list prefix
    if (matchFlavours(model, ['affine:list']) && model.type === 'numbered') {
        page.updateBlock(model, {});
    }
    model.children.forEach(child => {
        if (matchFlavours(child, ['affine:list']) && child.type === 'numbered') {
            page.updateBlock(child, {});
        }
    });
    parentNextSiblings
        .filter(sibling => matchFlavours(sibling, ['affine:list']) && sibling.type === 'numbered')
        .forEach(sibling => {
        page.updateBlock(sibling, {});
    });
    asyncSetInlineRange(editorHost, model, { index: offset, length: 0 }).catch(console.error);
}
export function handleMultiBlockOutdent(editorHost, models) {
    if (!models.length)
        return;
    const page = models[0].page;
    // Find the first model that can be unindented
    let firstOutdentIndex = -1;
    let firstParent;
    for (let i = 0; i < models.length; i++) {
        firstParent = page.getParent(models[i]);
        if (firstParent && !matchFlavours(firstParent, ['affine:note'])) {
            firstOutdentIndex = i;
            break;
        }
    }
    // Find all the models that can be unindented
    const outdentModels = models.slice(firstOutdentIndex);
    // Form bottom to top
    // Only outdent the models which parent is not in the outdentModels
    // When parent is in the outdentModels
    // It means that children will be unindented with their parent
    for (let i = outdentModels.length - 1; i >= 0; i--) {
        const model = outdentModels[i];
        const parent = page.getParent(model);
        assertExists(parent);
        if (!outdentModels.includes(parent)) {
            handleUnindent(editorHost, model);
        }
    }
}
export function handleRemoveAllIndent(editorHost, model, offset = 0) {
    const page = model.page;
    let parent = page.getParent(model);
    while (parent && !matchFlavours(parent, ['affine:note'])) {
        handleUnindent(editorHost, model, offset);
        parent = page.getParent(model);
    }
}
export function handleRemoveAllIndentForMultiBlocks(editorHost, models) {
    if (!models.length)
        return;
    const page = models[0].page;
    for (let i = models.length - 1; i >= 0; i--) {
        const model = models[i];
        const parent = page.getParent(model);
        assertExists(parent);
        if (!matchFlavours(parent, ['affine:note'])) {
            handleRemoveAllIndent(editorHost, model);
        }
    }
}
// When deleting at line end of a code block,
// do nothing
function handleCodeBlockForwardDelete(model) {
    if (!matchFlavours(model, ['affine:code']))
        return false;
    return true;
}
function handleDatabaseBlockForwardDelete(model) {
    const page = model.page;
    if (!isInsideBlockByFlavour(page, model, 'affine:database'))
        return false;
    return true;
}
// When deleting at line start of a list block,
// switch it to normal paragraph block.
function handleListBlockBackspace(editorHost, model) {
    const page = model.page;
    if (!matchFlavours(model, ['affine:list']))
        return false;
    const parent = page.getParent(model);
    if (!parent)
        return false;
    const nextSiblings = page.getNextSiblings(model);
    const index = parent.children.indexOf(model);
    const blockProps = {
        type: 'text',
        text: model.text?.clone(),
        children: model.children,
    };
    page.captureSync();
    page.deleteBlock(model, {
        deleteChildren: false,
    });
    nextSiblings
        .filter(sibling => matchFlavours(sibling, ['affine:list']) && sibling.type === 'numbered')
        .forEach(sibling => page.updateBlock(sibling, {}));
    const id = page.addBlock('affine:paragraph', blockProps, parent, index);
    asyncFocusRichText(editorHost, page, id)?.catch(console.error);
    return true;
}
// When deleting at line end of a list block,
// check current block's children and siblings
/**
 * Example:
- Line1  <-(cursor here)
    - Line2
        - Line3
        - Line4
    - Line5
        - Line6
- Line7
    - Line8
- Line9
 */
function handleListBlockForwardDelete(editorHost, model) {
    if (!matchFlavours(model, ['affine:list']))
        return false;
    const page = model.page;
    const firstChild = model.firstChild();
    if (firstChild) {
        model.text?.join(firstChild.text);
        const grandChildren = firstChild.children;
        if (grandChildren) {
            page.moveBlocks(grandChildren, model);
            page.deleteBlock(firstChild);
            return true;
        }
        else {
            page.deleteBlock(firstChild);
            return true;
        }
    }
    else {
        const nextSibling = page.getNextSibling(model);
        if (nextSibling) {
            model.text?.join(nextSibling.text);
            if (nextSibling.children) {
                const parent = page.getParent(nextSibling);
                if (!parent)
                    return false;
                page.moveBlocks(nextSibling.children, parent, model, false);
                page.deleteBlock(nextSibling);
                return true;
            }
            else {
                page.deleteBlock(nextSibling);
                return true;
            }
        }
        else {
            const nextBlock = getNextBlock(editorHost, model);
            if (!nextBlock) {
                // do nothing
                return true;
            }
            model.text?.join(nextBlock.text);
            if (nextBlock.children) {
                const parent = page.getParent(nextBlock);
                if (!parent)
                    return false;
                page.moveBlocks(nextBlock.children, parent, page.getParent(model), false);
                page.deleteBlock(nextBlock);
                return true;
            }
            else {
                page.deleteBlock(nextBlock);
                return true;
            }
        }
    }
}
function handleParagraphOrListSibling(editorHost, model, previousSibling, parent) {
    const page = model.page;
    if (!matchFlavours(previousSibling, ['affine:paragraph', 'affine:list']))
        return false;
    page.captureSync();
    const preTextLength = previousSibling.text?.length || 0;
    model.text?.length && previousSibling.text?.join(model.text);
    page.deleteBlock(model, {
        bringChildrenTo: parent,
    });
    const inlineEditor = getInlineEditorByModel(editorHost, previousSibling);
    inlineEditor?.setInlineRange({
        index: preTextLength,
        length: 0,
    });
    return true;
}
function handleEmbedDividerCodeSibling(editorHost, model, previousSibling, parent) {
    const page = model.page;
    if (matchFlavours(previousSibling, ['affine:divider'])) {
        page.deleteBlock(previousSibling);
        return true;
    }
    if (!matchFlavours(previousSibling, [
        'affine:image',
        'affine:code',
        'affine:bookmark',
        'affine:attachment',
        'affine:surface-ref',
    ]))
        return false;
    focusBlockByModel(editorHost, previousSibling);
    if (!model.text?.length) {
        page.captureSync();
        page.deleteBlock(model, {
            bringChildrenTo: parent,
        });
    }
    return true;
}
function handleNoPreviousSibling(editorHost, model) {
    const page = model.page;
    const text = model.text;
    const titleElement = getDocTitleByEditorHost(editorHost);
    // Probably no title, e.g. in edgeless mode
    if (!titleElement)
        return false;
    const pageModel = model.page.root;
    const title = pageModel.title;
    page.captureSync();
    let textLength = 0;
    if (text) {
        textLength = text.length;
        title.join(text);
    }
    // Preserve at least one block to be able to focus on container click
    if (page.getNextSibling(model) || model.children.length > 0) {
        const parent = page.getParent(model);
        assertExists(parent);
        page.deleteBlock(model, {
            bringChildrenTo: parent,
        });
    }
    else {
        text?.clear();
    }
    focusTitle(editorHost, title.length - textLength);
    return true;
}
function handleParagraphDeleteActions(editorHost, model) {
    const page = model.page;
    const parent = page.getParent(model);
    if (!parent)
        return false;
    const previousSibling = getPreviousBlock(editorHost, model);
    if (!previousSibling) {
        return handleNoPreviousSibling(editorHost, model);
    }
    else if (matchFlavours(previousSibling, ['affine:paragraph', 'affine:list'])) {
        const modelIndex = parent.children.indexOf(model);
        if ((modelIndex === -1 || modelIndex === parent.children.length - 1) &&
            parent.role === 'content')
            return false;
        const lengthBeforeJoin = previousSibling.text?.length ?? 0;
        previousSibling.text?.join(model.text);
        page.deleteBlock(model, {
            bringChildrenTo: parent,
        });
        asyncSetInlineRange(editorHost, previousSibling, {
            index: lengthBeforeJoin,
            length: 0,
        }).catch(console.error);
        return true;
    }
    // TODO handle in block service
    if (matchFlavours(parent, ['affine:database'])) {
        page.deleteBlock(model);
        focusBlockByModel(editorHost, previousSibling);
        return true;
    }
    else if (matchFlavours(parent, ['affine:note'])) {
        return (handleParagraphOrListSibling(editorHost, model, previousSibling, parent) ||
            handleEmbedDividerCodeSibling(editorHost, model, previousSibling, parent) ||
            handleUnknownBlockBackspace(previousSibling));
    }
    return false;
}
function handleParagraphBlockBackspace(editorHost, model) {
    const page = model.page;
    if (!matchFlavours(model, ['affine:paragraph']))
        return false;
    // When deleting at line start of a paragraph block,
    // firstly switch it to normal text, then delete this empty block.
    if (model.type !== 'text') {
        // Try to switch to normal text
        page.captureSync();
        page.updateBlock(model, { type: 'text' });
        return true;
    }
    // Before press backspace
    // - line1
    //   - line2
    //   - |aaa
    //   - line3
    //
    // After press backspace
    // - line1
    //   - line2|aaa
    //   - line3
    const isHandled = handleParagraphDeleteActions(editorHost, model);
    if (isHandled)
        return true;
    // Before press backspace
    // - line1
    //   - line2
    //   - |aaa
    //
    // After press backspace
    // - line1
    //   - line2
    // - |aaa
    handleUnindent(editorHost, model);
    return true;
}
function handleParagraphBlockForwardDelete(editorHost, model) {
    const page = model.page;
    function handleParagraphOrList(page, model, nextSibling, firstChild) {
        function handleParagraphOrListSibling(page, model, nextSibling) {
            if (nextSibling &&
                matchFlavours(nextSibling, ['affine:paragraph', 'affine:list'])) {
                model.text?.join(nextSibling.text);
                if (nextSibling.children) {
                    const parent = page.getParent(nextSibling);
                    if (!parent)
                        return false;
                    page.moveBlocks(nextSibling.children, parent, model, false);
                    page.deleteBlock(nextSibling);
                    return true;
                }
                else {
                    page.deleteBlock(nextSibling);
                    return true;
                }
            }
            else {
                const nextBlock = getNextBlock(editorHost, model);
                if (!nextBlock ||
                    !matchFlavours(nextBlock, ['affine:paragraph', 'affine:list']))
                    return false;
                model.text?.join(nextBlock.text);
                if (nextBlock.children) {
                    const parent = page.getParent(nextBlock);
                    if (!parent)
                        return false;
                    page.moveBlocks(nextBlock.children, parent, page.getParent(model), false);
                    page.deleteBlock(nextBlock);
                    return true;
                }
                else {
                    page.deleteBlock(nextBlock);
                    return true;
                }
            }
        }
        function handleParagraphOrListChild(page, model, firstChild) {
            if (!firstChild ||
                !matchFlavours(firstChild, ['affine:paragraph', 'affine:list'])) {
                return false;
            }
            const grandChildren = firstChild.children;
            model.text?.join(firstChild.text);
            if (grandChildren) {
                page.moveBlocks(grandChildren, model);
            }
            page.deleteBlock(firstChild);
            return true;
        }
        const nextBlock = getNextBlock(editorHost, model);
        if (!firstChild && !nextBlock)
            return true;
        return (handleParagraphOrListChild(page, model, firstChild) ||
            handleParagraphOrListSibling(page, model, nextSibling));
    }
    function handleEmbedDividerCode(nextSibling, firstChild) {
        function handleEmbedDividerCodeChild(firstChild) {
            if (!firstChild ||
                !matchFlavours(firstChild, [
                    'affine:image',
                    'affine:divider',
                    'affine:code',
                ]))
                return false;
            focusBlockByModel(editorHost, firstChild);
            return true;
        }
        function handleEmbedDividerCodeSibling(nextSibling) {
            if (matchFlavours(nextSibling, ['affine:divider'])) {
                page.deleteBlock(nextSibling);
                return true;
            }
            if (!nextSibling ||
                !matchFlavours(nextSibling, ['affine:image', 'affine:code']))
                return false;
            focusBlockByModel(editorHost, nextSibling);
            return true;
        }
        return (handleEmbedDividerCodeChild(firstChild) ||
            handleEmbedDividerCodeSibling(nextSibling));
    }
    if (!matchFlavours(model, ['affine:paragraph']))
        return false;
    const parent = page.getParent(model);
    if (!parent)
        return false;
    const nextSibling = page.getNextSibling(model);
    const firstChild = model.firstChild();
    if (matchFlavours(parent, ['affine:database'])) {
        // TODO
        return false;
    }
    else {
        return (handleParagraphOrList(page, model, nextSibling, firstChild) ||
            handleEmbedDividerCode(nextSibling, firstChild));
    }
}
function handleUnknownBlockBackspace(model) {
    throw new Error('Failed to handle backspace! Unknown block flavours! flavour:' +
        model.flavour);
}
function handleUnknownBlockForwardDelete(model) {
    throw new Error('Failed to handle forwarddelete! Unknown block flavours! flavour:' +
        model.flavour);
}
export function handleLineStartBackspace(editorHost, model) {
    if (handleListBlockBackspace(editorHost, model) ||
        handleParagraphBlockBackspace(editorHost, model)) {
        return;
    }
    handleUnknownBlockBackspace(model);
}
export function handleLineEndForwardDelete(editorHost, model) {
    if (handleCodeBlockForwardDelete(model) ||
        handleListBlockForwardDelete(editorHost, model) ||
        handleParagraphBlockForwardDelete(editorHost, model)) {
        handleDatabaseBlockForwardDelete(model);
        return;
    }
    handleUnknownBlockForwardDelete(model);
}
//# sourceMappingURL=rich-text-operations.js.map