"""Narrow repairs for malformed nested Markdown fence examples.

Models occasionally wrap a fenced example in an equally long ``markdown``
fence.  CommonMark cannot nest those fences: the inner closing fence closes
the outer block and the intended outer close opens a new, unclosed block.
Only that unambiguous shape is repaired here; arbitrary unclosed fences are
reported but left untouched.
"""
from __future__ import annotations

import re
from dataclasses import dataclass

_FENCE_RE = re.compile(r"^( {0,3})(`{3,}|~{3,})([^\r\n]*)(\r?\n)?$")


@dataclass(frozen=True)
class MarkdownFenceResult:
    text: str
    repairs: int = 0
    unclosed_fence: bool = False


def _fence(line: str) -> tuple[str, str, int, str] | None:
    match = _FENCE_RE.match(line)
    if not match:
        return None
    token = match.group(2)
    return match.group(1), token[0], len(token), match.group(3).strip()


def _next_nonblank(lines: list[str], start: int) -> int | None:
    for idx in range(start, len(lines)):
        if lines[idx].strip():
            return idx
    return None


def _replace_fence(line: str, length: int) -> str:
    match = _FENCE_RE.match(line)
    if not match:
        return line
    return (
        match.group(1)
        + match.group(2)[0] * length
        + match.group(3)
        + (match.group(4) or "")
    )


def _has_unclosed_fence(lines: list[str]) -> bool:
    opened: tuple[str, int] | None = None
    for line in lines:
        parsed = _fence(line)
        if parsed is None:
            continue
        _indent, char, length, info = parsed
        if opened is None:
            opened = (char, length)
        elif not info and char == opened[0] and length >= opened[1]:
            opened = None
    return opened is not None


def normalize_markdown_fences(text: str) -> MarkdownFenceResult:
    """Repair only an equally-long fenced block nested in markdown/md.

    Recognised shape (blank lines between the two closing fences are allowed):
    ``markdown opener -> language opener -> language close -> outer close``.
    The outer pair is lengthened by one character. Other malformed input is
    preserved so the platform never guesses broadly at author intent.
    """
    if not text:
        return MarkdownFenceResult(text=text)

    lines = text.splitlines(keepends=True)
    repairs = 0
    idx = 0
    while idx < len(lines):
        outer = _fence(lines[idx])
        if outer is None or outer[3].lower() not in {"markdown", "md"}:
            idx += 1
            continue

        inner_idx = _next_nonblank(lines, idx + 1)
        inner = _fence(lines[inner_idx]) if inner_idx is not None else None
        if (
            inner is None
            or not inner[3]
            or inner[1] != outer[1]
            or inner[2] < outer[2]
        ):
            idx += 1
            continue
        assert inner_idx is not None

        inner_close_idx = None
        for candidate in range(inner_idx + 1, len(lines)):
            closing = _fence(lines[candidate])
            if (
                closing is not None
                and not closing[3]
                and closing[1] == inner[1]
                and closing[2] >= inner[2]
            ):
                inner_close_idx = candidate
                break
        if inner_close_idx is None:
            idx += 1
            continue

        outer_close_idx = _next_nonblank(lines, inner_close_idx + 1)
        outer_close = _fence(lines[outer_close_idx]) if outer_close_idx is not None else None
        if (
            outer_close is None
            or outer_close[3]
            or outer_close[1] != outer[1]
            or outer_close[2] < outer[2]
        ):
            idx += 1
            continue
        assert outer_close_idx is not None

        repaired_length = max(outer[2], inner[2]) + 1
        lines[idx] = _replace_fence(lines[idx], repaired_length)
        lines[outer_close_idx] = _replace_fence(lines[outer_close_idx], repaired_length)
        repairs += 1
        idx = outer_close_idx + 1

    normalized = "".join(lines)
    return MarkdownFenceResult(
        text=normalized,
        repairs=repairs,
        unclosed_fence=_has_unclosed_fence(lines),
    )
