import type { UIEventStateContext } from '@blockeditor/block-std';
import {
  assertExists,
  DisposableGroup,
  throttle,
} from '@blockeditor/global/utils';
import { WidgetElement } from '@blockeditor/lit';
import type { BlockModel } from '@blockeditor/store';
import { customElement } from 'lit/decorators.js';

import {
  getCurrentNativeRange,
  getInlineEditorByModel,
  isControlledKeyboardEvent,
  matchFlavours,
} from '../../../_common/utils/index.js';
import type { PageBlockComponent } from '../../../page-block/types.js';
import { isPageComponent } from '../../../page-block/utils/guard.js';
import { getPopperPosition } from '../../../page-block/utils/position.js';
import { AIMenu } from './ai-menu-popover.js';
import { menuGroups } from './config.js';
import type { AIMenuOptions } from './utils.js';

let globalAbortController = new AbortController();

function showAIMenu({
  pageElement,
  model,
  range,
  container = document.body,
  abortController = new AbortController(),
  options,
  triggerKey,
}: {
  pageElement: PageBlockComponent;
  model: BlockModel;
  range: Range;
  container?: HTMLElement;
  abortController?: AbortController;
  options: AIMenuOptions;
  triggerKey: string;
}) {
  // Abort previous format quick bar
  globalAbortController.abort();
  globalAbortController = abortController;
  const disposables = new DisposableGroup();
  abortController.signal.addEventListener('abort', () => disposables.dispose());

  const aiMenu = new AIMenu();
  disposables.add(() => aiMenu.remove());
  aiMenu.model = model;
  aiMenu.abortController = abortController;
  aiMenu.options = options;
  aiMenu.pageElement = pageElement;
  aiMenu.triggerKey = triggerKey;

  // Handle position
  const updatePosition = throttle(() => {
    const aiMenuElement = aiMenu.aiMenuElement;
    assertExists(
      aiMenuElement,
      'You should render the ai menu node even if no position'
    );
    const position = getPopperPosition(aiMenuElement, range);
    aiMenu.updatePosition(position);
  }, 10);

  disposables.addFromEvent(window, 'resize', updatePosition);

  // FIXME(Flrande): It is not a best practice,
  // but merely a temporary measure for reusing previous components.
  // Mount
  container.appendChild(aiMenu);
  // Wait for the Node to be mounted
  setTimeout(updatePosition);
  return aiMenu;
}

export const AFFINE_AI_MENU_WIDGET = 'affine-ai-menu-widget';

@customElement(AFFINE_AI_MENU_WIDGET)
export class AffineAIMenuWidget extends WidgetElement {
  static DEFAULT_OPTIONS: AIMenuOptions = {
    isTriggerKey: (event: KeyboardEvent): false | string => {
      // const triggerKeys = [];
      // console.log(111, event);

      // if (
      //   event.key === 'Process' &&
      //   event.code === 'Slash' &&
      //   !event.shiftKey
      // ) {
      //   return 'A';
      // }
      if (isControlledKeyboardEvent(event) || event.key.length !== 1)
        return false;
      // const triggerKey = triggerKeys.find(key => key === event.key);
      // if (!triggerKey) return false;
      // return triggerKey;
      return false;
    },
    menus: menuGroups,
  };

  options = AffineAIMenuWidget.DEFAULT_OPTIONS;

  override connectedCallback() {
    super.connectedCallback();
    this.handleEvent('keyDown', this._onKeyDown);
  }

  private _onKeyDown = (ctx: UIEventStateContext) => {
    const eventState = ctx.get('keyboardState');
    const event = eventState.raw;
    // console.log(222, event);
    const triggerKey = this.options.isTriggerKey(event);
    if (triggerKey === false) return;
    const text = this.host.selection.value.find(selection =>
      selection.is('text')
    );
    // console.log(333, text, {...this.host})
    if (!text) {
      return;
    }
    const model = this.host.page.getBlockById(text.blockId);
    // console.log(444, model)
    if (!model || !model.text || model.text.length > 1) {
      return;
    }

    if (matchFlavours(model, ['affine:code'])) return;
    const inlineEditor = getInlineEditorByModel(this.host, model);
    if (!inlineEditor) return;
    inlineEditor.slots.inlineRangeApply.once(() => {
      const pageElement = this.blockElement;
      if (!isPageComponent(pageElement)) {
        throw new Error('AIMenuWidget should be used in PageBlock');
      }

      // Wait for dom update, see this case https://github.com/toeverything/blockeditor/issues/2611
      requestAnimationFrame(() => {
        const curRange = getCurrentNativeRange();
        showAIMenu({
          pageElement,
          model,
          range: curRange,
          triggerKey,
          options: this.options,
        });
      });
    });
  };
}

declare global {
  interface HTMLElementTagNameMap {
    [AFFINE_AI_MENU_WIDGET]: AffineAIMenuWidget;
  }
}
