import {
  CUSTOM_ROOT_DISPLAY_NAME,
  CUSTOM_ROOT_NAME,
  ELEMENT_LABELS,
  SCENE_NODE_TYPE,
} from './constants';

export function degreesToRadians(value) {
  return (Number(value) || 0) * (Math.PI / 180);
}

export function radiansToDegrees(value) {
  return (Number(value) || 0) * (180 / Math.PI);
}

export function degreesArrayToRadians(rotation = []) {
  return [
    degreesToRadians(rotation[0]),
    degreesToRadians(rotation[1]),
    degreesToRadians(rotation[2]),
  ];
}

export function radiansArrayToDegrees(rotation = []) {
  return [
    radiansToDegrees(rotation[0]),
    radiansToDegrees(rotation[1]),
    radiansToDegrees(rotation[2]),
  ];
}

export function getObjectByUuid(scene, uuid) {
  if (!scene || !uuid) return null;
  return scene.getObjectByProperty('uuid', uuid) || null;
}

export function getObjectIndex(object) {
  if (!object || !object.parent) return -1;
  return object.parent.children.indexOf(object);
}

export function clampInsertIndex(parent, index) {
  if (!parent) return 0;
  const length = parent.children.length;
  if (typeof index !== 'number' || Number.isNaN(index)) return length;
  if (index < 0) return 0;
  if (index > length) return length;
  return index;
}

export function insertObjectAt(parent, object, index) {
  if (!parent || !object) return;
  parent.add(object);
  const currentIndex = parent.children.indexOf(object);
  const targetIndex = clampInsertIndex(parent, index);
  if (currentIndex === -1 || currentIndex === targetIndex) return;
  parent.children.splice(currentIndex, 1);
  parent.children.splice(targetIndex, 0, object);
}

export function removeObjectFromParent(object) {
  if (!object || !object.parent) return;
  object.parent.remove(object);
}

export function isAncestor(target, possibleDescendant) {
  let current = possibleDescendant;
  while (current) {
    if (current === target) return true;
    current = current.parent;
  }
  return false;
}

export function isCustomRoot(object) {
  if (!object || !object.userData) return false;
  if (object.userData.nodeType) {
    return object.userData.nodeType === 'custom-root';
  }
  return object.userData.rootType === 'custom-root';
}

export function isCustomElement(object) {
  return !!(object && object.userData && object.userData.nodeType === 'custom-element');
}

export function isCustomGroup(object) {
  return !!(object && object.userData && object.userData.nodeType === 'custom-group');
}

export function isSelectableObject(object) {
  return isCustomRoot(object) || isCustomElement(object) || isCustomGroup(object);
}

export function isTransformControlHelperObject(object) {
  return !!(object && object.userData && object.userData.transformControlHelper === true);
}

export function isHelperObject(object) {
  if (!object) return false;
  if (isTransformControlHelperObject(object)) return true;
  if (object.type === 'Scene' || object.isCamera || object.isLight) return true;
  if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
  if (
    object.type === 'AxesHelper' ||
    object.type === 'GridHelper' ||
    object.type === 'BoxHelper' ||
    object.type === 'CSS2DObject' ||
    object.type === 'TransformControlsRoot'
  ) {
    return true;
  }
  return false;
}

export function getSceneNodeType(object) {
  if (!object) return SCENE_NODE_TYPE.UNKNOWN;
  if (isCustomElement(object)) return SCENE_NODE_TYPE.CUSTOM_ELEMENT;
  if (isCustomGroup(object)) return SCENE_NODE_TYPE.CUSTOM_GROUP;
  if (isCustomRoot(object)) return SCENE_NODE_TYPE.CUSTOM_ROOT;
  if (isHelperObject(object)) return SCENE_NODE_TYPE.HELPER;
  if (object.parent && !isHelperObject(object)) {
    return SCENE_NODE_TYPE.ORIGINAL_MODEL;
  }
  return SCENE_NODE_TYPE.UNKNOWN;
}

export function isOriginalModelObject(object) {
  return getSceneNodeType(object) === SCENE_NODE_TYPE.ORIGINAL_MODEL;
}

export function isEditableObject(object) {
  return !!(object && object.userData && object.userData.editable === true);
}

