"""
FieldCompiler — Compiles natural language prompts into FieldDefinitions.

Ties together DomainAnalyzer (LLM) + WorldModelTranslator (math) to produce
a FieldDefinition with a signed influence matrix.

Usage:
    compiler = FieldCompiler()
    result = await compiler.compile("Optimize coffee shop staffing")
    # result has: field_definition, world_model, validation
"""

import json
import numpy as np
from typing import Optional, Callable

from ..core.field_definition import FieldDefinition
from .domain_analyzer import DomainAnalyzer
from .world_model_translator import WorldModelTranslator


class FieldCompiler:
    """
    Main entry point for compiling prompts into FieldDefinitions.

    Pipeline:
      1. DomainAnalyzer extracts world model from LLM
      2. WorldModelTranslator builds signed influence matrix
      3. FieldDefinition constructed with concepts + matrix
    """

    def __init__(self, **options):
        self.analyzer = options.get("analyzer") or DomainAnalyzer(**options)
        self.translator = options.get("translator") or WorldModelTranslator()

    async def compile(
        self,
        prompt: str,
        options: Optional[dict] = None,
        on_progress: Optional[Callable] = None,
    ) -> dict:
        """
        Compile a natural language prompt into a FieldDefinition.

        Returns:
            {
                "field": FieldDefinition instance,
                "world_model": dict (full LLM output),
                "validation": dict with errors/warnings,
                "influence_matrix": np.ndarray (N×N),
            }
        """
        options = options or {}
        report = on_progress or (lambda *a: None)

        # 1. Domain analysis via LLM
        report("analyzing", "Extracting domain model...")
        world_model = await self.analyzer.analyze(prompt, options, on_progress)

        # 2. Build influence matrix from causal relationships
        report("building_matrix", "Computing influence matrix...")
        influence_matrix = self.translator.build_influence_matrix(
            world_model["concepts"],
            world_model["relationships"],
        )

        # 3. Build FieldDefinition
        field = self._build_field(world_model, influence_matrix)

        # 4. Validation enrichment
        validation = world_model.get("validation", {"valid": True, "errors": [], "warnings": []})

        # Check for trade-offs in matrix
        has_negative = np.any(influence_matrix < 0)
        if not has_negative:
            validation.setdefault("warnings", []).append(
                "No trade-off relationships in influence matrix — all values positive."
            )

        return {
            "field": field,
            "world_model": world_model,
            "validation": validation,
            "influence_matrix": influence_matrix,
        }

    def compile_from_world_model(self, world_model: dict) -> dict:
        """
        Build a FieldDefinition from an existing world model (JSON interchange).
        No LLM calls — pure math. Used when receiving world models from the JS engine.

        Args:
            world_model: dict with concepts, relationships, attractors, etc.

        Returns:
            Same structure as compile() but synchronous.
        """
        influence_matrix = self.translator.build_influence_matrix(
            world_model["concepts"],
            world_model["relationships"],
        )
        field = self._build_field(world_model, influence_matrix)
        validation = world_model.get("validation", {"valid": True, "errors": [], "warnings": []})

        return {
            "field": field,
            "world_model": world_model,
            "validation": validation,
            "influence_matrix": influence_matrix,
        }

    def _build_field(self, world_model: dict, influence_matrix: np.ndarray) -> FieldDefinition:
        """Build a FieldDefinition from a world model + influence matrix."""
        problem = world_model.get("problem", {})
        field = FieldDefinition(
            metadata={
                "problemStatement": problem.get("statement", ""),
                "domain": world_model.get("domain", "general"),
                "objectives": problem.get("objectives", []),
                "mode": "worldmodel",
            }
        )

        for concept in world_model["concepts"]:
            dd = concept.get("dataDictionary", {})
            dim_id = field.add_dimension(
                name=concept["name"],
                description=concept.get("description", ""),
                category=concept.get("category", "general"),
                display_range=dd.get("displayRange") or dd.get("validRange"),
                display_unit=dd.get("displayUnit") or dd.get("unitOfMeasure", ""),
            )
            field.promote_dimension(dim_id)

        if influence_matrix.size > 0:
            field.update_similarity_matrix(influence_matrix)

        # Store world model metadata
        field.metadata["worldModel"] = {
            "tradeoffs": world_model.get("tradeoffs", []),
            "referencePoints": world_model.get("referencePoints", []),
            "attractors": world_model.get("attractors", []),
            "confidence": world_model.get("confidence", {}),
            "relationshipCount": len(world_model.get("relationships", [])),
        }

        return field
