import { v1 as uuid } from 'uuid';

import type {
    BlockType as _BlockType,
    IActionsBlock,
    IBlock,
    IConditionalBlock,
    IConditionalBlockFilters,
    IContextBlock,
    IImageBlock,
    IInputBlock,
    ISectionBlock,
} from '@rocket.chat/apps-engine/definition/uikit/blocks/Blocks.ts';
import type {
    BlockElementType as _BlockElementType,
    IBlockElement,
    IButtonElement,
    IImageElement,
    IInputElement,
    IInteractiveElement,
    IMultiStaticSelectElement,
    IOverflowMenuElement,
    IPlainTextInputElement,
    ISelectElement,
    IStaticSelectElement,
} from '@rocket.chat/apps-engine/definition/uikit/blocks/Elements.ts';
import type { ITextObject, TextObjectType as _TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks/Objects.ts';

import { AppObjectRegistry } from '../../../AppObjectRegistry.ts';
import { require } from '../../../lib/require.ts';

const { BlockType } = require('@rocket.chat/apps-engine/definition/uikit/blocks/Blocks.js') as { BlockType: typeof _BlockType };
const { BlockElementType } = require('@rocket.chat/apps-engine/definition/uikit/blocks/Elements.js') as { BlockElementType: typeof _BlockElementType };
const { TextObjectType } = require('@rocket.chat/apps-engine/definition/uikit/blocks/Objects.js') as { TextObjectType: typeof _TextObjectType };

type BlockFunctionParameter<T extends IBlock> = Omit<T, 'type'>;
type ElementFunctionParameter<T extends IBlockElement> = T extends IInteractiveElement
    ? Omit<T, 'type' | 'actionId'> | Partial<Pick<T, 'actionId'>>
    : Omit<T, 'type'>;

type SectionBlockParam = BlockFunctionParameter<ISectionBlock>;
type ImageBlockParam = BlockFunctionParameter<IImageBlock>;
type ActionsBlockParam = BlockFunctionParameter<IActionsBlock>;
type ContextBlockParam = BlockFunctionParameter<IContextBlock>;
type InputBlockParam = BlockFunctionParameter<IInputBlock>;

type ButtonElementParam = ElementFunctionParameter<IButtonElement>;
type ImageElementParam = ElementFunctionParameter<IImageElement>;
type OverflowMenuElementParam = ElementFunctionParameter<IOverflowMenuElement>;
type PlainTextInputElementParam = ElementFunctionParameter<IPlainTextInputElement>;
type StaticSelectElementParam = ElementFunctionParameter<IStaticSelectElement>;
type MultiStaticSelectElementParam = ElementFunctionParameter<IMultiStaticSelectElement>;

/**
 * @deprecated please prefer the rocket.chat/ui-kit components
 */
export class BlockBuilder {
    private readonly blocks: Array<IBlock>;
    private readonly appId: string;

    constructor() {
        this.blocks = [];
        this.appId = String(AppObjectRegistry.get('id'));
    }

    public addSectionBlock(block: SectionBlockParam): BlockBuilder {
        this.addBlock({ type: BlockType.SECTION, ...block } as ISectionBlock);

        return this;
    }

    public addImageBlock(block: ImageBlockParam): BlockBuilder {
        this.addBlock({ type: BlockType.IMAGE, ...block } as IImageBlock);

        return this;
    }

    public addDividerBlock(): BlockBuilder {
        this.addBlock({ type: BlockType.DIVIDER });

        return this;
    }

    public addActionsBlock(block: ActionsBlockParam): BlockBuilder {
        this.addBlock({ type: BlockType.ACTIONS, ...block } as IActionsBlock);

        return this;
    }

    public addContextBlock(block: ContextBlockParam): BlockBuilder {
        this.addBlock({ type: BlockType.CONTEXT, ...block } as IContextBlock);

        return this;
    }

    public addInputBlock(block: InputBlockParam): BlockBuilder {
        this.addBlock({ type: BlockType.INPUT, ...block } as IInputBlock);

        return this;
    }

    public addConditionalBlock(innerBlocks: BlockBuilder | Array<IBlock>, condition?: IConditionalBlockFilters): BlockBuilder {
        const render = innerBlocks instanceof BlockBuilder ? innerBlocks.getBlocks() : innerBlocks;

        this.addBlock({
            type: BlockType.CONDITIONAL,
            render,
            when: condition,
        } as IConditionalBlock);

        return this;
    }

    public getBlocks() {
        return this.blocks;
    }

    public newPlainTextObject(text: string, emoji = false): ITextObject {
        return {
            type: TextObjectType.PLAINTEXT,
            text,
            emoji,
        };
    }

    public newMarkdownTextObject(text: string): ITextObject {
        return {
            type: TextObjectType.MARKDOWN,
            text,
        };
    }

    public newButtonElement(info: ButtonElementParam): IButtonElement {
        return this.newInteractiveElement({
            type: BlockElementType.BUTTON,
            ...info,
        } as IButtonElement);
    }

    public newImageElement(info: ImageElementParam): IImageElement {
        return {
            type: BlockElementType.IMAGE,
            ...info,
        };
    }

    public newOverflowMenuElement(info: OverflowMenuElementParam): IOverflowMenuElement {
        return this.newInteractiveElement({
            type: BlockElementType.OVERFLOW_MENU,
            ...info,
        } as IOverflowMenuElement);
    }

    public newPlainTextInputElement(info: PlainTextInputElementParam): IPlainTextInputElement {
        return this.newInputElement({
            type: BlockElementType.PLAIN_TEXT_INPUT,
            ...info,
        } as IPlainTextInputElement);
    }

    public newStaticSelectElement(info: StaticSelectElementParam): IStaticSelectElement {
        return this.newSelectElement({
            type: BlockElementType.STATIC_SELECT,
            ...info,
        } as IStaticSelectElement);
    }

    public newMultiStaticElement(info: MultiStaticSelectElementParam): IMultiStaticSelectElement {
        return this.newSelectElement({
            type: BlockElementType.MULTI_STATIC_SELECT,
            ...info,
        } as IMultiStaticSelectElement);
    }

    private newInteractiveElement<T extends IInteractiveElement>(element: T): T {
        if (!element.actionId) {
            element.actionId = this.generateActionId();
        }

        return element;
    }

    private newInputElement<T extends IInputElement>(element: T): T {
        if (!element.actionId) {
            element.actionId = this.generateActionId();
        }

        return element;
    }

    private newSelectElement<T extends ISelectElement>(element: T): T {
        if (!element.actionId) {
            element.actionId = this.generateActionId();
        }

        return element;
    }

    private addBlock(block: IBlock): void {
        if (!block.blockId) {
            block.blockId = this.generateBlockId();
        }

        block.appId = this.appId;

        this.blocks.push(block);
    }

    private generateBlockId(): string {
        return uuid();
    }

    private generateActionId(): string {
        return uuid();
    }
}
