<?php
/**
 * Smart Reflow Engine (PHP Version)
 * 
 * Enables content blocks authored for one container size to dynamically adapt
 * when placed in containers with different dimensions or aspect ratios.
 * 
 * This PHP version mirrors the JavaScript reflow-engine.js for server-side
 * preview rendering parity.
 * 
 * @package CastConductor
 * @since 5.3.0
 * @see /docs/SPEC-SMART-REFLOW.md
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Prevent direct access
}

/**
 * Smart Reflow Engine Class
 * 
 * Provides layout calculation for content blocks across different container sizes.
 */
class CC_Reflow_Engine {
    
    /**
     * Configuration constants
     */
    const FILL_PERCENTAGE = 0.88;
    const MIN_MARGIN = 16;
    
    const TEXT_MIN_FONT_SIZE = 14;
    const TEXT_MAX_FONT_SIZE = 72;
    
    const ARTWORK_MIN_SIZE = 80;
    const ARTWORK_MAX_SIZE = 600;
    
    const ASPECT_THRESHOLD_HORIZONTAL = 2.0;
    const ASPECT_THRESHOLD_VERTICAL = 0.75;
    
    /**
     * Role keywords for auto-detection
     */
    private static $role_keywords = array(
        'artwork' => array( 'artwork', 'image', 'poster', 'thumbnail', 'cover', 'logo' ),
        'primary-text' => array( 'title', 'name', 'heading', 'artist' ),
        'secondary-text' => array( 'subtitle', 'description', 'track', 'message', 'content' ),
        'decoration' => array( 'background', 'overlay', 'divider', 'border' ),
        'badge' => array( 'icon', 'badge', 'indicator', 'status' ),
    );
    
    /**
     * Detect aspect ratio change category between two sizes
     * 
     * @param array $authored { 'width' => int, 'height' => int }
     * @param array $target { 'width' => int, 'height' => int }
     * @return string 'horizontal-to-vertical' | 'vertical-to-horizontal' | 'similar'
     */
    public static function detect_aspect_change( $authored, $target ) {
        $authored_ratio = $authored['width'] / $authored['height'];
        $target_ratio = $target['width'] / $target['height'];
        
        // Horizontal (wide) to Vertical (tall) - need to stack
        if ( $authored_ratio > self::ASPECT_THRESHOLD_HORIZONTAL && $target_ratio < 1.5 ) {
            return 'horizontal-to-vertical';
        }
        
        // Vertical (tall) to Horizontal (wide) - need to spread
        if ( $authored_ratio < 1.5 && $target_ratio > self::ASPECT_THRESHOLD_HORIZONTAL ) {
            return 'vertical-to-horizontal';
        }
        
        return 'similar';
    }
    
    /**
     * Auto-detect layer role based on layer properties
     * 
     * @param array $layer Layer object with kind, id, token, templateText, etc.
     * @return string Detected role
     */
    public static function detect_layer_role( $layer ) {
        if ( empty( $layer ) ) {
            return 'decoration';
        }
        
        $kind = isset( $layer['kind'] ) ? $layer['kind'] : '';
        $id = strtolower( isset( $layer['id'] ) ? $layer['id'] : '' );
        $token = strtolower( isset( $layer['token'] ) ? $layer['token'] : '' );
        $template_text = strtolower( isset( $layer['templateText'] ) ? $layer['templateText'] : '' );
        
        // Image layers are artwork
        if ( $kind === 'token-image' ) {
            return 'artwork';
        }
        
        // Check text layers for role keywords
        if ( $kind === 'token-text' || $kind === 'static-text' ) {
            $search_str = "$id $token $template_text";
            
            foreach ( self::$role_keywords as $role => $keywords ) {
                foreach ( $keywords as $kw ) {
                    if ( strpos( $search_str, $kw ) !== false ) {
                        return $role;
                    }
                }
            }
            
            return 'secondary-text';
        }
        
        return 'decoration';
    }
    
