import type { BlockElement } from '@blockeditor/lit';
import { type BlockModel } from '@blockeditor/store';

import { assertFlavours } from '../../../../_common/utils/model.js';
import {
  asyncFocusRichText,
  asyncSetInlineRange,
} from '../../../../_common/utils/selection.js';
import type { Flavour } from '../../../../models.js';
import { onModelTextUpdated } from '../../callback.js';
import { mergeToCodeModel, transformModel } from '../model.js';

export function updateBlockElementType(
  blockElements: BlockElement[],
  flavour: Flavour,
  type?: string
) {
  if (blockElements.length === 0) {
    return [];
  }
  const editorHost = blockElements[0].host;
  const page = blockElements[0].page;
  const hasSamePage = blockElements.every(block => block.page === page);
  if (!hasSamePage) {
    // page check
    console.error(
      'Not all models have the same page instance, the result for update text type may not be correct',
      blockElements
    );
  }

  page.captureSync();

  const blockModels = blockElements.map(ele => ele.model);

  if (flavour === 'affine:code') {
    const id = mergeToCodeModel(blockModels);
    const model = page.getBlockById(id);
    if (!model) {
      throw new Error('Failed to get model after merge code block!');
    }
    asyncSetInlineRange(editorHost, model, {
      index: model.text?.length ?? 0,
      length: 0,
    }).catch(console.error);
    return [model];
  }
  if (flavour === 'affine:divider') {
    const model = blockModels.at(-1);
    if (!model) {
      return [];
    }
    const parent = page.getParent(model);
    if (!parent) {
      return [];
    }
    const index = parent.children.indexOf(model);
    const nextSibling = page.getNextSibling(model);
    let nextSiblingId = nextSibling?.id as string;
    const id = page.addBlock('affine:divider', {}, parent, index + 1);
    if (!nextSibling) {
      nextSiblingId = page.addBlock('affine:paragraph', {}, parent);
    }
    asyncFocusRichText(editorHost, page, nextSiblingId)?.catch(console.error);
    const newModel = page.getBlockById(id);
    if (!newModel) {
      throw new Error('Failed to get model after add divider block!');
    }
    return [newModel];
  }

  const newModels: BlockModel[] = [];
  blockModels.forEach(model => {
    assertFlavours(model, ['affine:paragraph', 'affine:list', 'affine:code']);
    if (model.flavour === flavour) {
      page.updateBlock(model, { type });
      newModels.push(model);
      return;
    }
    const newId = transformModel(model, flavour, { type });
    const newModel = page.getBlockById(newId);
    if (!newModel) {
      throw new Error('Failed to get new model after transform block!');
    }
    newModels.push(newModel);
  });

  const firstNewModel = newModels[0];
  const lastNewModel = newModels[newModels.length - 1];

  const allTextUpdated = newModels.map(model =>
    onModelTextUpdated(editorHost, model)
  );
  const selectionManager = editorHost.selection;
  const textSelection = selectionManager.find('text');
  const blockSelections = selectionManager.filter('block');

  if (textSelection) {
    const newTextSelection = selectionManager.create('text', {
      from: {
        path: textSelection.from.path.slice(0, -1).concat(firstNewModel.id),
        index: textSelection.from.index,
        length: textSelection.from.length,
      },
      to: textSelection.to
        ? {
            path: textSelection.to.path.slice(0, -1).concat(lastNewModel.id),
            index: textSelection.to.index,
            length: textSelection.to.length,
          }
        : null,
    });

    Promise.all(allTextUpdated)
      .then(() => {
        selectionManager.setGroup('note', [newTextSelection]);
      })
      .catch(console.error);
    return newModels;
  }

  if (blockSelections.length !== 0) {
    requestAnimationFrame(() => {
      const selections = newModels.map(model => {
        return selectionManager.create('block', {
          path: blockSelections[0].path.slice(0, -1).concat(model.id),
        });
      });

      selectionManager.setGroup('note', selections);
    });
    return newModels;
  }

  return newModels;
}
