"""Condition 6: LLM_SCAFFOLD — LLM-powered first pass + synthesis.

Tests Hypothesis 3 (Regex Ceiling): Does replacing dusun's
regex/heuristic engine with an LLM first pass rescue the
two-step architecture?

The first pass uses the SAME 7-lens ontology and produces
structured output (lens scores, patterns, guidance) but via
LLM semantic understanding instead of regex pattern matching.
The second pass synthesizes using these findings.

Design choices:
 - Same 7 lenses as dusun_tool() → direct comparison.
 - JSON-only first pass (no prose) → minimizes narrative anchoring.
 - English lens names, no framework jargon → avoids TWOSTEP v1 failure.
 - Temperature 0.0 (via JudgeLLM default) → deterministic.
 - Deliberately weaker authority signal ("preliminary assessment" not
   "computed by structured framework") → tests deference hypothesis.
"""

from __future__ import annotations

import json
import logging
import re
from typing import Any, Dict

from .base import ConditionOutput, ConditionRunner

logger = logging.getLogger(__name__)

# ── Step 1 prompt: semantic assessment ────────────────────────────

LLM_FIRST_PASS_PROMPT = """\
You are an analytical pre-processor. Your task is to quickly assess the
following text and produce a structured analytical brief for a second
analyst.

For each of these 7 analytical lenses, rate its relevance to the text
on a 0.00-1.00 scale (0 = not relevant, 1 = highly relevant):

1. Ontological (being, existence, entity hierarchies, categories)
2. Mereological (parts-wholes, composition, teleological structure)
3. Formal Logic (premises, validity, soundness, logical structure)
4. Bayesian (evidence, priors, posteriors, hypothesis updating)
5. Game-Theoretic (agents, strategies, payoffs, equilibria)
6. Category-Theoretic (morphisms, functors, structural mappings)
7. Topological/Holographic (continuity, dimensions, fractal patterns)

Also identify:
- The analytical domain (choose one: foundational principles, \
inferential reasoning, applied concepts, or integrative synthesis)
- Top 3 structural patterns you detect in the text (e.g., "part-whole \
hierarchies", "evidence chains", "recursive self-reference")
- Any notable features that deserve special analytical attention

Respond ONLY with this exact JSON (no markdown, no explanation):
{{
    "domain": "...",
    "lens_scores": {{
        "Ontological": 0.00,
        "Mereological": 0.00,
        "Formal Logic": 0.00,
        "Bayesian": 0.00,
        "Game-Theoretic": 0.00,
        "Category-Theoretic": 0.00,
        "Topological/Holographic": 0.00
    }},
    "top_lenses": ["...", "..."],
    "patterns": [
        {{"pattern": "...", "significance": "..."}},
        {{"pattern": "...", "significance": "..."}},
        {{"pattern": "...", "significance": "..."}}
    ],
    "focus_guidance": "..."
}}

Text:
{text}
"""

# ── Step 2 prompt: synthesis with assessment ──────────────────────

LLM_SCAFFOLD_SYNTHESIS_PROMPT = """\
You are an expert analyst. A preliminary assessment of the text below
has identified the most relevant analytical dimensions and structural
patterns. Use these findings to focus your analysis — prioritize
lenses with higher relevance scores and investigate the detected
patterns concretely within the text.

=== PRELIMINARY ASSESSMENT ===
Analytical domain: {domain}
Lens relevance: {lens_scores_fmt}
Top lenses to prioritize: {top_lenses}

Detected patterns:
{patterns_fmt}

Focus guidance: {focus_guidance}
=== END ASSESSMENT ===

Analyze the following text through these 7 analytical perspectives.
For each perspective, provide substantive analysis (not just mention).
Pay special attention to lenses with higher relevance scores and the
detected patterns above.

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}
"""

# ── Fallback assessment when first pass fails ─────────────────────

_EMPTY_ASSESSMENT: Dict[str, Any] = {
    "domain": "diverse analytical perspectives",
    "lens_scores": {},
    "top_lenses": [],
    "patterns": [],
    "focus_guidance": "",
}


def _parse_first_pass(raw: str) -> Dict[str, Any]:
    """Parse LLM first-pass JSON, with fallback for common formats."""
    # Try direct parse
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        pass

    # Try extracting from markdown code block
    match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw, re.DOTALL)
    if match:
        try:
            return json.loads(match.group(1))
        except json.JSONDecodeError:
            pass

    # Try finding the first { ... } block
    match = re.search(r"\{.*\}", raw, re.DOTALL)
    if match:
        try:
            return json.loads(match.group(0))
        except json.JSONDecodeError:
            pass

    return dict(_EMPTY_ASSESSMENT)


def _format_first_pass(result: Dict[str, Any]) -> Dict[str, str]:
    """Format first-pass assessment for injection into synthesis prompt."""
    lens_scores = result.get("lens_scores", {})
    lens_scores_fmt = ", ".join(
        f"{k}: {v:.2f}" for k, v in
        sorted(lens_scores.items(), key=lambda x: x[1], reverse=True)
    ) if lens_scores else "(no scores)"

    patterns = result.get("patterns", [])
    if patterns:
        lines = []
        for p in patterns[:5]:
            if isinstance(p, dict):
                lines.append(
                    f"  - {p.get('pattern', '?')}: "
                    f"{p.get('significance', '')}"
                )
            else:
                lines.append(f"  - {p}")
        patterns_fmt = "\n".join(lines)
    else:
        patterns_fmt = "  (none detected)"

    top_lenses = result.get("top_lenses", [])
    top_lenses_str = ", ".join(top_lenses) if top_lenses else "(none)"

    return {
        "domain": result.get("domain", "diverse analytical perspectives"),
        "lens_scores_fmt": lens_scores_fmt,
        "top_lenses": top_lenses_str,
        "patterns_fmt": patterns_fmt,
        "focus_guidance": result.get("focus_guidance", ""),
    }


class LLMScaffoldRunner(ConditionRunner):
    """Condition 6 — LLM-powered first pass replaces regex engine.

    Two LLM calls per text:
      1. Semantic assessment → structured JSON (lens scores, patterns)
      2. Synthesis using assessment → full 7-lens analysis narrative
    """

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

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

    async def run(self, text: str, text_id: str) -> ConditionOutput:
        # Step 1: LLM first pass — semantic assessment
        first_pass_prompt = LLM_FIRST_PASS_PROMPT.format(text=text)
        raw_assessment = await self.llm_client.generate(first_pass_prompt)

        assessment = _parse_first_pass(raw_assessment)
        if assessment == _EMPTY_ASSESSMENT:
            logger.warning(
                "LLM_SCAFFOLD first pass returned unparseable output for %s, "
                "using empty assessment fallback",
                text_id,
            )

        # Step 2: Synthesis using assessment
        fmt = _format_first_pass(assessment)
        synthesis_prompt = LLM_SCAFFOLD_SYNTHESIS_PROMPT.format(
            text=text, **fmt,
        )
        narrative = await self.llm_client.generate(synthesis_prompt)

        return ConditionOutput(
            condition_id="LLM_SCAFFOLD",
            text_id=text_id,
            raw_output=narrative,
            metadata={
                "first_pass_raw": raw_assessment,
                "first_pass_assessment": assessment,
                "first_pass_domain": assessment.get("domain"),
                "first_pass_lens_scores": assessment.get("lens_scores"),
                "first_pass_patterns": assessment.get("patterns"),
                "first_pass_top_lenses": assessment.get("top_lenses"),
                "prompt_template": "LLM_SCAFFOLD_SYNTHESIS_PROMPT",
            },
        )
