/**
 * AeThex Avatar Rigging System
 * Universal skeleton mapping and auto-rigging for cross-platform avatars
 */

import {
  AvatarPlatformId,
  BoneMapping,
  SkeletonSpec,
  UNIVERSAL_BONES,
  avatarPlatforms,
  getSkeletonForPlatform,
} from './avatar-platforms';

// Common bone name aliases across different software
export const BONE_ALIASES: Record<string, string[]> = {
  [UNIVERSAL_BONES.HIPS]: ['Hips', 'hips', 'pelvis', 'Pelvis', 'LowerTorso', 'Root', 'Bip01_Pelvis', 'mixamorig:Hips'],
  [UNIVERSAL_BONES.SPINE]: ['Spine', 'spine', 'Spine1', 'spine1', 'UpperTorso', 'Bip01_Spine', 'mixamorig:Spine'],
  [UNIVERSAL_BONES.SPINE1]: ['Spine1', 'Spine2', 'spine2', 'Bip01_Spine1', 'mixamorig:Spine1'],
  [UNIVERSAL_BONES.SPINE2]: ['Spine2', 'Spine3', 'spine3', 'Bip01_Spine2', 'mixamorig:Spine2'],
  [UNIVERSAL_BONES.CHEST]: ['Chest', 'chest', 'Ribcage', 'UpperChest', 'Bip01_Spine3', 'mixamorig:Spine2'],
  [UNIVERSAL_BONES.NECK]: ['Neck', 'neck', 'Neck1', 'Bip01_Neck', 'mixamorig:Neck'],
  [UNIVERSAL_BONES.HEAD]: ['Head', 'head', 'Bip01_Head', 'mixamorig:Head'],

  // Left arm
  [UNIVERSAL_BONES.LEFT_SHOULDER]: ['LeftShoulder', 'Left Shoulder', 'L_Shoulder', 'shoulder.L', 'Bip01_L_Clavicle', 'mixamorig:LeftShoulder'],
  [UNIVERSAL_BONES.LEFT_UPPER_ARM]: ['LeftUpperArm', 'Left Upper Arm', 'LeftArm', 'L_UpperArm', 'upperarm.L', 'Bip01_L_UpperArm', 'mixamorig:LeftArm'],
  [UNIVERSAL_BONES.LEFT_LOWER_ARM]: ['LeftLowerArm', 'Left Lower Arm', 'LeftForeArm', 'L_Forearm', 'forearm.L', 'Bip01_L_Forearm', 'mixamorig:LeftForeArm'],
  [UNIVERSAL_BONES.LEFT_HAND]: ['LeftHand', 'Left Hand', 'L_Hand', 'hand.L', 'Bip01_L_Hand', 'mixamorig:LeftHand'],

  // Right arm
  [UNIVERSAL_BONES.RIGHT_SHOULDER]: ['RightShoulder', 'Right Shoulder', 'R_Shoulder', 'shoulder.R', 'Bip01_R_Clavicle', 'mixamorig:RightShoulder'],
  [UNIVERSAL_BONES.RIGHT_UPPER_ARM]: ['RightUpperArm', 'Right Upper Arm', 'RightArm', 'R_UpperArm', 'upperarm.R', 'Bip01_R_UpperArm', 'mixamorig:RightArm'],
  [UNIVERSAL_BONES.RIGHT_LOWER_ARM]: ['RightLowerArm', 'Right Lower Arm', 'RightForeArm', 'R_Forearm', 'forearm.R', 'Bip01_R_Forearm', 'mixamorig:RightForeArm'],
  [UNIVERSAL_BONES.RIGHT_HAND]: ['RightHand', 'Right Hand', 'R_Hand', 'hand.R', 'Bip01_R_Hand', 'mixamorig:RightHand'],

  // Left leg
  [UNIVERSAL_BONES.LEFT_UPPER_LEG]: ['LeftUpperLeg', 'Left Upper Leg', 'LeftThigh', 'L_Thigh', 'thigh.L', 'Bip01_L_Thigh', 'mixamorig:LeftUpLeg'],
  [UNIVERSAL_BONES.LEFT_LOWER_LEG]: ['LeftLowerLeg', 'Left Lower Leg', 'LeftShin', 'LeftKnee', 'L_Calf', 'calf.L', 'Bip01_L_Calf', 'mixamorig:LeftLeg'],
  [UNIVERSAL_BONES.LEFT_FOOT]: ['LeftFoot', 'Left Foot', 'L_Foot', 'foot.L', 'Bip01_L_Foot', 'mixamorig:LeftFoot'],
  [UNIVERSAL_BONES.LEFT_TOES]: ['LeftToes', 'Left Toes', 'LeftToe', 'L_Toe', 'toe.L', 'Bip01_L_Toe0', 'mixamorig:LeftToeBase'],

  // Right leg
  [UNIVERSAL_BONES.RIGHT_UPPER_LEG]: ['RightUpperLeg', 'Right Upper Leg', 'RightThigh', 'R_Thigh', 'thigh.R', 'Bip01_R_Thigh', 'mixamorig:RightUpLeg'],
  [UNIVERSAL_BONES.RIGHT_LOWER_LEG]: ['RightLowerLeg', 'Right Lower Leg', 'RightShin', 'RightKnee', 'R_Calf', 'calf.R', 'Bip01_R_Calf', 'mixamorig:RightLeg'],
  [UNIVERSAL_BONES.RIGHT_FOOT]: ['RightFoot', 'Right Foot', 'R_Foot', 'foot.R', 'Bip01_R_Foot', 'mixamorig:RightFoot'],
  [UNIVERSAL_BONES.RIGHT_TOES]: ['RightToes', 'Right Toes', 'RightToe', 'R_Toe', 'toe.R', 'Bip01_R_Toe0', 'mixamorig:RightToeBase'],

  // Eyes and jaw
  [UNIVERSAL_BONES.LEFT_EYE]: ['LeftEye', 'Left Eye', 'L_Eye', 'eye.L', 'EyeLeft'],
  [UNIVERSAL_BONES.RIGHT_EYE]: ['RightEye', 'Right Eye', 'R_Eye', 'eye.R', 'EyeRight'],
  [UNIVERSAL_BONES.JAW]: ['Jaw', 'jaw', 'Jaw_Joint', 'LowerJaw'],
};

