<?php
/**
 * Smart Reflow Engine (PHP)
 * 
 * Server-side implementation for content block preview rendering.
 * Must produce IDENTICAL results to reflow-engine.js for cross-platform parity.
 * 
 * @package CastConductor
 * @see /docs/SPEC-SMART-REFLOW.md
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Smart Reflow Engine class
 * Handles layout calculations for content blocks across different container sizes
 */
class CastConductor_Reflow_Engine {
    
    // ========================================================================
    // CONFIGURATION - Must match reflow-engine.js exactly
    // ========================================================================
    
    const FILL_PERCENTAGE = 0.88;      // Use 88% of container (12% total margin)
    const MIN_MARGIN = 16;             // Minimum 16px margin on any side
    
    const TEXT_MIN_FONT_SIZE = 14;     // Never smaller than 14px
    const TEXT_MAX_FONT_SIZE = 72;     // Never larger than 72px
    
    const ARTWORK_MIN_SIZE = 80;       // Minimum 80px dimension
    const ARTWORK_MAX_SIZE = 600;      // Maximum 600px dimension
    
    // Aspect ratio thresholds for layout mode detection
    const ASPECT_HORIZONTAL = 2.0;     // > 2.0 = "horizontal" (e.g., 1280x240 = 5.33)
    const ASPECT_VERTICAL = 0.75;      // < 0.75 = "vertical" (e.g., 320x480 = 0.67)
    
    // Layer role keywords for auto-detection
    private static $role_keywords = [
        'artwork' => ['artwork', 'image', 'poster', 'thumbnail', 'cover', 'logo'],
        'primary-text' => ['title', 'name', 'heading', 'artist'],
        'secondary-text' => ['subtitle', 'description', 'track', 'message', 'content'],
        'decoration' => ['background', 'overlay', 'divider', 'border'],
        'badge' => ['icon', 'badge', 'indicator', 'status']
    ];
    
    // ========================================================================
    // UTILITY FUNCTIONS
    // ========================================================================
    
