from __future__ import annotations

from pathlib import Path
from typing import Mapping, Optional

from .config import (
    REPO_OVERRIDE_FILENAME,
    ROOT_CONFIG_FILENAME,
    ROOT_OVERRIDE_FILENAME,
    find_nearest_hub_config_path,
)

ABOUT_CAR_BASENAME = "ABOUT_CAR.md"
ABOUT_CAR_REL_PATH = Path(".codex-autorunner") / ABOUT_CAR_BASENAME
TICKET_FLOW_QUICKSTART_BASENAME = "TICKET_FLOW_QUICKSTART.md"
TICKET_FLOW_QUICKSTART_REL_PATH = (
    Path(".codex-autorunner") / TICKET_FLOW_QUICKSTART_BASENAME
)
DESTINATION_QUICKSTART_BASENAME = "DESTINATION_QUICKSTART.md"
DESTINATION_QUICKSTART_REL_PATH = (
    Path(".codex-autorunner") / DESTINATION_QUICKSTART_BASENAME
)
TICKETS_AGENTS_BASENAME = "AGENTS.md"
TICKETS_AGENTS_REL_PATH = (
    Path(".codex-autorunner") / "tickets" / TICKETS_AGENTS_BASENAME
)

# If this marker is present, codex-autorunner may safely refresh the file content.
ABOUT_CAR_GENERATED_MARKER = "<!-- CAR:AUTOGENERATED -->"
TICKET_FLOW_QUICKSTART_GENERATED_MARKER = "<!-- CAR:TICKET_FLOW_QUICKSTART -->"
DESTINATION_QUICKSTART_GENERATED_MARKER = "<!-- CAR:DESTINATION_QUICKSTART -->"
TICKETS_AGENTS_GENERATED_MARKER = "<!-- CAR:TICKETS_AGENTS -->"

CAR_CONTEXT_KEYWORDS = (
    "car",
    "codex",
    "spec",
    "autorunner",
    "workspace",
    "ticket",
    "tickets",
    "context",
    "decision",
    "decisions",
    "handoff",
    "dispatch",
    "inbox",
)

CAR_CONTEXT_HINT = (
    "Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules; "
    "for docker runtime setup use .codex-autorunner/DESTINATION_QUICKSTART.md "
    "or `car hub destination set --help`."
)


def _display_path(repo_root: Path, path: Path) -> str:
    try:
        return str(path.relative_to(repo_root))
    except ValueError:
        return str(path)


