// Utility functions for Structure Building System
// Handles structure placement logic, expression evaluation, and block operations

import { world, BlockPermutation } from '@minecraft/server';
import { structureTemplates } from './config.js';

const debugging = false;

export function debug(type = 'info', message) {
    if (debugging) {
      switch (type) {
        case 'info':
          console.log(`[§2ISS§r] INFO: ${message}`);
          break;
        case 'warn':
          console.warn(`[§4ISS§r] WARN: ${message}`);
          break;
        case 'error':
          console.error(`[§6ISS§r] ERROR: ${message}`);
          break;
        default:
          console.log(`[§2ISS§r] ${message}`);
          break;
      }
    }
}

/**
 * Calculate structure dimensions based on repeatable parts
 * @param {Object} template - The structure template
 * @returns {Object} Dimension info with height, width, and repeatable parts
 */
export function calculateStructureDimensions(template) {
  const dimensions = {
    height: { min: 0, max: 0, repeatable: [] },
    width: { min: 0, max: 0, repeatable: [] },
    depth: { min: 0, max: 0, repeatable: [] }
  };
  
  if (!template || !template.parts) {
    return dimensions;
  }
  
  // Track directional parts for improved width/depth calculation
  const directionalParts = {
    left: [],
    right: [],
    front: [],
    back: []
  };
  
  // Analyze each part for dimensional contribution
  for (const part of template.parts) {
    const partContribution = analyzePart(part);
    
    // Add to height calculation
    if (partContribution.height.fixed > 0) {
      dimensions.height.min += partContribution.height.fixed;
      dimensions.height.max += partContribution.height.fixed;
    }
    
    if (partContribution.height.repeatable) {
      dimensions.height.repeatable.push({
        name: part.name,
        min: partContribution.height.min,
        max: partContribution.height.max,
        direction: part.repeat.direction
      });
      dimensions.height.min += partContribution.height.min;
      dimensions.height.max += partContribution.height.max;
    }
    
    // Collect directional parts for improved width/depth calculation
    if (partContribution.width.repeatable) {
      const direction = part.repeat.direction;
      if (direction === 'left') {
        directionalParts.left.push({
          name: part.name,
          min: partContribution.width.min,
          max: partContribution.width.max,
          direction: direction
        });
      } else if (direction === 'right') {
        directionalParts.right.push({
          name: part.name,
          min: partContribution.width.min,
          max: partContribution.width.max,
          direction: direction
        });
      } else {
        // Handle other horizontal directions (east, west, horizontal)
        dimensions.width.repeatable.push({
          name: part.name,
          min: partContribution.width.min,
          max: partContribution.width.max,
          direction: direction
        });
        dimensions.width.min += partContribution.width.min;
        dimensions.width.max += partContribution.width.max;
      }
    }
    
    if (partContribution.depth.repeatable) {
      const direction = part.repeat.direction;
      if (direction === 'forward' || direction === 'north') {
        directionalParts.front.push({
          name: part.name,
          min: partContribution.depth.min,
          max: partContribution.depth.max,
          direction: direction
        });
      } else if (direction === 'backward' || direction === 'south') {
        directionalParts.back.push({
          name: part.name,
          min: partContribution.depth.min,
          max: partContribution.depth.max,
          direction: direction
        });
      } else {
        // Handle other depth directions
        dimensions.depth.repeatable.push({
          name: part.name,
          min: partContribution.depth.min,
          max: partContribution.depth.max,
          direction: direction
        });
        dimensions.depth.min += partContribution.depth.min;
        dimensions.depth.max += partContribution.depth.max;
      }
    }
    
    // Add fixed width/depth contributions
    if (partContribution.width.fixed > 0) {
      dimensions.width.min += partContribution.width.fixed;
      dimensions.width.max += partContribution.width.fixed;
    }
    
    if (partContribution.depth.fixed > 0) {
      dimensions.depth.min += partContribution.depth.fixed;
      dimensions.depth.max += partContribution.depth.fixed;
    }
  }
  
  // Calculate improved width based on left/right arms + base
  if (directionalParts.left.length > 0 || directionalParts.right.length > 0) {
    // Base width is always 1 (the center/attachment point)
    let baseWidth = 1;
    let minWidth = baseWidth;
    let maxWidth = baseWidth;
    
    // Add left side contributions
    if (directionalParts.left.length > 0) {
      const leftMin = Math.max(...directionalParts.left.map(p => p.min));
      const leftMax = Math.max(...directionalParts.left.map(p => p.max));
      minWidth += leftMin;
      maxWidth += leftMax;
      dimensions.width.repeatable.push(...directionalParts.left);
    }
    
    // Add right side contributions
    if (directionalParts.right.length > 0) {
      const rightMin = Math.max(...directionalParts.right.map(p => p.min));
      const rightMax = Math.max(...directionalParts.right.map(p => p.max));
      minWidth += rightMin;
      maxWidth += rightMax;
      dimensions.width.repeatable.push(...directionalParts.right);
    }
    
    dimensions.width.min += minWidth;
    dimensions.width.max += maxWidth;
    
    debug('info', `Improved width calculation: base(${baseWidth}) + left(${directionalParts.left.length > 0 ? Math.max(...directionalParts.left.map(p => p.min)) : 0}) + right(${directionalParts.right.length > 0 ? Math.max(...directionalParts.right.map(p => p.min)) : 0}) = ${minWidth}-${maxWidth}`);
  }
  
  // Calculate improved depth based on front/back arms + base
  if (directionalParts.front.length > 0 || directionalParts.back.length > 0) {
    // Base depth is always 1 (the center/attachment point)
    let baseDepth = 1;
    let minDepth = baseDepth;
    let maxDepth = baseDepth;
    
    // Add front side contributions
    if (directionalParts.front.length > 0) {
      const frontMin = Math.max(...directionalParts.front.map(p => p.min));
      const frontMax = Math.max(...directionalParts.front.map(p => p.max));
      minDepth += frontMin;
      maxDepth += frontMax;
      dimensions.depth.repeatable.push(...directionalParts.front);
    }
    
    // Add back side contributions
    if (directionalParts.back.length > 0) {
      const backMin = Math.max(...directionalParts.back.map(p => p.min));
      const backMax = Math.max(...directionalParts.back.map(p => p.max));
      minDepth += backMin;
      maxDepth += backMax;
      dimensions.depth.repeatable.push(...directionalParts.back);
    }
    
    dimensions.depth.min += minDepth;
    dimensions.depth.max += maxDepth;
    
    debug('info', `Improved depth calculation: base(${baseDepth}) + front(${directionalParts.front.length > 0 ? Math.max(...directionalParts.front.map(p => p.min)) : 0}) + back(${directionalParts.back.length > 0 ? Math.max(...directionalParts.back.map(p => p.min)) : 0}) = ${minDepth}-${maxDepth}`);
  }
  
  debug('info', `Structure dimensions - Height: ${dimensions.height.min}-${dimensions.height.max}, Width: ${dimensions.width.min}-${dimensions.width.max}, Depth: ${dimensions.depth.min}-${dimensions.depth.max}`);
  
  return dimensions;
}