    /**
     * Get default reflow metadata for a layer
     * 
     * @param array $layer Layer object
     * @param int $index Layer index
     * @return array Default reflow configuration
     */
    public static function get_default_reflow_metadata( $layer, $index = 0 ) {
        $role = self::detect_layer_role( $layer );
        
        $defaults = array(
            'role' => $role,
            'priority' => $index + 1,
            'stackOrder' => $index + 1,
            'anchor' => 'top-left',
            'aspectRatio' => ( $role === 'artwork' ) ? 'preserve' : 'flexible',
        );
        
        // Add role-specific defaults
        if ( $role === 'artwork' ) {
            $defaults['minSize'] = array( 'width' => self::ARTWORK_MIN_SIZE, 'height' => self::ARTWORK_MIN_SIZE );
            $defaults['maxSize'] = array( 'width' => self::ARTWORK_MAX_SIZE, 'height' => self::ARTWORK_MAX_SIZE );
        } elseif ( $role === 'primary-text' || $role === 'secondary-text' ) {
            $defaults['minFontSize'] = self::TEXT_MIN_FONT_SIZE;
            $defaults['maxFontSize'] = self::TEXT_MAX_FONT_SIZE;
        }
        
        return $defaults;
    }
    
    /**
     * Ensure layer has reflow metadata
     * 
     * @param array $layer Layer object
     * @param int $index Layer index
     * @return array Layer with reflow metadata
     */
    public static function ensure_reflow_metadata( $layer, $index = 0 ) {
        if ( empty( $layer ) ) {
            return $layer;
        }
        
        if ( ! isset( $layer['reflow'] ) ) {
            $layer['reflow'] = self::get_default_reflow_metadata( $layer, $index );
        }
        
        return $layer;
    }
    
    /**
     * Constrain a size value between min and max
     * 
     * @param float $value Value to constrain
     * @param float|null $min Minimum value
     * @param float|null $max Maximum value
     * @return float Constrained value
     */
    private static function constrain_size( $value, $min = null, $max = null ) {
        if ( $min !== null && $value < $min ) {
            return $min;
        }
        if ( $max !== null && $value > $max ) {
            return $max;
        }
        return $value;
    }
    
    /**
     * Apply proportional scaling to layers (similar aspect ratios)
     * 
     * @param array $layers Array of layer objects
     * @param array $authored { 'width' => int, 'height' => int }
     * @param array $target { 'width' => int, 'height' => int }
     * @return array Layers with _reflow property
     */
    private static function apply_proportional_scale( $layers, $authored, $target ) {
        // Calculate scale factor
        $scale_x = ( $target['width'] * self::FILL_PERCENTAGE ) / $authored['width'];
        $scale_y = ( $target['height'] * self::FILL_PERCENTAGE ) / $authored['height'];
        $scale = min( $scale_x, $scale_y );
        
        // Calculate offset to center
        $scaled_width = $authored['width'] * $scale;
        $scaled_height = $authored['height'] * $scale;
        $offset_x = ( $target['width'] - $scaled_width ) / 2;
        $offset_y = ( $target['height'] - $scaled_height ) / 2;
        
        $result = array();
        
        foreach ( $layers as $layer ) {
            if ( empty( $layer ) ) {
                $result[] = $layer;
                continue;
            }
            
            $reflow = isset( $layer['reflow'] ) ? $layer['reflow'] : self::get_default_reflow_metadata( $layer, 0 );
            
            $layer_x = isset( $layer['x'] ) ? $layer['x'] : 0;
            $layer_y = isset( $layer['y'] ) ? $layer['y'] : 0;
            $layer_width = isset( $layer['width'] ) ? $layer['width'] : 100;
            $layer_height = isset( $layer['height'] ) ? $layer['height'] : 100;
            
            $new_x = round( $layer_x * $scale + $offset_x );
            $new_y = round( $layer_y * $scale + $offset_y );
            $new_width = round( $layer_width * $scale );
            $new_height = round( $layer_height * $scale );
            
            // Apply constraints
            $min_w = isset( $reflow['minSize']['width'] ) ? $reflow['minSize']['width'] : null;
            $max_w = isset( $reflow['maxSize']['width'] ) ? $reflow['maxSize']['width'] : null;
            $min_h = isset( $reflow['minSize']['height'] ) ? $reflow['minSize']['height'] : null;
            $max_h = isset( $reflow['maxSize']['height'] ) ? $reflow['maxSize']['height'] : null;
            
            $layer['_reflow'] = array(
                'x' => $new_x,
                'y' => $new_y,
                'width' => self::constrain_size( $new_width, $min_w, $max_w ),
                'height' => self::constrain_size( $new_height, $min_h, $max_h ),
            );
            
            // Scale font size for text layers
            $kind = isset( $layer['kind'] ) ? $layer['kind'] : '';
            if ( ( $kind === 'token-text' || $kind === 'static-text' ) && isset( $layer['style']['font_size'] ) ) {
                $scaled_font = round( $layer['style']['font_size'] * $scale );
                $min_font = isset( $reflow['minFontSize'] ) ? $reflow['minFontSize'] : self::TEXT_MIN_FONT_SIZE;
                $max_font = isset( $reflow['maxFontSize'] ) ? $reflow['maxFontSize'] : self::TEXT_MAX_FONT_SIZE;
                $layer['_reflow']['fontSize'] = self::constrain_size( $scaled_font, $min_font, $max_font );
            }
            
            $result[] = $layer;
        }
        
        return $result;
    }
    
