import uuid
import dataclasses
import re
import os
import logging
from pathlib import Path
from typing import Optional, Tuple, Dict

import cv2
import numpy as np

from .base_stage import ProcessingStage
from ..asset_context import AssetProcessingContext
from rule_structure import FileRule
from utils.path_utils import sanitize_filename
from ...utils import image_processing_utils as ipu

logger = logging.getLogger(__name__)

class IndividualMapProcessingStage(ProcessingStage):
    """
    Processes individual texture map files based on FileRules.
    This stage finds the source file, loads it, applies transformations
    (resize, color space), saves a temporary processed version, and updates
    the AssetProcessingContext with details.
    """

    def execute(self, context: AssetProcessingContext) -> AssetProcessingContext:
        """
        Executes the individual map processing logic.
        """
        asset_name_for_log = context.asset_rule.asset_name if context.asset_rule else "Unknown Asset"
        if context.status_flags.get('skip_asset', False):
            logger.info(f"Asset '{asset_name_for_log}': Skipping individual map processing due to skip_asset flag.")
            return context

        if not hasattr(context, 'processed_maps_details') or context.processed_maps_details is None:
            context.processed_maps_details = {}
            logger.debug(f"Asset '{asset_name_for_log}': Initialized processed_maps_details.")

        if not context.files_to_process:
            logger.info(f"Asset '{asset_name_for_log}': No files to process in this stage.")
            return context
            
        # Source path for the asset group comes from SourceRule
        if not context.source_rule or not context.source_rule.input_path:
            logger.error(f"Asset '{asset_name_for_log}': SourceRule or SourceRule.input_path is not set. Cannot determine source base path.")
            context.status_flags['individual_map_processing_failed'] = True
            # Mark all file_rules as failed
            for fr_idx, file_rule_to_fail in enumerate(context.files_to_process):
                temp_id_for_fail = f"fr_fail_{fr_idx}" # Temporary ID for status update
                map_type_for_fail = file_rule_to_fail.item_type_override or file_rule_to_fail.item_type or "UnknownMapType"
                self._update_file_rule_status(context, temp_id_for_fail, 'Failed', map_type=map_type_for_fail, details="SourceRule.input_path missing")
            return context
        
        # The workspace_path in the context should be the directory where files are extracted/available.
        source_base_path = context.workspace_path
        if not source_base_path.is_dir():
            logger.error(f"Asset '{asset_name_for_log}': Workspace path '{source_base_path}' is not a valid directory.")
            context.status_flags['individual_map_processing_failed'] = True
            for fr_idx, file_rule_to_fail in enumerate(context.files_to_process):
                temp_id_for_fail = f"fr_fail_{fr_idx}" # Use a temporary unique ID for this status update
                map_type_for_fail = file_rule_to_fail.item_type_override or file_rule_to_fail.item_type or "UnknownMapType"
                self._update_file_rule_status(context, temp_id_for_fail, 'Failed', map_type=map_type_for_fail, details="Workspace path invalid")
            return context

        # Fetch config settings once before the loop
        respect_variant_map_types = getattr(context.config_obj, "respect_variant_map_types", [])
        image_resolutions = getattr(context.config_obj, "image_resolutions", {})
        output_filename_pattern = getattr(context.config_obj, "output_filename_pattern", "[assetname]_[maptype]_[resolution].[ext]")

        for file_rule_idx, file_rule in enumerate(context.files_to_process):
            # Generate a unique ID for this file_rule processing instance for processed_maps_details
            current_map_id_hex = f"map_{file_rule_idx}_{uuid.uuid4().hex[:8]}"
            
            initial_current_map_type = file_rule.item_type_override or file_rule.item_type or "UnknownMapType"

            # --- START NEW SUFFIXING LOGIC ---
            final_current_map_type = initial_current_map_type # Default to initial
            
            # 1. Determine Base Map Type from initial_current_map_type
            base_map_type_match = re.match(r"(MAP_[A-Z]{3})", initial_current_map_type)
            
            if base_map_type_match and context.asset_rule:
                true_base_map_type = base_map_type_match.group(1) # This is "MAP_XXX"

                # 2. Count Occurrences and Find Index of current_file_rule in context.asset_rule.files
                peers_of_same_base_type_in_asset_rule = []
                for fr_asset in context.asset_rule.files:
                    fr_asset_item_type = fr_asset.item_type_override or fr_asset.item_type or "UnknownMapType"
                    fr_asset_base_map_type_match = re.match(r"(MAP_[A-Z]{3})", fr_asset_item_type)
                    
                    if fr_asset_base_map_type_match:
                        fr_asset_base_map_type = fr_asset_base_map_type_match.group(1)
                        if fr_asset_base_map_type == true_base_map_type:
                            peers_of_same_base_type_in_asset_rule.append(fr_asset)
                
                num_occurrences_of_base_type = len(peers_of_same_base_type_in_asset_rule)
                current_instance_index = 0 # 1-based

                try:
                    current_instance_index = peers_of_same_base_type_in_asset_rule.index(file_rule) + 1
                except ValueError:
                    logger.warning(
                        f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (Initial Type: '{initial_current_map_type}', Base: '{true_base_map_type}'): "
                        f"Could not find its own instance in the list of peers from asset_rule.files. "
                        f"Number of peers found: {num_occurrences_of_base_type}. Suffixing may be affected."
                    )

                # 3. Determine Suffix
                map_type_for_respect_check = true_base_map_type.replace("MAP_", "") # e.g., "COL"
                is_in_respect_list = map_type_for_respect_check in respect_variant_map_types
                
                suffix_to_append = ""
                if num_occurrences_of_base_type > 1:
                    if current_instance_index > 0:
                        suffix_to_append = f"-{current_instance_index}"
                    else:
                        logger.warning(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}': Index for multi-occurrence map type '{true_base_map_type}' (count: {num_occurrences_of_base_type}) not determined. Omitting numeric suffix.")
                elif num_occurrences_of_base_type == 1 and is_in_respect_list:
                    suffix_to_append = "-1"
                
                # 4. Form the final_current_map_type
                if suffix_to_append:
                    final_current_map_type = true_base_map_type + suffix_to_append
                else:
                    final_current_map_type = initial_current_map_type
            
            current_map_type = final_current_map_type
            # --- END NEW SUFFIXING LOGIC ---

            # --- START: Filename-friendly map type derivation ---
            logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: --- Starting Filename-Friendly Map Type Logic for: {current_map_type} ---")
            filename_friendly_map_type = current_map_type  # Fallback

            # 1. Access FILE_TYPE_DEFINITIONS
            file_type_definitions = None
            logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Attempting to access context.config_obj.FILE_TYPE_DEFINITIONS.")
            try:
                file_type_definitions = context.config_obj.FILE_TYPE_DEFINITIONS
                if not file_type_definitions: # Check if it's None or empty
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: FILE_TYPE_DEFINITIONS is present but empty or None.")
                else:
                    sample_defs_log = {k: file_type_definitions[k] for k in list(file_type_definitions.keys())[:2]} # Log first 2 for brevity
                    logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Accessed FILE_TYPE_DEFINITIONS. Sample: {sample_defs_log}, Total keys: {len(file_type_definitions)}.")
            except AttributeError:
                logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Could not access context.config_obj.FILE_TYPE_DEFINITIONS via direct attribute.")
            
            base_map_key = None
            suffix_part = ""

            if file_type_definitions and isinstance(file_type_definitions, dict) and len(file_type_definitions) > 0:
                base_map_key = None
                suffix_part = ""
                
                sorted_known_base_keys = sorted(list(file_type_definitions.keys()), key=len, reverse=True)
                logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Sorted known base keys for parsing: {sorted_known_base_keys}")

                for known_key in sorted_known_base_keys:
                    logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Checking if '{current_map_type}' starts with '{known_key}'")
                    if current_map_type.startswith(known_key):
                        base_map_key = known_key
                        suffix_part = current_map_type[len(known_key):]
                        logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Match found! current_map_type: '{current_map_type}', base_map_key: '{base_map_key}', suffix_part: '{suffix_part}'")
                        break

                if base_map_key is None:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Could not parse base_map_key from '{current_map_type}' using known keys. Fallback: filename_friendly_map_type = '{filename_friendly_map_type}'.")
                else:
                    definition = file_type_definitions.get(base_map_key)
                    logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Definition for '{base_map_key}': {definition}")
                    if definition and isinstance(definition, dict):
                        standard_type_alias = definition.get("standard_type")
                        logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Standard type alias for '{base_map_key}': '{standard_type_alias}'")
                        if standard_type_alias and isinstance(standard_type_alias, str) and standard_type_alias.strip():
                            filename_friendly_map_type = standard_type_alias.strip() + suffix_part
                            logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Successfully transformed map type: '{current_map_type}' -> '{filename_friendly_map_type}' (standard_type_alias: '{standard_type_alias}', suffix_part: '{suffix_part}').")
                        else:
                            logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Standard type alias for '{base_map_key}' is missing, empty, or not a string (value: '{standard_type_alias}'). Using fallback. filename_friendly_map_type = '{filename_friendly_map_type}'.")
                    else:
                        logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: No definition or invalid definition for '{base_map_key}' (value: {definition}). Using fallback. filename_friendly_map_type = '{filename_friendly_map_type}'.")
            elif file_type_definitions is None:
                 logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: FILE_TYPE_DEFINITIONS not available for lookup (was None). Using fallback. filename_friendly_map_type = '{filename_friendly_map_type}'.")
            elif not isinstance(file_type_definitions, dict):
                logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: FILE_TYPE_DEFINITIONS is not a dictionary (type: {type(file_type_definitions)}). Using fallback. filename_friendly_map_type = '{filename_friendly_map_type}'.")
            else:
                logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: FILE_TYPE_DEFINITIONS is an empty dictionary. Using fallback. filename_friendly_map_type = '{filename_friendly_map_type}'.")
            
            logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Final filename_friendly_map_type: '{filename_friendly_map_type}'")
            # --- END: Filename-friendly map type derivation ---

            if not current_map_type or not current_map_type.startswith("MAP_") or current_map_type == "MAP_GEN_COMPOSITE":
                logger.debug(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}': Skipping, item_type '{current_map_type}' (initial: '{initial_current_map_type}') not targeted for individual processing.")
                continue

            logger.info(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (Type: {current_map_type}, Initial Type: {initial_current_map_type}, ID: {current_map_id_hex}): Starting individual processing.")

            # A. Find Source File (using file_rule.file_path as the pattern relative to source_base_path)
            # The _find_source_file might need adjustment if file_rule.file_path is absolute or needs complex globbing.
            # For now, assume file_rule.file_path is a relative pattern or exact name.
            source_file_path = self._find_source_file(source_base_path, file_rule.file_path, asset_name_for_log, current_map_id_hex)
            if not source_file_path:
                logger.error(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Source file not found with path/pattern '{file_rule.file_path}' in '{source_base_path}'.")
                self._update_file_rule_status(context, current_map_id_hex, 'Failed', map_type=filename_friendly_map_type, details="Source file not found")
                continue

            # B. Load and Transform Image
            image_data: Optional[np.ndarray] = ipu.load_image(str(source_file_path))
            if image_data is None:
                logger.error(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Failed to load image from '{source_file_path}'.")
                self._update_file_rule_status(context, current_map_id_hex, 'Failed', map_type=filename_friendly_map_type, source_file=str(source_file_path), details="Image load failed")
                continue
            
            original_height, original_width = image_data.shape[:2]
            logger.debug(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Loaded image '{source_file_path}' with dimensions {original_width}x{original_height}.")

            # 1. Initial Power-of-Two (POT) Downscaling
            pot_width = ipu.get_nearest_power_of_two_downscale(original_width)
            pot_height = ipu.get_nearest_power_of_two_downscale(original_height)

            # Maintain aspect ratio for initial POT scaling, using the smaller of the scaled dimensions
            # This ensures we only downscale.
            if original_width > 0 and original_height > 0 : # Avoid division by zero
                aspect_ratio = original_width / original_height
                
                # Calculate new dimensions based on POT width, then POT height, and pick the one that results in downscale or same size
                pot_h_from_w = int(pot_width / aspect_ratio)
                pot_w_from_h = int(pot_height * aspect_ratio)

                # Option 1: Scale by width, adjust height
                candidate1_w, candidate1_h = pot_width, ipu.get_nearest_power_of_two_downscale(pot_h_from_w)
                # Option 2: Scale by height, adjust width
                candidate2_w, candidate2_h = ipu.get_nearest_power_of_two_downscale(pot_w_from_h), pot_height
                
                # Ensure candidates are not upscaling
                if candidate1_w > original_width or candidate1_h > original_height:
                    candidate1_w, candidate1_h = original_width, original_height # Fallback to original if upscaling
                if candidate2_w > original_width or candidate2_h > original_height:
                    candidate2_w, candidate2_h = original_width, original_height # Fallback to original if upscaling
                
                # Choose the candidate that results in a larger area (preferring less downscaling if multiple POT options)
                # but still respects the POT downscale logic for each dimension individually.
                # The actual POT dimensions are already calculated by get_nearest_power_of_two_downscale.
                # We need to decide if we base the aspect ratio calc on pot_width or pot_height.
                # The goal is to make one dimension POT and the other POT while maintaining aspect as much as possible, only downscaling.

                final_pot_width = ipu.get_nearest_power_of_two_downscale(original_width)
                final_pot_height = ipu.get_nearest_power_of_two_downscale(original_height)

                # If original aspect is not 1:1, one of the POT dimensions might need further adjustment to maintain aspect
                # after the other dimension is set to its POT.
                # We prioritize fitting within the *downscaled* POT dimensions.
                
                # Scale to fit within final_pot_width, adjust height, then make height POT (downscale)
                scaled_h_for_pot_w = max(1, round(final_pot_width / aspect_ratio))
                h1 = ipu.get_nearest_power_of_two_downscale(scaled_h_for_pot_w)
                w1 = final_pot_width
                if h1 > final_pot_height: # If this adjustment made height too big, re-evaluate
                    h1 = final_pot_height
                    w1 = ipu.get_nearest_power_of_two_downscale(max(1, round(h1 * aspect_ratio)))


                # Scale to fit within final_pot_height, adjust width, then make width POT (downscale)
                scaled_w_for_pot_h = max(1, round(final_pot_height * aspect_ratio))
                w2 = ipu.get_nearest_power_of_two_downscale(scaled_w_for_pot_h)
                h2 = final_pot_height
                if w2 > final_pot_width: # If this adjustment made width too big, re-evaluate
                    w2 = final_pot_width
                    h2 = ipu.get_nearest_power_of_two_downscale(max(1, round(w2 / aspect_ratio)))
                
                # Choose the option that results in larger area (less aggressive downscaling)
                # while ensuring both dimensions are POT and not upscaled from original.
                if w1 * h1 >= w2 * h2:
                    base_pot_width, base_pot_height = w1, h1
                else:
                    base_pot_width, base_pot_height = w2, h2
                
                # Final check to ensure no upscaling from original dimensions
                base_pot_width = min(base_pot_width, original_width)
                base_pot_height = min(base_pot_height, original_height)
                # And ensure they are POT
                base_pot_width = ipu.get_nearest_power_of_two_downscale(base_pot_width)
                base_pot_height = ipu.get_nearest_power_of_two_downscale(base_pot_height)

            else: # Handle cases like 0-dim images, though load_image should prevent this
                base_pot_width, base_pot_height = 1, 1


            logger.info(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Original dims: ({original_width},{original_height}), Initial POT Scaled Dims: ({base_pot_width},{base_pot_height}).")

            # Calculate and store aspect ratio change string
            if original_width > 0 and original_height > 0 and base_pot_width > 0 and base_pot_height > 0:
                aspect_change_str = ipu.normalize_aspect_ratio_change(
                    original_width, original_height,
                    base_pot_width, base_pot_height
                )
                if aspect_change_str:
                    # This will overwrite if multiple maps are processed; specified by requirements.
                    context.asset_metadata['aspect_ratio_change_string'] = aspect_change_str
                    logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type {current_map_type}: Calculated aspect ratio change string: '{aspect_change_str}' (Original: {original_width}x{original_height}, Base POT: {base_pot_width}x{base_pot_height}). Stored in asset_metadata.")
                else:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type {current_map_type}: Failed to calculate aspect ratio change string.")
            else:
                logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type {current_map_type}: Skipping aspect ratio change string calculation due to invalid dimensions (Original: {original_width}x{original_height}, Base POT: {base_pot_width}x{base_pot_height}).")

            base_pot_image_data = image_data.copy()
            if (base_pot_width, base_pot_height) != (original_width, original_height):
                interpolation = cv2.INTER_AREA # Good for downscaling
                base_pot_image_data = ipu.resize_image(base_pot_image_data, base_pot_width, base_pot_height, interpolation=interpolation)
                if base_pot_image_data is None:
                    logger.error(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Failed to resize image to base POT dimensions.")
                    self._update_file_rule_status(context, current_map_id_hex, 'Failed', map_type=filename_friendly_map_type, source_file=str(source_file_path), original_dimensions=(original_width, original_height), details="Base POT resize failed")
                    continue
            
            # Color Profile Management (after initial POT resize, before multi-res saving)
            # Initialize transform settings with defaults for color management
            transform_settings = {
                "color_profile_management": False, # Default, can be overridden by FileRule
                "target_color_profile": "sRGB",    # Default
                "output_format_settings": None     # For JPG quality, PNG compression
            }
            if file_rule.channel_merge_instructions and 'transform' in file_rule.channel_merge_instructions:
                custom_transform_settings = file_rule.channel_merge_instructions['transform']
                if isinstance(custom_transform_settings, dict):
                    transform_settings.update(custom_transform_settings)
                    logger.info(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Loaded transform settings for color/output from file_rule.")
            
            if transform_settings['color_profile_management'] and transform_settings['target_color_profile'] == "RGB":
                if len(base_pot_image_data.shape) == 3 and base_pot_image_data.shape[2] == 3: # BGR to RGB
                    logger.info(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Converting BGR to RGB for base POT image.")
                    base_pot_image_data = ipu.convert_bgr_to_rgb(base_pot_image_data)
                elif len(base_pot_image_data.shape) == 3 and base_pot_image_data.shape[2] == 4: # BGRA to RGBA
                     logger.info(f"Asset '{asset_name_for_log}', FileRule path '{file_rule.file_path}' (ID: {current_map_id_hex}): Converting BGRA to RGBA for base POT image.")
                     base_pot_image_data = ipu.convert_bgra_to_rgba(base_pot_image_data)
            
            # Ensure engine_temp_dir exists before saving base POT
            if not context.engine_temp_dir.exists():
                try:
                    context.engine_temp_dir.mkdir(parents=True, exist_ok=True)
                    logger.info(f"Asset '{asset_name_for_log}': Created engine_temp_dir at '{context.engine_temp_dir}'")
                except OSError as e:
                    logger.error(f"Asset '{asset_name_for_log}': Failed to create engine_temp_dir '{context.engine_temp_dir}': {e}")
                    self._update_file_rule_status(context, current_map_id_hex, 'Failed', map_type=filename_friendly_map_type, source_file=str(source_file_path), details="Failed to create temp directory for base POT")
                    continue
            
            temp_filename_suffix = Path(source_file_path).suffix
            base_pot_temp_filename = f"{current_map_id_hex}_basePOT{temp_filename_suffix}"
            base_pot_temp_path = context.engine_temp_dir / base_pot_temp_filename

            # Determine save parameters for base POT image (can be different from variants if needed)
            base_save_params = []
            base_output_ext = temp_filename_suffix.lstrip('.') # Default to original, can be overridden by format rules
            # TODO: Add logic here to determine base_output_ext and base_save_params based on bit depth and config, similar to variants.
            # For now, using simple save.

            if not ipu.save_image(str(base_pot_temp_path), base_pot_image_data, params=base_save_params):
                logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Failed to save base POT image to '{base_pot_temp_path}'.")
                self._update_file_rule_status(context, current_map_id_hex, 'Failed', map_type=filename_friendly_map_type, source_file=str(source_file_path), original_dimensions=(original_width, original_height), base_pot_dimensions=(base_pot_width, base_pot_height), details="Base POT image save failed")
                continue
            
            logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Successfully saved base POT image to '{base_pot_temp_path}' with dims ({base_pot_width}x{base_pot_height}).")

            # Initialize/update the status for this map in processed_maps_details
            self._update_file_rule_status(
                context,
                current_map_id_hex,
                'BasePOTSaved', # Intermediate status, will be updated after variant check
                map_type=filename_friendly_map_type,
                source_file=str(source_file_path),
                original_dimensions=(original_width, original_height),
                base_pot_dimensions=(base_pot_width, base_pot_height),
                temp_processed_file=str(base_pot_temp_path) # Store path to the saved base POT
            )

            # 2. Multiple Resolution Output (Variants)
            processed_at_least_one_resolution_variant = False
            # Resolution variants are attempted for all map types individually processed.
            # The filter at the beginning of the loop (around line 72) ensures only relevant maps reach this stage.
            generate_variants_for_this_map_type = True

            if generate_variants_for_this_map_type: # This will now always be true if code execution reaches here
                logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Map type '{current_map_type}' is eligible for individual processing. Attempting to generate resolution variants.")
                # Sort resolutions from largest to smallest
                sorted_resolutions = sorted(image_resolutions.items(), key=lambda item: item[1], reverse=True)
                logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Sorted resolutions for variant processing: {sorted_resolutions}")

                for res_key, res_max_dim in sorted_resolutions:
                    current_w, current_h = base_pot_image_data.shape[1], base_pot_image_data.shape[0]
                    
                    if current_w <= 0 or current_h <=0:
                        logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Base POT image has zero dimension ({current_w}x{current_h}). Skipping this resolution variant.")
                        continue

                    if max(current_w, current_h) >= res_max_dim:
                        target_w_res, target_h_res = current_w, current_h
                        if max(current_w, current_h) > res_max_dim:
                            if current_w >= current_h:
                                target_w_res = res_max_dim
                                target_h_res = max(1, round(target_w_res / (current_w / current_h)))
                            else:
                                target_h_res = res_max_dim
                                target_w_res = max(1, round(target_h_res * (current_w / current_h)))
                    else:
                        logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Base POT image ({current_w}x{current_h}) is smaller than target max dim {res_max_dim}. Skipping this resolution variant.")
                        continue

                    target_w_res = min(target_w_res, current_w)
                    target_h_res = min(target_h_res, current_h)
                    
                    if target_w_res <=0 or target_h_res <=0:
                        logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Calculated target variant dims are zero or negative ({target_w_res}x{target_h_res}). Skipping.")
                        continue

                    logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Processing variant for {res_max_dim}. Base POT Dims: ({current_w}x{current_h}), Target Dims for {res_key}: ({target_w_res}x{target_h_res}).")

                    output_image_data_for_res = base_pot_image_data
                    if (target_w_res, target_h_res) != (current_w, current_h):
                        interpolation_res = cv2.INTER_AREA
                        output_image_data_for_res = ipu.resize_image(base_pot_image_data, target_w_res, target_h_res, interpolation=interpolation_res)
                        if output_image_data_for_res is None:
                            logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Failed to resize image for resolution variant {res_key}.")
                            continue
                    
                    assetname_placeholder = context.asset_rule.asset_name if context.asset_rule else "UnknownAsset"
                    resolution_placeholder = res_key
                    
                    # TODO: Implement proper output format/extension determination for variants
                    output_ext_variant = temp_filename_suffix.lstrip('.')

                    temp_output_filename_variant = output_filename_pattern.replace("[assetname]", sanitize_filename(assetname_placeholder)) \
                                                                  .replace("[maptype]", sanitize_filename(filename_friendly_map_type)) \
                                                                  .replace("[resolution]", sanitize_filename(resolution_placeholder)) \
                                                                  .replace("[ext]", output_ext_variant)
                    temp_output_filename_variant = f"{current_map_id_hex}_variant_{temp_output_filename_variant}" # Distinguish variant temp files
                    temp_output_path_variant = context.engine_temp_dir / temp_output_filename_variant

                    save_params_variant = []
                    if transform_settings.get('output_format_settings'):
                        if output_ext_variant.lower() in ['jpg', 'jpeg']:
                            quality = transform_settings['output_format_settings'].get('quality', context.config_obj.get("JPG_QUALITY", 95))
                            save_params_variant = [cv2.IMWRITE_JPEG_QUALITY, quality]
                        elif output_ext_variant.lower() == 'png':
                            compression = transform_settings['output_format_settings'].get('compression_level', context.config_obj.get("PNG_COMPRESSION_LEVEL", 6))
                            save_params_variant = [cv2.IMWRITE_PNG_COMPRESSION, compression]
                    
                    save_success_variant = ipu.save_image(str(temp_output_path_variant), output_image_data_for_res, params=save_params_variant)

                    if not save_success_variant:
                        logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Failed to save temporary variant image to '{temp_output_path_variant}'.")
                        continue
                    
                    logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Res {res_key}: Successfully saved temporary variant map to '{temp_output_path_variant}' with dims ({target_w_res}x{target_h_res}).")
                    processed_at_least_one_resolution_variant = True

                    if 'variants' not in context.processed_maps_details[current_map_id_hex]:
                        context.processed_maps_details[current_map_id_hex]['variants'] = []
                    
                    context.processed_maps_details[current_map_id_hex]['variants'].append({
                        'resolution_key': res_key,
                        'temp_path': str(temp_output_path_variant), # Changed 'path' to 'temp_path'
                        'dimensions': (target_w_res, target_h_res),
                        'resolution_name': f"{target_w_res}x{target_h_res}" # Retain for potential use
                    })
                    
                    if 'processed_files' not in context.asset_metadata:
                        context.asset_metadata['processed_files'] = []
                    context.asset_metadata['processed_files'].append({
                        'processed_map_key': current_map_id_hex,
                        'resolution_key': res_key,
                        'path': str(temp_output_path_variant),
                        'type': 'temporary_map_variant',
                        'map_type': current_map_type,
                        'dimensions_w': target_w_res,
                        'dimensions_h': target_h_res
                    })
            # Calculate and store image statistics for the lowest resolution output
            lowest_res_image_data_for_stats = None
            image_to_stat_path_for_log = "N/A"
            source_of_stats_image = "unknown"

            if processed_at_least_one_resolution_variant and \
               current_map_id_hex in context.processed_maps_details and \
               'variants' in context.processed_maps_details[current_map_id_hex] and \
               context.processed_maps_details[current_map_id_hex]['variants']:
                
                variants_list = context.processed_maps_details[current_map_id_hex]['variants']
                valid_variants_for_stats = [
                    v for v in variants_list
                    if isinstance(v.get('dimensions'), tuple) and len(v['dimensions']) == 2 and v['dimensions'][0] > 0 and v['dimensions'][1] > 0
                ]

                if valid_variants_for_stats:
                    smallest_variant = min(valid_variants_for_stats, key=lambda v: v['dimensions'][0] * v['dimensions'][1])
                    
                    if smallest_variant and 'temp_path' in smallest_variant and smallest_variant.get('dimensions'):
                        smallest_res_w, smallest_res_h = smallest_variant['dimensions']
                        logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Identified smallest variant for stats: {smallest_variant.get('resolution_key', 'N/A')} ({smallest_res_w}x{smallest_res_h}) at {smallest_variant['temp_path']}")
                        lowest_res_image_data_for_stats = ipu.load_image(smallest_variant['temp_path'])
                        image_to_stat_path_for_log = smallest_variant['temp_path']
                        source_of_stats_image = f"variant {smallest_variant.get('resolution_key', 'N/A')}"
                        if lowest_res_image_data_for_stats is None:
                            logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Failed to load smallest variant image '{smallest_variant['temp_path']}' for stats.")
                    else:
                        logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Could not determine smallest variant for stats from valid variants list (details missing).")
                else:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: No valid variants found to determine the smallest one for stats.")

            if lowest_res_image_data_for_stats is None:
                if base_pot_image_data is not None:
                    logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Using base POT image for stats (dimensions: {base_pot_width}x{base_pot_height}). Smallest variant not available/loaded or no variants generated.")
                    lowest_res_image_data_for_stats = base_pot_image_data
                    image_to_stat_path_for_log = f"In-memory base POT image (dims: {base_pot_width}x{base_pot_height})"
                    source_of_stats_image = "base POT"
                else:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Base POT image data is also None. Cannot calculate stats.")

            if lowest_res_image_data_for_stats is not None:
                stats_dict = ipu.calculate_image_stats(lowest_res_image_data_for_stats)
                if stats_dict and "error" not in stats_dict:
                    if 'image_stats_lowest_res' not in context.asset_metadata:
                        context.asset_metadata['image_stats_lowest_res'] = {}
                    
                    context.asset_metadata['image_stats_lowest_res'][current_map_type] = stats_dict
                    logger.info(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type '{current_map_type}': Calculated and stored image stats from '{source_of_stats_image}' (source ref: '{image_to_stat_path_for_log}').")
                elif stats_dict and "error" in stats_dict:
                    logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type '{current_map_type}': Error calculating image stats from '{source_of_stats_image}': {stats_dict['error']}.")
                else:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type '{current_map_type}': Failed to calculate image stats from '{source_of_stats_image}' (result was None or empty).")
            else:
                logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}, Map Type '{current_map_type}': No image data available (from variant or base POT) to calculate stats.")

            # Final status update based on whether variants were generated (and expected)
            if generate_variants_for_this_map_type:
                if processed_at_least_one_resolution_variant:
                    self._update_file_rule_status(context, current_map_id_hex, 'Processed_With_Variants', map_type=filename_friendly_map_type, details="Successfully processed with multiple resolution variants.")
                else:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Variants were expected for map type '{current_map_type}', but none were generated (e.g., base POT too small for any variant tier).")
                    self._update_file_rule_status(context, current_map_id_hex, 'Processed_No_Variants', map_type=filename_friendly_map_type, details="Variants expected but none generated (e.g., base POT too small).")
            else: # No variants were expected for this map type
                self._update_file_rule_status(context, current_map_id_hex, 'Processed_No_Variants', map_type=filename_friendly_map_type, details="Processed to base POT; variants not applicable for this map type.")

        logger.info(f"Asset '{asset_name_for_log}': Finished individual map processing stage.")
        return context

    def _find_source_file(self, base_path: Path, pattern: str, asset_name_for_log: str, current_map_id_hex: str) -> Optional[Path]: # asset_id -> asset_name_for_log, file_rule_id_hex -> current_map_id_hex
        """
        Finds a single source file matching the pattern within the base_path.
        """
        if not pattern: # pattern is now file_rule.file_path
            logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Empty file_path provided in FileRule.")
            return None
        
        # If pattern is an absolute path, use it directly
        potential_abs_path = Path(pattern)
        if potential_abs_path.is_absolute() and potential_abs_path.exists():
            logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: file_path '{pattern}' is absolute and exists. Using it directly.")
            return potential_abs_path
        elif potential_abs_path.is_absolute():
            logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: file_path '{pattern}' is absolute but does not exist.")
            # Fall through to try resolving against base_path if it's just a name/relative pattern

        # Treat pattern as relative to base_path
        # This could be an exact name or a glob pattern
        try:
            # First, check if pattern is an exact relative path
            exact_match_path = base_path / pattern
            if exact_match_path.exists() and exact_match_path.is_file():
                logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Found exact match for '{pattern}' at '{exact_match_path}'.")
                return exact_match_path

            # If not an exact match, try as a glob pattern (recursive)
            matched_files_rglob = list(base_path.rglob(pattern))
            if matched_files_rglob:
                if len(matched_files_rglob) > 1:
                    logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Multiple files ({len(matched_files_rglob)}) found for pattern '{pattern}' in '{base_path}' (recursive). Using first: {matched_files_rglob[0]}. Files: {matched_files_rglob}")
                return matched_files_rglob[0]

            # Try non-recursive glob if rglob fails
            matched_files_glob = list(base_path.glob(pattern))
            if matched_files_glob:
                if len(matched_files_glob) > 1:
                     logger.warning(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Multiple files ({len(matched_files_glob)}) found for pattern '{pattern}' in '{base_path}' (non-recursive). Using first: {matched_files_glob[0]}. Files: {matched_files_glob}")
                return matched_files_glob[0]

            logger.debug(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: No files found matching pattern '{pattern}' in '{base_path}' (exact, recursive, or non-recursive).")
            return None
        except Exception as e:
            logger.error(f"Asset '{asset_name_for_log}', Map ID {current_map_id_hex}: Error searching for file with pattern '{pattern}' in '{base_path}': {e}")
            return None

    def _update_file_rule_status(self, context: AssetProcessingContext, map_id_hex: str, status: str, **kwargs): # file_rule_id_hex -> map_id_hex
        """Helper to update processed_maps_details for a map."""
        asset_name_for_log = context.asset_rule.asset_name if context.asset_rule else "Unknown Asset"
        if map_id_hex not in context.processed_maps_details:
            context.processed_maps_details[map_id_hex] = {}
        
        context.processed_maps_details[map_id_hex]['status'] = status
        for key, value in kwargs.items():
            context.processed_maps_details[map_id_hex][key] = value
        
        if 'map_type' not in context.processed_maps_details[map_id_hex] and 'map_type' in kwargs:
             context.processed_maps_details[map_id_hex]['map_type'] = kwargs['map_type']

        # Add formatted resolution names
        if 'original_dimensions' in kwargs and isinstance(kwargs['original_dimensions'], tuple) and len(kwargs['original_dimensions']) == 2:
            orig_w, orig_h = kwargs['original_dimensions']
            context.processed_maps_details[map_id_hex]['original_resolution_name'] = f"{orig_w}x{orig_h}"

        # Determine the correct dimensions to use for 'processed_resolution_name'
        # This name refers to the base POT scaled image dimensions before variant generation.
        dims_to_log_as_base_processed = None
        if 'base_pot_dimensions' in kwargs and isinstance(kwargs['base_pot_dimensions'], tuple) and len(kwargs['base_pot_dimensions']) == 2:
            # This key is used when status is 'Processed_With_Variants'
            dims_to_log_as_base_processed = kwargs['base_pot_dimensions']
        elif 'processed_dimensions' in kwargs and isinstance(kwargs['processed_dimensions'], tuple) and len(kwargs['processed_dimensions']) == 2:
            # This key is used when status is 'Processed_No_Variants' (and potentially others)
            dims_to_log_as_base_processed = kwargs['processed_dimensions']

        if dims_to_log_as_base_processed:
            proc_w, proc_h = dims_to_log_as_base_processed
            resolution_name_str = f"{proc_w}x{proc_h}"
            context.processed_maps_details[map_id_hex]['base_pot_resolution_name'] = resolution_name_str
            # Ensure 'processed_resolution_name' is also set for OutputOrganizationStage compatibility
            context.processed_maps_details[map_id_hex]['processed_resolution_name'] = resolution_name_str
        elif 'processed_dimensions' in kwargs or 'base_pot_dimensions' in kwargs:
            details_for_warning = kwargs.get('processed_dimensions', kwargs.get('base_pot_dimensions'))
            logger.warning(f"Asset '{asset_name_for_log}', Map ID {map_id_hex}: 'processed_dimensions' or 'base_pot_dimensions' key present but its value is not a valid 2-element tuple: {details_for_warning}")
        
        # If temp_processed_file was passed, ensure it's in the details
        if 'temp_processed_file' in kwargs:
            context.processed_maps_details[map_id_hex]['temp_processed_file'] = kwargs['temp_processed_file']


        # Log all details being stored for clarity, including the newly added resolution names
        log_details = context.processed_maps_details[map_id_hex].copy()
        # Avoid logging full image data if it accidentally gets into kwargs
        if 'image_data' in log_details: del log_details['image_data']
        if 'base_pot_image_data' in log_details: del log_details['base_pot_image_data']
        logger.debug(f"Asset '{asset_name_for_log}', Map ID {map_id_hex}: Status updated to '{status}'. Details: {log_details}")