#!/usr/bin/env python3
"""CLI entrypoint for Phase 7 step 1.5 — render a final-report HTML view.

Usage:
  okstra-render-report-views.py <path-to-final-report.data.json|final-report.md>
      [--task-key <task-group/task-id>]
      [--task-type <profile>]
      [--seq <NNN>]
      [--source-report <relative-path>]

Structured ``.data.json`` input (schema 2.0 or 3.0) is rendered directly into
the dedicated task HTML template and always produces an HTML sibling. Markdown
input resolves its data sibling first; schema-v1 and quick reports keep the
legacy conditional Markdown renderer.

Output (idempotent — overwrites):
  - <stem>.html  — single-file self-contained HTML view

This script is the canonical single-reference-point. The Node CLI
(``bin/okstra render-views``) is a thin wrapper that spawns it.
"""
from __future__ import annotations

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

sys.path.insert(0, str(Path(__file__).resolve().parent))
from okstra_bootstrap import prefer_colocated_modules  # noqa: E402

prefer_colocated_modules(__file__, "okstra_ctl/report_views.py")

from okstra_ctl.clarification_items import STRUCTURED_REPORT_VERSIONS  # noqa: E402
from okstra_ctl.report_views import infer_run_meta, render_html_view  # noqa: E402
from okstra_ctl.final_report_paths import (  # noqa: E402
    final_report_data_path,
    final_report_markdown_path,
)


REPO_ROOT = Path(__file__).resolve().parents[1]
_OKSTRA_HOME = Path(os.environ.get("OKSTRA_HOME", str(Path.home() / ".okstra")))
# Search order:
#   1) dev tree (`<repo>/templates/reports`) — wins when this script runs from
#      a source checkout or a link-mode install (the symlinked bin entrypoint
#      resolves __file__ back into the repo).
#   2) install tree (`~/.okstra/templates/reports`) — populated by
#      `okstra install` copy mode via src/install.mjs. The legacy
#      `~/.okstra/lib/templates/reports` path was never written by any
#      install flow and is gone.
_TEMPLATES_DIRS = (
    REPO_ROOT / "templates" / "reports",
    _OKSTRA_HOME / "templates" / "reports",
)

def _load_assets() -> tuple[str, str]:
    css_text: str | None = None
    js_text: str | None = None
    for d in _TEMPLATES_DIRS:
        css = d / "report.css"
        js = d / "report.js"
        if css.is_file() and js.is_file():
            css_text = css.read_text(encoding="utf-8")
            js_text = js.read_text(encoding="utf-8")
            break
    if css_text is None or js_text is None:
        raise SystemExit(
            "report.css / report.js not found. Looked under: "
            + ", ".join(str(d) for d in _TEMPLATES_DIRS)
        )
    return css_text, js_text


def _templates_root() -> Path:
    for directory in _TEMPLATES_DIRS:
        if (directory / "html" / "base.template.html").is_file():
            return directory
    raise SystemExit(
        "schema-v2 HTML templates not found. Looked under: "
        + ", ".join(str(directory) for directory in _TEMPLATES_DIRS)
    )


def _report_pair(report_path: Path) -> tuple[Path, Path]:
    if report_path.name.endswith(".data.json"):
        return report_path, final_report_markdown_path(report_path)
    return final_report_data_path(report_path), report_path