    /**
     * Apply vertical stacking layout (horizontal → vertical transition)
     * 
     * @param array $layers Array of layer objects
     * @param array $target { 'width' => int, 'height' => int }
     * @return array Layers with _reflow property
     */
    private static function apply_vertical_stack( $layers, $target ) {
        // Sort layers by stack order
        $sorted_layers = array_filter( $layers );
        usort( $sorted_layers, function( $a, $b ) {
            $a_order = isset( $a['reflow']['stackOrder'] ) ? $a['reflow']['stackOrder'] : 999;
            $b_order = isset( $b['reflow']['stackOrder'] ) ? $b['reflow']['stackOrder'] : 999;
            return $a_order - $b_order;
        });
        
        $margin = self::MIN_MARGIN;
        $available_width = $target['width'] - ( $margin * 2 );
        $available_height = $target['height'] - ( $margin * 2 );
        
        // Separate layer types
        $artwork_layers = array();
        $text_layers = array();
        $other_layers = array();
        
        foreach ( $sorted_layers as $layer ) {
            $role = isset( $layer['reflow']['role'] ) ? $layer['reflow']['role'] : self::detect_layer_role( $layer );
            if ( $role === 'artwork' ) {
                $artwork_layers[] = $layer;
            } elseif ( $role === 'primary-text' || $role === 'secondary-text' ) {
                $text_layers[] = $layer;
            } else {
                $other_layers[] = $layer;
            }
        }
        
        $max_artwork_height = $available_height * 0.6;
        $text_spacing = 8;
        $current_y = $margin;
        $results = array();
        
        // Position artwork first
        foreach ( $artwork_layers as $layer ) {
            $reflow = isset( $layer['reflow'] ) ? $layer['reflow'] : self::get_default_reflow_metadata( $layer, 0 );
            
            $max_size = isset( $reflow['maxSize']['width'] ) ? $reflow['maxSize']['width'] : self::ARTWORK_MAX_SIZE;
            $min_size = isset( $reflow['minSize']['width'] ) ? $reflow['minSize']['width'] : self::ARTWORK_MIN_SIZE;
            
            $art_size = min( $available_width, $max_artwork_height, $max_size );
            $constrained_size = self::constrain_size( $art_size, $min_size, $max_size );
            
            $layer['_reflow'] = array(
                'x' => round( ( $target['width'] - $constrained_size ) / 2 ),
                'y' => $current_y,
                'width' => $constrained_size,
                'height' => $constrained_size,
            );
            
            $results[] = $layer;
            $current_y += $constrained_size + $margin;
        }
        
        // Position text layers
        $remaining_height = $target['height'] - $current_y - $margin;
        $text_count = max( 1, count( $text_layers ) );
        $text_height = max( 24, $remaining_height / $text_count - $text_spacing );
        
        foreach ( $text_layers as $layer ) {
            $reflow = isset( $layer['reflow'] ) ? $layer['reflow'] : self::get_default_reflow_metadata( $layer, 0 );
            
            $base_font_size = min( $text_height * 0.8, 48 );
            $min_font = isset( $reflow['minFontSize'] ) ? $reflow['minFontSize'] : self::TEXT_MIN_FONT_SIZE;
            $max_font = isset( $reflow['maxFontSize'] ) ? $reflow['maxFontSize'] : self::TEXT_MAX_FONT_SIZE;
            $font_size = self::constrain_size( $base_font_size, $min_font, $max_font );
            
            $layer['_reflow'] = array(
                'x' => $margin,
                'y' => $current_y,
                'width' => $available_width,
                'height' => round( $text_height ),
                'fontSize' => $font_size,
            );
            
            $results[] = $layer;
            $current_y += $text_height + $text_spacing;
        }
        
        // Hide other layers
        foreach ( $other_layers as $layer ) {
            $layer['_reflow'] = array(
                'x' => 0,
                'y' => 0,
                'width' => 0,
                'height' => 0,
                'hidden' => true,
            );
            $results[] = $layer;
        }
        
        return $results;
    }
    
