"""Shared initial analysis-worker prompt body rendering."""
from __future__ import annotations

from typing import Any, Mapping, Sequence

from .worker_prompt_policy import PromptPlan


def analysis_worker_label(worker_id: str) -> str:
    """The role label this worker's prompt body is titled with.

    The label is `{worker_id} worker` for every id. A three-provider display
    map made grok/kimi a second branch, and any consumer that restated the map
    treated those ids as unnamed.
    """
    return f"{worker_id} worker"


def analysis_prompt_body(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    worker_id: str,
    model: str,
    plan: PromptPlan,
) -> list[str]:
    """Render the provider-neutral body for an initial analysis audience."""
    label = analysis_worker_label(worker_id)
    pane_role = {
        "implementation-executor": "executor",
        "implementation-verifier": "verifier",
    }.get(plan.audience, "worker")
    return [
        f"**Model:** {label}, {model}",
        f"**Pane role:** {pane_role}",
        "",
        f"# {label} Dispatch",
        "",
        "## Task",
        f"- Task key: `{_require_string(manifest, 'taskKey')}`",
        f"- Task type: `{_require_string(manifest, 'taskType')}`",
        "",
        "## Inputs",
        *analysis_input_lines(manifest, active_context, plan),
        "",
        mcp_pointer_line(),
        "",
        "## Output Contract",
        "- Read the Worker Preamble Path end-to-end before analysis.",
        "- Write the worker result to Result Path and no other canonical result path.",
        "- Write the audit sidecar to Audit sidecar path as the preamble requires.",
        "- Cite evidence with file paths and line numbers whenever you make a claim.",
    ]
def analysis_input_lines(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    plan: PromptPlan,
) -> list[str]:
    if plan.packet_only:
        packet_path = instruction_path(manifest, active_context, "analysisPacketPath")
        return existing_input_lines((("Primary analysis packet", packet_path),))
    inputs = [
        ("Primary analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
        ("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
        ("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
        ("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
        ("Reference expectations", instruction_path(manifest, active_context, "referenceExpectationsPath")),
        ("Clarification response", instruction_path(manifest, active_context, "clarificationResponsePath")),
    ]
    return existing_input_lines(inputs)


REPORT_WRITER_WORKER_ID = "report-writer"


def report_writer_prompt_body(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    team_state: Mapping[str, Any],
    model: str,
    report_language: str,
    synthesis_packet_path: str = "",
) -> list[str]:
    """Render the provider-neutral body for the report-writer audience."""
    if str(manifest.get("reportContractVersion") or "") == "3.0":
        return _report_writer_v3_body(
            manifest,
            active_context,
            team_state,
            model,
            report_language,
            synthesis_packet_path,
        )
    return [
        f"**Model:** Report writer worker, {model}",
        f"**Report Language:** {report_language}",
        "",
        "# Report Writer Worker Dispatch",
        "",
        "## Task",
        f"- Task key: `{_require_string(manifest, 'taskKey')}`",
        f"- Task type: `{_require_string(manifest, 'taskType')}`",
        "",
        "## Inputs",
        *(
            [f"- Report synthesis packet: `{synthesis_packet_path}`"]
            if synthesis_packet_path
            else report_writer_input_lines(manifest, active_context, team_state)
        ),
        "",
        "## Output Contract",
        "You are the author of TWO files:",
        "- The report record (data.json) at Result Path.",
        "- The worker-result pointer at Worker Result Path.",
        (
            "Keep the pointer to two entries: the data.json path and the "
            "Convergence state input path."
        ),
        "Maintain the separate audit sidecar at Audit sidecar path.",
        (
            "Do not invoke okstra render-final-report; the full reading copy "
            "is on-demand."
        ),
        "Do not return the report inline.",
        "Copy Report Language verbatim into data.json.meta.reportLanguage.",
    ]


def _report_writer_v3_body(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    team_state: Mapping[str, Any],
    model: str,
    report_language: str,
    synthesis_packet_path: str,
) -> list[str]:
    return [
        f"**Model:** Report writer worker, {model}",
        f"**Report Language:** {report_language}",
        "",
        "# Report Writer Worker Dispatch",
        "",
        "## Task",
        f"- Task key: `{_require_string(manifest, 'taskKey')}`",
        f"- Task type: `{_require_string(manifest, 'taskType')}`",
        "",
        "## Inputs",
        *(
            [f"- Report synthesis packet: `{synthesis_packet_path}`"]
            if synthesis_packet_path
            else report_writer_input_lines(manifest, active_context, team_state)
        ),
        "",
        "## Output Contract",
        "You are the author of the report narrative Markdown at Result Path.",
        "Write the worker-result pointer at Worker Result Path.",
        "Maintain the separate audit sidecar at Audit sidecar path.",
        "Do not write the final report record or another owner's input ledger.",
        "Do not return the report inline.",
    ]


def report_writer_input_lines(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    team_state: Mapping[str, Any],
) -> list[str]:
    inputs = [
        ("Analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
        ("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
        ("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
        ("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
        ("Reference expectations", instruction_path(manifest, active_context, "referenceExpectationsPath")),
        ("Clarification response", instruction_path(manifest, active_context, "clarificationResponsePath")),
        ("Final report template", instruction_path(manifest, active_context, "finalReportTemplatePath")),
        ("Final report schema", instruction_path(manifest, active_context, "finalReportSchemaPath")),
        ("Convergence state", run_path(manifest, active_context, "convergenceStatePath")),
        ("Worker results directory", _string_value(manifest.get("workerResultsDirectoryPath"))),
    ]
    lines = existing_input_lines(inputs)
    lines.extend(analysis_worker_result_lines(team_state))
    return lines or ["- No report-writer inputs were recorded in active-run-context."]


def analysis_worker_result_lines(team_state: Mapping[str, Any]) -> list[str]:
    lines = []
    workers = team_state.get("workers")
    if not isinstance(workers, list):
        return lines
    for worker in workers:
        if not isinstance(worker, Mapping):
            continue
        worker_id = worker.get("workerId")
        if worker_id == REPORT_WRITER_WORKER_ID:
            continue
        result_path = _string_value(worker.get("resultPath"))
        if result_path:
            lines.append(f"- Analysis result ({worker_id}): `{result_path}`")
    return lines


def existing_input_lines(inputs: Sequence[tuple[str, str]]) -> list[str]:
    lines = [f"- {label}: `{path}`" for label, path in inputs if path]
    return lines or ["- No input paths were recorded in active-run-context."]


def instruction_path(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    key: str,
) -> str:
    instruction_set = active_context.get("instructionSet")
    if isinstance(instruction_set, Mapping):
        value = _string_value(instruction_set.get(key))
        if value:
            return value
    return _string_value(manifest.get(key))


def run_path(
    manifest: Mapping[str, Any],
    active_context: Mapping[str, Any],
    key: str,
) -> str:
    run = active_context.get("run")
    if isinstance(run, Mapping):
        value = _string_value(run.get(key))
        if value:
            return value
    return _string_value(manifest.get(key))


def mcp_pointer_line() -> str:
    return (
        '**MCP servers:** follow the analysis packet\'s "Available MCP Servers" '
        "section. If the section is absent or says none, treat MCP as unavailable "
        "for this run; never infer tools from host configuration."
    )


def _require_string(payload: Mapping[str, Any], key: str) -> str:
    value = _string_value(payload.get(key))
    if not value:
        raise ValueError(f"missing required string field: {key}")
    return value


def _string_value(value: Any) -> str:
    return value.strip() if isinstance(value, str) else ""
