"""Read-only flattened view of a phase profile (`okstra profile show`).

A profile is assembled from three places — the top-level body, its
`{{INCLUDE:}}` targets, and the lazy-read sidecars the body's own table names —
so grepping the top-level file and finding nothing does not mean the rule is
absent. This prints the whole thing, so one grep answers "does this task-type
cover X".

Read-only is the point, not a nicety: `render-bundle` would answer the same
question, but it writes a manifest and registers the run in `recent.jsonl`.
That side effect is exactly why it cannot be used to look something up.
"""
from __future__ import annotations

import argparse
import os
import re
import sys
from pathlib import Path

from .paths import find_asset_root
from .run import PrepareError, _expand_profile_includes

_PROFILES_REL = ("prompts", "profiles")

# The sidecar table spells its targets as inline-code repo-relative paths, e.g.
# `prompts/profiles/_implementation-executor.md`. Reading the list from the
# profile body rather than hard-coding it here is deliberate: a list in code
# goes stale the moment a profile adds a sidecar, and does so silently.
_SIDECAR_RE = re.compile(r"`(prompts/profiles/_[\w-]+\.md)`")


def workspace_root(start: Path | None = None) -> Path:
    """Locate the runtime root that carries the `prompts/profiles` tree."""
    root = find_asset_root(_PROFILES_REL, start=start, is_present=Path.is_dir)
    if root is not None:
        return root

    raise PrepareError(
        "could not locate prompts/profiles. Set OKSTRA_HOME or run from a "
        "checkout that contains prompts/profiles/."
    )


def profile_path(root: Path, task_type: str) -> Path:
    path = root.joinpath(*_PROFILES_REL, f"{task_type}.md")
    if not path.is_file():
        raise PrepareError(f"unknown task-type: {task_type} (no {path})")
    return path


def sidecar_bodies(root: Path, profile_text: str) -> list[str]:
    """Every lazy-read sidecar reachable from the profile body, breadth-first.

    The walk runs to a fixpoint because sidecars name sidecars of their own:
    `_implementation-executor.md` points at the coding-conventions preflight,
    the diff-review sweep, and the completion self-check. Stopping after one
    hop would rebuild the very false negative this command exists to prevent,
    one level down.

    Each body is expanded rather than read raw, so nested `{{INCLUDE:}}`
    directives resolve and the maintainer-only HTML comments drop out — the
    same treatment the lead's rendered profile gets.
    """
    bodies: list[str] = []
    seen: set[str] = set()
    pending = list(dict.fromkeys(_SIDECAR_RE.findall(profile_text)))
    while pending:
        relative = pending.pop(0)
        if relative in seen:
            continue
        seen.add(relative)
        path = root / relative
        if not path.is_file():
            continue
        body = _expand_profile_includes(path)
        bodies.append(f"\n\n<!-- lazy-read sidecar: {relative} -->\n\n{body}")
        pending.extend(ref for ref in _SIDECAR_RE.findall(body) if ref not in seen)
    return bodies


def render(root: Path, task_type: str, *, resolved: bool) -> str:
    path = profile_path(root, task_type)
    if not resolved:
        return path.read_text(encoding="utf-8")
    expanded = _expand_profile_includes(path)
    return expanded + "".join(sidecar_bodies(root, expanded))


def _write_stdout(text: str) -> None:
    """Write the profile, tolerating a reader that stops early.

    The command's whole purpose is to be piped into `grep` or `head`, and both
    close the pipe as soon as they have enough. Without this, that normal usage
    ends in a BrokenPipeError traceback — and the interpreter raises a second
    one when it flushes stdout at shutdown, which is why the fd is redirected
    to devnull rather than merely swallowing the first exception.
    """
    try:
        sys.stdout.write(text)
        sys.stdout.flush()
    except BrokenPipeError:
        devnull = os.open(os.devnull, os.O_WRONLY)
        try:
            os.dup2(devnull, sys.stdout.fileno())
        finally:
            os.close(devnull)


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        prog="okstra profile show",
        description="Print a phase profile, optionally fully resolved (read-only).",
    )
    parser.add_argument("command", choices=("show",))
    parser.add_argument("task_type")
    parser.add_argument(
        "--resolved",
        action="store_true",
        help="expand {{INCLUDE:}} targets and append the lazy-read sidecars",
    )
    args = parser.parse_args(argv)

    try:
        text = render(workspace_root(), args.task_type, resolved=args.resolved)
    except PrepareError as exc:
        print(f"profile show: {exc}", file=sys.stderr)
        return 2
    _write_stdout(text)
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