    /**
     * Apply horizontal spread layout (vertical → horizontal transition)
     * 
     * @param array $layers Array of layer objects
     * @param array $target { 'width' => int, 'height' => int }
     * @return array Layers with _reflow property
     */
    private static function apply_horizontal_spread( $layers, $target ) {
        // Sort layers by stack order
        $sorted_layers = array_filter( $layers );
        usort( $sorted_layers, function( $a, $b ) {
            $a_order = isset( $a['reflow']['stackOrder'] ) ? $a['reflow']['stackOrder'] : 999;
            $b_order = isset( $b['reflow']['stackOrder'] ) ? $b['reflow']['stackOrder'] : 999;
            return $a_order - $b_order;
        });
        
        $margin = self::MIN_MARGIN;
        $available_width = $target['width'] - ( $margin * 2 );
        $available_height = $target['height'] - ( $margin * 2 );
        
        // Separate layer types
        $artwork_layers = array();
        $text_layers = array();
        
        foreach ( $sorted_layers as $layer ) {
            $role = isset( $layer['reflow']['role'] ) ? $layer['reflow']['role'] : self::detect_layer_role( $layer );
            if ( $role === 'artwork' ) {
                $artwork_layers[] = $layer;
            } elseif ( $role === 'primary-text' || $role === 'secondary-text' ) {
                $text_layers[] = $layer;
            }
        }
        
        $art_size = min( $available_height, $available_width * 0.3 );
        $current_x = $margin;
        $results = array();
        
        // Position artwork on left
        foreach ( $artwork_layers as $layer ) {
            $reflow = isset( $layer['reflow'] ) ? $layer['reflow'] : self::get_default_reflow_metadata( $layer, 0 );
            
            $max_size = isset( $reflow['maxSize']['width'] ) ? $reflow['maxSize']['width'] : self::ARTWORK_MAX_SIZE;
            $min_size = isset( $reflow['minSize']['width'] ) ? $reflow['minSize']['width'] : self::ARTWORK_MIN_SIZE;
            $constrained_size = self::constrain_size( $art_size, $min_size, $max_size );
            
            $layer['_reflow'] = array(
                'x' => $current_x,
                'y' => round( ( $target['height'] - $constrained_size ) / 2 ),
                'width' => $constrained_size,
                'height' => $constrained_size,
            );
            
            $results[] = $layer;
            $current_x += $constrained_size + $margin;
        }
        
        // Text layers fill remaining width
        $text_width = $available_width - ( $current_x - $margin );
        $text_spacing = 4;
        $text_count = max( 1, count( $text_layers ) );
        $per_text_height = ( $available_height - ( $text_spacing * ( $text_count - 1 ) ) ) / $text_count;
        
        $text_y = $margin;
        foreach ( $text_layers as $layer ) {
            $reflow = isset( $layer['reflow'] ) ? $layer['reflow'] : self::get_default_reflow_metadata( $layer, 0 );
            
            $base_font_size = min( $per_text_height * 0.7, 56 );
            $min_font = isset( $reflow['minFontSize'] ) ? $reflow['minFontSize'] : self::TEXT_MIN_FONT_SIZE;
            $max_font = isset( $reflow['maxFontSize'] ) ? $reflow['maxFontSize'] : self::TEXT_MAX_FONT_SIZE;
            $font_size = self::constrain_size( $base_font_size, $min_font, $max_font );
            
            $layer['_reflow'] = array(
                'x' => $current_x,
                'y' => $text_y,
                'width' => $text_width,
                'height' => round( $per_text_height ),
                'fontSize' => $font_size,
            );
            
            $results[] = $layer;
            $text_y += $per_text_height + $text_spacing;
        }
        
        return $results;
    }
    