/**
 * Analyze a part's contribution to structure dimensions
 * @param {Object} part - The part to analyze
 * @returns {Object} Dimensional contribution info
 */
function analyzePart(part) {
  const contribution = {
    height: { fixed: 0, repeatable: false, min: 0, max: 0 },
    width: { fixed: 0, repeatable: false, min: 0, max: 0 },
    depth: { fixed: 0, repeatable: false, min: 0, max: 0 }
  };
  
  if (!part.repeat) {
    // Non-repeatable part contributes 1 block in its placement direction
    if (part.placement) {
      switch (part.placement.type) {
        case 'on_ground':
        case 'on_previous':
        case 'on_top':
          contribution.height.fixed = 1;
          break;
        case 'attach_left':
        case 'attach_right':
          contribution.width.fixed = 1;
          break;
        case 'attach_front':
        case 'attach_back':
          contribution.depth.fixed = 1;
          break;
      }
    }
  } else {
    // Repeatable part
    const minRepeats = part.repeat.min || 1;
    const maxRepeats = part.repeat.max || minRepeats;
    const direction = part.repeat.direction;
    
    switch (direction) {
      case 'up':
      case 'down':
      case 'vertical':
        contribution.height.repeatable = true;
        contribution.height.min = minRepeats;
        contribution.height.max = maxRepeats;
        break;
        
      case 'left':
      case 'right':
      case 'east':
      case 'west':
      case 'horizontal':
        contribution.width.repeatable = true;
        contribution.width.min = minRepeats;
        contribution.width.max = maxRepeats;
        break;
        
      case 'north':
      case 'south':
      case 'forward':
      case 'backward':
        contribution.depth.repeatable = true;
        contribution.depth.min = minRepeats;
        contribution.depth.max = maxRepeats;
        break;
    }
  }
  
  return contribution;
}

/**
 * Calculate repeat counts for parts based on selected dimensions
 * @param {Object} template - The structure template
 * @param {Object} selectedDimensions - Selected dimensions from UI
 * @returns {Map} Map of part names to their calculated repeat counts
 */