def _load_data_if_present(data_path: Path) -> dict | None:
    if not data_path.is_file():
        return None
    try:
        data = json.loads(data_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise SystemExit(
            f"final-report data is not valid JSON: {data_path} ({exc})"
        ) from exc
    if not isinstance(data, dict):
        raise SystemExit(f"final-report data must be a JSON object: {data_path}")
    return data


def _v2_run_meta(data: dict, data_path: Path, args: argparse.Namespace):
    """Build the HTML run meta from the data.json and its own filename.

    The markdown sibling contributes nothing but its name, which is derived
    from this path — so a report whose markdown has not been rendered (or was
    deleted) still produces the human view.
    """
    from okstra_ctl.report_html.models import HtmlRunMeta

    header = data.get("header", {})
    task_key = args.task_key or header.get("taskKey")
    task_type = args.task_type or header.get("taskType")
    match = re.search(r"-(\d+)\.data\.json$", data_path.name)
    seq = (
        args.seq
        or data.get("analysisCommon", {}).get("runSeq")
        or (match.group(1) if match else None)
    )
    if not all(
        isinstance(value, str) and value for value in (task_key, task_type, seq)
    ):
        raise SystemExit("schema-v2 report metadata requires task-key, task-type, and seq")
    return HtmlRunMeta(
        task_key,
        task_type,
        seq,
        args.source_report or final_report_markdown_path(data_path).name,
        _elapsed_ms(data_path, task_type, seq, args.team_state),
    )


def _elapsed_ms(
    report_path, task_type: str, seq: str, team_state: Path | None
) -> int | None:
    """Wall-clock milliseconds for this run, or None when it cannot be measured.

    The measurement lives in the run's team-state, not in the report data, so
    the CLI resolves it here rather than teaching the renderer about run
    layout. `report-finalize` names that state outright (`--team-state`); a
    bare invocation resolves it through the run manifest that names this
    report. A missing or timestamp-less state yields None — the header then
    omits the field instead of printing a zero.
    """
    from okstra_ctl.report_view_artifacts import team_state_path_for_report
    from okstra_ctl.time_report import wall_clock_ms

    state_path = (
        team_state.resolve()
        if team_state is not None
        else team_state_path_for_report(report_path, task_type, seq)
    )
    try:
        state = json.loads(state_path.read_text(encoding="utf-8"))
    except (OSError, ValueError):
        return None
    return wall_clock_ms(state) or None if isinstance(state, dict) else None


_CLI_EPILOG = r"""A thin spawn shim over `scripts/okstra-render-report-views.py` (installed
at `$HOME/.okstra/bin/okstra-render-report-views.py`). Resolves the final-
report data and Markdown pair, then writes a single sibling:

  <stem>.html       — single-file self-contained human view; schema v2 is
                       always generated, while v1/quick remains conditional

Usage:
  okstra render-views <path-to-final-report.data.json|path-to-final-report.md>
      [--task-key <key>] [--task-type <type>] [--seq <NNN>]
      [--source-report <project-relative-path>] [--team-state <path>]

Schema v2 data is rendered directly into its task-specific HTML view and is
always generated. Schema v1 and quick reports keep the legacy conditional
Markdown view. Optional flags override inferred report metadata.
"""


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        prog="okstra render-views",
        epilog=_CLI_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="Render the self-contained HTML view of an okstra final-report."
    )
    parser.add_argument("report_path", type=Path)
    parser.add_argument("--task-key", default=None)
    parser.add_argument("--task-type", default=None)
    parser.add_argument("--seq", default=None)
    parser.add_argument("--source-report", default=None)
    parser.add_argument(
        "--team-state",
        type=Path,
        default=None,
        help=(
            "team-state of the run that produced this report, for the header's "
            "elapsed time (default: resolved through the run manifest naming "
            "this report)"
        ),
    )
    args = parser.parse_args(argv)

    report_path = args.report_path.resolve()
    if not report_path.is_file():
        parser.error(f"final-report not found: {report_path}")
    data_path, markdown_path = _report_pair(report_path)
    data = _load_data_if_present(data_path)

    if data is not None and data.get("schemaVersion") in STRUCTURED_REPORT_VERSIONS:
        from okstra_ctl.report_html.render import render_v2_html_view

        html_path = render_v2_html_view(
            data_path,
            run_meta=_v2_run_meta(data, data_path, args),
            templates_root=_templates_root(),
        )
        print(f"html: {html_path}")
        return 0

    if not markdown_path.is_file():
        parser.error(f"legacy markdown sibling not found: {markdown_path}")
    meta = infer_run_meta(
        markdown_path,
        task_key=args.task_key,
        task_type=args.task_type,
        seq=args.seq,
        source_report=args.source_report,
    )
    css, js = _load_assets()
    html_path = render_html_view(markdown_path, run_meta=meta, css=css, js=js)
    if html_path is None:
        print(
            "html: skipped (no §1 clarification rows — html view carries no "
            "interactive forms for this report)"
        )
    else:
        print(f"html: {html_path}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