    /**
     * Calculate reflowed layout for layers
     * 
     * @param array $layers Array of layer objects from visual_config
     * @param array $authored_size { 'width' => int, 'height' => int }
     * @param array $target_size { 'width' => int, 'height' => int }
     * @return array Layers with _reflow property containing transformed positions/sizes
     */
    public static function calculate_reflow_layout( $layers, $authored_size, $target_size ) {
        if ( empty( $layers ) || ! is_array( $layers ) ) {
            return array();
        }
        
        // Validate sizes
        if ( empty( $authored_size['width'] ) || empty( $authored_size['height'] ) ||
             empty( $target_size['width'] ) || empty( $target_size['height'] ) ) {
            // Return layers with no reflow (use original positions)
            return array_map( function( $layer ) {
                if ( empty( $layer ) ) return $layer;
                $layer['_reflow'] = array(
                    'x' => isset( $layer['x'] ) ? $layer['x'] : 0,
                    'y' => isset( $layer['y'] ) ? $layer['y'] : 0,
                    'width' => isset( $layer['width'] ) ? $layer['width'] : 100,
                    'height' => isset( $layer['height'] ) ? $layer['height'] : 100,
                );
                return $layer;
            }, $layers );
        }
        
        // Ensure all layers have reflow metadata
        $layers_with_metadata = array();
        foreach ( $layers as $i => $layer ) {
            $layers_with_metadata[] = self::ensure_reflow_metadata( $layer, $i );
        }
        
        // Detect aspect change and apply appropriate layout
        $aspect_change = self::detect_aspect_change( $authored_size, $target_size );
        
        switch ( $aspect_change ) {
            case 'horizontal-to-vertical':
                return self::apply_vertical_stack( $layers_with_metadata, $target_size );
            
            case 'vertical-to-horizontal':
                return self::apply_horizontal_spread( $layers_with_metadata, $target_size );
            
            case 'similar':
            default:
                return self::apply_proportional_scale( $layers_with_metadata, $authored_size, $target_size );
        }
    }
    
    /**
     * Check if reflow is needed (sizes differ significantly)
     * 
     * @param array $authored_size { 'width' => int, 'height' => int }
     * @param array $target_size { 'width' => int, 'height' => int }
     * @return bool True if reflow calculation should be applied
     */
    public static function needs_reflow( $authored_size, $target_size ) {
        if ( empty( $authored_size ) || empty( $target_size ) ) {
            return false;
        }
        
        // Check if sizes differ by more than 5%
        $width_diff = abs( $authored_size['width'] - $target_size['width'] ) / $authored_size['width'];
        $height_diff = abs( $authored_size['height'] - $target_size['height'] ) / $authored_size['height'];
        
        return $width_diff > 0.05 || $height_diff > 0.05;
    }
    
    /**
     * Get the layout mode that will be used for given sizes
     * 
     * @param array $authored_size { 'width' => int, 'height' => int }
     * @param array $target_size { 'width' => int, 'height' => int }
     * @return string 'proportional' | 'vertical-stack' | 'horizontal-spread' | 'none'
     */
    public static function get_reflow_layout_mode( $authored_size, $target_size ) {
        if ( ! self::needs_reflow( $authored_size, $target_size ) ) {
            return 'none';
        }
        
        $aspect_change = self::detect_aspect_change( $authored_size, $target_size );
        
        switch ( $aspect_change ) {
            case 'horizontal-to-vertical':
                return 'vertical-stack';
            case 'vertical-to-horizontal':
                return 'horizontal-spread';
            default:
                return 'proportional';
        }
    }
}