export interface RigValidationResult {
  isValid: boolean;
  missingRequiredBones: string[];
  missingOptionalBones: string[];
  extraBones: string[];
  boneMapping: Map<string, string>;
  warnings: string[];
  errors: string[];
  score: number; // 0-100 compatibility score
}

export interface ConversionResult {
  success: boolean;
  warnings: string[];
  errors: string[];
  sourceBoneCount: number;
  targetBoneCount: number;
  mappedBones: number;
  unmappedBones: string[];
  addedBones: string[];
  removedBones: string[];
}

export interface BoneTransform {
  position: { x: number; y: number; z: number };
  rotation: { x: number; y: number; z: number; w: number };
  scale: { x: number; y: number; z: number };
}

export interface AvatarRig {
  name: string;
  platform: AvatarPlatformId;
  bones: Map<string, BoneTransform>;
  hierarchy: Map<string, string | null>;
  blendShapes?: string[];
}

/**
 * Find the universal bone name for a given bone name
 */
export function findUniversalBoneName(boneName: string): string | null {
  // Direct match
  if (Object.values(UNIVERSAL_BONES).includes(boneName as any)) {
    return boneName;
  }

  // Search through aliases
  for (const [universalName, aliases] of Object.entries(BONE_ALIASES)) {
    if (aliases.some(alias =>
      alias.toLowerCase() === boneName.toLowerCase() ||
      boneName.toLowerCase().includes(alias.toLowerCase()) ||
      alias.toLowerCase().includes(boneName.toLowerCase())
    )) {
      return universalName;
    }
  }

  return null;
}