export function isDeletableObject(object) {
  return !!(object && object.userData && object.userData.deletable === true);
}

export function isCloneableObject(object) {
  return !!(object && object.userData && object.userData.cloneable === true);
}

export function isObjectVisible(object) {
  let current = object;
  while (current) {
    if (current.visible === false) {
      return false;
    }
    current = current.parent;
  }
  return true;
}

export function isTransformAttachableObject(object) {
  return !!(
    object &&
    (isCustomElement(object) || isCustomGroup(object)) &&
    isEditableObject(object) &&
    isObjectVisible(object)
  );
}

export function findSelectableObject(target) {
  let current = target;
  while (current) {
    if (isSelectableObject(current) && isObjectVisible(current)) {
      return current;
    }
    current = current.parent;
  }
  return null;
}

export function findSelectableSceneObject(target) {
  let current = target;
  while (current) {
    if (isSelectableObject(current) && isObjectVisible(current)) {
      return current;
    }
    if (isOriginalModelObject(current) && isObjectVisible(current)) {
      return current;
    }
    current = current.parent;
  }
  return null;
}

export function ensureCustomRoot(scene, THREE) {
  if (!scene || !THREE) return null;
  let root = scene.children.find(child => isCustomRoot(child));
  if (root) return root;

  root = new THREE.Group();
  root.name = CUSTOM_ROOT_NAME;
  root.userData = {
    nodeType: 'custom-root',
    rootType: 'custom-root',
    // displayName: CUSTOM_ROOT_DISPLAY_NAME,
    editable: false,
    deletable: false,
    cloneable: false,
    selectable: true,
  };
  scene.add(root);
  return root;
}

export function getElementBaseLabel(type) {
  return ELEMENT_LABELS[type] || '元素';
}

export function getSiblingNames(parent, ignoreUuid) {
  if (!parent) return [];
  return parent.children
    .filter(child => child && child.uuid !== ignoreUuid)
    .map(child => child.name)
    .filter(Boolean);
}

export function createUniqueName(parent, baseName, options = {}) {
  const siblingNames = getSiblingNames(parent, options.ignoreUuid);
  if (!options.forceSuffix && !siblingNames.includes(baseName)) {
    return baseName;
  }

  let index = typeof options.startIndex === 'number' ? options.startIndex : 1;
  while (index < 100000) {
    const suffix =
      options.padding && options.padding > 1
        ? String(index).padStart(options.padding, '0')
        : String(index);
    const nextName = `${baseName}_${suffix}`;
    if (!siblingNames.includes(nextName)) {
      return nextName;
    }
    index += 1;
  }
  return `${baseName}_${Date.now()}`;
}

export function createDefaultElementName(parent, type) {
  return createUniqueName(parent, getElementBaseLabel(type), {
    startIndex: 1,
    forceSuffix: true,
  });
}

export function createDefaultGroupName(parent) {
  return createUniqueName(parent, ELEMENT_LABELS.group, {
    startIndex: 1,
    padding: 3,
    forceSuffix: true,
  });
}

export function applyTransform(object, transform = {}) {
  if (!object || !transform) return;
  const { position, rotation, scale } = transform;
  if (Array.isArray(position) && position.length === 3) {
    object.position.set(
      Number(position[0]) || 0,
      Number(position[1]) || 0,
      Number(position[2]) || 0
    );
  }
  if (Array.isArray(rotation) && rotation.length === 3) {
    const [x, y, z] = degreesArrayToRadians(rotation);
    object.rotation.set(x, y, z);
  }
  if (Array.isArray(scale) && scale.length === 3) {
    object.scale.set(Number(scale[0]) || 1, Number(scale[1]) || 1, Number(scale[2]) || 1);
  }
  object.updateMatrixWorld(true);
}

export function cloneUserData(userData = {}) {
  return JSON.parse(JSON.stringify(userData));
}

export function updateDisplayName(object) {
  if (!object || !object.userData) return;
  object.userData.displayName = object.name;
}

export function buildTreeSnapshot(object) {
  if (!object) return null;
  return {
    uuid: object.uuid,
    name: object.name,
    // displayName:
    //   object.userData && object.userData.displayName ? object.userData.displayName : object.name,
    nodeType: object.userData && object.userData.nodeType ? object.userData.nodeType : '',
    elementType: object.userData && object.userData.elementType ? object.userData.elementType : '',
    visible: object.visible,
    children: object.children
      .filter(child => isCustomElement(child) || isCustomGroup(child))
      .map(child => buildTreeSnapshot(child)),
  };
}

