import {
    supportChildren,
    type BlockEditor,
} from '@toeverything/components/editor-core';
import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '@toeverything/framework/virgo';
import type { TodoAsyncBlock } from '../blocks/todo/types';

/**
 * Is the block in top level
 */
export const isTopLevelBlock = (
    editor: BlockEditor,
    block: AsyncBlock
): boolean => {
    return (
        editor.getRootBlockId() === block.id ||
        block.type === Protocol.Block.Type.group ||
        block.type === Protocol.Block.Type.page
    );
};

/**
 * Move down
 * @returns true if indent is success
 * @example
 * ```
 * [ ]
 *  └─ [ ]
 * [x]     <- tab
 *  └─ [ ]
 *
 * ↓
 *
 * [ ]
 *  ├─ [ ]
 *  ├─ [x] <-
 *  └─ [ ]
 * ```
 */
const indentBlock = async (block: TodoAsyncBlock) => {
    const previousBlock = await block.previousSibling();

    if (!previousBlock || !supportChildren(previousBlock)) {
        // Bottom, can not indent, do nothing
        return false;
    }

    // Indent current node, but preserve child node hierarchy
    const previousTodo = previousBlock as TodoAsyncBlock;
    await previousTodo.setProperties({
        collapsed: { value: false },
    });

    // 1. save target block children
    const children = await block.children();

    // 2. remove current block and children
    await block.remove();
    await block.removeChildren();
    // 3. append target block and children to previous node
    await previousTodo.append(block, ...children);
    return true;
};

/**
 * Move up
 * @returns true if dedent is success
 * @example
 * ```
 * [ ]
 *  ├─ [ ]
 *  ├─ [x] <- shift + tab
 *  └─ [ ]
 *
 * ↓
 *
 * [ ]
 *  └─ [ ]
 * [x]     <-
 *  └─ [ ]
 * ```
 */
export const dedentBlock = async (editor: BlockEditor, block: AsyncBlock) => {
    if (editor.getRootBlockId() === block.id) {
        return false;
    }
    let parentBlock = await block.parent();
    if (!parentBlock) {
        throw new Error('Failed to dedent block! Parent block not found!');
    }
    if (isTopLevelBlock(editor, parentBlock)) {
        // Top, do nothing
        return false;
    }
    // 1. save child blocks of the parent block
    const previousSiblings = await block.previousSiblings();
    const nextSiblings = await block.nextSiblings();
    // const children = await parentBlock.children();
    // 2. remove all child blocks after the target block from the parent block
    await parentBlock.removeChildren();
    // TODO fix block sync with db-service
    // Need update block from service now
    parentBlock = await block.parent();
    if (!parentBlock) {
        throw new Error('Failed to dedent block! Parent block not found!');
    }
    await parentBlock.append(...previousSiblings);

    parentBlock = await block.parent();
    if (!parentBlock) {
        throw new Error('Failed to dedent block! Parent block not found!');
    }

    // 3. remove current block
    await block.remove();
    // 4. append parent children to target block
    await block.append(...nextSiblings);
    // 5. append block to parent
    await parentBlock.after(block);
    return true;
};

export const tabBlock = async (
    editor: BlockEditor,
    block: AsyncBlock,
    isShiftKey: boolean
) => {
    if (isShiftKey) {
        return await dedentBlock(editor, block);
    } else {
        return await indentBlock(block);
    }
};

const type2Map: Record<number, string> = {};
const type3Map: Record<number, string> = {};
export function getNumber(type: string, index: number) {
    if (type === 'type2') {
        if (type2Map[index]) {
            return type2Map[index];
        }
        type2Map[index] = getType2(index - 1);

        return type2Map[index];
    }
    if (type === 'type3') {
        if (type3Map[index]) {
            return type3Map[index];
        }
        type3Map[index] = getType3(index);

        return type3Map[index];
    }
    return index;
}

const getType2 = (n: number) => {
    const ordA = 'a'.charCodeAt(0);
    const ordZ = 'z'.charCodeAt(0);
    const len = ordZ - ordA + 1;
    let s = '';
    while (n >= 0) {
        s = String.fromCharCode((n % len) + ordA) + s;
        n = Math.floor(n / len) - 1;
    }
    return s;
};
const getType3 = (num: number) => {
    const lookup = {
        m: 1000,
        cm: 900,
        d: 500,
        cd: 400,
        c: 100,
        xc: 90,
        l: 50,
        xl: 40,
        x: 10,
        ix: 9,
        v: 5,
        iv: 4,
        i: 1,
    };
    let romanStr = '';
    for (const i in lookup) {
        while (num >= lookup[i as keyof typeof lookup]) {
            romanStr += i;
            num -= lookup[i as keyof typeof lookup];
        }
    }
    return romanStr;
};

export function getChildrenType(type: string) {
    const typeMap: Record<string, string> = {
        type1: 'type2',
        type2: 'type3',
        type3: 'type1',
    };
    return typeMap[type];
}
