"""Discover the projects the console shows — each one a warden + a fed-resources tree.

A project participates iff it has a ``fed-resources/`` directory (where its warden
socket lives and where the catalog / run-records / live logs are read). Sources, in
order: an explicit ``roots`` argument (each taken as a *project* dir), then
``MGF_TEST_WEB_ROOTS`` (a ``:``-separated list of project dirs), then
``MGF_TEST_WEB_ROOT`` (one base dir whose immediate children are scanned), else the
sibling-projects workspace this package lives in. The console never authors here.
"""

from __future__ import annotations

import os
import tomllib
from dataclasses import dataclass
from pathlib import Path

#: The sibling-projects workspace (``…/PycharmProjects``): discovery.py → web →
#: test_web → mgf → src → <project-root> → workspace (six parents up).
_DEFAULT_ROOT = Path(__file__).resolve().parents[5]

ROOTS_ENV = "MGF_TEST_WEB_ROOTS"
ROOT_ENV = "MGF_TEST_WEB_ROOT"


#: The fleet's Woodpecker host — the default when a project declares no `[ci] host`.
#: (The old hardcoded ``woodpecker.magogi.local`` was a placeholder.)
WOODPECKER_DEFAULT = "https://195.15.241.48.sslip.io"


@dataclass(frozen=True)
class Project:
    """A project the console can read + submit run-requests to."""

    slug: str
    root: Path
    name: str
    kind: str
    ci_host: str = WOODPECKER_DEFAULT
    ci_repo: str = ""


def _meta(root: Path) -> tuple[str, str, str, str]:
    """``(name, kind, ci_host, ci_repo)`` from ``fed-resources/source/project.toml``.

    ``[project] name/id/kind`` as before; plus an optional ``[ci] host/repo`` that
    turns the CI view's Watch links into real per-repo Woodpecker links. ``host``
    defaults to the fleet's; ``repo`` is the Woodpecker repo id/path (unset ⇒ the
    CI view shows honest absence rather than a fabricated link).
    """
    source = root / "fed-resources" / "source" / "project.toml"
    name, kind, ci_host, ci_repo = root.name, "", WOODPECKER_DEFAULT, ""
    if source.is_file():
        try:
            data = tomllib.loads(source.read_text(encoding="utf-8"))
        except (tomllib.TOMLDecodeError, OSError):
            return name, kind, ci_host, ci_repo
        project = data.get("project", {})
        if isinstance(project, dict):
            name = str(project.get("name", project.get("id", name)))
            kind = str(project.get("kind", ""))
        ci = data.get("ci", {})
        if isinstance(ci, dict):
            ci_host = str(ci.get("host", ci_host)).rstrip("/")
            ci_repo = str(ci.get("repo", "")).strip("/")
    return name, kind, ci_host, ci_repo


def _project(root: Path) -> Project:
    name, kind, ci_host, ci_repo = _meta(root)
    return Project(slug=root.name, root=root, name=name, kind=kind, ci_host=ci_host, ci_repo=ci_repo)


def _participates(child: Path) -> bool:
    return (child / "fed-resources").is_dir()


def _explicit_roots() -> list[Path] | None:
    """Explicit project dirs from ``roots`` env (``None`` ⇒ fall through to a scan)."""
    raw = os.environ.get(ROOTS_ENV, "").strip()
    if not raw:
        return None
    return [Path(p).expanduser() for p in raw.split(os.pathsep) if p.strip()]


def discover_projects(roots: list[Path] | None = None) -> list[Project]:
    """Projects to show, sorted by slug, deduped.

    Explicit project dirs (the ``roots`` arg or ``MGF_TEST_WEB_ROOTS``) are taken
    as-is — each becomes a project regardless of contents, so a freshly-pointed
    project renders honest absence rather than vanishing. Otherwise a *base* dir
    (``MGF_TEST_WEB_ROOT`` or the workspace default) is scanned and only
    ``fed-resources/``-bearing children participate.
    """
    explicit = roots if roots is not None else _explicit_roots()
    seen: dict[str, Project] = {}
    if explicit is not None:
        for root in explicit:
            if root.is_dir() and root.name not in seen:
                seen[root.name] = _project(root)
        return [seen[slug] for slug in sorted(seen)]

    base = Path(os.environ.get(ROOT_ENV, "").strip() or _DEFAULT_ROOT).expanduser()
    if base.is_dir():
        for child in sorted(base.iterdir()):
            if child.is_dir() and _participates(child) and child.name not in seen:
                seen[child.name] = _project(child)
    return [seen[slug] for slug in sorted(seen)]


def find_project(slug: str, roots: list[Path] | None = None) -> Project | None:
    return next((p for p in discover_projects(roots) if p.slug == slug), None)