def build_about_car_markdown(
    *,
    repo_root: Path,
    active_context_path: Path,
    decisions_path: Path,
    spec_path: Path,
    hub_config_path: Optional[Path] = None,
    repo_override_path: Optional[Path] = None,
) -> str:
    hub_config_path = (
        hub_config_path
        or find_nearest_hub_config_path(repo_root)
        or (repo_root / ".codex-autorunner" / "config.yml")
    )
    repo_override_path = repo_override_path or (repo_root / REPO_OVERRIDE_FILENAME)
    root_config_path = repo_root / ROOT_CONFIG_FILENAME
    root_override_path = repo_root / ROOT_OVERRIDE_FILENAME
    active_context_disp = _display_path(repo_root, active_context_path)
    decisions_disp = _display_path(repo_root, decisions_path)
    spec_disp = _display_path(repo_root, spec_path)
    hub_config_disp = _display_path(repo_root, hub_config_path)
    repo_override_disp = _display_path(repo_root, repo_override_path)
    root_config_disp = _display_path(repo_root, root_config_path)
    root_override_disp = _display_path(repo_root, root_override_path)

    return (
        f"{ABOUT_CAR_GENERATED_MARKER}\n"
        "# ABOUT_CAR — Codex Autorunner (CAR)\n\n"
        "You are running inside **Codex Autorunner (CAR)**.\n\n"
        "CAR uses a ticket-first workflow.\n\n"
        "## Required for operation\n"
        "- Tickets live under `.codex-autorunner/tickets/` (per-repo/worktree).\n"
        "- If the user provides ticket files, place them in the repo's `.codex-autorunner/tickets/` folder.\n"
        "- Lint ticket frontmatter after edits (runs against all tickets): `python3 .codex-autorunner/bin/lint_tickets.py`.\n\n"
        "## Ticket flow quickstart\n"
        f"- Read `{_display_path(repo_root, TICKET_FLOW_QUICKSTART_REL_PATH)}` for CLI entrypoints + gotchas.\n\n"
        "## Runtime quickstart\n"
        f"- Read `{_display_path(repo_root, DESTINATION_QUICKSTART_REL_PATH)}` for local/docker destination setup.\n\n"
        "## Optional contextspace docs\n"
        "- **Active context**: "
        f"`{active_context_disp}`\n"
        "- **Decisions**: "
        f"`{decisions_disp}`\n"
        "- **Spec**: "
        f"`{spec_disp}`\n\n"
        "## Web UI quick map (repo page)\n"
        "- Repo view: `/repos/<repo_id>/`\n"
        "- Tabs: **Tickets** = `.codex-autorunner/tickets/` queue.\n"
        "- Tabs: **Inbox** = paused run dispatches/handoffs.\n"
        "- Tabs: **Contextspace** = edit `active_context.md`, `spec.md`, `decisions.md`.\n"
        "- Tabs: **Terminal** = launches the configured `codex` binary in a PTY.\n"
        "- Tabs: **Archive** = browse worktree snapshots.\n\n"
        "## FileBox (attachments)\n"
        "- Repo FileBox root: `.codex-autorunner/filebox/`.\n"
        "- User uploads: `.codex-autorunner/filebox/inbox/`.\n"
        "- Files to send back: `.codex-autorunner/filebox/outbox/`.\n"
        "- Browser render outputs from `car render` are written to outbox by default.\n"
        "- Note: ticket_flow uses per-run dispatch directories; do not confuse dispatch with FileBox.\n\n"
        "## Critical rules\n"
        "- Do **not** create new copies of contextspace docs elsewhere in the repo.\n"
        "- Treat `.codex-autorunner/` as intentional project structure even though it is hidden/gitignored.\n\n"
        "## Agent Flow\n"
        "- **Dispatch**: An update or message from the agent.\n"
        "- **Handoff**: Passing control from agent to user (or vice versa).\n"
        "- **Inbox**: Where the agent receives files/messages.\n\n"
        "## Ticket helpers\n"
        "- Use `.codex-autorunner/bin/ticket_tool.py` to list/create/insert/move tickets; it is portable and venv-free.\n"
        '- Common workflows: insert gap before N (`python3 .codex-autorunner/bin/ticket_tool.py insert --before N`); move a block (`... move --start A --end B --to T`); create with auto-quoted frontmatter (`... create --title "Fix #123" --agent codex`).\n'
        "- After any ticket edits, lint all tickets: `python3 .codex-autorunner/bin/lint_tickets.py`.\n\n"
        "## Ticket templates (optional)\n"
        "- CAR can fetch ticket templates from configured git repos (treat templates as code).\n"
        "- Fetch (prints template to stdout): `car templates fetch <repo_id>:<path>[@<ref>]`\n"
        "- Pin to a commit for determinism: `...@<commit_sha>`\n"
        "- Trusted repos skip scanning. Untrusted repos are scanned (cached by blob SHA) before content is returned.\n"
        "- If fetch or scan fails, pause and notify the user rather than guessing.\n\n"
        "## How CAR works (short)\n"
        "- The web UI provides ticket editing + unified file chat.\n"
        "- `car serve` starts the hub web UI. The **Terminal** tab launches the configured `codex` binary in a PTY.\n"
        f"- Hub config lives at `{hub_config_disp}` (generated).\n"
        f"- Repo overrides (optional) live at `{repo_override_disp}`.\n"
        f"- Root defaults live at `{root_config_disp}` with optional `{root_override_disp}` overrides.\n"
    )


def ensure_about_car_file_for_repo(
    repo_root: Path,
    *,
    doc_paths: Mapping[str, Path],
    force: bool = False,
) -> Path:
    """
    Ensure `.codex-autorunner/ABOUT_CAR.md` exists for this repo.

    If the file already exists and contains the generated marker, it may be refreshed
    (unless force=False and content already matches).
    """
    path = repo_root / ABOUT_CAR_REL_PATH
    path.parent.mkdir(parents=True, exist_ok=True)

    content = build_about_car_markdown(
        repo_root=repo_root,
        active_context_path=doc_paths["active_context"],
        decisions_path=doc_paths["decisions"],
        spec_path=doc_paths["spec"],
    )
    if content and not content.endswith("\n"):
        content += "\n"

    if path.exists() and not force:
        try:
            existing = path.read_text(encoding="utf-8")
        except OSError:
            existing = ""
        # Only overwrite if it's our generated file (marker) and content changed.
        if ABOUT_CAR_GENERATED_MARKER not in existing:
            return path
        if existing == content:
            return path

    path.write_text(content, encoding="utf-8")
    return path


