"""High-level prompt services used by the CLI and Python API."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from dotpromptz.compiler import CompiledPrompt, compile_prompt, render_compiled_prompt
from dotpromptz.executor import BatchResult, execute_plan, part_to_output_dict
from dotpromptz.logging import generate_build_output_filename, get_logger
from dotpromptz.planner import plan_prompt
from dotpromptz.typing import RenderedPrompt
from dotpromptz.yamlio import dump_yaml

logger = get_logger(__name__)


@dataclass(frozen=True)
class BuildReport:
    """Result of ``build_prompt``."""

    output_file: Path
    rendered: RenderedPrompt
    compiled: CompiledPrompt


@dataclass(frozen=True)
class RunReport:
    """Result of ``run_prompt``."""

    build_output_file: Path
    batch_results: list[BatchResult]
    written_files: list[Path]
    output_dir: Path

    @property
    def all_failed(self) -> bool:
        """Return whether every batch item failed."""
        return bool(self.batch_results) and all(item.status == 'error' for item in self.batch_results)


def _build_rendered_output(rendered: RenderedPrompt) -> dict[str, Any]:
    """Build a structured dict from a rendered prompt for inspection output."""
    output: dict[str, Any] = {}
    if rendered.config:
        output['config'] = rendered.config
    output['messages'] = []
    for msg in rendered.messages:
        msg_dict: dict[str, Any] = {'role': str(msg.role)}
        if msg.content:
            parts = []
            for part in msg.content:
                parts.append(part_to_output_dict(part))
            msg_dict['content'] = parts
        output['messages'].append(msg_dict)
    if rendered.output:
        output['output'] = rendered.output.model_dump(exclude_none=True)
    if rendered.input:
        output['input'] = rendered.input.model_dump(exclude_none=True)
    return output


def _save_build_output(rendered: RenderedPrompt, prompt_path: Path, log_dir: Path) -> Path:
    """Save rendered prompt output to a YAML file."""
    output = _build_rendered_output(rendered)
    output_filename = generate_build_output_filename(prompt_path.stem)
    output_file = log_dir / output_filename
    payload = dump_yaml(
        output,
        allow_unicode=True,
        default_flow_style=False,
        sort_keys=False,
        width=120,
        literal_block_strings=True,
    )
    output_file.write_text(
        payload,
        encoding='utf-8',
    )
    logger.info('Build output saved.', output_file=str(output_file))
    return output_file


def build_prompt(
    prompt_file: str | Path,
    *,
    log_dir: Path,
    default_model: str | None = None,
    model_configs: dict[str, Any] | None = None,
) -> BuildReport:
    """Build a prompt without model execution and emit YAML inspection output."""
    compiled = compile_prompt(
        prompt_file,
        default_model=default_model,
        model_configs=model_configs,
    )
    if len(compiled.input_list) > 1:
        logger.info('Batch mode detected. Rendering first item as sample.', total_items=len(compiled.input_list))

    rendered = render_compiled_prompt(compiled, compiled.input_list[0])
    output_file = _save_build_output(rendered, compiled.prompt_path, log_dir)
    return BuildReport(output_file=output_file, rendered=rendered, compiled=compiled)


async def run_prompt(
    prompt_file: str | Path,
    *,
    log_dir: Path,
    force: bool = False,
    default_model: str | None = None,
    model_configs: dict[str, Any] | None = None,
) -> RunReport:
    """Run prompt execution pipeline and persist outputs."""
    compiled = compile_prompt(
        prompt_file,
        default_model=default_model,
        model_configs=model_configs,
    )

    first_rendered = render_compiled_prompt(compiled, compiled.input_list[0])
    build_output_file = _save_build_output(first_rendered, compiled.prompt_path, log_dir)

    plan = plan_prompt(compiled, force=force)
    execution = await execute_plan(plan)
    return RunReport(
        build_output_file=build_output_file,
        batch_results=execution.batch_results,
        written_files=execution.written_files,
        output_dir=execution.output_dir,
    )