/**
 * Get the platform-specific bone name from universal name
 */
export function getPlatformBoneName(universalName: string, platformId: AvatarPlatformId): string | null {
  const skeleton = getSkeletonForPlatform(platformId);
  const bone = skeleton.bones.find(b => b.universalName === universalName);
  return bone?.name || null;
}

/**
 * Create a bone mapping from source platform to target platform
 */
export function createBoneMapping(
  sourcePlatform: AvatarPlatformId,
  targetPlatform: AvatarPlatformId
): Map<string, string> {
  const sourceSkeleton = getSkeletonForPlatform(sourcePlatform);
  const targetSkeleton = getSkeletonForPlatform(targetPlatform);
  const mapping = new Map<string, string>();

  for (const sourceBone of sourceSkeleton.bones) {
    const targetBone = targetSkeleton.bones.find(
      b => b.universalName === sourceBone.universalName
    );
    if (targetBone) {
      mapping.set(sourceBone.name, targetBone.name);
    }
  }

  return mapping;
}

/**
 * Validate a rig against a platform's requirements
 */
export function validateRig(
  boneNames: string[],
  targetPlatform: AvatarPlatformId
): RigValidationResult {
  const skeleton = getSkeletonForPlatform(targetPlatform);
  const platform = avatarPlatforms[targetPlatform];

  const result: RigValidationResult = {
    isValid: true,
    missingRequiredBones: [],
    missingOptionalBones: [],
    extraBones: [],
    boneMapping: new Map(),
    warnings: [],
    errors: [],
    score: 100,
  };

  // Map input bones to universal names
  const inputBoneSet = new Set(boneNames);
  const mappedUniversalBones = new Set<string>();

  for (const boneName of boneNames) {
    const universalName = findUniversalBoneName(boneName);
    if (universalName) {
      const platformBoneName = getPlatformBoneName(universalName, targetPlatform);
      if (platformBoneName) {
        result.boneMapping.set(boneName, platformBoneName);
        mappedUniversalBones.add(universalName);
      }
    } else {
      result.extraBones.push(boneName);
    }
  }

  // Check for missing required and optional bones
  for (const bone of skeleton.bones) {
    if (!mappedUniversalBones.has(bone.universalName)) {
      if (bone.required) {
        result.missingRequiredBones.push(bone.name);
        result.errors.push(`Missing required bone: ${bone.name}`);
      } else {
        result.missingOptionalBones.push(bone.name);
        result.warnings.push(`Missing optional bone: ${bone.name}`);
      }
    }
  }

  // Calculate score
  const requiredBoneCount = skeleton.bones.filter(b => b.required).length;
  const foundRequiredCount = requiredBoneCount - result.missingRequiredBones.length;
  const baseScore = (foundRequiredCount / requiredBoneCount) * 80;

  const optionalBoneCount = skeleton.bones.filter(b => !b.required).length;
  const foundOptionalCount = optionalBoneCount > 0
    ? (optionalBoneCount - result.missingOptionalBones.length) / optionalBoneCount
    : 1;
  const bonusScore = foundOptionalCount * 20;

  result.score = Math.round(baseScore + bonusScore);

  // Check bone count limit
  if (boneNames.length > platform.constraints.maxBones) {
    result.warnings.push(
      `Bone count (${boneNames.length}) exceeds platform limit (${platform.constraints.maxBones})`
    );
    result.score -= 10;
  }

  // Determine validity
  result.isValid = result.missingRequiredBones.length === 0;
  result.score = Math.max(0, Math.min(100, result.score));

  return result;
}

/**
 * Auto-map bones from an unknown rig to a target platform
 */
export function autoMapBones(
  inputBones: string[],
  targetPlatform: AvatarPlatformId
): Map<string, string> {
  const skeleton = getSkeletonForPlatform(targetPlatform);
  const mapping = new Map<string, string>();

  for (const inputBone of inputBones) {
    const universalName = findUniversalBoneName(inputBone);
    if (universalName) {
      const targetBone = skeleton.bones.find(b => b.universalName === universalName);
      if (targetBone) {
        mapping.set(inputBone, targetBone.name);
      }
    }
  }

  return mapping;
}