function calculateRepeatCounts(template, selectedDimensions) {
  const repeatCounts = new Map();
  
  if (!selectedDimensions || !template || !template.parts) {
    return repeatCounts;
  }
  
  // Calculate structure dimensions to understand the relationship
  const dimensions = calculateStructureDimensions(template);
  
  // Calculate repeat counts for height-affecting parts
  if (selectedDimensions.height && dimensions.height.repeatable.length > 0) {
    const selectedHeight = selectedDimensions.height;
    const fixedHeight = dimensions.height.min - dimensions.height.repeatable.reduce((sum, part) => sum + part.min, 0);
    const availableHeight = selectedHeight - fixedHeight;
    
    // For now, distribute available height equally among repeatable parts
    // In the future, this could be more sophisticated
    for (const repeatablePart of dimensions.height.repeatable) {
      const repeatCount = Math.max(repeatablePart.min, Math.min(repeatablePart.max, availableHeight));
      repeatCounts.set(repeatablePart.name, repeatCount);
      debug('info', `Part '${repeatablePart.name}' will repeat ${repeatCount} times (height)`);
    }
  }
  
  // Calculate repeat counts for width-affecting parts (improved logic for left/right arms)
  if (selectedDimensions.width && dimensions.width.repeatable.length > 0) {
    const selectedWidth = selectedDimensions.width;
    
    // Group repeatable parts by direction
    const leftParts = dimensions.width.repeatable.filter(p => p.direction === 'left');
    const rightParts = dimensions.width.repeatable.filter(p => p.direction === 'right');
    const otherParts = dimensions.width.repeatable.filter(p => p.direction !== 'left' && p.direction !== 'right');
    
    // For left/right directional parts, we need to reverse the calculation
    // The dimension calculation uses: totalWidth = base(1) + leftCount + rightCount
    // But the actual UI width range accounts for structure positioning
    // We need to work backwards from selectedWidth to get the actual repeat counts
    
    if (leftParts.length > 0 || rightParts.length > 0) {
      // Calculate the minimum width from dimension calculation to understand the offset
      const minWidthFromDimensions = dimensions.width.min;
      const maxWidthFromDimensions = dimensions.width.max;
      
      // Calculate how much of the selected width should go to arms
      // selectedWidth is in the range [minWidthFromDimensions, maxWidthFromDimensions]
      // We need to map this to the actual arm repeat counts
      
      const widthProgress = (selectedWidth - minWidthFromDimensions) / (maxWidthFromDimensions - minWidthFromDimensions);
      
      debug('info', `[WIDTH DEBUG] Width mapping: selected=${selectedWidth}, min=${minWidthFromDimensions}, max=${maxWidthFromDimensions}, progress=${widthProgress}`);
      
      // Calculate repeat counts for left parts
      if (leftParts.length > 0) {
        for (const leftPart of leftParts) {
          // Map the width progress to the repeat count range
          const repeatRange = leftPart.max - leftPart.min;
          const repeatCount = Math.round(leftPart.min + (repeatRange * widthProgress));
          const clampedRepeatCount = Math.max(leftPart.min, Math.min(leftPart.max, repeatCount));
          
          repeatCounts.set(leftPart.name, clampedRepeatCount);
          debug('info', `Part '${leftPart.name}' (left) will repeat ${clampedRepeatCount} times (width: ${selectedWidth}, progress: ${widthProgress})`);
        }
      }
      
      // Calculate repeat counts for right parts
      if (rightParts.length > 0) {
        for (const rightPart of rightParts) {
          // Map the width progress to the repeat count range
          const repeatRange = rightPart.max - rightPart.min;
          const repeatCount = Math.round(rightPart.min + (repeatRange * widthProgress));
          const clampedRepeatCount = Math.max(rightPart.min, Math.min(rightPart.max, repeatCount));
          
          repeatCounts.set(rightPart.name, clampedRepeatCount);
          debug('info', `Part '${rightPart.name}' (right) will repeat ${clampedRepeatCount} times (width: ${selectedWidth}, progress: ${widthProgress})`);
        }
      }
    }
    
    // Handle other horizontal directions with old logic
    for (const repeatablePart of otherParts) {
      const fixedWidth = dimensions.width.min - dimensions.width.repeatable.reduce((sum, part) => sum + part.min, 0);
      const availableWidth = selectedWidth - fixedWidth;
      const repeatCount = Math.max(repeatablePart.min, Math.min(repeatablePart.max, availableWidth));
      repeatCounts.set(repeatablePart.name, repeatCount);
      debug('info', `Part '${repeatablePart.name}' will repeat ${repeatCount} times (width)`);
    }
  }
  
  // Calculate repeat counts for depth-affecting parts (improved logic for front/back arms)
  if (selectedDimensions.depth && dimensions.depth.repeatable.length > 0) {
    const selectedDepth = selectedDimensions.depth;
    
    // Group repeatable parts by direction
    const frontParts = dimensions.depth.repeatable.filter(p => p.direction === 'forward' || p.direction === 'north');
    const backParts = dimensions.depth.repeatable.filter(p => p.direction === 'backward' || p.direction === 'south');
    const otherParts = dimensions.depth.repeatable.filter(p => !['forward', 'north', 'backward', 'south'].includes(p.direction));
    
    // For front/back directional parts, we need to reverse the calculation
    // selectedDepth = base(1) + frontRepeatCount + backRepeatCount
    // So: frontRepeatCount + backRepeatCount = selectedDepth - 1
    
    if (frontParts.length > 0 || backParts.length > 0) {
      const baseDepth = 1; // The center/attachment point
      const availableForArms = selectedDepth - baseDepth;
      
      // Calculate repeat counts for front parts
      if (frontParts.length > 0) {
        // If we have both front and back, split the available depth
        // If only front, use all available depth
        const frontShare = backParts.length > 0 ? Math.floor(availableForArms / 2) : availableForArms;
        
        for (const frontPart of frontParts) {
          const repeatCount = Math.max(frontPart.min, Math.min(frontPart.max, frontShare));
          repeatCounts.set(frontPart.name, repeatCount);
          debug('info', `Part '${frontPart.name}' (front) will repeat ${repeatCount} times (depth: ${selectedDepth})`);
        }
      }
      
      // Calculate repeat counts for back parts
      if (backParts.length > 0) {
        // If we have both front and back, split the available depth
        // If only back, use all available depth
        const backShare = frontParts.length > 0 ? Math.ceil(availableForArms / 2) : availableForArms;
        
        for (const backPart of backParts) {
          const repeatCount = Math.max(backPart.min, Math.min(backPart.max, backShare));
          repeatCounts.set(backPart.name, repeatCount);
          debug('info', `Part '${backPart.name}' (back) will repeat ${repeatCount} times (depth: ${selectedDepth})`);
        }
      }
    }
    
    // Handle other depth directions with old logic
    for (const repeatablePart of otherParts) {
      const fixedDepth = dimensions.depth.min - dimensions.depth.repeatable.reduce((sum, part) => sum + part.min, 0);
      const availableDepth = selectedDepth - fixedDepth;
      const repeatCount = Math.max(repeatablePart.min, Math.min(repeatablePart.max, availableDepth));
      repeatCounts.set(repeatablePart.name, repeatCount);
      debug('info', `Part '${repeatablePart.name}' will repeat ${repeatCount} times (depth)`);
    }
  }
  
  return repeatCounts;
}

