from __future__ import annotations

from copy import deepcopy
from pathlib import Path
from typing import Any

from . import __version__
from .source import SourcePackage
from .util import sha256_file, sha256_json, utc_now


def empty_evidence() -> dict[str, Any]:
    return {
        "status": "not_attempted",
        "method": None,
        "ocr_word_ids": [],
        "bbox": None,
        "matched_tokens": 0,
        "total_tokens": 0,
    }


def _normalize_lines(lines: list[dict[str, Any]]) -> None:
    for number, line in enumerate(lines, start=1):
        line["line_number"] = number


def _normalize_part(part: dict[str, Any], page: int, block_order: int, order: int) -> None:
    part["id"] = f"p{page:03d}-b{block_order:03d}-p{order:03d}"
    part["order"] = order
    part["evidence"] = empty_evidence()
    if "lines" in part:
        _normalize_lines(part["lines"])
    if part["type"] == "list":
        for item_order, item in enumerate(part["items"], start=1):
            item["order"] = item_order
            if item.get("label_lines"):
                _normalize_lines(item["label_lines"])
            _normalize_lines(item["content_lines"])
    if part["type"] == "table":
        for row_order, row in enumerate(part["rows"], start=1):
            row["order"] = row_order
            row["cells"].sort(key=lambda cell: cell["column"])
            for cell in row["cells"]:
                _normalize_lines(cell["lines"])


def normalize_draft(draft: dict[str, Any], page: int) -> dict[str, Any]:
    normalized = deepcopy(draft)
    normalized["page_id"] = f"page-{page:03d}"
    old_to_new: dict[str, str] = {}
    for order, block in enumerate(normalized["blocks"], start=1):
        old_id = block.get("id")
        new_id = f"p{page:03d}-b{order:03d}"
        if old_id:
            old_to_new[old_id] = new_id
        block["id"] = new_id
        block["order"] = order
        block["evidence"] = empty_evidence()
        block["continuation"]["previous"] = None
        block["continuation"]["next"] = None
        if "lines" in block:
            _normalize_lines(block["lines"])
        if block["type"] in {"diary_entry", "letter"}:
            for part_order, part in enumerate(block["content"], start=1):
                _normalize_part(part, page, order, part_order)
        if block["type"] == "list":
            for item_order, item in enumerate(block["items"], start=1):
                item["order"] = item_order
                if item.get("label_lines"):
                    _normalize_lines(item["label_lines"])
                _normalize_lines(item["content_lines"])
        if block["type"] == "table":
            for row_order, row in enumerate(block["rows"], start=1):
                row["order"] = row_order
                row["cells"].sort(key=lambda cell: cell["column"])
                for cell in row["cells"]:
                    _normalize_lines(cell["lines"])

    for block in normalized["blocks"]:
        if block["type"] == "graphical_feature":
            block["related_block_ids"] = [
                old_to_new.get(block_id, block_id)
                for block_id in block["related_block_ids"]
                if old_to_new.get(block_id, block_id) in old_to_new.values()
            ]

    for order, note in enumerate(normalized["editorial_notes"], start=1):
        note["id"] = f"p{page:03d}-n{order:03d}"
        target = note.get("target_block_id")
        if target in old_to_new:
            note["target_block_id"] = old_to_new[target]
        elif target not in {None, *old_to_new.values()}:
            note["target_block_id"] = None
        note["responsibility"] = "model"
    return normalized


def build_candidate_page(
    *,
    draft: dict[str, Any],
    source: SourcePackage,
    page: int,
    run_id: str,
    backend: str,
    model: str,
    reasoning_effort: str,
    prompt_path: Path,
    schema_path: Path,
    context_pages: list[int],
    created_at: str | None = None,
) -> dict[str, Any]:
    timestamp = created_at or utc_now()
    normalized = normalize_draft(draft, page)
    return {
        "schema_version": "1.2.0",
        "page_id": f"page-{page:03d}",
        "source": source.source_evidence(page),
        "segment": source.segment_for_page(page),
        "page_role": normalized["page_role"],
        "boundary": normalized["boundary"],
        "blocks": normalized["blocks"],
        "editorial_notes": normalized["editorial_notes"],
        "provenance": {
            "pipeline_version": __version__,
            "model_run_id": run_id,
            "backend": backend,
            "model": model,
            "reasoning_effort": reasoning_effort,
            "prompt_sha256": sha256_file(prompt_path),
            "schema_sha256": sha256_file(schema_path),
            "candidate_sha256": sha256_json(draft),
            "context_pages": context_pages,
            "steps": [
                {
                    "kind": "ocr",
                    "actor": source.lock["ocr_engine"],
                    "artifact": source.ocr_text_relative(page),
                    "created_at": timestamp,
                },
                {
                    "kind": "model_transcription",
                    "actor": f"{backend}:{model}",
                    "artifact": run_id,
                    "created_at": timestamp,
                },
            ],
        },
        "review": {
            "status": "model_candidate",
            "authority": "diagnostic_only",
            "reviewer": None,
            "reviewed_at": None,
            "notes": [
                "Model candidate only; the page image remains authoritative."
            ],
        },
    }
