/**
 * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
 * SPDX-License-Identifier: MIT
 */

/* eslint-disable complexity */
import { inject, injectable } from 'inversify';
import { type IPoint } from '@flowgram.ai/utils';
import { SelectorBoxConfigEntity } from '@flowgram.ai/renderer';
import {
  WorkflowDocument,
  WorkflowDragService,
  WorkflowHoverService,
  WorkflowLineEntity,
  WorkflowLinesManager,
  WorkflowNodeEntity,
  WorkflowSelectService,
} from '@flowgram.ai/free-layout-core';
import { WorkflowPortEntity } from '@flowgram.ai/free-layout-core';
import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
import {
  EditorState,
  EditorStateConfigEntity,
  Layer,
  PlaygroundConfigEntity,
  observeEntities,
  observeEntity,
  observeEntityDatas,
  type LayerOptions,
} from '@flowgram.ai/core';

import { getSelectionBounds } from './selection-utils';
const PORT_BG_CLASS_NAME = 'workflow-port-bg';

export interface HoverLayerOptions extends LayerOptions {
  canHovered?: (e: MouseEvent, service: WorkflowHoverService) => boolean;
}

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace HoverLayerOptions {
  export const DEFAULT: HoverLayerOptions = {
    canHovered: () => true,
  };
}

const LINE_CLASS_NAME = '.gedit-flow-activity-line';
const NODE_CLASS_NAME = '.gedit-flow-activity-node';

@injectable()
export class HoverLayer extends Layer<HoverLayerOptions> {
  static type = 'HoverLayer';

  @inject(WorkflowDocument) document: WorkflowDocument;

  @inject(WorkflowSelectService) selectionService: WorkflowSelectService;

  @inject(WorkflowDragService) dragService: WorkflowDragService;

  @inject(WorkflowHoverService) hoverService: WorkflowHoverService;

  @inject(WorkflowLinesManager)
  linesManager: WorkflowLinesManager;

  @observeEntity(EditorStateConfigEntity)
  protected editorStateConfig: EditorStateConfigEntity;

  @observeEntity(SelectorBoxConfigEntity)
  protected selectorBoxConfigEntity: SelectorBoxConfigEntity;

  @inject(PlaygroundConfigEntity) configEntity: PlaygroundConfigEntity;

  /**
   * 监听节点 transform
   */
  @observeEntityDatas(WorkflowNodeEntity, FlowNodeTransformData)
  protected readonly nodeTransforms: FlowNodeTransformData[];

  /**
   * 按选中排序
   * @private
   */
  protected nodeTransformsWithSort: FlowNodeTransformData[] = [];

  autorun(): void {
    const { activatedNode } = this.selectionService;
    this.nodeTransformsWithSort = this.nodeTransforms
      .filter((n) => n.entity.id !== 'root' && n.entity.flowNodeType !== FlowNodeBaseType.GROUP)
      .reverse() // 后创建的排在前面
      .sort((n1) => (n1.entity === activatedNode ? -1 : 0));
  }

  /**
   * 监听线条
   */
  @observeEntities(WorkflowLineEntity)
  protected readonly lines: WorkflowLineEntity[];

  /**
   * 是否正在调整线条
   * @protected
   */
  get isDrawing(): boolean {
    return this.linesManager.isDrawing;
  }

  onReady(): void {
    this.options = {
      ...HoverLayerOptions.DEFAULT,
      ...this.options,
    };
    this.toDispose.pushAll([
      // 监听主动触发的 hover 事件
      this.hoverService.onUpdateHoverPosition((hoverPosition) => {
        const { position, target } = hoverPosition;
        const canvasPosition = this.config.getPosFromMouseEvent({
          clientX: position.x,
          clientY: position.y,
        });
        this.updateHoveredState(canvasPosition, target);
      }),
      // 监听画布鼠标移动事件
      this.listenPlaygroundEvent('mousemove', (e: MouseEvent) => {
        this.hoverService.hoveredPos = this.config.getPosFromMouseEvent(e);
        if (!this.isEnabled()) {
          return;
        }
        if (!this.options.canHovered!(e, this.hoverService)) {
          return;
        }
        const mousePos = this.config.getPosFromMouseEvent(e);
        // 更新 hover 状态
        this.updateHoveredState(mousePos, e?.target as HTMLElement);
      }),
      this.selectionService.onSelectionChanged(() => this.autorun()),
      // 控制触控
      this.listenPlaygroundEvent('touchstart', (e: MouseEvent): boolean | undefined => {
        if (!this.isEnabled() || this.isDrawing) {
          return undefined;
        }
        return this.handleDragLine(e);
      }),
      // 控制选中逻辑
      this.listenPlaygroundEvent('mousedown', (e: MouseEvent): boolean | undefined => {
        if (!this.isEnabled() || this.isDrawing) {
          return undefined;
        }
        const { hoveredNode } = this.hoverService;
        const lineDrag = this.handleDragLine(e);
        if (lineDrag) {
          return true;
        }
        const mousePos = this.config.getPosFromMouseEvent(e);
        const selectionBounds = getSelectionBounds(
          this.selectionService.selection,
          // 这里只考虑多选模式，单选模式已经下沉到 use-node-render 中
          true
        );
        if (selectionBounds.width > 0 && selectionBounds.contains(mousePos.x, mousePos.y)) {
          /**
           * 拖拽选择框
           */
          this.dragService.startDragSelectedNodes(e)?.then((dragSuccess) => {
            if (!dragSuccess) {
              // 拖拽没有成功触发了点击
              if (hoveredNode && hoveredNode instanceof WorkflowNodeEntity) {
                // 追加选择
                if (e.shiftKey) {
                  this.selectionService.toggleSelect(hoveredNode);
                } else {
                  this.selectionService.selectNode(hoveredNode);
                }
              } else {
                this.selectionService.clear();
              }
            }
          });
          // 这里会组织触发 selector box
          return true;
        } else {
          if (!hoveredNode) {
            this.selectionService.clear();
          }
        }
        return undefined;
      }),
    ]);
  }