/**
 * Suggest fixes for a rig that doesn't meet platform requirements
 */
export function suggestRigFixes(
  validationResult: RigValidationResult,
  targetPlatform: AvatarPlatformId
): string[] {
  const suggestions: string[] = [];
  const platform = avatarPlatforms[targetPlatform];

  if (validationResult.missingRequiredBones.length > 0) {
    suggestions.push(
      `Add missing required bones: ${validationResult.missingRequiredBones.join(', ')}`
    );
    suggestions.push(
      `Consider using the AeThex auto-rigger to automatically add missing bones`
    );
  }

  if (validationResult.extraBones.length > 5) {
    suggestions.push(
      `Remove or rename ${validationResult.extraBones.length} unrecognized bones for better compatibility`
    );
  }

  if (validationResult.score < 70) {
    suggestions.push(
      `Consider starting with an ${platform.name} compatible base rig`
    );
  }

  return suggestions;
}

/**
 * Convert a rig from one platform to another
 */
export function convertRig(
  sourceBones: string[],
  sourcePlatform: AvatarPlatformId,
  targetPlatform: AvatarPlatformId
): ConversionResult {
  const result: ConversionResult = {
    success: false,
    warnings: [],
    errors: [],
    sourceBoneCount: sourceBones.length,
    targetBoneCount: 0,
    mappedBones: 0,
    unmappedBones: [],
    addedBones: [],
    removedBones: [],
  };

  const targetSkeleton = getSkeletonForPlatform(targetPlatform);
  const boneMapping = createBoneMapping(sourcePlatform, targetPlatform);

  // Track mapped and unmapped bones
  const mappedSourceBones = new Set<string>();

  for (const sourceBone of sourceBones) {
    if (boneMapping.has(sourceBone)) {
      mappedSourceBones.add(sourceBone);
      result.mappedBones++;
    } else {
      // Try auto-mapping
      const universalName = findUniversalBoneName(sourceBone);
      if (universalName) {
        const targetBone = getPlatformBoneName(universalName, targetPlatform);
        if (targetBone) {
          boneMapping.set(sourceBone, targetBone);
          mappedSourceBones.add(sourceBone);
          result.mappedBones++;
        } else {
          result.unmappedBones.push(sourceBone);
        }
      } else {
        result.unmappedBones.push(sourceBone);
      }
    }
  }

  // Check for required bones that need to be added
  for (const bone of targetSkeleton.bones) {
    if (bone.required) {
      const isMapped = Array.from(boneMapping.values()).includes(bone.name);
      if (!isMapped) {
        result.addedBones.push(bone.name);
        result.warnings.push(`Will generate required bone: ${bone.name}`);
      }
    }
  }

  // Calculate removed bones (source bones not needed in target)
  result.removedBones = sourceBones.filter(b => !mappedSourceBones.has(b));

  result.targetBoneCount = result.mappedBones + result.addedBones.length;

  // Determine success
  const targetRequiredBones = targetSkeleton.bones.filter(b => b.required);
  const missingRequired = targetRequiredBones.filter(b => {
    const isMapped = Array.from(boneMapping.values()).includes(b.name);
    const willBeAdded = result.addedBones.includes(b.name);
    return !isMapped && !willBeAdded;
  });

  if (missingRequired.length > 0) {
    result.errors.push(
      `Cannot generate required bones: ${missingRequired.map(b => b.name).join(', ')}`
    );
  } else {
    result.success = true;
  }

  if (result.unmappedBones.length > 0) {
    result.warnings.push(
      `${result.unmappedBones.length} bones will not be transferred`
    );
  }

  return result;
}

/**
 * Get the bone hierarchy for a platform
 */
