import {
  EDITOR_CONFIG,
  type MapAreaInfo,
  MapAreaType,
  type MapPen,
  type MapPointInfo,
  MapPointType,
  type MapRouteInfo,
  MapRoutePassType,
  MapRouteType,
  type Point,
  type Rect,
} from '@api/map';
import type { RobotGroup, RobotInfo, RobotRealtimeInfo, RobotType } from '@api/robot';
import type {
  GroupSceneDetail,
  SceneData,
  StandardScene,
  StandardSceneArea,
  StandardScenePoint,
  StandardSceneRoute,
} from '@api/scene';
import sTheme from '@core/theme.service';
import { CanvasLayer, LockState, Meta2d, type Meta2dStore, type Pen, s8 } from '@meta2d/core';
import { useObservable } from '@vueuse/rxjs';
import { clone, get, isEmpty, isNil, isString, nth, omitBy, pick, remove, some } from 'lodash-es';
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
import { reactive, watch } from 'vue';

/**
 * 场景编辑器服务类
 * 继承自Meta2D，提供完整的场景编辑功能
 *
 * 主要功能：
 * - 场景文件的加载、保存和管理
 * - 点位、路线、区域的创建和编辑
 * - 机器人组的管理和实时状态更新
 * - 鼠标事件的处理和响应式数据流
 * - 自定义绘制和渲染逻辑
 */
export class EditorService extends Meta2d {
  //#region 场景文件管理
  /**
   * 加载场景文件到编辑器
   * @param map 场景文件的JSON字符串，为空则创建新场景
   * @param editable 是否可编辑状态，控制编辑器锁定状态
   * @param detail 群组场景详情，包含机器人组和机器人信息
   */
  public async load(map?: string, editable = false, detail?: Partial<GroupSceneDetail>): Promise<void> {
    const scene: StandardScene = map ? JSON.parse(map) : {};
    if (!isEmpty(detail?.group)) {
      scene.robotGroups = [detail.group];
      scene.robots = detail.robots;
    }
    const { robotGroups, robots, points, routes, areas } = scene;
    this.open();
    this.setState(editable);
    this.#loadRobots(robotGroups, robots);
    await this.#loadScenePoints(points);
    this.#loadSceneRoutes(routes);
    await this.#loadSceneAreas(areas);
    this.store.historyIndex = undefined;
    this.store.histories = [];
  }
  /**
   * 保存当前场景为JSON字符串
   * @returns 包含完整场景数据的JSON字符串
   */
  public save(): string {
    const scene: StandardScene = {
      robotGroups: this.robotGroups.value,
      robots: this.robots,
      points: this.points.value.map((v) => this.#mapScenePoint(v)).filter((v) => !isNil(v)),
      routes: this.routes.value.map((v) => this.#mapSceneRoute(v)).filter((v) => !isNil(v)),
      areas: this.areas.value.map((v) => this.#mapSceneArea(v)).filter((v) => !isNil(v)),
      blocks: [],
    };
    return JSON.stringify(scene);
  }

  /**
   * 加载机器人数据到编辑器
   * @param groups 机器人组列表
   * @param robots 机器人信息列表
   */
  #loadRobots(groups?: RobotGroup[], robots?: RobotInfo[]): void {
    this.#robotMap.clear();
    robots?.forEach((v) => this.#robotMap.set(v.id, v));
    this.#robotGroups$$.next(groups ?? []);
  }
  /**
   * 从场景数据加载点位到画布
   * @param points 标准场景点位数据数组
   */
  async #loadScenePoints(points?: StandardScenePoint[]): Promise<void> {
    if (!points?.length) return;
    await Promise.all(
      points.map(async (v) => {
        const { id, name, desc, x, y, type, robots, actions, properties } = v;
        await this.addPoint({ x, y }, type, id);
        this.setValue(
          { id, label: name, desc, properties, point: { type, robots, actions } },
          { render: false, history: false, doEvent: false },
        );
      }),
    );
  }
  /**
   * 从场景数据加载路线到画布
   * @param routes 标准场景路线数据数组
   */
  #loadSceneRoutes(routes?: StandardSceneRoute[]): void {
    if (!routes?.length) return;
    routes.map((v) => {
      const { id, desc, from, to, type, pass, c1, c2, properties } = v;
      const p1 = this.getPenById(from);
      const p2 = this.getPenById(to);
      if (isNil(p1) || isNil(p2)) return;
      this.addRoute([p1, p2], <MapRouteType>type, id);
      const { x: x1, y: y1 } = this.getPointRect(p1)!;
      const { x: x2, y: y2 } = this.getPointRect(p2)!;
      this.setValue(
        {
          id,
          desc,
          properties,
          route: {
            type,
            pass,
            c1: { x: (c1?.x ?? 0) - x1, y: (c1?.y ?? 0) - y1 },
            c2: { x: (c2?.x ?? 0) - x2, y: (c2?.y ?? 0) - y2 },
          },
        },
        { render: false, history: false, doEvent: false },
      );
    });
  }
  async #loadSceneAreas(areas?: StandardSceneArea[]): Promise<void> {
    if (!areas?.length) return;
    await Promise.all(
      areas.map(async (v) => {
        const { id, name, desc, x, y, w, h, type, points, routes, properties } = v;
        await this.addArea({ x, y }, { x: x + w, y: y + h }, type, id);
        this.setValue(
          { id, label: name, desc, properties, area: { type, points, routes } },
          { render: false, history: false, doEvent: false },
        );
      }),
    );
  }

