import Command from '../command';
import { COMMAND_TYPE } from '../constants';
import { cloneCustomObject } from '../element-factory';
import { insertObjectAt, removeObjectFromParent } from '../scene-helpers';

const DEFAULT_CLONE_OFFSET_MIN = 1;
const DEFAULT_CLONE_OFFSET_RATIO = 0.2;

function normalizeOffsetArray(offset = []) {
  if (!Array.isArray(offset) || offset.length !== 3) return null;
  return offset.map(value => Number(value) || 0);
}

function createWorldOffsetVector(THREE, source) {
  const bounds = new THREE.Box3();
  const size = new THREE.Vector3();
  source.updateMatrixWorld(true);
  bounds.setFromObject(source);
  bounds.getSize(size);
  const baseOffset = Math.max(
    Math.max(size.x || 0, size.y || 0, size.z || 0) * DEFAULT_CLONE_OFFSET_RATIO,
    DEFAULT_CLONE_OFFSET_MIN
  );
  return new THREE.Vector3(baseOffset, 0, baseOffset);
}

function convertWorldOffsetToLocal(THREE, parent, worldOffset) {
  if (!parent) return worldOffset.clone();
  parent.updateMatrixWorld(true);
  const worldOrigin = parent.getWorldPosition(new THREE.Vector3());
  const localOrigin = parent.worldToLocal(worldOrigin.clone());
  const localTarget = parent.worldToLocal(worldOrigin.clone().add(worldOffset));
  return localTarget.sub(localOrigin);
}

function resolveCloneOffset(THREE, source, targetParent, options = {}) {
  const normalizedOffset = normalizeOffsetArray(options.positionOffset);
  if (normalizedOffset) {
    return new THREE.Vector3().fromArray(normalizedOffset);
  }
  if (options.disableAutoOffset === true || !THREE || !source) {
    return new THREE.Vector3(0, 0, 0);
  }
  return convertWorldOffsetToLocal(THREE, targetParent, createWorldOffsetVector(THREE, source));
}

export default class CloneElementCommand extends Command {
  constructor(service, uuid, options = {}) {
    super(service);
    this.type = COMMAND_TYPE.CLONE_ELEMENT;
    this.name = '复制元素';
    this.source = service.getObjectByUuid(uuid);
    const targetParent =
      service.getObjectByUuid(options.parentUuid) ||
      (this.source && this.source.parent) ||
      service.getCustomRoot();
    this.parentUuid = targetParent.uuid;
    this.index = options.index;
    this.object = this.source ? cloneCustomObject(service.THREE, this.source, targetParent) : null;
    this.positionOffset = resolveCloneOffset(service.THREE, this.source, targetParent, options);
    if (this.object) {
      // 复制后默认沿平面做一次可见偏移，避免与原对象完全重合。
      this.object.position.add(this.positionOffset);
      this.object.updateMatrixWorld(true);
    }
  }

  execute() {
    if (!this.object) return;
    const parent = this.service.getObjectByUuid(this.parentUuid) || this.service.getCustomRoot();
    insertObjectAt(parent, this.object, this.index);
    this.index = parent.children.indexOf(this.object);
    this.object.updateMatrixWorld(true);
  }

  undo() {
    if (!this.object) return;
    removeObjectFromParent(this.object);
  }

  toJSON() {
    return {
      ...super.toJSON(),
      sourceUuid: this.source ? this.source.uuid : '',
      parentUuid: this.parentUuid,
      index: this.index,
      positionOffset: this.positionOffset ? this.positionOffset.toArray() : [0, 0, 0],
      object: this.object ? this.object.toJSON() : null,
    };
  }

  fromJSON(json) {
    this.parentUuid = json.parentUuid;
    this.index = json.index;
    this.positionOffset = Array.isArray(json.positionOffset)
      ? new this.service.THREE.Vector3().fromArray(json.positionOffset)
      : new this.service.THREE.Vector3(0, 0, 0);
    this.object = json.object ? this.service.parseObjectJSON(json.object) : null;
  }
}