    /**
     * 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'] / max(1, $authored['height']);
        $target_ratio = $target['width'] / max(1, $target['height']);
        
        // Horizontal (wide) to Vertical (tall) - need to stack
        if ($authored_ratio > self::ASPECT_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_HORIZONTAL) {
            return 'vertical-to-horizontal';
        }
        
        return 'similar';
    }
    
    /**
     * Auto-detect layer role based on layer properties
     * 
     * @param array $layer Layer data
     * @return string Detected role
     */
    public static function detect_layer_role($layer) {
        if (empty($layer)) {
            return 'decoration';
        }
        
        $kind = $layer['kind'] ?? '';
        $id = strtolower($layer['id'] ?? '');
        $token = strtolower($layer['token'] ?? '');
        $template_text = strtolower($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 data
     * @param int $index Layer index for stack order
     * @return array Default reflow configuration
     */
    public static function get_default_reflow_metadata($layer, $index = 0) {
        $role = self::detect_layer_role($layer);
        
        $defaults = [
            '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'] = ['width' => self::ARTWORK_MIN_SIZE, 'height' => self::ARTWORK_MIN_SIZE];
            $defaults['maxSize'] = ['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, adding defaults if missing
     * 
     * @param array $layer Layer data
     * @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']) || empty($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;
    }
    
    // ========================================================================
    // LAYOUT CALCULATION FUNCTIONS
    // ========================================================================
    
    /**
     * Apply proportional scaling to layers (similar aspect ratios)
     * 
     * @param array $layers Array of layer objects
     * @param array $authored Authored size
     * @param array $target Target size
     * @return array Layers with _reflow property
     */
    private static function apply_proportional_scale($layers, $authored, $target) {
        // Calculate scale factor to fit within target while respecting fill percentage
        $scale_x = ($target['width'] * self::FILL_PERCENTAGE) / max(1, $authored['width']);
        $scale_y = ($target['height'] * self::FILL_PERCENTAGE) / max(1, $authored['height']);
        $scale = min($scale_x, $scale_y);
        
        // Calculate offset to center the scaled content
        $scaled_width = $authored['width'] * $scale;
        $scaled_height = $authored['height'] * $scale;
        $offset_x = ($target['width'] - $scaled_width) / 2;
        $offset_y = ($target['height'] - $scaled_height) / 2;
        
        $results = [];
        foreach ($layers as $layer) {
            if (empty($layer)) {
                $results[] = $layer;
                continue;
            }
            
            $reflow = $layer['reflow'] ?? self::get_default_reflow_metadata($layer, 0);
            
            // Scale position and size
            $new_x = round(($layer['x'] ?? 0) * $scale + $offset_x);
            $new_y = round(($layer['y'] ?? 0) * $scale + $offset_y);
            $new_width = round(($layer['width'] ?? 100) * $scale);
            $new_height = round(($layer['height'] ?? 100) * $scale);
            
            // Apply min/max constraints
            $layer['_reflow'] = [
                'x' => $new_x,
                'y' => $new_y,
                'width' => self::constrain_size(
                    $new_width,
                    $reflow['minSize']['width'] ?? null,
                    $reflow['maxSize']['width'] ?? null
                ),
                'height' => self::constrain_size(
                    $new_height,
                    $reflow['minSize']['height'] ?? null,
                    $reflow['maxSize']['height'] ?? null
                )
            ];
            
            // Scale font size for text layers
            $kind = $layer['kind'] ?? '';
            if (($kind === 'token-text' || $kind === 'static-text') && !empty($layer['style']['font_size'])) {
                $scaled_font_size = round($layer['style']['font_size'] * $scale);
                $layer['_reflow']['fontSize'] = self::constrain_size(
                    $scaled_font_size,
                    $reflow['minFontSize'] ?? self::TEXT_MIN_FONT_SIZE,
                    $reflow['maxFontSize'] ?? self::TEXT_MAX_FONT_SIZE
                );
            }
            
            $results[] = $layer;
        }
        
        return $results;
    }
    
    /**
     * Apply vertical stacking layout (horizontal → vertical transition)
     * 
     * @param array $layers Array of layer objects
     * @param array $target Target size
     * @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 = $a['reflow']['stackOrder'] ?? 999;
            $b_order = $b['reflow']['stackOrder'] ?? 999;
            return $a_order - $b_order;
        });
        
        // Calculate available space with margins
        $margin = self::MIN_MARGIN;
        $available_width = $target['width'] - ($margin * 2);
        $available_height = $target['height'] - ($margin * 2);
        
        // Separate layer types
        $artwork_layers = [];
        $text_layers = [];
        $other_layers = [];
        
        foreach ($sorted_layers as $layer) {
            $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;
            }
        }
        
        // Artwork takes up to 60% of vertical space
        $max_artwork_height = $available_height * 0.6;
        $text_spacing = 8;
        
        $current_y = $margin;
        $results = [];
        
        // Position artwork first
        foreach ($artwork_layers as $layer) {
            $reflow = $layer['reflow'] ?? self::get_default_reflow_metadata($layer, 0);
            
            // Keep artwork square, fit to available width
            $art_size = min(
                $available_width,
                $max_artwork_height,
                $reflow['maxSize']['width'] ?? self::ARTWORK_MAX_SIZE
            );
            $constrained_size = self::constrain_size(
                $art_size,
                $reflow['minSize']['width'] ?? self::ARTWORK_MIN_SIZE,
                $reflow['maxSize']['width'] ?? self::ARTWORK_MAX_SIZE
            );
            
            $layer['_reflow'] = [
                '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 below artwork
        $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 = $layer['reflow'] ?? self::get_default_reflow_metadata($layer, 0);
            
            // Calculate font size based on available height
            $base_font_size = min($text_height * 0.8, 48);
            $font_size = self::constrain_size(
                $base_font_size,
                $reflow['minFontSize'] ?? self::TEXT_MIN_FONT_SIZE,
                $reflow['maxFontSize'] ?? self::TEXT_MAX_FONT_SIZE
            );
            
            $layer['_reflow'] = [
                'x' => $margin,
                'y' => $current_y,
                'width' => $available_width,
                'height' => round($text_height),
                'fontSize' => $font_size
            ];
            
            $results[] = $layer;
            $current_y += $text_height + $text_spacing;
        }
        
        // Other layers: hide
        foreach ($other_layers as $layer) {
            $layer['_reflow'] = [
                '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 Target size
     * @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 = $a['reflow']['stackOrder'] ?? 999;
            $b_order = $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 = [];
        $text_layers = [];
        
        foreach ($sorted_layers as $layer) {
            $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;
            }
        }
        
        // Artwork takes left portion (keep square)
        $art_size = min($available_height, $available_width * 0.3);
        $current_x = $margin;
        
        $results = [];
        
        // Position artwork on left
        foreach ($artwork_layers as $layer) {
            $reflow = $layer['reflow'] ?? self::get_default_reflow_metadata($layer, 0);
            $constrained_size = self::constrain_size(
                $art_size,
                $reflow['minSize']['width'] ?? self::ARTWORK_MIN_SIZE,
                $reflow['maxSize']['width'] ?? self::ARTWORK_MAX_SIZE
            );
            
            $layer['_reflow'] = [
                '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, stack vertically
        $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 = $layer['reflow'] ?? self::get_default_reflow_metadata($layer, 0);
            
            $font_size = self::constrain_size(
                min($per_text_height * 0.7, 56),
                $reflow['minFontSize'] ?? self::TEXT_MIN_FONT_SIZE,
                $reflow['maxFontSize'] ?? self::TEXT_MAX_FONT_SIZE
            );
            
            $layer['_reflow'] = [
                '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;
    }
    
    // ========================================================================
    // MAIN REFLOW API
    // ========================================================================
    
    /**
     * Calculate reflowed layout for layers
     * 
     * @param array $layers Array of layer objects from visual_config
     * @param array $authored_size ['width' => int, 'height' => int] size content block was authored for
     * @param array $target_size ['width' => int, 'height' => int] of target container
     * @return array Layers with _reflow property containing transformed positions/sizes
     */
    public static function calculate_reflow_layout($layers, $authored_size, $target_size) {
        if (!is_array($layers) || empty($layers)) {
            return [];
        }
        
        // 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'] = [
                    'x' => $layer['x'] ?? 0,
                    'y' => $layer['y'] ?? 0,
                    'width' => $layer['width'] ?? 100,
                    'height' => $layer['height'] ?? 100
                ];
                return $layer;
            }, $layers);
        }
        
        // Ensure all layers have reflow metadata
        $layers_with_metadata = [];
        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']) / max(1, $authored_size['width']);
        $height_diff = abs($authored_size['height'] - $target_size['height']) / max(1, $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';
        }
    }
}