  #mapScenePoint(pen?: MapPen): StandardScenePoint | null {
    if (!pen?.id || isEmpty(pen?.point)) return null;
    const { id, label, desc, properties } = pen;
    const { type, robots, actions } = pen.point;
    const { x = 0, y = 0 } = this.getPointRect(pen) ?? {};
    const point: StandardScenePoint = {
      id: id,
      name: label || id,
      desc,
      x,
      y,
      type,
      config: {},
      properties,
    };
    if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
      point.robots = robots?.filter((v) => this.#robotMap.has(v));
    }
    if (MapPointType.等待点 === type) {
      point.actions = actions?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
    }
    return point;
  }
  #mapSceneRoute(pen?: MapPen): StandardSceneRoute | null {
    if (!pen?.id || pen.anchors?.length !== 2 || isEmpty(pen?.route)) return null;
    const { id, anchors, desc, properties } = pen;
    const { type, direction = 1, pass, c1, c2 } = pen.route;
    const [p1, p2] = anchors.map((v) => this.getPenById(v.connectTo!));
    if (isNil(p1) || isNil(p2)) return null;
    const route: StandardSceneRoute = {
      id: id,
      desc,
      from: direction < 0 ? p2.id! : p1.id!,
      to: direction < 0 ? p1.id! : p2.id!,
      type,
      pass,
      config: {},
      properties,
    };
    const { x: x1, y: y1 } = this.getPointRect(p1)!;
    const { x: x2, y: y2 } = this.getPointRect(p2)!;
    const cp1 = { x: x1 + (c1?.x ?? 0), y: y1 + (c1?.y ?? 0) };
    const cp2 = { x: x2 + (c2?.x ?? 0), y: y2 + (c2?.y ?? 0) };
    switch (type) {
      case MapRouteType.二阶贝塞尔曲线:
        route.c1 = cp1;
        break;
      case MapRouteType.三阶贝塞尔曲线:
        route.c1 = direction < 0 ? cp2 : cp1;
        route.c2 = direction < 0 ? cp1 : cp2;
        break;
      default:
        break;
    }
    return route;
  }
  #mapSceneArea(pen: MapPen): StandardSceneArea | null {
    if (!pen.id || isEmpty(pen.area)) return null;
    const { id, label, desc, properties } = pen;
    const { type, points, routes } = pen.area;
    const { x, y, width, height } = this.getPenRect(pen);
    const area: StandardSceneArea = {
      id,
      name: label || id,
      desc,
      x,
      y,
      w: width,
      h: height,
      type,
      config: {},
      properties,
    };
    if (MapAreaType.库区 === type) {
      area.points = points?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
    }
    if ([MapAreaType.互斥区, MapAreaType.非互斥区].includes(type)) {
      area.points = points?.filter((v) => {
        const { point } = this.getPenById(v) ?? {};
        if (isNil(point)) return false;
        if (point.type === MapPointType.禁行点) return false;
        return true;
      });
    }
    if (MapAreaType.互斥区 === type) {
      area.routes = routes?.filter((v) => !isEmpty(this.getPenById(v)?.area));
    }
    return area;
  }
  //#endregion

  /**
   * 设置编辑器状态
   * @param editable 是否可编辑，true为可编辑状态，false为只读状态
   */
  public setState(editable?: boolean): void {
    this.lock(editable ? LockState.None : LockState.DisableEdit);
  }

  public override data(): SceneData {
    return super.data();
  }

  /** 鼠标事件流主体，用于内部事件分发 */
  readonly #mouse$$ = new Subject<{ type: 'click' | 'mousedown' | 'mouseup'; value: Point }>();

  /** 鼠标点击事件的响应式流，防抖处理后的点击坐标 */
  public readonly mouseClick = useObservable<Point>(
    this.#mouse$$.pipe(
      filter(({ type }) => type === 'click'),
      debounceTime(100),
      map(({ value }) => value),
    ),
  );

  /** 鼠标拖拽事件的响应式流，返回起始点和结束点坐标，用于创建区域 */
  public readonly mouseBrush = useObservable<[Point, Point]>(
    this.#mouse$$.pipe(
      filter(({ type }) => type === 'mousedown'),
      switchMap(({ value: s }) =>
        this.#mouse$$.pipe(
          filter(({ type }) => type === 'mouseup'),
          map(({ value: e }) => <[Point, Point]>[s, e]),
        ),
      ),
    ),
  );

  //#region 机器人组管理
  /** 机器人信息映射表，响应式存储所有机器人数据 */
  readonly #robotMap = reactive<Map<RobotInfo['id'], RobotInfo>>(new Map());

  /** 获取所有机器人信息数组 */
  public get robots(): RobotInfo[] {
    return Array.from(this.#robotMap.values());
  }

  public checkRobotById(id: RobotInfo['id']): boolean {
    return this.#robotMap.has(id);
  }
  public getRobotById(id: RobotInfo['id']): RobotInfo | undefined {
    return this.#robotMap.get(id);
  }

  public updateRobot(id: RobotInfo['id'], value: Partial<RobotInfo>): void {
    const robot = this.getRobotById(id);
    if (isNil(robot)) return;
    this.#robotMap.set(id, { ...robot, ...value });
    if (value.label) {
      this.setValue({ id, text: value.label }, { render: true, history: false, doEvent: false });
    }
    (<SceneData>this.store.data).robots = [...this.#robotMap.values()];
  }

  public addRobots(gid: RobotInfo['gid'], robots: RobotInfo[]): void {
    const groups = clone(this.#robotGroups$$.value);
    const group = groups.find((v) => v.id === gid);
    if (isNil(group)) throw Error('未找到目标机器人组');
    group.robots ??= [];
    robots.forEach((v) => {
      if (this.#robotMap.has(v.id)) return;
      this.#robotMap.set(v.id, { ...v, gid });
      group.robots?.push(v.id);
    });
    this.#robotGroups$$.next(groups);
    (<SceneData>this.store.data).robots = [...this.#robotMap.values()];
    (<SceneData>this.store.data).robotGroups = this.#robotGroups$$.value;
  }
  public removeRobots(ids: RobotInfo['id'][]): void {
    ids?.forEach((v) => this.#robotMap.delete(v));
    const groups = clone(this.#robotGroups$$.value);
    groups.forEach(({ robots }) => remove(robots ?? [], (v) => !this.#robotMap.has(v)));
    this.#robotGroups$$.next(groups);
    (<SceneData>this.store.data).robots = [...this.#robotMap.values()];
    (<SceneData>this.store.data).robotGroups = this.#robotGroups$$.value;
  }
  public updateRobots(ids: RobotInfo['id'][], value: Partial<RobotInfo>): void {
    ids?.forEach((v) => {
      const robot = this.#robotMap.get(v);
      if (isNil(robot)) return;
      this.#robotMap.set(v, { ...robot, ...value });
    });
    (<SceneData>this.store.data).robots = [...this.#robotMap.values()];
  }

  readonly #robotGroups$$ = new BehaviorSubject<RobotGroup[]>([]);
  public readonly robotGroups = useObservable<RobotGroup[]>(this.#robotGroups$$.pipe(debounceTime(300)));

  public createRobotGroup(): void {
    const id = s8();
    const label = `RG${id}`;
    const groups = clone(this.#robotGroups$$.value);
    groups.push({ id, label });
    this.#robotGroups$$.next(groups);
    (<SceneData>this.store.data).robotGroups = this.#robotGroups$$.value;
  }
  public deleteRobotGroup(id: RobotGroup['id']): void {
    const groups = clone(this.#robotGroups$$.value);
    const group = groups.find((v) => v.id === id);
    group?.robots?.forEach((v) => this.#robotMap.delete(v));
    remove(groups, group);
    this.#robotGroups$$.next(groups);
    (<SceneData>this.store.data).robots = [...this.#robotMap.values()];
    (<SceneData>this.store.data).robotGroups = this.#robotGroups$$.value;
  }
  public updateRobotGroupLabel(id: RobotGroup['id'], label: RobotGroup['label']): void {
    const groups = this.#robotGroups$$.value;
    const group = groups.find((v) => v.id === id);
    if (isNil(group)) throw Error('未找到目标机器人组');
    if (some(groups, ['label', label])) throw Error('机器人组名称已经存在');
    group.label = label;
    this.#robotGroups$$.next([...groups]);
    (<SceneData>this.store.data).robotGroups = this.#robotGroups$$.value;
  }
  //#endregion

  /** 画布变化事件流，用于触发响应式数据更新 */
  readonly #change$$ = new Subject<boolean>();

  /** 当前选中的图形对象，响应式更新 */
  public readonly current = useObservable<MapPen>(
    this.#change$$.pipe(
      debounceTime(100),
      map(() => <MapPen>clone(this.store.active?.[0])),
    ),
  );

  /** 当前选中的图形ID列表，响应式更新 */
  public readonly selected = useObservable<string[], string[]>(
    this.#change$$.pipe(
      filter((v) => !v),
      debounceTime(100),
      map(() => this.store.active?.map(({ id }) => id).filter((v) => !isNil(v)) ?? []),
    ),
    { initialValue: new Array<string>() },
  );

  /** 画布上所有图形对象列表，响应式更新 */
  public readonly pens = useObservable<MapPen[]>(
    this.#change$$.pipe(
      filter((v) => v),
      debounceTime(100),
      map(() => this.data().pens),
    ),
  );

  public override find(target: string): MapPen[] {
    return super.find(target);
  }
  public getPenById(id?: string): MapPen | undefined {
    if (!id) return;
    return this.find(id)[0];
  }

  public override active(target: string | Pen[], emit?: boolean): void {
    const pens = isString(target) ? this.find(target) : target;
    super.active(pens, emit);
    this.render();
  }
  public override inactive(): void {
    super.inactive();
    this.render();
  }

  public deleteById(id?: string): void {
    const pen = this.getPenById(id);
    if (pen?.name !== 'area') return;
    this.delete([pen], true, true);
  }

  public updatePen(id: string, pen: Partial<MapPen>, record = true): void {
    this.setValue({ ...pen, id }, { render: true, history: record, doEvent: true });
  }

  //#region 实时机器人
  public async initRobots(): Promise<void> {
    await Promise.all(
      this.robots.map(async ({ id, label, type }) => {
        const pen: MapPen = {
          ...this.#mapRobotImage(type, true),
          id,
          name: 'robot',
          tags: ['robot'],
          x: 0,
          y: 0,
          width: 74,
          height: 74,
          lineWidth: 1,
          robot: { type },
          visible: false,
          text: label,
          textTop: -24,
          whiteSpace: 'nowrap',
          ellipsis: false,
          locked: LockState.Disable,
        };
        await this.addPen(pen, false, true, true);
      }),
    );
  }

  public refreshRobot(id: RobotInfo['id'], info: Partial<RobotRealtimeInfo>): void {
    const pen = this.getPenById(id);
    const { rotate: or, robot } = pen ?? {};
    if (!robot?.type) return;
    const { x: ox, y: oy } = this.getPenRect(pen!);
    const { x: cx = 37, y: cy = 37, active, angle, path: points } = info;
    const x = cx - 37;
    const y = cy - 37;
    const rotate = angle ?? or;
    const path =
      points?.map((p) => ({ x: p.x - cx, y: p.y - cy })) ??
      robot.path?.map((p) => ({ x: p.x + ox! - x, y: p.y + oy! - y }));
    const o = { ...robot, ...omitBy({ active, path }, isNil) };
    if (isNil(active)) {
      this.setValue({ id, x, y, rotate, robot: o, visible: true }, { render: true, history: false, doEvent: false });
    } else {
      this.setValue(
        { id, ...this.#mapRobotImage(robot.type, active), x, y, rotate, robot: o, visible: true },
        { render: true, history: false, doEvent: false },
      );
    }
  }

  #mapRobotImage(
    type: RobotType,
    active?: boolean,
  ): Required<Pick<MapPen, 'image' | 'iconWidth' | 'iconHeight' | 'iconTop'>> {
    const theme = this.data().theme;
    const image =
      import.meta.env.BASE_URL + (active ? `/robot/${type}-active-${theme}.png` : `/robot/${type}-${theme}.png`);
    return { image, iconWidth: 34, iconHeight: 54, iconTop: -5 };
  }
  //#endregion

  //#region 点位
  /** 画布上所有点位对象列表，响应式更新 */
  public readonly points = useObservable<MapPen[], MapPen[]>(
    this.#change$$.pipe(
      filter((v) => v),
      debounceTime(100),
      map(() => this.find('point')),
    ),
    { initialValue: new Array<MapPen>() },
  );

  public getPointRect(pen?: MapPen): Rect | null {
    if (isNil(pen)) return null;
    const { x, y, width, height } = this.getPenRect(pen);
    return { x: x + width / 2, y: y + height / 2, width, height };
  }

  /**
   * 在指定位置添加点位
   * @param p 点位坐标
   * @param type 点位类型，默认为普通点
   * @param id 点位ID，未指定则自动生成
   */
  public async addPoint(p: Point, type = MapPointType.普通点, id?: string): Promise<void> {
    id ||= s8();
    const pen: MapPen = {
      ...p,
      ...this.#mapPoint(type),
      ...this.#mapPointImage(type),
      id,
      name: 'point',
      tags: ['point'],
      label: `P${id}`,
      point: { type },
    };
    pen.x! -= pen.width! / 2;
    pen.y! -= pen.height! / 2;
    await this.addPen(pen, false, true, true);
  }

  public updatePoint(id: string, info: Partial<MapPointInfo>): void {
    const { point } = this.getPenById(id) ?? {};
    if (!point?.type) return;
    const o = { ...point, ...info };
    this.setValue({ id, point: o }, { render: true, history: true, doEvent: true });
  }
  public changePointType(id: string, type: MapPointType): void {
    const pen = this.getPenById(id);
    const rect = this.getPointRect(pen);
    if (isNil(rect)) return;
    const point = this.#mapPoint(type);
    this.setValue(
      {
        id,
        x: rect.x - point.width / 2,
        y: rect.y - point.height / 2,
        ...point,
        ...this.#mapPointImage(type),
        point: { type },
      },
      { render: true, history: true, doEvent: true },
    );
  }

  #mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'iconSize'>> {
    const width = type < 10 ? 24 : 48;
    const height = type < 10 ? 24 : 60;
    const lineWidth = type < 10 ? 2 : 3;
    const iconSize = type < 10 ? 4 : 10;
    return { width, height, lineWidth, iconSize };
  }
  #mapPointImage(type: MapPointType): Required<Pick<MapPen, 'image' | 'canvasLayer'>> {
    const theme = this.data().theme;
    const image = type < 10 ? '' : `${import.meta.env.BASE_URL}/point/${type}-${theme}.png`;
    return { image, canvasLayer: CanvasLayer.CanvasMain };
  }
  //#endregion

  //#region 线路
  /** 画布上所有路线对象列表，响应式更新，包含动态生成的标签 */
  public readonly routes = useObservable<MapPen[], MapPen[]>(
    this.#change$$.pipe(
      filter((v) => v),
      debounceTime(100),
      map(() => this.find('route').map((v) => ({ ...v, label: this.getRouteLabel(v.id) }))),
    ),
    { initialValue: new Array<MapPen>() },
  );

  public getRouteLabel(id?: string, d?: number): string {
    if (!id) return '';
    const pen = this.getPenById(id);
    if (isNil(pen)) return '';
    const [a1, a2] = pen.anchors ?? [];
    if (!a1?.connectTo || !a2?.connectTo) return '';
    const p1 = this.getPenById(a1.connectTo);
    const p2 = this.getPenById(a2.connectTo);
    if (isNil(p1) || isNil(p2)) return '';
    const { direction = 1 } = pen.route ?? {};
    return `${p1.label}${(d ?? direction) > 0 ? '→' : '←'}${p2.label}`;
  }

  /**
   * 在两个点位之间添加路线
   * @param p 两个点位的数组
   * @param type 路线类型，默认为直线
   * @param id 路线ID，未指定则自动生成
   */
  public addRoute(p: [MapPen, MapPen], type = MapRouteType.直线, id?: string): void {
    const [p1, p2] = p;
    if (!p1?.anchors?.length || !p2?.anchors?.length) return;
    const line = this.connectLine(p1, p2, undefined, undefined, false);
    id ||= line.id!;
    this.changePenId(line.id!, id);
    const pen: MapPen = { tags: ['route'], route: { type }, lineWidth: 1 };
    this.setValue({ id, ...pen }, { render: false, history: false, doEvent: false });
    this.updateLineType(line, type);
    this.active(id);
    this.render();
  }

  public updateRoute(id: string, info: Partial<MapRouteInfo>): void {
    const { route } = this.getPenById(id) ?? {};
    if (!route?.type) return;
    const o = { ...route, ...info };
    this.setValue({ id, route: o }, { render: true, history: true, doEvent: true });
  }
  public changeRouteType(id: string, type: MapRouteType): void {
    const pen = this.getPenById(id);
    if (isNil(pen)) return;
    this.updateLineType(pen, type);
    this.setValue({ id, route: { type } }, { render: true, history: true, doEvent: true });
  }
  //#endregion

  //#region 区域
  /** 画布上所有区域对象列表，响应式更新 */
  public readonly areas = useObservable<MapPen[], MapPen[]>(
    this.#change$$.pipe(
      filter((v) => v),
      debounceTime(100),
      map(() => this.find('area')),
    ),
    { initialValue: new Array<MapPen>() },
  );

  public getBoundAreas(id: string = '', name: 'point' | 'line', type: MapAreaType): MapPen[] {
    if (!id) return [];
    return this.find(`area-${type}`).filter(({ area }) => {
      if (name === 'point') return area?.points?.includes(id);
      if (name === 'line') return area?.routes?.includes(id);
      return false;
    });
  }

  /**
   * 在指定区域添加功能区域
   * @param p1 区域起始坐标
   * @param p2 区域结束坐标
   * @param type 区域类型，默认为库区
   * @param id 区域ID，未指定则自动生成
   */
  public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string) {
    const scale = this.data().scale ?? 1;
    const w = Math.abs(p1.x - p2.x);
    const h = Math.abs(p1.y - p2.y);
    if (w * scale < 50 || h * scale < 60) return;
    const points = new Array<string>();
    const routes = new Array<string>();
    if (!id) {
      id = s8();
      const selected = <MapPen[]>this.store.active;
      switch (type) {
        case MapAreaType.库区:
          selected?.filter(({ point }) => point?.type === MapPointType.动作点).forEach(({ id }) => points.push(id!));
          break;
        case MapAreaType.互斥区:
          selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
          selected?.filter(({ route }) => route?.type).forEach(({ id }) => routes.push(id!));
          break;
        case MapAreaType.非互斥区:
          selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
          break;
        default:
          break;
      }
    }
    const pen: MapPen = {
      id,
      name: 'area',
      tags: ['area', `area-${type}`],
      label: `A${id}`,
      x: Math.min(p1.x, p2.x),
      y: Math.min(p1.y, p2.y),
      width: w,
      height: h,
      lineWidth: 1,
      area: { type, points, routes },
      locked: LockState.DisableMoveScale,
    };
    const area = await this.addPen(pen, true, true, true);
    this.bottom(area);
  }

  public updateArea(id: string, info: Partial<MapAreaInfo>): void {
    const { area } = this.getPenById(id) ?? {};
    if (!area?.type) return;
    const o = { ...area, ...info };
    this.setValue({ id, area: o }, { render: true, history: true, doEvent: true });
  }
  //#endregion

  /**
   * 构造函数 - 初始化场景编辑器
   * @param container 编辑器容器DOM元素
   */
  constructor(container: HTMLDivElement) {
    super(container, EDITOR_CONFIG);

    // 禁用第6个子元素的拖放功能
    (<HTMLDivElement>container.children.item(5)).ondrop = null;
    // 监听所有画布事件
    this.on('*', (e, v) => this.#listen(e, v));
    // 注册自定义绘制函数和锚点
    this.#register();

    // 监听主题变化并重新加载样式
    watch(
      () => sTheme.theme,
      (v) => this.#load(v),
      { immediate: true },
    );
  }

  #load(theme: string): void {
    this.setTheme(theme);

    this.setOptions({ color: get(sTheme.editor, 'color') });
    this.find('point').forEach((pen) => {
      if (!pen.point?.type) return;
      if (pen.point.type < 10) return;
      this.canvas.updateValue(pen, this.#mapPointImage(pen.point.type));
    });
    this.find('robot').forEach((pen) => {
      if (!pen.robot?.type) return;
      this.canvas.updateValue(pen, this.#mapRobotImage(pen.robot.type, pen.robot.active));
    });
    this.render();
  }

  #onDelete(pens?: MapPen[]): void {
    pens?.forEach((pen) => {
      switch (pen.name) {
        case 'point':
          this.delete(this.getLines(pen), true, false);
          break;
        default:
          break;
      }
    });
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  #listen(e: unknown, v: any) {
    switch (e) {
      case 'opened':
        this.#load(sTheme.theme);
        this.#change$$.next(true);
        break;

      case 'add':
        this.#change$$.next(true);
        break;
      case 'delete':
        this.#onDelete(v);
        this.#change$$.next(true);
        break;
      case 'update':
        this.#change$$.next(true);
        break;
      case 'valueUpdate':
        this.#change$$.next(true);
        break;

      case 'active':
      case 'inactive':
        this.#change$$.next(false);
        break;

      case 'click':
      case 'mousedown':
      case 'mouseup':
        this.#mouse$$.next({ type: e, value: pick(v, 'x', 'y') });
        break;

      default:
        // console.log(e, v);
        break;
    }
  }

  #register() {
    this.register({ line: () => new Path2D() });
    this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot });
    this.registerAnchors({ point: anchorPoint });
    this.addDrawLineFn('bezier2', lineBezier2);
    this.addDrawLineFn('bezier3', lineBezier3);
  }
}

//#region 自定义绘制函数
/**
 * 绘制点位的自定义函数
 * @param ctx Canvas 2D绘制上下文
 * @param pen 点位图形对象
 */
function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
  const theme = sTheme.editor;
  const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
  const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
  const { type } = pen.point ?? {};
  const { label = '' } = pen ?? {};

  ctx.save();
  switch (type) {
    case MapPointType.普通点:
    case MapPointType.等待点:
    case MapPointType.避让点:
    case MapPointType.临时避让点:
      ctx.beginPath();
      ctx.moveTo(x + w / 2 - r, y + r);
      ctx.arcTo(x + w / 2, y, x + w - r, y + h / 2 - r, r);
      ctx.arcTo(x + w, y + h / 2, x + w / 2 + r, y + h - r, r);
      ctx.arcTo(x + w / 2, y + h, x + r, y + h / 2 + r, r);
      ctx.arcTo(x, y + h / 2, x + r, y + h / 2 - r, r);
      ctx.closePath();
      ctx.fillStyle = get(theme, `point-s.fill-${type}`) ?? '';
      ctx.fill();
      ctx.strokeStyle = get(theme, active ? 'point-s.strokeActive' : 'point-s.stroke') ?? '';
      if (type === MapPointType.临时避让点) {
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.moveTo(x + 0.66 * r, y + h / 2 - 0.66 * r);
        ctx.lineTo(x + r, y + h / 2 - r);
        ctx.moveTo(x + w / 2 - 0.66 * r, y + 0.66 * r);
        ctx.lineTo(x + w / 2 - r, y + r);
        ctx.moveTo(x + w / 2 + 0.66 * r, y + 0.66 * r);
        ctx.lineTo(x + w / 2 + r, y + r);
        ctx.moveTo(x + w - 0.66 * r, y + h / 2 - 0.66 * r);
        ctx.lineTo(x + w - r, y + h / 2 - r);
        ctx.moveTo(x + w - 0.66 * r, y + h / 2 + 0.66 * r);
        ctx.lineTo(x + w - r, y + h / 2 + r);
        ctx.moveTo(x + w / 2 + 0.66 * r, y + h - 0.66 * r);
        ctx.lineTo(x + w / 2 + r, y + h - r);
        ctx.moveTo(x + w / 2 - 0.66 * r, y + h - 0.66 * r);
        ctx.lineTo(x + w / 2 - r, y + h - r);
        ctx.moveTo(x + 0.66 * r, y + h / 2 + 0.66 * r);
        ctx.lineTo(x + r, y + h / 2 + r);
      }
      ctx.stroke();
      break;
    case MapPointType.电梯点:
    case MapPointType.自动门点:
    case MapPointType.充电点:
    case MapPointType.停靠点:
    case MapPointType.动作点:
    case MapPointType.禁行点:
      ctx.roundRect(x, y, w, h, r);
      ctx.strokeStyle = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke') ?? '';
      ctx.stroke();
      break;
    default:
      break;
  }
  ctx.fillStyle = get(theme, 'color') ?? '';
  ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
  ctx.restore();
}
/**
 * 设置点位的连接锚点
 * @param pen 点位图形对象
 */
function anchorPoint(pen: MapPen): void {
  pen.anchors = [
    { penId: pen.id, id: '0', x: 0.5, y: 0.5 },
    // { penId: pen.id, id: 't', x: 0.5, y: 0 },
    // { penId: pen.id, id: 'b', x: 0.5, y: 1 },
    // { penId: pen.id, id: 'l', x: 0, y: 0.5 },
    // { penId: pen.id, id: 'r', x: 1, y: 0.5 },
  ];
}

/**
 * 绘制路线的自定义函数
 * @param ctx Canvas 2D绘制上下文
 * @param pen 路线图形对象
 */
function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
  const theme = sTheme.editor;
  const { active, lineWidth: s = 1 } = pen.calculative ?? {};
  const [p1, p2] = pen.calculative?.worldAnchors ?? [];
  const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
  const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
  const { type, direction = 1, pass = 0, c1, c2 } = pen.route ?? {};
  const { x: dx1 = 0, y: dy1 = 0 } = c1 ?? {};
  const { x: dx2 = 0, y: dy2 = 0 } = c2 ?? {};

  ctx.save();
  ctx.beginPath();
  ctx.strokeStyle = get(theme, active ? 'route.strokeActive' : `route.stroke-${pass}`) ?? '';
  ctx.lineWidth = active ? 3 * s : 2 * s;
  ctx.moveTo(x1, y1);
  switch (type) {
    case MapRouteType.直线:
      ctx.lineTo(x2, y2);
      break;
    case MapRouteType.二阶贝塞尔曲线:
      ctx.quadraticCurveTo(x1 + dx1 * s, y1 + dy1 * s, x2, y2);
      p1.next = { x: x1 + (2 / 3) * dx1 * s, y: y1 + (2 / 3) * dy1 * s };
      p2.prev = { x: x2 / 3 + (2 / 3) * (x1 + dx1 * s), y: y2 / 3 + (2 / 3) * (y1 + dy1 * s) };
      break;
    case MapRouteType.三阶贝塞尔曲线:
      ctx.bezierCurveTo(x1 + dx1 * s, y1 + dy1 * s, x2 + dx2 * s, y2 + dy2 * s, x2, y2);
      p1.next = { x: x1 + dx1 * s, y: y1 + dy1 * s };
      p2.prev = { x: x2 + dx2 * s, y: y2 + dy2 * s };
      break;
    default:
      break;
  }
  if (pass === MapRoutePassType.禁行) {
    ctx.setLineDash([s * 5]);
  }
  ctx.stroke();
  ctx.beginPath();
  ctx.setLineDash([0]);
  let r = (() => {
    switch (type) {
      case MapRouteType.直线:
        return Math.atan2(y2 - y1, x2 - x1);
      case MapRouteType.二阶贝塞尔曲线:
        return direction < 0 ? Math.atan2(dy1 * s, dx1 * s) : Math.atan2(y2 - y1 - dy1 * s, x2 - x1 - dx1 * s);
      case MapRouteType.三阶贝塞尔曲线:
        return direction < 0 ? Math.atan2(dy1 * s, dx1 * s) : Math.atan2(-dy2 * s, -dx2 * s);
      default:
        return 0;
    }
  })();
  if (direction < 0) {
    ctx.translate(x1, y1);
  } else {
    ctx.translate(x2, y2);
    r += Math.PI;
  }
  ctx.moveTo(Math.cos(r + Math.PI / 5) * s * 10, Math.sin(r + Math.PI / 5) * s * 10);
  ctx.lineTo(0, 0);
  ctx.lineTo(Math.cos(r - Math.PI / 5) * s * 10, Math.sin(r - Math.PI / 5) * s * 10);
  ctx.stroke();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.restore();
}
function lineBezier2(_: Meta2dStore, pen: MapPen): void {
  if (pen.calculative?.worldAnchors?.length !== 2) return;
  const { c1 } = pen.route ?? {};
  const { lineWidth: s = 1 } = pen.calculative ?? {};
  const [p1, p2] = pen.calculative?.worldAnchors ?? [];
  const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
  const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
  const { x: dx = 0, y: dy = 0 } = c1 ?? {};
  pen.calculative.worldAnchors[0].next = { x: x1 + (2 / 3) * dx * s, y: y1 + (2 / 3) * dy * s };
  pen.calculative.worldAnchors[1].prev = { x: x2 / 3 + (2 / 3) * (x1 + dx * s), y: y2 / 3 + (2 / 3) * (y1 + dy * s) };
}
function lineBezier3(_: Meta2dStore, pen: MapPen): void {
  if (pen.calculative?.worldAnchors?.length !== 2) return;
  const { c1, c2 } = pen.route ?? {};
  const { lineWidth: s = 1 } = pen.calculative ?? {};
  const [p1, p2] = pen.calculative?.worldAnchors ?? [];
  const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
  const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
  const { x: dx1 = 0, y: dy1 = 0 } = c1 ?? {};
  const { x: dx2 = 0, y: dy2 = 0 } = c2 ?? {};
  pen.calculative.worldAnchors[0].next = { x: x1 + dx1 * s, y: y1 + dy1 * s };
  pen.calculative.worldAnchors[1].prev = { x: x2 + dx2 * s, y: y2 + dy2 * s };
}

/**
 * 绘制区域的自定义函数
 * @param ctx Canvas 2D绘制上下文
 * @param pen 区域图形对象
 */
function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
  const theme = sTheme.editor;
  const { active, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
  const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
  const { type } = pen.area ?? {};
  const { label = '' } = pen ?? {};

  ctx.save();
  ctx.rect(x, y, w, h);
  ctx.fillStyle = get(theme, `area.fill-${type}`) ?? '';
  ctx.fill();
  ctx.strokeStyle = get(theme, active ? 'area.strokeActive' : `area.stroke-${type}`) ?? '';
  ctx.stroke();
  ctx.fillStyle = get(theme, 'color') ?? '';
  ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
  ctx.restore();
}

/**
 * 绘制机器人的自定义函数
 * @param ctx Canvas 2D绘制上下文
 * @param pen 机器人图形对象
 */
function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void {
  const theme = sTheme.editor;
  const { lineWidth: s = 1 } = pen.calculative ?? {};
  const { x = 0, y = 0, width: w = 0, height: h = 0, rotate: deg = 0 } = pen.calculative?.worldRect ?? {};
  const { active, path } = pen.robot ?? {};

  if (!active) return;
  const ox = x + w / 2;
  const oy = y + h / 2;
  ctx.save();
  ctx.ellipse(ox, oy, w / 2, h / 2, 0, 0, Math.PI * 2);
  ctx.fillStyle = get(theme, 'robot.fill') ?? '';
  ctx.fill();
  ctx.strokeStyle = get(theme, 'robot.stroke') ?? '';
  ctx.stroke();
  if (path?.length) {
    ctx.strokeStyle = get(theme, 'robot.line') ?? '';
    ctx.lineCap = 'round';
    ctx.lineWidth = s * 4;
    ctx.setLineDash([s * 5, s * 10]);
    ctx.translate(ox, oy);
    ctx.rotate((-deg * Math.PI) / 180);
    ctx.beginPath();
    ctx.moveTo(0, 0);
    path.forEach((d) => ctx.lineTo(d.x * s, d.y * s));
    ctx.stroke();
    const { x: ex1 = 0, y: ey1 = 0 } = nth(path, -1) ?? {};
    const { x: ex2 = 0, y: ey2 = 0 } = nth(path, -2) ?? {};
    const r = Math.atan2(ey1 - ey2, ex1 - ex2) + Math.PI;
    ctx.setLineDash([0]);
    ctx.translate(ex1 * s, ey1 * s);
    ctx.beginPath();
    ctx.moveTo(Math.cos(r + Math.PI / 4) * s * 10, Math.sin(r + Math.PI / 4) * s * 10);
    ctx.lineTo(0, 0);
    ctx.lineTo(Math.cos(r - Math.PI / 4) * s * 10, Math.sin(r - Math.PI / 4) * s * 10);
    ctx.stroke();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
  }
  ctx.restore();
}
//#endregion
