"""Initiative management: scoped product specs that drive the build loop.

An **initiative** is a bounded chunk of work defined by the user, with its
own spec, optional constraints, and a self-contained task queue.

Directory layout::

    .autobuilder/
        scope.md                    # global project context
        constraints.md              # global constraints
        active-initiative           # plain text: initiative dir name
        initiatives/
            0001-my-app/
                spec.md             # initiative product spec
                constraints.md      # optional per-initiative constraints
                tasks/
                    0001-root.md
                    0002-auth.md
                    ...
            0002-screener-v2/
                spec.md
                tasks/
                    0001-root.md
                    ...
"""

import re
from pathlib import Path
from typing import List, Optional, Tuple

from common import TASK_NUM_WIDTH
from frontmatter import read_frontmatter
from tasks import next_task_number


# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------

INITIATIVE_NUM_WIDTH = 4


def initiatives_base_dir(project_dir: Path) -> Path:
    return project_dir / ".autobuilder" / "initiatives"


def get_initiative_dir(project_dir: Path, initiative_id: str) -> Path:
    return initiatives_base_dir(project_dir) / initiative_id


def get_initiative_tasks_dir(project_dir: Path, initiative_id: str) -> Path:
    return get_initiative_dir(project_dir, initiative_id) / "tasks"


# ---------------------------------------------------------------------------
# Reading
# ---------------------------------------------------------------------------


def read_active_initiative(project_dir: Path) -> Optional[str]:
    """Return the active initiative dir name, or ``None`` if unset."""
    path = project_dir / ".autobuilder" / "active-initiative"
    if not path.exists():
        return None
    try:
        text = path.read_text(encoding="utf-8").strip()
    except Exception:
        return None
    if not text:
        return None
    init_dir = initiatives_base_dir(project_dir) / text
    if init_dir.is_dir():
        return text
    return None


def resolve_active_initiative(project_dir: Path) -> Optional[str]:
    """Return the active initiative, falling back to the first available."""
    active = read_active_initiative(project_dir)
    if active:
        return active
    for init_id, _ in list_initiatives(project_dir):
        set_active_initiative(project_dir, init_id)
        return init_id
    return None


def get_initiative_spec(project_dir: Path, initiative_id: str) -> str:
    spec_path = get_initiative_dir(project_dir, initiative_id) / "spec.md"
    if not spec_path.exists():
        return ""
    try:
        return spec_path.read_text(encoding="utf-8").strip()
    except Exception:
        return ""


def get_initiative_constraints(project_dir: Path, initiative_id: str) -> str:
    path = get_initiative_dir(project_dir, initiative_id) / "constraints.md"
    if not path.exists():
        return ""
    try:
        return path.read_text(encoding="utf-8").strip()
    except Exception:
        return ""


def list_initiatives(project_dir: Path) -> List[Tuple[str, Path]]:
    """Return ``(initiative_dir_name, init_dir_path)`` for every initiative."""
    base = initiatives_base_dir(project_dir)
    if not base.exists():
        return []
    results: List[Tuple[str, Path]] = []
    for child in sorted(base.iterdir()):
        if child.is_dir() and (child / "spec.md").exists():
            results.append((child.name, child))
    return results


def initiative_slug(initiative_id: str) -> str:
    """Strip the numeric prefix from an initiative dir name."""
    m = re.match(r"^\d+-(.+)$", initiative_id)
    return m.group(1) if m else initiative_id


# ---------------------------------------------------------------------------
# Numbering
# ---------------------------------------------------------------------------


def max_initiative_number(project_dir: Path) -> int:
    """Return the highest numeric prefix among initiative directories."""
    highest = 0
    base = initiatives_base_dir(project_dir)
    if not base.exists():
        return highest
    for child in base.iterdir():
        if child.is_dir():
            m = re.match(r"^(\d+)-", child.name)
            if m:
                highest = max(highest, int(m.group(1)))
    return highest


def next_initiative_number(project_dir: Path) -> int:
    return max_initiative_number(project_dir) + 1


# ---------------------------------------------------------------------------
# Creation
# ---------------------------------------------------------------------------


def create_initiative(
    project_dir: Path,
    slug: str,
    spec: str,
    constraints: str = "",
    num: Optional[int] = None,
) -> str:
    """Create a new initiative directory with spec and optional constraints.

    Returns the full initiative dir name (e.g. ``0002-screener-v2``).
    """
    if num is None:
        num = next_initiative_number(project_dir)
    dir_name = f"{num:0{INITIATIVE_NUM_WIDTH}d}-{slug}"
    init_dir = initiatives_base_dir(project_dir) / dir_name
    tasks_dir = init_dir / "tasks"
    tasks_dir.mkdir(parents=True, exist_ok=True)
    (init_dir / "spec.md").write_text(spec + "\n", encoding="utf-8")
    if constraints:
        (init_dir / "constraints.md").write_text(constraints + "\n", encoding="utf-8")
    return dir_name


# ---------------------------------------------------------------------------
# Bootstrap: seed a root task from an initiative spec
# ---------------------------------------------------------------------------


def has_tasks(project_dir: Path, initiative_id: str) -> bool:
    """Return ``True`` if the initiative's tasks/ dir contains any .md files."""
    td = get_initiative_tasks_dir(project_dir, initiative_id)
    if not td.exists():
        return False
    return any(td.glob("*.md"))


def create_root_task_from_spec(
    project_dir: Path,
    initiative_id: str,
    root_id: str,
) -> Optional[Path]:
    """Create a root task file seeded from the initiative's spec.md.

    Returns the path to the created file, or ``None`` if spec is empty.
    """
    spec = get_initiative_spec(project_dir, initiative_id)
    if not spec:
        return None

    tasks_dir = get_initiative_tasks_dir(project_dir, initiative_id)
    tasks_dir.mkdir(parents=True, exist_ok=True)

    slug = initiative_slug(initiative_id)
    task_id = f"{slug}-root"
    num = next_task_number(tasks_dir)
    filename = f"{num:0{TASK_NUM_WIDTH}d}-{task_id}.md"
    path = tasks_dir / filename

    title = slug.replace("-", " ").title()
    for line in spec.splitlines():
        stripped = line.strip()
        if stripped.startswith("# "):
            title = stripped.lstrip("# ").strip()
            break

    content = (
        f"---\n"
        f"id: {task_id}\n"
        f'title: "{title}"\n'
        f"parent: {root_id}\n"
        f"deps: []\n"
        f"split: null\n"
        f"depth: 0\n"
        f"planned: false\n"
        f"executed: false\n"
        f"---\n\n"
        f"{spec}\n"
    )
    path.write_text(content, encoding="utf-8")
    return path


# ---------------------------------------------------------------------------
# Lifecycle helpers
# ---------------------------------------------------------------------------


def set_active_initiative(project_dir: Path, initiative_id: Optional[str]) -> None:
    """Set (or clear) the active initiative."""
    path = project_dir / ".autobuilder" / "active-initiative"
    if initiative_id:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(initiative_id + "\n", encoding="utf-8")
    elif path.exists():
        path.unlink(missing_ok=True)