export function getBoneHierarchy(platformId: AvatarPlatformId): Map<string, string | null> {
  const skeleton = getSkeletonForPlatform(platformId);
  const hierarchy = new Map<string, string | null>();

  for (const bone of skeleton.bones) {
    hierarchy.set(bone.name, bone.parent || null);
  }

  return hierarchy;
}

/**
 * Generate T-pose bone transforms for a platform
 */
export function generateTPose(platformId: AvatarPlatformId): Map<string, BoneTransform> {
  const skeleton = getSkeletonForPlatform(platformId);
  const transforms = new Map<string, BoneTransform>();

  // Default T-pose positions (simplified)
  const defaultTransform: BoneTransform = {
    position: { x: 0, y: 0, z: 0 },
    rotation: { x: 0, y: 0, z: 0, w: 1 },
    scale: { x: 1, y: 1, z: 1 },
  };

  for (const bone of skeleton.bones) {
    transforms.set(bone.name, { ...defaultTransform });
  }

  return transforms;
}

/**
 * Calculate the compatibility between two platforms for avatar conversion
 */
export function calculatePlatformCompatibility(
  sourcePlatform: AvatarPlatformId,
  targetPlatform: AvatarPlatformId
): number {
  const sourceSkeleton = getSkeletonForPlatform(sourcePlatform);
  const targetSkeleton = getSkeletonForPlatform(targetPlatform);

  // Count matching bones
  let matches = 0;
  for (const sourceBone of sourceSkeleton.bones) {
    const hasMatch = targetSkeleton.bones.some(
      b => b.universalName === sourceBone.universalName
    );
    if (hasMatch) matches++;
  }

  const totalBones = Math.max(sourceSkeleton.bones.length, targetSkeleton.bones.length);
  const boneScore = (matches / totalBones) * 70;

  // Feature compatibility
  let featureScore = 0;
  if (sourceSkeleton.fingerTracking === targetSkeleton.fingerTracking) featureScore += 10;
  if (sourceSkeleton.eyeTracking === targetSkeleton.eyeTracking) featureScore += 10;
  if (sourceSkeleton.blendShapeSupport === targetSkeleton.blendShapeSupport) featureScore += 10;

  return Math.round(boneScore + featureScore);
}

/**
 * Get all supported conversion paths from a source platform
 */
export function getConversionPaths(sourcePlatform: AvatarPlatformId): Array<{
  target: AvatarPlatformId;
  compatibility: number;
  warnings: string[];
}> {
  const paths: Array<{
    target: AvatarPlatformId;
    compatibility: number;
    warnings: string[];
  }> = [];

  const platforms = Object.keys(avatarPlatforms) as AvatarPlatformId[];

  for (const targetPlatform of platforms) {
    if (targetPlatform === sourcePlatform) continue;

    const compatibility = calculatePlatformCompatibility(sourcePlatform, targetPlatform);
    const warnings: string[] = [];

    const sourcePlatformData = avatarPlatforms[sourcePlatform];
    const targetPlatformData = avatarPlatforms[targetPlatform];

    // Add warnings for feature loss
    if (sourcePlatformData.skeleton.fingerTracking && !targetPlatformData.skeleton.fingerTracking) {
      warnings.push('Finger tracking will be lost');
    }
    if (sourcePlatformData.skeleton.eyeTracking && !targetPlatformData.skeleton.eyeTracking) {
      warnings.push('Eye tracking will be lost');
    }
    if (sourcePlatformData.skeleton.fullBodyTracking && !targetPlatformData.skeleton.fullBodyTracking) {
      warnings.push('Full body tracking will be lost');
    }
    if (sourcePlatformData.constraints.maxPolygons > targetPlatformData.constraints.maxPolygons) {
      warnings.push(`Mesh may need reduction (${targetPlatformData.constraints.maxPolygons} max polys)`);
    }

    paths.push({
      target: targetPlatform,
      compatibility,
      warnings,
    });
  }

  // Sort by compatibility
  paths.sort((a, b) => b.compatibility - a.compatibility);

  return paths;
}