/**
 * Build a structure at the specified location
 * @param {Object} structureItem - The structure item configuration
 * @param {string} templateKey - The template key to use
 * @param {Vector3} location - The build location
 * @param {string} face - The block face that was clicked
 * @param {Player} player - The player building the structure
 * @param {Object} selectedDimensions - Optional selected dimensions from UI sliders
 * @returns {Object} Result object with success status and error message
 */
export function buildStructure(structureItem, templateKey, location, face, player, selectedDimensions = null) {
  const template = structureTemplates[templateKey];
  if (!template) {
    return { success: false, error: `Template '${templateKey}' not found` };
  }
  
  debug(`Building structure: ${template.display_name} at ${location.x}, ${location.y}, ${location.z}`);
  
  // Calculate base position based on clicked face
  const baseLocation = calculateBaseLocation(location, face);
  
  // Calculate repeat counts based on selected dimensions
  const repeatCounts = calculateRepeatCounts(template, selectedDimensions);
  
  // Track placed parts for expression evaluation
  const placedParts = new Map();
  
  // Process each part in order
  for (const part of template.parts) {
    debug('info', `Processing part '${part.name}' with original repeat config: ${part.repeat ? JSON.stringify(part.repeat) : 'none'}`);
    
    // Create a modified part with calculated repeat count if applicable
    const modifiedPart = { ...part };
    if (repeatCounts.has(part.name) && part.repeat) {
      const calculatedCount = repeatCounts.get(part.name);
      modifiedPart.repeat = { ...part.repeat, max: calculatedCount };
      debug('info', `Using calculated repeat count ${calculatedCount} for part '${part.name}' (modified repeat: ${JSON.stringify(modifiedPart.repeat)})`);
    } else if (part.repeat) {
      debug('info', `Part '${part.name}' has repeat config but no calculated count - using original: ${JSON.stringify(part.repeat)}`);
    } else {
      debug('info', `Part '${part.name}' has no repeat configuration`);
    }
    
    const result = placePart(modifiedPart, baseLocation, placedParts, player.dimension, player);
    if (!result.success) {
      return { success: false, error: `Failed to place part '${part.name}': ${result.error}` };
    }
    
    debug('info', `Successfully placed part '${part.name}', result positions: ${result.partInfo.positions ? result.partInfo.positions.length : 0}`);
    
    // Store placed part info for future reference
    placedParts.set(part.name, result.partInfo);
  }
  
  return { success: true, placedParts: placedParts.size };
}

/**
 * Place a single part of a structure
 * @param {Object} part - The part configuration
 * @param {Vector3} baseLocation - The base build location
 * @param {Map} placedParts - Map of already placed parts
 * @param {Dimension} dimension - The dimension to build in
 * @returns {Object} Result with success status and part info
 */
function placePart(part, baseLocation, placedParts, dimension, player) {
  const positions = calculatePartPositions(part, baseLocation, placedParts, player);
  let blockPermutation = BlockPermutation.resolve(part.block);
  
  if (!blockPermutation) {
    return { success: false, error: `Invalid block: ${part.block}` };
  }
  
  // Apply player facing direction to block orientation if supported
  if (player) {
    // Get target part info if this part references a target
    let targetPartInfo = null;
    if (part.placement && part.placement.target) {
      targetPartInfo = placedParts.get(part.placement.target);
    }
    
    blockPermutation = applyPlayerFacingOrientation(blockPermutation, player, part, targetPartInfo);
  }
  
  const placedBlocks = [];
  
  for (const position of positions) {
    const block = dimension.getBlock(position);
    if (block) {
      block.setPermutation(blockPermutation);
      placedBlocks.push(position);
    }
  }
  
  const partInfo = {
    name: part.name,
    positions: placedBlocks,
    count: placedBlocks.length,
    height: Math.max(...placedBlocks.map(p => p.y)) - Math.min(...placedBlocks.map(p => p.y)) + 1
  };
  
  return { success: true, partInfo };
}

/**
 * Calculate positions for a part based on its placement and repeat configuration
 * @param {Object} part - The part configuration
 * @param {Vector3} baseLocation - The base build location
 * @param {Map} placedParts - Map of already placed parts
 * @param {Player} player - The player placing the structure (for facing-relative placement)
 * @returns {Array} Array of Vector3 positions
 */
