"""MCP tool definitions — thin handlers that delegate to use cases."""

from __future__ import annotations

from src.application.composite_figure import CompositeFigureRequest
from src.application.edit_figure import EditFigureRequest
from src.application.evaluate_figure import EvaluateFigureRequest
from src.application.generate_figure import GenerateFigureRequest
from src.application.list_manifests import ListManifestsRequest
from src.application.multi_turn_edit import MultiTurnEditRequest
from src.application.plan_figure import PlanFigureRequest
from src.application.replay_manifest import ReplayManifestRequest
from src.application.retarget_journal import RetargetJournalRequest
from src.application.verify_figure import VerifyFigureRequest
from src.domain.exceptions import ConfigurationError, DomainError, ValidationError
from src.presentation.dependencies import Container
from src.presentation.server import mcp
from src.presentation.validation import (
    normalize_expected_labels,
    normalize_feedback,
    normalize_figure_type,
    normalize_image_path,
    normalize_instructions,
    normalize_language,
    normalize_list_limit,
    normalize_manifest_id,
    normalize_optional_pmid,
    normalize_output_dir,
    normalize_output_size,
    normalize_planned_payload,
    normalize_pmid,
    normalize_pmids,
    normalize_style_preset,
    normalize_target_journal,
)


def _error_payload(error: Exception, **extra: object) -> dict[str, object]:
    payload: dict[str, object] = {"status": "error", "error": str(error)}
    payload.update(extra)
    return payload


@mcp.tool()
def plan_figure(
    pmid: str,
    figure_type: str = "auto",
    style_preset: str = "journal_default",
    language: str = "zh-TW",
    output_size: str = "1024x1536",
    target_journal: str | None = None,
    expected_labels: list[str] | None = None,
) -> dict[str, object]:
    """Plan the best figure type, route, and guardrails before generation.

    This tool returns a structured plan so an MCP host can decide whether to use
    direct image generation, SVG-style rendering, or deterministic chart routes.

    expected_labels: Optional list of exact text labels (especially CJK) the
    figure must contain. Enables CJK text fidelity guardrails and model escalation.
    """
    try:
        request = PlanFigureRequest(
            pmid=normalize_pmid(pmid),
            figure_type=normalize_figure_type(figure_type),
            style_preset=normalize_style_preset(style_preset),
            language=normalize_language(language),
            output_size=normalize_output_size(output_size),
            target_journal=normalize_target_journal(target_journal),
            expected_labels=normalize_expected_labels(expected_labels),
        )
        uc = Container.get().plan_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc, pmid=pmid)


@mcp.tool()
def generate_figure(
    pmid: str | None = None,
    planned_payload: dict[str, object] | None = None,
    figure_type: str = "auto",
    language: str = "zh-TW",
    output_size: str = "1024x1536",
    output_dir: str | None = None,
    target_journal: str | None = None,
) -> dict[str, object]:
    """Generate a publication-ready visual asset.

    Preferred path: provide a generic planned_payload produced by planning or
    another harness layer. For backwards compatibility, callers may still pass
    a PMID directly and let the use case build the prompt internally.

    figure_type: auto | flowchart | mechanism | comparison |
                 infographic | anatomical | timeline | data_visualization
    """
    try:
        if pmid is None and planned_payload is None:
            raise ValidationError("Either pmid or planned_payload is required")
        if pmid is not None and planned_payload is not None:
            raise ValidationError("Provide either pmid or planned_payload, not both")

        request = GenerateFigureRequest(
            pmid=normalize_pmid(pmid) if pmid is not None else None,
            planned_payload=(
                normalize_planned_payload(planned_payload) if planned_payload is not None else None
            ),
            figure_type=normalize_figure_type(figure_type),
            language=normalize_language(language),
            output_size=normalize_output_size(output_size),
            output_dir=normalize_output_dir(output_dir),
            target_journal=normalize_target_journal(target_journal),
        )
        uc = Container.get().generate_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc, pmid=pmid)


@mcp.tool()
def edit_figure(
    image_path: str,
    feedback: str,
    output_path: str | None = None,
) -> dict[str, object]:
    """Refine an academic figure using natural language feedback.

    Examples: "箭頭改紅色", "標題字大一點", "Add PMID in footer"
    """
    try:
        request = EditFigureRequest(
            image_path=normalize_image_path(image_path),
            feedback=normalize_feedback(feedback),
            output_path=(
                normalize_image_path(output_path, field_name="output_path")
                if output_path
                else None
            ),
        )
        uc = Container.get().edit_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)


@mcp.tool()
def evaluate_figure(
    image_path: str,
    figure_type: str = "infographic",
    reference_pmid: str | None = None,
) -> dict[str, object]:
    """Evaluate an academic figure using the 8-domain quality checklist.

    Domains: text accuracy, anatomy, color, layout,
             scientific accuracy, legibility, visual polish, citation.
    """
    try:
        request = EvaluateFigureRequest(
            image_path=normalize_image_path(image_path),
            figure_type=normalize_figure_type(figure_type, allow_auto=False),
            reference_pmid=normalize_optional_pmid(reference_pmid, field_name="reference_pmid"),
        )
        uc = Container.get().evaluate_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)


