"""
Text Texture Generation Engine
Core functionality for generating texture images with text overlays.
"""

import math

import bpy
from array import array

from ..utils.constants import has_text_fitting
from ..utils.overlay_assets import (
    SVGConversionError,
    ensure_svg_raster,
    is_svg_path,
)
from ..utils.system import find_default_truetype_font

def generate_texture_image(props, width, height):
    pass
    """
    Generate the actual texture image with overlays.
    
    Args:
        props: Text texture properties object
        width: Target image width in pixels
        height: Target image height in pixels
        
    Returns:
        tuple: (diffuse_image, normal_map_image) or (diffuse_image, None) if normal map disabled
        Returns (None, None) on error
    """
    try:
        pass
        
        # Enhanced texture generation with proper error handling and logging
        
        # Create Blender image directly without large numpy arrays
        img_name = f"TextTexture_{width}x{height}"
        if img_name in bpy.data.images:
            pass
            bpy.data.images.remove(bpy.data.images[img_name])
        
        blender_img = bpy.data.images.new(img_name, width, height, alpha=True)
        
        # Use PIL to create a proper text texture instead of solid color
        # PIL dependency is declared in blender_manifest.toml and should be auto-installed by Blender
        try:
            pass
            from PIL import Image, ImageDraw, ImageFont
            
            # Determine resampling filters compatible with current Pillow version
            try:
                resampling_module = getattr(Image, 'Resampling', Image)
                # Check Image before accessing to prevent AttributeError in tests where Image is mocked as None
                fallback_filter = getattr(Image, 'BICUBIC', 3) if Image else 3
                lanczos_filter = getattr(resampling_module, 'LANCZOS', getattr(Image, 'LANCZOS', fallback_filter))
                bicubic_filter = getattr(resampling_module, 'BICUBIC', getattr(Image, 'BICUBIC', getattr(Image, 'BILINEAR', 2) if Image else 2))
            except (ImportError, AttributeError):
                lanczos_filter = 3  # Manual fallback to BICUBIC
                bicubic_filter = 2  # Manual fallback to BILINEAR

            def _iter_enabled_components():
                """Yield enabled composition components with their index."""
                overlays = getattr(props, 'image_overlays', [])
                try:
                    overlay_list = list(overlays)
                except TypeError:
                    overlay_list = []
                for index, overlay in enumerate(overlay_list):
                    if not getattr(overlay, 'enabled', True):
                        continue
                    yield index, overlay

            def _calculate_overlay_position(overlay, overlay_size):
                """Calculate top-left pixel position for an overlay image."""
                overlay_width, overlay_height = overlay_size
                canvas_width = width
                canvas_height = height
                mode = getattr(overlay, 'placement_mode', 'ABSOLUTE') or 'ABSOLUTE'

                x_norm = float(getattr(overlay, 'x_position', 0.5) or 0.5)
                y_norm = float(getattr(overlay, 'y_position', 0.5) or 0.5)
                center_x = x_norm * canvas_width
                center_y = y_norm * canvas_height

                spacing_ratio = max(0.0, float(getattr(overlay, 'text_spacing', 0.0) or 0.0)) / 100.0

                if mode == 'APPEND':
                    center_x = (canvas_width * 0.5) + (spacing_ratio * canvas_width) + (overlay_width / 2.0)
                elif mode == 'PREPEND':
                    center_x = (canvas_width * 0.5) - (spacing_ratio * canvas_width) - (overlay_width / 2.0)

                x = int(round(center_x - overlay_width / 2.0))
                y = int(round(center_y - overlay_height / 2.0))

                max_x = max(0, canvas_width - overlay_width)
                max_y = max(0, canvas_height - overlay_height)
                x = max(0, min(max_x, x))
                y = max(0, min(max_y, y))
                return x, y
            
            def _make_text_overlay(component, base_font_size, stroke_kwargs={}):
                """Return (image, baseline_offset, descent, render_meta) for a text component."""
                text_value = getattr(component, 'text_content', '').strip()
                if not text_value:
                    return None, None, None, None
                scale = getattr(component, 'scale', 1.0)
                try:
                    scale = float(scale if scale is not None else 1.0)
                except (TypeError, ValueError):
                    scale = 1.0
                if not math.isfinite(scale) or scale <= 0:
                    return None, None, None, None
                desired_size = max(4, int(round(max(base_font_size, 4) * scale)))
                text_font = load_font_for_size(desired_size)

                # Prepare stroke measurement for the component
                comp_stroke_width = stroke_kwargs.get('stroke_width', 0)
                bbox_stroke_kwargs = {'stroke_width': comp_stroke_width} if comp_stroke_width > 0 else {}

                temp_draw = ImageDraw.Draw(Image.new('RGBA', (1, 1)))

                overlay_img = None
                baseline_offset = None
                component_descent = None
                render_meta = None

                # Prefer anchoring to the left baseline so we can align with the main text baseline.
                try:
                    baseline_bbox = temp_draw.textbbox(
                        (0, 0),
                        text_value,
                        font=text_font,
                        anchor='ls',
                        **bbox_stroke_kwargs
                    )
                except Exception:
                    baseline_bbox = None

                if baseline_bbox:
                    left, top, right, bottom = baseline_bbox
                    text_width = max(1, int(round(right - left)))
                    text_height = max(1, int(round(bottom - top)))
                    overlay_img = Image.new('RGBA', (text_width, text_height), (0, 0, 0, 0))
                    overlay_draw = ImageDraw.Draw(overlay_img)
                    text_color = tuple(int(c * 255) for c in props.text_color[:4])
                    # Place the baseline at a consistent offset from the top of the overlay.
                    baseline_offset = -top
                    draw_x = -left
                    draw_y = -top
                    overlay_draw.text((draw_x, draw_y), text_value, font=text_font, fill=text_color, anchor='ls', **stroke_kwargs)
                else:
                    # Fallback for PIL versions without baseline-aware textbbox.
                    try:
                        text_bbox = temp_draw.textbbox((0, 0), text_value, font=text_font, **bbox_stroke_kwargs)
                    except Exception:
                        try:
                            font_size_val = int(getattr(props, 'font_size', 100))
                        except (ValueError, TypeError):
                            font_size_val = 100
                        width_est = font_size_val * len(text_value) * 0.6 + comp_stroke_width * 2
                        height_est = font_size_val + comp_stroke_width * 2
                        text_bbox = (0, 0, width_est, height_est)
                    left, top, right, bottom = text_bbox
                    text_width = max(1, int(round(right - left)))
                    text_height = max(1, int(round(bottom - top)))
                    overlay_img = Image.new('RGBA', (text_width, text_height), (0, 0, 0, 0))
                    overlay_draw = ImageDraw.Draw(overlay_img)
                    text_color = tuple(int(c * 255) for c in props.text_color[:4])
                    offset_x = -left
                    offset_y = -top
                    baseline_offset = offset_y
                    overlay_draw.text((offset_x, offset_y), text_value, font=text_font, fill=text_color, **stroke_kwargs)

                content_box = overlay_img.getbbox() if overlay_img else None
                crop_left = crop_top = 0
                if content_box:
                    crop_left, crop_top, crop_right, crop_bottom = content_box
                    overlay_img = overlay_img.crop(content_box)
                    if baseline_offset is not None:
                        baseline_offset -= crop_top
                    component_descent = (crop_bottom - crop_top) - (baseline_offset or 0)
                else:
                    component_descent = (overlay_img.height if overlay_img else 0) - (baseline_offset or 0)

                if overlay_img and (baseline_offset is None or baseline_offset < 0 or baseline_offset > overlay_img.height):
                    try:
                        ascent, descent = text_font.getmetrics()
                    except Exception:
                        ascent, descent = overlay_img.height, 0
                    baseline_offset = min(max(0, int(round(ascent))), overlay_img.height)
                    component_descent = max(0, overlay_img.height - baseline_offset)

                baseline_offset = max(0, int(round(baseline_offset or 0)))
                component_descent = max(0, int(round(component_descent or 0)))
                render_meta = {
                    'text': text_value,
                    'font': text_font,
                    'color': tuple(int(c * 255) for c in props.text_color[:4]),
                    'draw_offset_x': (draw_x - crop_left) if baseline_bbox else (offset_x - crop_left),
                    'draw_offset_y': (draw_y - crop_top) if baseline_bbox else (offset_y - crop_top),
                    'anchor': 'ls' if baseline_bbox else None,
                    'width': overlay_img.width if overlay_img else 0,
                    'height': overlay_img.height if overlay_img else 0
                }
                return overlay_img, baseline_offset, component_descent, render_meta

            def _make_image_overlay(component):
                """Return RGBA image for an image component or None."""
                resolved_path = getattr(component, 'image_path', '')
                if not resolved_path:
                    return None
                scale = getattr(component, 'scale', 1.0)
                try:
                    scale = float(scale if scale is not None else 1.0)
                except (TypeError, ValueError):
                    scale = 1.0
                if not math.isfinite(scale) or scale <= 0:
                    return None
                scale_to_apply = scale
                if is_svg_path(resolved_path):
                    try:
                        raster_info = ensure_svg_raster(resolved_path, scale)
                        resolved_path = raster_info.path
                        scale_to_apply = raster_info.applied_scale
                    except SVGConversionError as svg_error:
                        print(f"WARNING: Failed to rasterize SVG overlay '{component.image_path}': {svg_error}")
                        return None
                try:
                    with Image.open(resolved_path) as raw_overlay:
                        overlay_img = raw_overlay.convert('RGBA')
                except Exception as overlay_error:
                    print(f"WARNING: Failed to load overlay image '{resolved_path}': {overlay_error}")
                    return None
                if scale_to_apply != 1.0:
                    new_size = (
                        max(1, int(round(overlay_img.width * scale_to_apply))),
                        max(1, int(round(overlay_img.height * scale_to_apply)))
                    )
                    if new_size != overlay_img.size:
                        overlay_img = overlay_img.resize(new_size, lanczos_filter)
                return overlay_img

            def _apply_component_layers(pil_canvas, stage, base_text_rect, base_font_size, s_draw=None, g_draw=None, s_dx=0.0, s_dy=0.0, stroke_kwargs={}):
                """Composite enabled components according to z-index and placement."""
                components_data = []
                for index, component in _iter_enabled_components():
                    z_value = getattr(component, 'z_index', 0)
                    if stage == 'before' and z_value >= 0:
                        continue
                    if stage == 'after' and z_value < 0:
                        continue
                    component_type = getattr(component, 'component_type', 'IMAGE')
                    if component_type == 'TEXT':
                        overlay_img, baseline_offset, component_descent, render_meta = _make_text_overlay(component, base_font_size, stroke_kwargs)
                    else:
                        overlay_img = _make_image_overlay(component)
                        baseline_offset = overlay_img.height if overlay_img is not None else None
                        component_descent = overlay_img.height if overlay_img is not None else None
                        render_meta = None
                    if overlay_img is None and not render_meta:
                        continue
                    rotation = float(getattr(component, 'rotation', 0.0) or 0.0)
                    direct_draw = False
                    width_from_image = overlay_img.width if overlay_img is not None else 0
                    height_from_image = overlay_img.height if overlay_img is not None else 0
                    if component_type == 'TEXT' and render_meta and not (rotation % 360):
                        direct_draw = True
                    else:
                        if overlay_img is None:
                            continue
                        if rotation % 360:
                            overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
                            width_from_image = overlay_img.width
                            height_from_image = overlay_img.height
                    width_value = 0
                    height_value = 0
                    if component_type == 'TEXT' and render_meta:
                        if direct_draw:
                            width_value = render_meta.get('width', width_from_image)
                            height_value = render_meta.get('height', height_from_image)
                        else:
                            width_value = width_from_image
                            height_value = height_from_image
                    elif overlay_img is not None:
                        width_value = width_from_image
                        height_value = height_from_image
                    components_data.append({
                        'index': index,
                        'component': component,
                        'image': overlay_img,
                        'z': z_value,
                        'type': component_type,
                        'baseline_offset': baseline_offset,
                        'descent': component_descent,
                        'render_info': render_meta,
                        'direct_draw': direct_draw,
                        'rotation': rotation,
                        'width': width_value,
                        'height': height_value
                    })
                
                if not components_data:
                    return pil_canvas
                
                sequential_groups = {'PREPEND': [], 'APPEND': []}
                absolute_entries = []
                for entry in components_data:
                    placement = getattr(entry['component'], 'placement_mode', 'ABSOLUTE') or 'ABSOLUTE'
                    if placement in sequential_groups:
                        sequential_groups[placement].append(entry)
                    else:
                        x_pos, y_pos = _calculate_overlay_position(entry['component'], entry['image'].size)
                        entry['position'] = (x_pos, y_pos)
                        absolute_entries.append(entry)
                
                base_center_x = width * 0.5
                base_center_y = height * 0.5
                base_left = base_center_x
                base_right = base_center_x
                base_top = base_center_y - (base_font_size / 2.0)
                base_bottom = base_center_y + (base_font_size / 2.0)
                base_baseline = base_bottom
                if base_text_rect:
                    base_center_x = base_text_rect['center_x']
                    base_center_y = base_text_rect['center_y']
                    base_left = base_text_rect['left']
                    base_right = base_text_rect['right']
                    base_top = base_text_rect.get('top', base_top)
                    base_bottom = base_text_rect.get('bottom', base_bottom)
                    base_baseline = base_text_rect.get('baseline', base_bottom)
                else:
                    base_top = max(0.0, min(float(height - base_font_size), base_top))
                    base_bottom = max(base_top + 1.0, min(float(height), base_bottom))
                    base_baseline = base_bottom
                
                def assign_sequential_positions(entries, placement):
                    if not entries:
                        return []
                    sorted_entries = sorted(entries, key=lambda item: item['index'])
                    cursor = base_left if placement == 'PREPEND' else base_right
                    assigned = []
                    for entry in sorted_entries:
                        component = entry['component']
                        entry_width = entry.get('width', entry['image'].width if entry['image'] else 0)
                        entry_height = entry.get('height', entry['image'].height if entry['image'] else 0)
                        spacing_factor = float(getattr(component, 'text_spacing', 0.0) or 0.0) / 100.0
                        inter_spacing_factor = float(getattr(component, 'image_spacing', 0.0) or 0.0) / 100.0
                        spacing_pixels = spacing_factor * base_font_size
                        inter_pixels = inter_spacing_factor * base_font_size
                        
                        if placement == 'PREPEND':
                            cursor -= spacing_pixels
                            cursor -= entry_width
                            x_pos = cursor
                            cursor -= inter_pixels
                        else:
                            cursor += spacing_pixels
                            x_pos = cursor
                            cursor += entry_width
                            cursor += inter_pixels
                        if base_text_rect:
                            if entry['type'] == 'TEXT' and entry['baseline_offset'] is not None:
                                y_pos = int(round(base_baseline - entry['baseline_offset']))
                            else:
                                y_pos = int(round(base_bottom - entry_height))
                        else:
                            center_y = base_center_y
                            y_pos = int(round(center_y - entry_height / 2.0))
                        x_pos = int(round(x_pos))
                        # For sequential layouts (PREPEND/APPEND), we don't strictly clamp.
                        # If the spacing is huge, it should render off-screen, not clump together at x=0
                        if placement == 'ABSOLUTE':
                            y_pos = max(0, min(height - entry_height, y_pos))
                            x_pos = max(0, min(width - entry_width, x_pos))
                        entry['position'] = (x_pos, y_pos)
                        assigned.append(entry)
                    return assigned
                
                positioned_entries = absolute_entries
                positioned_entries.extend(assign_sequential_positions(sequential_groups['PREPEND'], 'PREPEND'))
                positioned_entries.extend(assign_sequential_positions(sequential_groups['APPEND'], 'APPEND'))
                
                positioned_entries.sort(key=lambda item: item['z'])
                
                # Main canvas drawing context
                draw_ctx = ImageDraw.Draw(pil_canvas)
                
                # Effect background/shadow colors
                s_color = tuple(int(c * 255) for c in getattr(props, 'shadow_color', (0,0,0,1))[:4]) if s_draw else None
                g_color = tuple(int(c * 255) for c in getattr(props, 'glow_color', (1,1,1,0.8))[:4]) if g_draw else None
                
                for entry in positioned_entries:
                    x_pos, y_pos = entry.get('position', (0, 0))
                    is_text = (entry['type'] == 'TEXT')
                    
                    if entry.get('direct_draw') and entry.get('render_info'):
                        info = entry['render_info']
                        draw_pos = (
                            x_pos + info.get('draw_offset_x', 0),
                            y_pos + info.get('draw_offset_y', 0)
                        )
                        anchor = info.get('anchor')
                        text_val = info.get('text', '')
                        text_font = info.get('font')
                        
                        # Apply shadow to the shadow layer if drawing context exists
                        if s_draw and is_text:
                            s_pos = (draw_pos[0] + s_dx, draw_pos[1] + s_dy)
                            s_draw.text(s_pos, text_val, font=text_font, fill=s_color, anchor=anchor, **stroke_kwargs)
                        
                        # Apply glow to glow layer
                        if g_draw and is_text:
                            g_draw.text(draw_pos, text_val, font=text_font, fill=g_color, anchor=anchor, **stroke_kwargs)
                        
                        # Apply main text
                        draw_ctx.text(
                            draw_pos,
                            text_val,
                            font=text_font,
                            fill=info.get('color'),
                            anchor=anchor,
                            **stroke_kwargs
                        )
                    else:
                        overlay_img = entry.get('image')
                        if overlay_img is None:
                            continue
                        
                        # Paste shadow for component? Actually images don't get the same stroke/shadow easily
                        # but we can paste them into shadow/glow layers if they are icons.
                        if is_text: # Only apply shadow/glow to text components for now
                            if s_draw:
                                s_draw.bitmap((x_pos + s_dx, y_pos + s_dy), overlay_img, fill=s_color)
                            if g_draw:
                                g_draw.bitmap((x_pos, y_pos), overlay_img, fill=g_color)
                        
                        overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
                        overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
                        pil_canvas = Image.alpha_composite(pil_canvas, overlay_layer)
                
                return pil_canvas
            
            # Create PIL image with transparent background
            if props.background_transparent:
                pass
                pil_img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
            else:
                pass
                bg_color = tuple(int(c * 255) for c in props.background_color[:3]) + (255,)
                pil_img = Image.new('RGBA', (width, height), bg_color)
            
            base_text_rect = None
            final_font_size = props.font_size
            before_layers_applied = False
            
            # Initialize effect-related variables early to avoid UnboundLocalError
            s_draw, g_draw = None, None
            s_dx, s_dy = 0.0, 0.0
            stroke_kwargs = {}

            # Draw text if provided (including prepend/append)
            full_text_parts = []
            if props.prepend_text.strip():
                pass
                full_text_parts.append(props.prepend_text.strip())
            if props.text.strip():
                pass
                full_text_parts.append(props.text.strip())
            if props.append_text.strip():
                pass
                full_text_parts.append(props.append_text.strip())
                
            if full_text_parts:
                pass
                measure_draw = ImageDraw.Draw(pil_img)
                
                # Gather static widths (Images) and prepare dynamic components for fitting loop
                static_overlay_width = 0.0
                dynamic_components = []
                
                if hasattr(props, "image_overlays"):
                    for component in props.image_overlays:
                        if component.enabled and getattr(component, 'placement_mode', 'ABSOLUTE') in ['PREPEND', 'APPEND']:
                            c_type = getattr(component, 'component_type', 'IMAGE')
                            dynamic_components.append(component)
                            
                            if c_type == 'IMAGE':
                                # Image width is static to font scaling
                                resolved_path = getattr(component, 'image_path', '')
                                if resolved_path:
                                    try:
                                        with Image.open(resolved_path) as raw:
                                            static_overlay_width += raw.width * float(getattr(component, 'scale', 1.0) or 1.0)
                                    except Exception:
                                        pass

                # Combine text based on layout mode
                if props.prepend_append_layout == 'HORIZONTAL':
                    pass
                    # For horizontal layout, use simple space separation with margin control
                    if len(full_text_parts) == 1:
                        pass
                        full_text = full_text_parts[0]
                    else:
                        pass
                        # Calculate number of spaces based on margin (but reasonable amounts)
                        prepend_spaces = max(1, int(props.prepend_margin / 10)) if props.prepend_text.strip() else 0
                        append_spaces = max(1, int(props.append_margin / 10)) if props.append_text.strip() else 0
                        
                        # Build text with controlled spacing
                        text_parts_with_spacing = []
                        for i, part in enumerate(full_text_parts):
                            pass
                            if i == 0 and prepend_spaces > 0:
                                pass
                                # Add spacing after prepend text
                                text_parts_with_spacing.append(part + " " * prepend_spaces)
                            elif i == len(full_text_parts) - 1 and append_spaces > 0:
                                # Add spacing before append text
                                text_parts_with_spacing.append(" " * append_spaces + part)
                            else:
                                pass
                                text_parts_with_spacing.append(part)
                        
                        full_text = " ".join(text_parts_with_spacing)
                        
                    layout_mode = "horizontal"
                else:
                    # Vertical layout - keep parts separate for individual positioning
                    full_text = "\n".join(full_text_parts)
                    
                    layout_mode = "vertical"
                
                
                # Determine candidate font paths based on user configuration
                candidate_paths = []
                seen_candidates = set()
                for candidate in (
                    getattr(props, 'custom_font_path', '') if getattr(props, 'use_custom_font', False) else '',
                    getattr(props, 'font_path', '') if getattr(props, 'font_path', 'default') != "default" else '',
                    find_default_truetype_font()
                ):
                    if candidate and candidate not in seen_candidates:
                        candidate_paths.append(candidate)
                        seen_candidates.add(candidate)

                failed_candidates = set()
                active_font_path = None

                def load_font_for_size(size):
                    pass
                    nonlocal active_font_path
                    for path in candidate_paths:
                        pass
                        if path in failed_candidates:
                            pass
                            continue
                        try:
                            pass
                            font_obj = ImageFont.truetype(path, size)
                            active_font_path = path
                            return font_obj
                        except (OSError, IOError, AttributeError, TypeError) as font_error:
                            pass
                            failed_candidates.add(path)
                            continue
                    active_font_path = None
                    return ImageFont.load_default()

                # Determine font size (with text fitting if enabled)
                font_size = props.font_size
                try:
                    stroke_width = int(getattr(props, 'stroke_width', 0)) if getattr(props, 'enable_stroke', False) else 0
                except (ValueError, TypeError):
                    stroke_width = 0
                stroke_color = tuple(int(c * 255) for c in getattr(props, 'stroke_color', (0, 0, 0, 1))[:4])
                stroke_kwargs = {'stroke_width': stroke_width, 'stroke_fill': stroke_color} if stroke_width > 0 else {}
                bbox_stroke_kwargs = {'stroke_width': stroke_width} if stroke_width > 0 else {}
                
                if props.enable_text_fitting and has_text_fitting():
                    pass
                    
                    try:
                        pass
                        min_size = props.min_font_size
                        max_size = props.max_font_size
                        target_width = width * (1.0 - props.text_fitting_margin / 100.0)
                        target_height = height * (1.0 - props.text_fitting_margin / 100.0)
                        
                        optimal_size = min_size
                        for test_size in range(min_size, max_size + 1, 2):  # Step by 2 for performance
                            test_font = load_font_for_size(test_size)
                            
                            try:
                                bbox = measure_draw.textbbox((0, 0), full_text, font=test_font, **bbox_stroke_kwargs)
                                test_width = bbox[2] - bbox[0]
                                test_height = bbox[3] - bbox[1]
                            except Exception:
                                # Fallback gracefully if font measurement fails
                                test_width = test_size * len(full_text) * 0.6 + stroke_width * 2
                                test_height = test_size + stroke_width * 2
                            
                            # Add dynamic components width + spacing mathematically
                            for component in dynamic_components:
                                c_type = getattr(component, 'component_type', 'IMAGE')
                                c_spacing = (float(getattr(component, 'text_spacing', 0.0) or 0.0) / 100.0) * test_size
                                c_inter_spacing = (float(getattr(component, 'image_spacing', 0.0) or 0.0) / 100.0) * test_size
                                test_width += c_spacing + c_inter_spacing
                                
                                if c_type == 'TEXT' and component.text_content.strip():
                                    try:
                                        c_bbox = measure_draw.textbbox((0, 0), component.text_content.strip(), font=test_font, **bbox_stroke_kwargs)
                                        test_width += (c_bbox[2] - c_bbox[0]) * float(getattr(component, 'scale', 1.0) or 1.0)
                                    except Exception:
                                        pass
                            
                            if test_width + static_overlay_width <= target_width and test_height <= target_height:
                                optimal_size = test_size
                            else:
                                break
                        
                        font_size = optimal_size
                    except Exception as e:
                        pass
                        font_size = props.font_size
                
                final_font_size = font_size
                font = load_font_for_size(font_size)
                try:
                    base_ascent, base_descent = font.getmetrics()
                except Exception:
                    base_ascent, base_descent = (final_font_size, 0)
                
                # Calculate text position and draw
                if layout_mode == "vertical" and props.prepend_append_layout == 'VERTICAL':
                    pass
                    # Draw each part separately for vertical layout
                    y_offset = 0
                    total_height = 0
                    part_widths = []
                    part_baselines = []

                    # Calculate total height first
                    part_heights = []
                    for part in full_text_parts:
                        pass
                        try:
                            bbox = measure_draw.textbbox((0, 0), part, font=font, **bbox_stroke_kwargs)
                            part_height = bbox[3] - bbox[1]
                            part_width = bbox[2] - bbox[0]
                            baseline = base_ascent
                        except Exception:
                            # Fallback if font measurement fails
                            part_width = font_size * len(part) * 0.6 + stroke_width * 2
                            part_height = font_size + stroke_width * 2
                            baseline = base_ascent
                        part_heights.append(part_height)
                        part_widths.append(part_width)
                        part_baselines.append(baseline)
                        total_height += part_height
                    
                    # Add spacing between parts
                    if len(full_text_parts) > 1:
                        pass
                        spacing = font_size // 4  # 25% of font size as line spacing
                        total_height += spacing * (len(full_text_parts) - 1)
                    
                    # Center vertically
                    start_y = (height - total_height) // 2
                    max_width = max(part_widths) if part_widths else 0
                    left_x = (width - max_width) // 2
                    base_text_rect = {
                        'left': float(left_x),
                        'right': float(left_x + max_width),
                        'top': float(start_y),
                        'bottom': float(start_y + total_height),
                        'center_x': float(left_x + max_width / 2.0),
                        'center_y': float(start_y + total_height / 2.0),
                        'baseline': float(start_y + base_ascent),
                        'descent': float(base_descent),
                        'draw_y': float(start_y),
                        'ascent': float(base_ascent)
                    }
                    # Pre-initialize effect layers and drawing contexts
                    from PIL import ImageFilter
                    text_color = tuple(int(c * 255) for c in props.text_color[:4])
                    has_shadow = getattr(props, 'enable_shadow', False)
                    has_glow = getattr(props, 'enable_glow', False)
                    has_blur = getattr(props, 'enable_blur', False)
                    
                    shadow_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_shadow else None
                    glow_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_glow else None
                    text_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_blur else None
                    
                    s_draw = ImageDraw.Draw(shadow_layer) if has_shadow else None
                    g_draw = ImageDraw.Draw(glow_layer) if has_glow else None
                    
                    try:
                        s_dx = float(getattr(props, 'shadow_offset_x', 8.0)) if has_shadow else 0.0
                        s_dy = float(getattr(props, 'shadow_offset_y', 8.0)) if has_shadow else 0.0
                    except (ValueError, TypeError):
                        s_dx, s_dy = 0.0, 0.0
                        
                    pil_img = _apply_component_layers(pil_img, 'before', base_text_rect, final_font_size, s_draw=s_draw, g_draw=g_draw, s_dx=s_dx, s_dy=s_dy, stroke_kwargs=stroke_kwargs)
                    before_layers_applied = True
                    
                    main_draw = ImageDraw.Draw(pil_img) if not has_blur else ImageDraw.Draw(text_layer)
                    s_color = tuple(int(c * 255) for c in getattr(props, 'shadow_color', (0,0,0,1))[:4]) if has_shadow else None
                    g_color = tuple(int(c * 255) for c in getattr(props, 'glow_color', (1,1,1,0.8))[:4]) if has_glow else None
                    
                    current_y = start_y
                    for i, part in enumerate(full_text_parts):
                        part_width = part_widths[i]
                        x = (width - part_width) // 2  # Center horizontally
                        
                        if has_shadow:
                            s_draw.text((x + s_dx, current_y + s_dy), part, font=font, fill=s_color, **stroke_kwargs)
                        if has_glow:
                            g_draw.text((x, current_y), part, font=font, fill=g_color, **stroke_kwargs)
                            
                        main_draw.text((x, current_y), part, font=font, fill=text_color, **stroke_kwargs)
                        
                        current_y += part_heights[i]
                        if i < len(full_text_parts) - 1:  # Add spacing except after last part
                            current_y += font_size // 4
                            
                    # Apply effects and composite
                    if has_shadow:
                        try:
                            s_blur = float(getattr(props, 'shadow_blur', 6.0))
                        except (ValueError, TypeError):
                            s_blur = 0.0
                        if s_blur > 0:
                            shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(radius=s_blur))
                        pil_img = Image.alpha_composite(pil_img, shadow_layer)
                        
                    if has_glow:
                        try:
                            g_blur = float(getattr(props, 'glow_blur', 10.0))
                        except (ValueError, TypeError):
                            g_blur = 0.0
                        if g_blur > 0:
                            glow_layer = glow_layer.filter(ImageFilter.GaussianBlur(radius=g_blur))
                        pil_img = Image.alpha_composite(pil_img, glow_layer)
                        
                    if has_blur:
                        try:
                            t_blur = float(getattr(props, 'blur_radius', 2.0))
                        except (ValueError, TypeError):
                            t_blur = 0.0
                        if t_blur > 0:
                            text_layer = text_layer.filter(ImageFilter.GaussianBlur(radius=t_blur))
                        pil_img = Image.alpha_composite(pil_img, text_layer)
                    
                else:
                    pass
                    # Horizontal layout or single text
                    left_offset = 0
                    right_offset = 0
                    top_offset = 0
                    bottom_offset = 0

                    try:
                        text_bbox = measure_draw.textbbox((0, 0), full_text, font=font, **bbox_stroke_kwargs)
                        text_width = text_bbox[2] - text_bbox[0]
                        text_height = text_bbox[3] - text_bbox[1]
                        left_offset = text_bbox[0]
                        right_offset = text_bbox[2]
                        top_offset = text_bbox[1]
                        bottom_offset = text_bbox[3]
                    except Exception:
                        # Fallback for bitmap fonts that don't support textbbox
                        text_width = font_size * len(full_text) * 0.6 + stroke_width * 2
                        text_height = font_size + stroke_width * 2
                        right_offset = text_width
                        bottom_offset = text_height
                    
                    # Calculate total widths for PREPEND and APPEND components
                    # to properly center the entire logical sequence.
                    total_prepend_width = 0.0
                    total_append_width = 0.0
                    
                    if hasattr(props, "image_overlays"):
                        for component in props.image_overlays:
                            if component.enabled and getattr(component, 'placement_mode', 'ABSOLUTE') in ['PREPEND', 'APPEND']:
                                c_type = getattr(component, 'component_type', 'IMAGE')
                                c_spacing = float(getattr(component, 'text_spacing', 0.0) or 0.0) / 100.0 * final_font_size
                                c_inter_spacing = float(getattr(component, 'image_spacing', 0.0) or 0.0) / 100.0 * final_font_size
                                
                                c_width = 0
                                if c_type == 'TEXT' and component.text_content.strip():
                                    # Use the same helper to get the final (possibly cropped/stroked) width
                                    temp_img, _, _, meta = _make_text_overlay(component, final_font_size, stroke_kwargs)
                                    if meta:
                                        c_width = meta.get('width', 0)
                                elif c_type == 'IMAGE':
                                    resolved_path = getattr(component, 'image_path', '')
                                    if resolved_path:
                                        try:
                                            with Image.open(resolved_path) as raw:
                                                c_width = raw.width * float(getattr(component, 'scale', 1.0) or 1.0)
                                        except Exception:
                                            pass
                                
                                if getattr(component, 'placement_mode', 'ABSOLUTE') == 'PREPEND':
                                    total_prepend_width += (c_width + c_spacing + c_inter_spacing)
                                else:
                                    total_append_width += (c_width + c_spacing + c_inter_spacing)

                    # Center the entire logical block
                    total_logical_width = total_prepend_width + text_width + total_append_width
                    logical_start_x = (width - total_logical_width) // 2
                    
                    x = logical_start_x + int(total_prepend_width)
                    y = (height - text_height) // 2
                    
                    left = x + left_offset
                    right = x + right_offset
                    top = y + top_offset
                    bottom = y + bottom_offset
                    base_text_rect = {
                        'left': float(left),
                        'right': float(right),
                        'top': float(top),
                        'bottom': float(bottom),
                        'center_x': float((left + right) / 2.0),
                        'center_y': float((top + bottom) / 2.0),
                        'baseline': float(y + base_ascent),
                        'descent': float(base_descent),
                        'draw_y': float(y),
                        'ascent': float(base_ascent)
                    }
                    # Pre-initialize effect layers and drawing contexts
                    from PIL import ImageFilter
                    text_color = tuple(int(c * 255) for c in props.text_color[:4])
                    has_shadow = getattr(props, 'enable_shadow', False)
                    has_glow = getattr(props, 'enable_glow', False)
                    has_blur = getattr(props, 'enable_blur', False)
                    
                    shadow_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_shadow else None
                    glow_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_glow else None
                    text_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0)) if has_blur else None
                    
                    s_draw = ImageDraw.Draw(shadow_layer) if has_shadow else None
                    g_draw = ImageDraw.Draw(glow_layer) if has_glow else None
                    
                    try:
                        s_dx = float(getattr(props, 'shadow_offset_x', 8.0)) if has_shadow else 0.0
                        s_dy = float(getattr(props, 'shadow_offset_y', 8.0)) if has_shadow else 0.0
                    except (ValueError, TypeError):
                        s_dx, s_dy = 0.0, 0.0
                        
                    pil_img = _apply_component_layers(pil_img, 'before', base_text_rect, final_font_size, s_draw=s_draw, g_draw=g_draw, s_dx=s_dx, s_dy=s_dy, stroke_kwargs=stroke_kwargs)
                    before_layers_applied = True
                    
                    main_draw = ImageDraw.Draw(pil_img) if not has_blur else ImageDraw.Draw(text_layer)
                    s_color = tuple(int(c * 255) for c in getattr(props, 'shadow_color', (0,0,0,1))[:4]) if has_shadow else None
                    g_color = tuple(int(c * 255) for c in getattr(props, 'glow_color', (1,1,1,0.8))[:4]) if has_glow else None
                    
                    if has_shadow:
                        s_draw.text((x + s_dx, y + s_dy), full_text, font=font, fill=s_color, **stroke_kwargs)
                        
                    if has_glow:
                        g_draw.text((x, y), full_text, font=font, fill=g_color, **stroke_kwargs)
                        
                    main_draw.text((x, y), full_text, font=font, fill=text_color, **stroke_kwargs)
                    
                    # Apply effects and composite
                    if has_shadow:
                        try:
                            s_blur = float(getattr(props, 'shadow_blur', 6.0))
                        except (ValueError, TypeError):
                            s_blur = 0.0
                        if s_blur > 0:
                            shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(radius=s_blur))
                        pil_img = Image.alpha_composite(pil_img, shadow_layer)
                        
                    if has_glow:
                        try:
                            g_blur = float(getattr(props, 'glow_blur', 10.0))
                        except (ValueError, TypeError):
                            g_blur = 0.0
                        if g_blur > 0:
                            glow_layer = glow_layer.filter(ImageFilter.GaussianBlur(radius=g_blur))
                        pil_img = Image.alpha_composite(pil_img, glow_layer)
                        
                    if has_blur:
                        try:
                            t_blur = float(getattr(props, 'blur_radius', 2.0))
                        except (ValueError, TypeError):
                            t_blur = 0.0
                        if t_blur > 0:
                            text_layer = text_layer.filter(ImageFilter.GaussianBlur(radius=t_blur))
                        pil_img = Image.alpha_composite(pil_img, text_layer)
            else:
                pass

            if not before_layers_applied:
                pil_img = _apply_component_layers(pil_img, 'before', base_text_rect, final_font_size, s_draw=s_draw, g_draw=g_draw, s_dx=s_dx, s_dy=s_dy, stroke_kwargs=stroke_kwargs)
            pil_img = _apply_component_layers(pil_img, 'after', base_text_rect, final_font_size, s_draw=s_draw, g_draw=g_draw, s_dx=s_dx, s_dy=s_dy, stroke_kwargs=stroke_kwargs)
            
            # Convert PIL image to Blender format (flip vertically to fix mirroring)
            pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM)  # Fix mirroring issue
            raw_bytes = pil_img.tobytes()
            normalized = array('f', (channel / 255.0 for channel in raw_bytes))
            pixels_attr = getattr(blender_img, "pixels", None)
            if hasattr(pixels_attr, "foreach_set"):
                pixels_attr.foreach_set(normalized)
            else:
                blender_img.pixels[:] = normalized.tolist()
            return blender_img, None
        except ImportError as e:
            pass
            # PIL not available - should not happen with proper Blender manifest dependencies
            print(f"ERROR: PIL dependency not available despite manifest declaration: {e}")
            print(f"ERROR: Check if Blender properly installed addon dependencies from manifest")
            # Create a visible yellow error pattern to indicate dependency issue
            error_pattern = [1.0, 1.0, 0.0, 1.0]  # Yellow color to indicate dependency problem
            total_pixels = width * height
            blender_img.pixels[:] = error_pattern * total_pixels
            return blender_img, None
            
    except Exception as e:
        pass
        print(f"ERROR: Texture generation failed: {e}")
        import traceback
        traceback.print_exc()
        return None, None

def generate_preview(props, preview_width=None, preview_height=None):
    pass
    """
    Generate a preview version of the texture at lower resolution.
    
    Args:
        props: Text texture properties object
        preview_width: Preview image width. Defaults to the texture width if available.
        preview_height: Preview image height. Defaults to the texture height if available.
        
    Returns:
        Blender image object or None on error
    """
    try:
        pass
        # Use full texture resolution when not explicitly provided
        if not preview_width or preview_width <= 0:
            try:
                preview_width = int(getattr(props, 'texture_width', 512))
            except (ValueError, TypeError):
                preview_width = 512
        if not preview_height or preview_height <= 0:
            try:
                preview_height = int(getattr(props, 'texture_height', 512))
            except (ValueError, TypeError):
                preview_height = 512
        
        # Generate at preview resolution
        result = generate_texture_image(props, preview_width, preview_height)
        if result and result[0]:
            pass
            preview_img = result[0]
            return preview_img
        else:
            pass
            return None
            
    except Exception as e:
        pass
        import traceback
        traceback.print_exc()
        return None