function calculatePartPositions(part, baseLocation, placedParts, player = null) {
  const positions = [];
  
  debug('info', `[WIDTH DEBUG] Calculating positions for part '${part.name}'`);
  debug('info', `[WIDTH DEBUG] Part repeat config: ${part.repeat ? JSON.stringify(part.repeat) : 'none'}`);
  
  // Calculate initial position based on placement type
  const initialPosition = calculateInitialPosition(part, baseLocation, placedParts, player);
  debug('info', `[WIDTH DEBUG] Initial position for '${part.name}': ${JSON.stringify(initialPosition)}`);
  
  if (!part.repeat) {
    // Single placement
    debug('info', `[WIDTH DEBUG] Part '${part.name}' has no repeat - single placement`);
    positions.push(initialPosition);
  } else {
    // Repeated placement
    const repeatCount = evaluateExpression(part.repeat.max, placedParts);
    const direction = part.repeat.direction;
    const spacing = part.repeat.spacing || 1;
    
    debug('info', `[WIDTH DEBUG] Part '${part.name}' repeat details:`);
    debug('info', `[WIDTH DEBUG]   - Raw max: ${part.repeat.max}`);
    debug('info', `[WIDTH DEBUG]   - Evaluated repeat count: ${repeatCount}`);
    debug('info', `[WIDTH DEBUG]   - Direction: ${direction}`);
    debug('info', `[WIDTH DEBUG]   - Spacing: ${spacing}`);
    
    for (let i = 0; i < repeatCount; i++) {
      const offset = calculateDirectionOffset(direction, i * spacing, player);
      const position = {
        x: initialPosition.x + offset.x,
        y: initialPosition.y + offset.y,
        z: initialPosition.z + offset.z
      };
      positions.push(position);
      debug('info', `[WIDTH DEBUG]   - Position ${i + 1}/${repeatCount}: ${JSON.stringify(position)} (offset: ${JSON.stringify(offset)})`);
    }
    
    debug('info', `[WIDTH DEBUG] Part '${part.name}' final positions count: ${positions.length}`);
  }
  
  return positions;
}

/**
 * Calculate initial position for a part based on its placement configuration
 * @param {Object} part - The part configuration
 * @param {Vector3} baseLocation - The base build location
 * @param {Map} placedParts - Map of already placed parts
 * @param {Player} player - The player placing the structure (for facing-relative placement)
 * @returns {Vector3} Initial position
 */
function calculateInitialPosition(part, baseLocation, placedParts, player = null) {
  const placement = part.placement;
  
  switch (placement.type) {
    case 'on_ground':
      return { ...baseLocation };
      
    case 'on_previous':
      const prevPart = getPreviousPart(part, placedParts);
      if (prevPart && prevPart.positions.length > 0) {
        const lastPos = prevPart.positions[prevPart.positions.length - 1];
        return { x: lastPos.x, y: lastPos.y + 1, z: lastPos.z };
      }
      return { ...baseLocation };
      
    case 'on_top':
      const targetPart = placedParts.get(placement.target);
      if (targetPart && targetPart.positions.length > 0) {
        const topY = Math.max(...targetPart.positions.map(p => p.y));
        const targetPos = getTargetPosition(targetPart, placement);
        return { x: targetPos.x, y: topY + 1, z: targetPos.z };
      }
      return { ...baseLocation };
      
    case 'attach_left':
      const leftTarget = placedParts.get(placement.target);
      if (leftTarget && leftTarget.positions.length > 0) {
        let attachPos = getTargetPosition(leftTarget, placement);
        
        if (placement.at_level) {
          const level = evaluateExpression(placement.at_level, placedParts);
          attachPos.y = Math.min(...leftTarget.positions.map(p => p.y)) + level;
        }
        
        // Attach to the left relative to player facing
        const leftOffset = getPlayerRelativeOffset(player, 'left');
        return { 
          x: attachPos.x + leftOffset.x, 
          y: attachPos.y, 
          z: attachPos.z + leftOffset.z 
        };
      }
      return { ...baseLocation };
      
    case 'attach_right':
      const rightTarget = placedParts.get(placement.target);
      if (rightTarget && rightTarget.positions.length > 0) {
        let attachPos = getTargetPosition(rightTarget, placement);
        
        if (placement.at_level) {
          const level = evaluateExpression(placement.at_level, placedParts);
          attachPos.y = Math.min(...rightTarget.positions.map(p => p.y)) + level;
        }
        
        // Attach to the right relative to player facing
        const rightOffset = getPlayerRelativeOffset(player, 'right');
        return { 
          x: attachPos.x + rightOffset.x, 
          y: attachPos.y, 
          z: attachPos.z + rightOffset.z 
        };
      }
      return { ...baseLocation };
      
    case 'attach_front':
      const frontTarget = placedParts.get(placement.target);
      if (frontTarget && frontTarget.positions.length > 0) {
        let attachPos = getTargetPosition(frontTarget, placement);
        
        if (placement.at_level) {
          const level = evaluateExpression(placement.at_level, placedParts);
          attachPos.y = Math.min(...frontTarget.positions.map(p => p.y)) + level;
        }
        
        // Attach to the front relative to player facing
        const frontOffset = getPlayerRelativeOffset(player, 'forward');
        return { 
          x: attachPos.x + frontOffset.x, 
          y: attachPos.y, 
          z: attachPos.z + frontOffset.z 
        };
      }
      return { ...baseLocation };
      
    case 'attach_back':
      const backTarget = placedParts.get(placement.target);
      if (backTarget && backTarget.positions.length > 0) {
        let attachPos = getTargetPosition(backTarget, placement);
        
        if (placement.at_level) {
          const level = evaluateExpression(placement.at_level, placedParts);
          attachPos.y = Math.min(...backTarget.positions.map(p => p.y)) + level;
        }
        
        // Attach to the back relative to player facing
        const backOffset = getPlayerRelativeOffset(player, 'backward');
        return { 
          x: attachPos.x + backOffset.x, 
          y: attachPos.y, 
          z: attachPos.z + backOffset.z 
        };
      }
      return { ...baseLocation };
      
    default:
      return { ...baseLocation };
  }
}

