# SPDX-FileCopyrightText: 2025 - 2026 Rahil Miten Doshi, Friedrich-Alexander-Universität Erlangen-Nürnberg
# SPDX-FileCopyrightText: 2026 Matthias Markl, Friedrich-Alexander-Universität Erlangen-Nürnberg
# SPDX-License-Identifier: BSD-3-Clause

import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union

import sympy as sp
from materforge.core.materials import Material
from materforge.parsing.validation.property_type_detector import PropertyType
from materforge.parsing.processors.property_processor_base import PropertyProcessorBase
from materforge.parsing.processors.property_handlers import (
    ConstantValuePropertyHandler,
    StepFunctionPropertyHandler,
    FileImportPropertyHandler,
    TabularDataPropertyHandler,
    PiecewiseEquationPropertyHandler,
    ComputedPropertyHandler,
)
from materforge.parsing.processors.post_processor import PropertyPostProcessor

logger = logging.getLogger(__name__)


class PropertyProcessor(PropertyProcessorBase):
    """Orchestrates property processing by delegating to specialised handlers.

    Properties are processed in PropertyType enum definition order, which
    guarantees CONSTANT_VALUE is resolved before any type that may reference
    those scalars (STEP_FUNCTION, TABULAR_DATA, etc.).
    """

    def __init__(self) -> None:
        super().__init__()
        self.handlers = {
            PropertyType.CONSTANT_VALUE: ConstantValuePropertyHandler(),
            PropertyType.STEP_FUNCTION: StepFunctionPropertyHandler(),
            PropertyType.FILE_IMPORT: FileImportPropertyHandler(),
            PropertyType.TABULAR_DATA: TabularDataPropertyHandler(),
            PropertyType.PIECEWISE_EQUATION: PiecewiseEquationPropertyHandler(),
            PropertyType.COMPUTED_PROPERTY: ComputedPropertyHandler(),
        }
        self.post_processor = PropertyPostProcessor()
        self.properties: Optional[Dict[str, Any]] = None
        self.categorized_properties: Optional[Dict[PropertyType, List[Tuple[str, Any]]]] = None
        self.base_dir: Optional[Path] = None
        logger.debug("PropertyProcessor initialised with %d handlers", len(self.handlers))

    # --- Public API ---
    def process_properties(self, material: Material,
                           dependency: Union[float, sp.Symbol],
                           properties: Dict[str, Any],
                           categorized_properties: Dict[PropertyType, List[Tuple[str, Any]]],
                           base_dir: Path, visualizer) -> None:
        """Processes all properties for the material.

        Args:
            material:               Target Material instance.
            dependency:             SymPy symbol (symbolic mode) or float (numeric mode).
            properties:             Raw properties dict from the YAML config.
            categorized_properties: Properties pre-grouped by PropertyType.
            base_dir:               Base directory for resolving relative file paths.
            visualizer:             PropertyVisualizer instance, or None.
        """
        logger.info("Starting property processing for '%s'", material.name)
        self._initialize_processing_context(material, properties, categorized_properties, base_dir, visualizer)
        try:
            self._process_by_category(material, dependency)
            logger.info("Starting post-processing for '%s'", material.name)
            self.post_processor.post_process_properties(
                material, dependency, self.properties, self.categorized_properties, self.processed_properties)
            logger.info("Finished processing all properties for '%s'", material.name)
        except Exception as e:
            # Intermediate layer - do not log here, bubble up to api.py
            raise ValueError(f"Failed to process properties\n -> {str(e)}") from e

    # --- Private helpers ---
    def _initialize_processing_context(self, material: Material,
                                       properties: Dict[str, Any],
                                       categorized_properties: Dict[PropertyType, List[Tuple[str, Any]]],
                                       base_dir: Path, visualizer) -> None:
        """Sets up shared state and injects context into all handlers."""
        logger.debug("Initialising processing context for '%s'", material.name)
        self.properties = properties
        self.categorized_properties = categorized_properties
        self.base_dir = base_dir
        self.visualizer = visualizer
        self.processed_properties = set()
        for handler_type, handler in self.handlers.items():
            handler.set_processing_context(self.base_dir, visualizer, self.processed_properties)
            logger.debug("Context set for handler: %s", handler_type.name)
        computed_handler = self.handlers.get(PropertyType.COMPUTED_PROPERTY)
        if computed_handler:
            computed_handler.set_computed_property_processor(properties)

    def _process_by_category(self, material: Material,
                              dependency: Union[float, sp.Symbol]) -> None:
        """Iterates PropertyType enum order and processes each category.

        CONSTANT_VALUE is the first enum value, so all scalar constants are
        assigned to the material before any other type runs - enabling safe
        forward references from STEP_FUNCTION, TABULAR_DATA, etc.
        """
        total = sum(len(v) for v in self.categorized_properties.values())
        active = sum(1 for v in self.categorized_properties.values() if v)
        logger.info("Processing %d properties across %d categories", total, active)
        for prop_type, prop_list in self.categorized_properties.items():
            if not prop_list:
                continue
            handler = self.handlers.get(prop_type)
            if handler is None:
                raise ValueError(f"No handler for property type: {prop_type.name}")
            logger.info("Processing %d %s properties", len(prop_list), prop_type.name)
            for prop_name, config in prop_list:
                logger.debug("Processing '%s'", prop_name)
                handler.process_property(material, prop_name, config, dependency)
                # No try/except here - errors propagate cleanly to process_properties
            logger.info("Completed %s", prop_type.name)