  /**
   * 更新 hoverd
   * @param mousePos
   */
  updateHoveredState(mousePos: IPoint, target?: HTMLElement): void {
    const { hoverService } = this;
    const nodeTransforms = this.nodeTransformsWithSort;
    const outputPortHovered = this.linesManager.getPortFromMousePos(mousePos, 'output');
    const inputPortHovered = this.linesManager.getPortFromMousePos(mousePos, 'input');
    // 在两个端口叠加情况，优先使用 outputPort
    const portHovered = outputPortHovered || inputPortHovered;

    const lineDomNodes = this.playgroundNode.querySelectorAll(LINE_CLASS_NAME);
    const checkTargetFromLine = [...lineDomNodes].some((lineDom) =>
      lineDom.contains(target as HTMLElement)
    );
    if (portHovered) {
      if (this.document.options.twoWayConnection) {
        hoverService.updateHoveredKey(portHovered.id);
      } else {
        // 默认 只有 output 点位可以 hover
        if (portHovered.portType === 'output') {
          hoverService.updateHoveredKey(portHovered.id);
        } else if (checkTargetFromLine || target?.className?.includes?.(PORT_BG_CLASS_NAME)) {
          // 输入点采用获取最接近的线条
          const lineHovered = this.linesManager.getCloseInLineFromMousePos(mousePos);
          if (lineHovered) {
            this.updateHoveredKey(lineHovered.id);
          }
        }
      }
      return;
    }

    // Drawing 情况，不能选中节点和线条
    if (this.isDrawing) {
      return;
    }

    const nodeHovered = nodeTransforms.find((trans: FlowNodeTransformData) =>
      trans.bounds.contains(mousePos.x, mousePos.y)
    )?.entity as WorkflowNodeEntity;

    // 判断当前鼠标位置所在元素是否在节点内部
    const nodeDomNodes = this.playgroundNode.querySelectorAll(NODE_CLASS_NAME);
    const checkTargetFromNode = [...nodeDomNodes].some((nodeDom) =>
      nodeDom.contains(target as HTMLElement)
    );

    if (nodeHovered || checkTargetFromNode) {
      if (nodeHovered?.id) {
        this.updateHoveredKey(nodeHovered.id);
      }
    }

    // 获取最接近的线条
    // 线条会相交需要获取最接近点位的线条，不能删除的线条不能被选中
    const lineHovered = checkTargetFromLine
      ? this.linesManager.getCloseInLineFromMousePos(mousePos)
      : undefined;

    if (nodeHovered && lineHovered) {
      const nodeStackIndex = nodeHovered.renderData.stackIndex;
      const lineStackIndex = lineHovered.stackIndex;
      if (nodeStackIndex > lineStackIndex) {
        return this.updateHoveredKey(nodeHovered.id);
      } else {
        return this.updateHoveredKey(lineHovered.id);
      }
    }

    // 判断节点是否 hover
    if (nodeHovered) {
      return this.updateHoveredKey(nodeHovered.id);
    }
    // 判断线条是否 hover
    if (lineHovered) {
      return this.updateHoveredKey(lineHovered.id);
    }

    // 上述逻辑都未命中 则清空 hovered
    hoverService.clearHovered();

    const currentState = this.editorStateConfig.getCurrentState();
    const isMouseFriendly = currentState === EditorState.STATE_MOUSE_FRIENDLY_SELECT;

    // 鼠标优先，并且不是按住 shift 键，更新为小手
    if (isMouseFriendly && !this.editorStateConfig.isPressingShift) {
      this.configEntity.updateCursor('grab');
    }
  }

  updateHoveredKey(key: string): void {
    // 鼠标优先交互模式，如果是 hover，需要将鼠标的小手去掉，还原鼠标原有样式
    this.configEntity.updateCursor('default');
    this.hoverService.updateHoveredKey(key);
  }

  /**
   * 判断是否能够 hover
   * @returns 是否能 hover
   */
  isEnabled(): boolean {
    const currentState = this.editorStateConfig.getCurrentState();
    // 选择框情况禁止 hover
    return (
      // 鼠标友好模式下，也需要支持 hover 效果，不然线条选择不到
      // Coze 中没有使用该插件，需要在 workflow/render 包相应位置改动
      (currentState === EditorState.STATE_SELECT ||
        currentState === EditorState.STATE_MOUSE_FRIENDLY_SELECT) &&
      !this.selectorBoxConfigEntity.isStart &&
      !this.dragService.isDragging
    );
  }

  private handleDragLine(e: MouseEvent): boolean | undefined {
    const { someHovered } = this.hoverService;
    // 重置线条
    if (someHovered && someHovered instanceof WorkflowLineEntity) {
      this.dragService.resetLine(someHovered, e);
      return true;
    }
    if (
      someHovered &&
      someHovered instanceof WorkflowPortEntity &&
      !someHovered.disabled &&
      e.button !== 1
    ) {
      e.stopPropagation();
      e.preventDefault();
      this.dragService.startDrawingLine(someHovered, e);
      return true;
    }
  }
}