/**
 * Calculate direction offset for repeated parts
 * @param {string} direction - The direction (up, down, north, south, east, west, horizontal, vertical, left, right)
 * @param {number} distance - The distance to offset
 * @param {Player} player - The player (for relative directions like left/right)
 * @returns {Vector3} Offset vector
 */
function calculateDirectionOffset(direction, distance, player = null) {
  switch (direction.toLowerCase()) {
    case 'up':
    case 'vertical':
      return { x: 0, y: distance, z: 0 };
    case 'down':
      return { x: 0, y: -distance, z: 0 };
    case 'north':
      return { x: 0, y: 0, z: -distance };
    case 'south':
      return { x: 0, y: 0, z: distance };
    case 'east':
      return { x: distance, y: 0, z: 0 };
    case 'west':
      return { x: -distance, y: 0, z: 0 };
    case 'horizontal':
      return { x: distance, y: 0, z: 0 }; // Default to east for horizontal
    case 'left':
    case 'right':
      // For player-relative directions, we need to calculate based on player facing
      if (player) {
        const relativeOffset = getPlayerRelativeOffset(player, direction);
        return {
          x: relativeOffset.x * distance,
          y: 0,
          z: relativeOffset.z * distance
        };
      }
      // Fallback if no player provided
      debug('warn', `Player-relative direction '${direction}' used without player context, defaulting to zero offset`);
      return { x: 0, y: 0, z: 0 };
    default:
      debug('warn', `Unknown direction '${direction}', defaulting to zero offset`);
      return { x: 0, y: 0, z: 0 };
  }
}

/**
 * Evaluate mathematical expressions using placed part data
 * @param {string|number} expression - The expression to evaluate
 * @param {Map} placedParts - Map of placed parts for variable resolution
 * @returns {number} Evaluated result
 */
function evaluateExpression(expression, placedParts) {
  if (typeof expression === 'number') {
    return expression;
  }
  
  if (typeof expression !== 'string') {
    return 0;
  }
  
  // Replace part names with their values
  let evalExpression = expression;
  
  for (const [partName, partInfo] of placedParts) {
    const regex = new RegExp(`\\b${partName}\\b`, 'g');
    evalExpression = evalExpression.replace(regex, partInfo.count.toString());
  }
  
  // Safe math evaluation without Function() constructor
  const result = safeEvaluateMath(evalExpression);
  return Math.floor(Math.max(0, result)); // Ensure positive integer
}

/**
 * Safely evaluate simple mathematical expressions without using Function()
 * Supports: +, -, *, /, parentheses, and numbers
 * @param {string} expression - The mathematical expression to evaluate
 * @returns {number} The result of the evaluation
 */
function safeEvaluateMath(expression) {
    // Remove whitespace
    expression = expression.replace(/\s/g, '');
    
    // Handle simple cases first
    if (/^\d+$/.test(expression)) {
      return parseInt(expression);
    }
    
    // Handle division operations (pole/2, base/3, etc.)
    if (expression.includes('/')) {
      const parts = expression.split('/');
      if (parts.length === 2) {
        const numerator = safeEvaluateMath(parts[0]);
        const denominator = safeEvaluateMath(parts[1]);
        return denominator !== 0 ? numerator / denominator : 0;
      }
    }
    
    // Handle multiplication
    if (expression.includes('*')) {
      const parts = expression.split('*');
      if (parts.length === 2) {
        return safeEvaluateMath(parts[0]) * safeEvaluateMath(parts[1]);
      }
    }
    
    // Handle addition
    if (expression.includes('+')) {
      const parts = expression.split('+');
      if (parts.length === 2) {
        return safeEvaluateMath(parts[0]) + safeEvaluateMath(parts[1]);
      }
    }
    
    // Handle subtraction (be careful with negative numbers)
    if (expression.includes('-') && !expression.startsWith('-')) {
      const parts = expression.split('-');
      if (parts.length === 2) {
        return safeEvaluateMath(parts[0]) - safeEvaluateMath(parts[1]);
      }
    }
    
    // If we can't parse it, try to extract a number
    const numberMatch = expression.match(/\d+/);
    if (numberMatch) {
      return parseInt(numberMatch[0]);
    }
    
    debug('warn', `Could not evaluate expression: ${expression}`);
    return 0;
}

/**
 * Get the previous part in the placement sequence
 * @param {Object} currentPart - The current part
 * @param {Map} placedParts - Map of placed parts
 * @returns {Object|null} Previous part info or null
 */
function getPreviousPart(currentPart, placedParts) {
  const partEntries = Array.from(placedParts.entries());
  if (partEntries.length > 0) {
    return partEntries[partEntries.length - 1][1];
  }
  return null;
}