def build_ticket_flow_quickstart_markdown(*, repo_root: Path) -> str:
    ticket_dir = ".codex-autorunner/tickets/"
    destination_quickstart_path = _display_path(
        repo_root, DESTINATION_QUICKSTART_REL_PATH
    )
    return (
        f"{TICKET_FLOW_QUICKSTART_GENERATED_MARKER}\n"
        "# Ticket Flow Quickstart\n\n"
        "## Start ticket flow via CLI\n"
        "- Bootstrap the first run (creates TICKET-001 if needed):\n"
        "  `car flow ticket_flow bootstrap --repo <path>`\n"
        "  (alias: `car ticket-flow bootstrap --repo <path>`)\n"
        "- Start/resume without seeding tickets:\n"
        "  `car flow ticket_flow start --repo <path>`\n"
        "- Check status:\n"
        "  `car flow ticket_flow status --repo <path> [--run-id <uuid>]`\n"
        "- Resume/stop:\n"
        "  `car flow ticket_flow start --repo <path>`\n"
        "  `car flow ticket_flow stop --repo <path> [--run-id <uuid>]`\n\n"
        "## Recovery when resume metadata is stale\n"
        "- Use `resume` only when the target run metadata is healthy and there is no conflicting active run.\n"
        "- If resume fails because another run is active, inspect that active run first.\n"
        "- If the active run is healthy (`status: running`, `worker.status: alive`), resume or stop that run instead of starting a new one.\n"
        "- Use `start --force-new` only after confirming the conflicting active run is stale (`worker.status: absent` / `worker metadata missing`).\n"
        "  `car flow ticket_flow start --repo <path> --force-new`\n"
        "- Verify the selected run is actually live:\n"
        "  `car flow ticket_flow status --json --repo <path> [--run-id <uuid>]`\n"
        "- Confirm status reports `status: running` and `worker.status: alive` before continuing.\n\n"
        "## Runtime destination prerequisite\n"
        "- Before starting runtime-sensitive work, confirm execution destination:\n"
        "  `car hub destination show <repo_id> --path <hub_root>`\n"
        "- If the repo should run in docker, configure image/runtime first:\n"
        "  `car hub destination set <repo_id> docker --image <image> --path <hub_root>`\n"
        f"- For profile/mount/env options, see `{destination_quickstart_path}`.\n\n"
        "## Where tickets live\n"
        f"- Tickets are per-repo/worktree under `{ticket_dir}`.\n"
        "- If the user provides ticket files, save them directly into that folder.\n\n"
        "## Common gotchas\n"
        "- Hub vs repo: ticket flows run per-repo; CLI commands need a repo path.\n"
        "- `--repo` expects a filesystem path, not a hub repo_id.\n"
        "- Each worktree has its own `.codex-autorunner/` directory.\n"
        "- If this repo/worktree lives under a hub, it must be registered in the hub manifest to show up in the hub UI. Run: `car hub scan` (or create it via `car hub worktree create`).\n"
    )