@mcp.tool()
def batch_generate(
    pmids: list[str],
    figure_type: str = "auto",
    language: str = "zh-TW",
    output_size: str = "1024x1536",
    output_dir: str | None = None,
) -> dict[str, object]:
    """Generate academic figures for multiple PMIDs in sequence.

    Batch mode validates the full PMID list up front and propagates language,
    output size, and output directory into every generation request.
    """
    try:
        normalized_pmids = normalize_pmids(pmids)
        normalized_figure_type = normalize_figure_type(figure_type)
        normalized_language = normalize_language(language)
        normalized_output_size = normalize_output_size(output_size)
        normalized_output_dir = normalize_output_dir(output_dir)
        uc = Container.get().batch_generate_uc()
        return uc.execute(
            pmids=normalized_pmids,
            figure_type=normalized_figure_type,
            language=normalized_language,
            output_size=normalized_output_size,
            output_dir=normalized_output_dir,
        )
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)


@mcp.tool()
def composite_figure(
    panels: list[list[str]],
    labels: list[str],
    title: str,
    caption: str = "",
    citation: str = "",
    output_path: str | None = None,
) -> dict[str, object]:
    """Composite multiple panel images into a publication-ready figure."""
    try:
        if not panels:
            raise ValidationError("panels must contain at least one item")
        if len(panels) != len(labels):
            raise ValidationError("panels and labels must be the same length")

        normalized_panels: list[dict[str, str]] = []
        for index, panel in enumerate(panels):
            if len(panel) != 2:
                raise ValidationError(f"panels[{index}] must contain [image_path, panel_type]")

            image_path = normalize_image_path(str(panel[0]), field_name=f"panels[{index}][0]")
            panel_type = str(panel[1]).strip() or "anatomy"
            label = labels[index].strip()
            if not label:
                raise ValidationError(f"labels[{index}] cannot be blank")

            normalized_panels.append(
                {
                    "prompt": "composite panel",
                    "label": label,
                    "panel_type": panel_type,
                    "image_path": image_path,
                }
            )

        normalized_output_path = (
            normalize_image_path(output_path, field_name="output_path") if output_path else None
        )
        request = CompositeFigureRequest(
            panels=normalized_panels,
            title=title.strip(),
            caption=caption.strip(),
            citation=citation.strip(),
            output_path=normalized_output_path,
        )
        uc = Container.get().composite_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)


@mcp.tool()
def replay_manifest(
    manifest_id: str,
    output_dir: str | None = None,
) -> dict[str, object]:
    """Replay a previously saved manifest using the same prompt."""
    try:
        normalized_id = normalize_manifest_id(manifest_id)
        normalized_output_dir = normalize_output_dir(output_dir)
        uc = Container.get().replay_manifest_uc()
        return uc.execute(
            ReplayManifestRequest(
                manifest_id=normalized_id,
                output_dir=normalized_output_dir,
            )
        )
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc, manifest_id=manifest_id)


@mcp.tool()
def retarget_journal(
    manifest_id: str,
    target_journal: str,
    output_dir: str | None = None,
) -> dict[str, object]:
    """Apply a new journal profile to an existing manifest and regenerate the figure."""
    try:
        normalized_id = normalize_manifest_id(manifest_id)
        normalized_target = normalize_target_journal(target_journal)
        normalized_output_dir = normalize_output_dir(output_dir)
        uc = Container.get().retarget_journal_uc()
        return uc.execute(
            RetargetJournalRequest(
                manifest_id=normalized_id,
                target_journal=normalized_target or "",
                output_dir=normalized_output_dir,
            )
        )
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc, manifest_id=manifest_id, target_journal=target_journal)


@mcp.tool()
def list_manifests(limit: int = 20) -> dict[str, object]:
    """List recent manifests for replay or retargeting."""
    try:
        normalized_limit = normalize_list_limit(limit)
        uc = Container.get().list_manifests_uc()
        return uc.execute(ListManifestsRequest(limit=normalized_limit))
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc, limit=limit)


@mcp.tool()
def verify_figure(
    image_path: str,
    expected_labels: list[str] | None = None,
    figure_type: str = "infographic",
    language: str = "zh-TW",
) -> dict[str, object]:
    """Run the automated quality gate on a generated figure.

    Uses vision self-check to evaluate 8 quality domains and verify CJK text
    rendering accuracy. Returns pass/fail verdict, domain scores, and any
    missing or garbled labels.

    expected_labels: Exact text strings (e.g. CJK labels) the figure should contain.
    """
    try:
        request = VerifyFigureRequest(
            image_path=normalize_image_path(image_path),
            expected_labels=normalize_expected_labels(expected_labels) or [],
            figure_type=normalize_figure_type(figure_type, allow_auto=False),
            language=normalize_language(language),
        )
        uc = Container.get().verify_figure_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)


@mcp.tool()
def multi_turn_edit(
    image_path: str,
    instructions: list[str],
    max_turns: int = 5,
) -> dict[str, object]:
    """Iteratively refine a figure through a multi-turn editing session.

    Sends multiple editing instructions turn-by-turn to fix CJK labels,
    adjust layout, or improve details. Each turn builds on the previous
    result for precise iterative corrections.

    instructions: List of natural language editing instructions applied in order.
    Examples: ["修正標題為「急性冠心症處置流程」", "箭頭改紅色", "加大字體"]
    """
    try:
        request = MultiTurnEditRequest(
            image_path=normalize_image_path(image_path),
            instructions=normalize_instructions(instructions),
            max_turns=max_turns,
        )
        uc = Container.get().multi_turn_edit_uc()
        return uc.execute(request)
    except (ConfigurationError, ValidationError, DomainError) as exc:
        return _error_payload(exc)