/**
 * Get target position from a part based on attachment preference
 * @param {Object} targetPart - The target part with positions array
 * @param {Object} placement - The placement configuration
 * @returns {Vector3} Target position
 */
function getTargetPosition(targetPart, placement = {}) {
  if (!targetPart || !targetPart.positions || targetPart.positions.length === 0) {
    return { x: 0, y: 0, z: 0 };
  }
  
  const positions = targetPart.positions;
  
  // Check for specific attachment preference
  const attachTo = placement.attach_to || 'center';
  
  switch (attachTo) {
    case 'first':
      return { ...positions[0] };
      
    case 'last':
      return { ...positions[positions.length - 1] };
      
    case 'center':
    default:
      // Calculate center position
      const sum = positions.reduce((acc, pos) => ({
        x: acc.x + pos.x,
        y: acc.y + pos.y,
        z: acc.z + pos.z
      }), { x: 0, y: 0, z: 0 });
      
      return {
        x: Math.floor(sum.x / positions.length),
        y: Math.floor(sum.y / positions.length),
        z: Math.floor(sum.z / positions.length)
      };
  }
}

/**
 * Calculate center position from an array of positions (legacy function)
 * @param {Array} positions - Array of Vector3 positions
 * @returns {Vector3} Center position
 */
function getCenterPosition(positions) {
  if (positions.length === 0) {
    return { x: 0, y: 0, z: 0 };
  }
  
  const sum = positions.reduce((acc, pos) => ({
    x: acc.x + pos.x,
    y: acc.y + pos.y,
    z: acc.z + pos.z
  }), { x: 0, y: 0, z: 0 });
  
  return {
    x: Math.floor(sum.x / positions.length),
    y: Math.floor(sum.y / positions.length),
    z: Math.floor(sum.z / positions.length)
  };
}

/**
 * Calculate base location based on clicked block face
 * @param {Vector3} clickedLocation - The location of the clicked block
 * @param {string} face - The face that was clicked
 * @returns {Vector3} Base location for structure placement
 */
function calculateBaseLocation(clickedLocation, face) {
  const offset = getFaceOffset(face);
  return {
    x: clickedLocation.x + offset.x,
    y: clickedLocation.y + offset.y,
    z: clickedLocation.z + offset.z
  };
}

/**
 * Get offset vector for a block face
 * @param {string} face - The block face
 * @returns {Vector3} Offset vector
 */
function getFaceOffset(face) {
  switch (face) {
    case 'Up': return { x: 0, y: 1, z: 0 };
    case 'Down': return { x: 0, y: -1, z: 0 };
    case 'North': return { x: 0, y: 0, z: -1 };
    case 'South': return { x: 0, y: 0, z: 1 };
    case 'East': return { x: 1, y: 0, z: 0 };
    case 'West': return { x: -1, y: 0, z: 0 };
    default: return { x: 0, y: 1, z: 0 }; // Default to up
  }
}

/**
 * Apply player facing direction and part-specific facing to block orientation
 * @param {BlockPermutation} blockPermutation - The block permutation to orient
 * @param {Player} player - The player placing the block
 * @param {Object} part - The part configuration (optional, for facing property)
 * @param {Object} targetPart - The target part info (optional, for target-relative facing)
 * @returns {BlockPermutation} Oriented block permutation
 */
function applyPlayerFacingOrientation(blockPermutation, player, part = null, targetPart = null) {
  try {
    // Get player's facing direction
    const playerRotation = player.getRotation();
    const yaw = playerRotation.y;
    
    // Convert yaw to cardinal direction
    let playerFacing = getCardinalFromYaw(yaw);
    
    debug('info', `Player facing: ${playerFacing} (yaw: ${yaw})`);
    
    // Determine final facing direction
    let finalFacing = playerFacing;
    
    if (part && part.placement && part.placement.facing) {
      finalFacing = calculateFinalFacing(playerFacing, part.placement.facing, targetPart);
      debug('info', `Part facing: ${part.placement.facing}, Final facing: ${finalFacing}`);
    }
    
    // Get block type information
    const blockType = blockPermutation.type;
    const blockId = blockType.id;
    
    debug('info', `Block type: ${blockId}`);
    
    // Check if block supports cardinal direction state
    try {
      // Try to get current state to see if cardinal_direction is supported
      const currentState = blockPermutation.getState('minecraft:cardinal_direction');
      debug('info', `Block supports cardinal_direction, current: ${currentState}`);
      
      // Apply the cardinal direction state
      const orientedPermutation = blockPermutation.withState('minecraft:cardinal_direction', finalFacing);
      debug('info', `Successfully applied cardinal direction: ${finalFacing}`);
      return orientedPermutation;
      
    } catch (stateError) {
      // Block doesn't support cardinal_direction state
      debug('info', `Block ${blockId} does not support cardinal_direction state`);
      
      // Try alternative state names that might be used
      const alternativeStates = ['minecraft:facing_direction', 'minecraft:direction', 'facing_direction'];
      
      for (const stateName of alternativeStates) {
        try {
          const currentAltState = blockPermutation.getState(stateName);
          debug('info', `Block supports ${stateName}, current: ${currentAltState}`);
          
          // Convert cardinal to appropriate value for this state
          let stateValue = finalFacing;
          if (stateName === 'minecraft:facing_direction') {
            // Some blocks use numeric facing direction
            const facingMap = { 'south': 0, 'west': 1, 'north': 2, 'east': 3 };
            stateValue = facingMap[finalFacing] || 0;
          }
          
          const orientedPermutation = blockPermutation.withState(stateName, stateValue);
          debug('info', `Successfully applied ${stateName}: ${stateValue}`);
          return orientedPermutation;
          
        } catch (altStateError) {
          // This alternative state is not supported either
          continue;
        }
      }
    }
    
    // If no orientation states are supported, return original
    debug('info', `Block ${blockId} does not support any known orientation states`);
    return blockPermutation;
    
  } catch (error) {
    debug('error', `Error applying player facing orientation: ${error.message}`);
    return blockPermutation;
  }
}

