"""Condition 3: SCAFFOLD_PARTIAL — 3-lens MCP subset (dose-response).

Runs the dusun pipeline with a forced subset of lenses (Ontoloji, FOL, Bayes)
by calling internal functions directly with a hardcoded fire-plan.
The LLM then produces a multi-lens analysis guided by the 3-lens scaffolding.
"""

from __future__ import annotations

import json
from typing import Any, Dict, List

from fw_server.dusun import (
    _auto_classify,
    _build_ax57,
    _build_translation_hints,
    _detect_patterns,
    _detect_vocabulary,
    _execute_fire_plan,
)
from fw_server.grade_map import score_to_grade, grade_to_string
from fw_server.session import reset_ledger

from .base import ConditionOutput, ConditionRunner

# Fixed 3-lens subset — chosen for maximum diversity:
#   Ontoloji  (entities/categories — structural)
#   FOL       (formal logic — deductive)
#   Bayes     (probabilistic — inductive)
PARTIAL_LENSES: List[str] = ["Ontoloji", "FOL", "Bayes"]

PARTIAL_SYNTHESIS_PROMPT = """\
You are an expert analyst. A structured analytical framework has
pre-analyzed the text below using a subset of 3 out of 7 lenses and
produced the findings shown under ANALYTICAL SCAFFOLDING. Use these
computed findings as a guide for your analysis.

=== ANALYTICAL SCAFFOLDING (3-lens subset: {active_lenses}) ===
Lens scores: {lens_scores_fmt}
Patterns detected: {patterns_fmt}
=== END SCAFFOLDING ===

Analyze the following text through these 7 analytical perspectives.
For each perspective, provide substantive analysis (not just mention).
The 3 computed lenses above have scaffolding to guide you; for the
remaining 4, reason independently.

1. **Ontological Analysis**: Identify the core entities, their categories,
   hierarchical relations, and constitutive dependencies.

2. **Mereological Analysis**: Identify whole-part structures, teleological
   relations between parts, and how parts relate to the whole.

3. **Formal Logic (FOL)**: Identify premises, conclusions, logical structure.
   Formalize key arguments. Check for validity and soundness.

4. **Bayesian Analysis**: Identify hypotheses, evidence, prior probabilities.
   Show how evidence updates beliefs. Quantify where possible.

5. **Game-Theoretic Analysis**: Identify agents/players, strategies, payoffs,
   equilibria. What incentive structures are at play?

6. **Category-Theoretic Analysis**: Identify objects, morphisms, functors.
   What structural transformations preserve essential properties?

7. **Topological/Holographic Analysis**: Identify dimensional structure,
   continuity properties, seed-whole relationships, fractal patterns.

After analyzing each lens, synthesize: How do the lenses connect?
What does the combination reveal that no single lens shows alone?

Text:
{text}
"""


class ScaffoldPartialRunner(ConditionRunner):
    """Condition 3 — constrained 3-lens MCP subset."""

    def __init__(self, llm_client=None):
        self.llm_client = llm_client

    def condition_id(self) -> str:
        return "SCAFFOLD_PARTIAL"

    async def run(self, text: str, text_id: str) -> ConditionOutput:
        reset_ledger()
        result = _run_partial(text, PARTIAL_LENSES)

        narrative = await self._synthesize_narrative(text, result)

        return ConditionOutput(
            condition_id="SCAFFOLD_PARTIAL",
            text_id=text_id,
            raw_output=narrative,
            metadata={
                "active_lenses": PARTIAL_LENSES,
                "lens_scores": result.get("lens_scores"),
                "composite_score": result.get("composite_score"),
                "structured_result": result,
            },
        )

    async def _synthesize_narrative(self, text: str, result: dict) -> str:
        """Produce analysis of TEXT, scaffolded by 3-lens findings."""
        if self.llm_client is None:
            return json.dumps(result, indent=2, ensure_ascii=False)

        lens_scores = result.get("lens_scores", {})
        lens_scores_fmt = ", ".join(
            f"{k}: {v:.2f}" for k, v in lens_scores.items()
        )
        patterns = result.get("patterns_applied", [])
        patterns_fmt = ", ".join(patterns) if patterns else "(none detected)"

        prompt = PARTIAL_SYNTHESIS_PROMPT.format(
            text=text,
            active_lenses=", ".join(PARTIAL_LENSES),
            lens_scores_fmt=lens_scores_fmt,
            patterns_fmt=patterns_fmt,
        )
        return await self.llm_client.generate(prompt)


def _run_partial(text: str, lenses: List[str]) -> Dict[str, Any]:
    """Run dusun pipeline with a forced subset of lenses.

    Steps:
    1. _detect_vocabulary(text)      — runs normally
    2. _auto_classify(text, vocab)   — runs normally
    3. Skip _build_fire_plan         — use *lenses* arg instead
    4. _execute_fire_plan(lenses, …) — only fires the 3 chosen lenses
    5. Remaining steps               — run normally with partial results
    """
    vocab = _detect_vocabulary(text)
    path = _auto_classify(text, vocab)
    result = _execute_fire_plan(lenses, text, vocab, hints=None)
    patterns = _detect_patterns(path, vocab, result.get("lens_scores"))
    translations = _build_translation_hints(path, patterns)
    grade_enum = score_to_grade(result.get("composite_score", 0))
    grade = grade_to_string(grade_enum)
    kavaid_ids = list((result.get("kavaid") or {}).keys())
    ax57 = _build_ax57(path, patterns, kavaid_ids, grade, result.get("composite_score", 0))

    result.update(
        {
            "path": path,
            "vocabulary_detected": vocab,
            "fire_plan": lenses,
            "patterns_applied": patterns,
            "translation_hints": translations,
            "ax57_compact": ax57,
        }
    )
    return result