def build_destination_quickstart_markdown(*, repo_root: Path) -> str:
    destinations_doc = _display_path(
        repo_root, Path("docs/configuration/destinations.md")
    )
    schema_doc = _display_path(repo_root, Path("docs/reference/hub-manifest-schema.md"))
    return (
        f"{DESTINATION_QUICKSTART_GENERATED_MARKER}\n"
        "# Destination Quickstart\n\n"
        "## Goal\n"
        "- Configure where a repo/worktree executes (`local` or `docker`).\n"
        "- Bring your own docker image with `--image <registry/image:tag>`.\n\n"
        "## Core commands\n"
        "- Show effective destination:\n"
        "  `car hub destination show <repo_id> --path <hub_root>`\n"
        "- Set local destination:\n"
        "  `car hub destination set <repo_id> local --path <hub_root>`\n"
        "- Set docker destination with a custom image:\n"
        "  `car hub destination set <repo_id> docker --image <registry/image:tag> --path <hub_root>`\n"
        "- Discover advanced flags (profile/workdir/env/mounts):\n"
        "  `car hub destination set --help`\n\n"
        "## Docs for progressive discovery\n"
        f"- Runtime guide: `{destinations_doc}`\n"
        f"- Manifest contract: `{schema_doc}`\n\n"
        "## Notes\n"
        "- `profile` is optional; currently only `full-dev` is supported.\n"
        "- If `profile` is omitted, docker still runs your image using only explicitly configured env/mount settings.\n"
    )


def build_tickets_agents_markdown(*, repo_root: Path) -> str:
    quickstart_path = _display_path(repo_root, TICKET_FLOW_QUICKSTART_REL_PATH)
    return (
        f"{TICKETS_AGENTS_GENERATED_MARKER}\n"
        "# Tickets — AGENTS\n\n"
        "This folder is the authoritative ticket queue for this repo/worktree.\n\n"
        "## Ticket files\n"
        "- Store work items as `TICKET-###*.md` (ordered by number).\n"
        "- Keep frontmatter `done: true|false` in sync with completion.\n"
        "- After edits, lint tickets: `python3 .codex-autorunner/bin/lint_tickets.py`.\n\n"
        "## Ticket CLI (portable)\n"
        "- List: `python3 .codex-autorunner/bin/ticket_tool.py list`\n"
        '- Create: `python3 .codex-autorunner/bin/ticket_tool.py create --title "..." --agent codex`\n'
        "- Insert gap: `python3 .codex-autorunner/bin/ticket_tool.py insert --before N`\n"
        "- Move block: `python3 .codex-autorunner/bin/ticket_tool.py move --start A --end B --to T`\n"
        "- Lint: `python3 .codex-autorunner/bin/ticket_tool.py lint`\n\n"
        "## Ticket flow (runner)\n"
        f"- See `{quickstart_path}` for `car flow ticket_flow ...` commands.\n"
    )


def ensure_ticket_flow_quickstart_file_for_repo(
    repo_root: Path, *, force: bool = False
) -> Path:
    path = repo_root / TICKET_FLOW_QUICKSTART_REL_PATH
    path.parent.mkdir(parents=True, exist_ok=True)
    content = build_ticket_flow_quickstart_markdown(repo_root=repo_root)
    if content and not content.endswith("\n"):
        content += "\n"

    if path.exists() and not force:
        try:
            existing = path.read_text(encoding="utf-8")
        except OSError:
            existing = ""
        if TICKET_FLOW_QUICKSTART_GENERATED_MARKER not in existing:
            return path
        if existing == content:
            return path

    path.write_text(content, encoding="utf-8")
    return path


def ensure_destination_quickstart_file_for_repo(
    repo_root: Path, *, force: bool = False
) -> Path:
    path = repo_root / DESTINATION_QUICKSTART_REL_PATH
    path.parent.mkdir(parents=True, exist_ok=True)
    content = build_destination_quickstart_markdown(repo_root=repo_root)
    if content and not content.endswith("\n"):
        content += "\n"

    if path.exists() and not force:
        try:
            existing = path.read_text(encoding="utf-8")
        except OSError:
            existing = ""
        if DESTINATION_QUICKSTART_GENERATED_MARKER not in existing:
            return path
        if existing == content:
            return path

    path.write_text(content, encoding="utf-8")
    return path


def ensure_tickets_agents_file_for_repo(
    repo_root: Path, *, force: bool = False
) -> Path:
    path = repo_root / TICKETS_AGENTS_REL_PATH
    path.parent.mkdir(parents=True, exist_ok=True)
    content = build_tickets_agents_markdown(repo_root=repo_root)
    if content and not content.endswith("\n"):
        content += "\n"

    if path.exists() and not force:
        try:
            existing = path.read_text(encoding="utf-8")
        except OSError:
            existing = ""
        if TICKETS_AGENTS_GENERATED_MARKER not in existing:
            return path
        if existing == content:
            return path

    path.write_text(content, encoding="utf-8")
    return path