function getMaterialSnapshot(material) {
  if (!material) return null;
  return {
    color: material.color && material.color.isColor ? `#${material.color.getHexString()}` : null,
    opacity: typeof material.opacity === 'number' ? material.opacity : 1,
  };
}

function flattenCustomSaveNodeMaterial(saveNode = {}) {
  const nextNode = { ...saveNode };
  const material =
    saveNode && saveNode.material && typeof saveNode.material === 'object' ? saveNode.material : null;
  if (material) {
    if (Object.prototype.hasOwnProperty.call(material, 'color')) {
      nextNode.color = material.color;
    }
    if (Object.prototype.hasOwnProperty.call(material, 'opacity')) {
      nextNode.opacity = material.opacity;
    }
  }
  if (Object.prototype.hasOwnProperty.call(nextNode, 'material')) {
    delete nextNode.material;
  }
  return nextNode;
}

export function buildObjectSnapshot(object) {
  const nodeType = getSceneNodeType(object);
  const primaryMaterial = object
    ? Array.isArray(object.material)
      ? object.material[0]
      : object.material
    : null;
  if (!object) return null;
  return {
    uuid: object.uuid,
    name: object.name,
    // displayName:
    //   object.userData && object.userData.displayName ? object.userData.displayName : object.name,
    nodeType,
    // nodeType: object.userData && object.userData.nodeType ? object.userData.nodeType : '',
    elementType: object.userData && object.userData.elementType ? object.userData.elementType : '',
    visible: object.visible !== false,
    position: [object.position.x, object.position.y, object.position.z],
    rotation: radiansArrayToDegrees([object.rotation.x, object.rotation.y, object.rotation.z]),
    scale: [object.scale.x, object.scale.y, object.scale.z],
    material: getMaterialSnapshot(primaryMaterial),
    geometryParams:
      object.userData && object.userData.geometryParams
        ? cloneUserData(object.userData.geometryParams)
        : null,
  };
}

export function buildCustomTree(root) {
  if (!root) return null;
  return {
    uuid: root.uuid,
    name: root.name,
    // displayName: root.userData && root.userData.displayName ? root.userData.displayName : root.name,
    nodeType: root.userData && root.userData.nodeType ? root.userData.nodeType : 'custom-root',
    children: root.children
      .filter(child => isCustomElement(child) || isCustomGroup(child))
      .map(child => buildTreeSnapshot(child)),
  };
}

export function buildCustomSaveTree(root) {
  if (!root) return null;
  const snapshot = buildObjectSnapshot(root);
  const saveNode = {
    ...(snapshot || {
      uuid: root.uuid,
      name: root.name,
      displayName:
        root.userData && root.userData.displayName ? root.userData.displayName : root.name,
      nodeType: root.userData && root.userData.nodeType ? root.userData.nodeType : 'custom-root',
      elementType: '',
      visible: root.visible !== false,
      position: [root.position.x, root.position.y, root.position.z],
      rotation: radiansArrayToDegrees([root.rotation.x, root.rotation.y, root.rotation.z]),
      scale: [root.scale.x, root.scale.y, root.scale.z],
      material: null,
      geometryParams: null,
    }),
    children: root.children
      .filter(child => isCustomElement(child) || isCustomGroup(child))
      .map(child => buildCustomSaveTree(child)),
  };
  if (Object.prototype.hasOwnProperty.call(saveNode, 'uuid')) {
    delete saveNode.uuid;
  }
  return flattenCustomSaveNodeMaterial(saveNode);
}

export function disposeObjectResources(object) {
  if (!object) return;
  object.traverse(child => {
    if (child.isMesh) {
      if (child.geometry && child.geometry.dispose) {
        child.geometry.dispose();
      }
      if (Array.isArray(child.material)) {
        child.material.forEach(material => {
          if (material && material.dispose) {
            material.dispose();
          }
        });
      } else if (child.material && child.material.dispose) {
        child.material.dispose();
      }
    }
  });
}