/**
 * Get offset coordinates for player-relative directions
 * @param {Player} player - The player
 * @param {string} direction - Relative direction (left, right, forward, backward)
 * @returns {Object} Offset coordinates {x, z}
 */
function getPlayerRelativeOffset(player, direction) {
  if (!player) {
    // Fallback to fixed directions if no player
    const fallbackOffsets = {
      'left': { x: -1, z: 0 },
      'right': { x: 1, z: 0 },
      'forward': { x: 0, z: -1 },
      'backward': { x: 0, z: 1 }
    };
    return fallbackOffsets[direction] || { x: 0, z: 0 };
  }
  
  // Get player's facing direction
  const playerRotation = player.getRotation();
  const yaw = playerRotation.y;
  const playerFacing = getCardinalFromYaw(yaw);
  
  // Direction offset mappings based on player facing
  const offsetMaps = {
    'north': {
      'left': { x: -1, z: 0 },    // West
      'right': { x: 1, z: 0 },    // East
      'forward': { x: 0, z: -1 }, // North
      'backward': { x: 0, z: 1 }  // South
    },
    'east': {
      'left': { x: 0, z: -1 },    // North
      'right': { x: 0, z: 1 },    // South
      'forward': { x: 1, z: 0 },  // East
      'backward': { x: -1, z: 0 } // West
    },
    'south': {
      'left': { x: 1, z: 0 },     // East
      'right': { x: -1, z: 0 },   // West
      'forward': { x: 0, z: 1 },  // South
      'backward': { x: 0, z: -1 } // North
    },
    'west': {
      'left': { x: 0, z: 1 },     // South
      'right': { x: 0, z: -1 },   // North
      'forward': { x: -1, z: 0 }, // West
      'backward': { x: 1, z: 0 }  // East
    }
  };
  
  const offsetMap = offsetMaps[playerFacing];
  if (offsetMap && offsetMap[direction]) {
    debug('info', `Player facing ${playerFacing}, ${direction} offset: ${JSON.stringify(offsetMap[direction])}`);
    return offsetMap[direction];
  }
  
  debug('warn', `Unknown direction ${direction} for player facing ${playerFacing}`);
  return { x: 0, z: 0 };
}

/**
 * Convert yaw angle to cardinal direction
 * @param {number} yaw - The yaw angle
 * @returns {string} Cardinal direction (north, east, south, west)
 */
function getCardinalFromYaw(yaw) {
  // Normalize yaw to 0-360 range
  const normalizedYaw = ((yaw % 360) + 360) % 360;
  
  if (normalizedYaw >= 315 || normalizedYaw < 45) {
    return 'south';
  } else if (normalizedYaw >= 45 && normalizedYaw < 135) {
    return 'west';
  } else if (normalizedYaw >= 135 && normalizedYaw < 225) {
    return 'north';
  } else if (normalizedYaw >= 225 && normalizedYaw < 315) {
    return 'east';
  }
  return 'south'; // Default
}

/**
 * Calculate final facing direction based on player facing and part facing property
 * @param {string} playerFacing - Player's facing direction
 * @param {string} partFacing - Part's facing property value
 * @param {Object} targetPart - Target part info for relative calculations
 * @returns {string} Final cardinal direction
 */
function calculateFinalFacing(playerFacing, partFacing, targetPart) {
  // Absolute directions - use as-is
  if (['north', 'east', 'south', 'west'].includes(partFacing)) {
    return partFacing;
  }
  
  // Relative to player directions
  const directionMap = {
    'north': { left: 'west', right: 'east', forward: 'north', backward: 'south' },
    'east': { left: 'north', right: 'south', forward: 'east', backward: 'west' },
    'south': { left: 'east', right: 'west', forward: 'south', backward: 'north' },
    'west': { left: 'south', right: 'north', forward: 'west', backward: 'east' }
  };
  
  if (directionMap[playerFacing] && directionMap[playerFacing][partFacing]) {
    return directionMap[playerFacing][partFacing];
  }
  
  // Target-relative directions (simplified - could be enhanced with actual target position)
  if (partFacing === 'inward_target' || partFacing === 'toward_target') {
    // For now, default to opposite of player facing
    const oppositeMap = { 'north': 'south', 'south': 'north', 'east': 'west', 'west': 'east' };
    return oppositeMap[playerFacing] || playerFacing;
  }
  
  if (partFacing === 'outward_target' || partFacing === 'away_target') {
    // Same as player facing
    return playerFacing;
  }
  
  // Default to player facing if unknown
  return playerFacing;
}