"""The Alexandria launch screen.

Single source of truth for the ASCII splash the installer and CLI print (and the
in-vault agent can show on first contact). The 77-column ANSI-shadow wordmark is
centered inside an 81-column frame; every glyph used is single display-width in a
standard terminal (full-block, box-drawing, ``● ▸ · “ ”`` — deliberately no emoji),
so the box stays aligned wherever it is printed.

    python -m scripts.banner            # generic screen
    alexandria banner                   # same, via the CLI
    alexandria banner --vault <path>    # personalized from a vault's config
"""
from pathlib import Path

from . import __version__

W = 81  # interior width; the 77-wide wordmark centers with 2/2 margins

ART = [
    " █████╗ ██╗     ███████╗██╗  ██╗ █████╗ ███╗   ██╗██████╗ ██████╗ ██╗ █████╗ ",
    "██╔══██╗██║     ██╔════╝╚██╗██╔╝██╔══██╗████╗  ██║██╔══██╗██╔══██╗██║██╔══██╗",
    "███████║██║     █████╗   ╚███╔╝ ███████║██╔██╗ ██║██║  ██║██████╔╝██║███████║",
    "██╔══██║██║     ██╔══╝   ██╔██╗ ██╔══██║██║╚██╗██║██║  ██║██╔══██╗██║██╔══██║",
    "██║  ██║███████╗███████╗██╔╝ ██╗██║  ██║██║ ╚████║██████╔╝██║  ██║██║██║  ██║",
    "╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝ ╚═╝  ╚═╝╚═╝╚═╝  ╚═╝",
]

DEFAULT_TAGLINE = "Memory Server and Agent Library"

# The static subsystems, shown as a (guaranteed-to-succeed) boot loader. Only
# what every vault truly has by construction belongs here — the render itself
# bakes these in, so "ok" is always the truth. The scheduled automations
# (digest, sweep, sync) are deliberately NOT in this list: their state depends
# on the vault's config, so the generic screen omits them and from_vault()
# reports the status each one has actually earned (_automation_systems).
SYSTEMS = [
    "knowledge base",
    "persona",
    "working memory",
    "skills engine",
]


def _automation_systems(cfg: dict) -> list:
    """The scheduled automations, with the status THIS vault's config has earned.

    A fresh zip install has no runner and no remote — a banner that greets its
    owner with "daily digest ok · vault sync ok" is claiming machinery that
    doesn't exist. Statuses:
      daily digest — "ok" only when an automation runner will send it on a
                     schedule; otherwise "on demand" (say "good morning").
      hourly sweep — "ok" only with a runner; otherwise "not scheduled".
      vault sync   — "ok" only with a remote repo configured; otherwise
                     "local-only" (writes still commit to local git history).
    """
    runner = (cfg.get("automation") or {}).get("runner")
    return [
        ("daily digest", "ok" if runner else "on demand"),
        ("hourly sweep", "ok" if runner else "not scheduled"),
        ("vault sync", "ok" if cfg.get("repo") else "local-only"),
    ]

# The in-vault skills the owner can invoke. Names match template/core/skills/;
# from_vault() re-derives the list from an actual vault so module skills show up too.
SKILLS = [
    ("capabilities",      "everything I can do & how"),
    ("settings",          "tune any agent or vault setting"),
    ("vault-ingestion",   "file anything you paste"),
    ("digest-now",        "a fresh brief on demand"),
    ("daily-progress",    "the end-of-day debrief"),
    ("alexandria-update", "pull the latest core"),
]

_SKILL_BLURB = dict(SKILLS)  # name -> one-liner, for skills discovered on disk
_SKILL_BLURB.update({        # module skills (surface only when their module is enabled)
    "attention-digest": "what needs you, on demand",
    "vault-federation": "share with a peer vault",
    "imessage":         "read my texts (local, read-only)",
    "news":             "the cross-checked world briefing",
    "screentime":       "where did my time go",
    "screenshots":      "sweep my screenshots into notes",
    "contacts":         "import my Mac contacts (read-only)",
    "whatsapp":         "read my WhatsApp (local, read-only)",
    # core utilities (discovered on disk; listed here for their one-liners)
    "github-sync":      "connect a private GitHub backup",
    "clean-root":       "tidy stray empties from the root",
    "vault-stats":      "how big & how fast it's growing",
    "vault-integrity":  "check conventions & links",
    "vault-query":      "structured retrieval (vaultq)",
})


def brand_version(version=None) -> str:
    """Marketing-style short version for the footer: ``2.2.0`` -> ``v2.2``."""
    parts = (version or __version__).split(".")
    return "v" + ".".join(parts[:2]) if parts and parts[0].isdigit() else "v?"


def _demarkup(note: str) -> str:
    """Strip inline-markdown emphasis for the plain-text frame WITHOUT mangling
    identifiers: a ``code`` span keeps its content (only the backticks go) and
    snake_case underscores survive — only ``**bold**`` / ``*italic*`` / ``_italic_``
    emphasis markers are removed. (A blanket ``[*`_]`` strip turned ``vault_core``
    into ``vaultcore`` and ``test_x.py`` into ``testx.py`` on the launch screen.)"""
    import re
    note = re.sub(r"`([^`]*)`", r"\1", note)               # `code` -> code (keep content)
    note = re.sub(r"(?<!\w)_([^_]*)_(?!\w)", r"\1", note)   # _italic_ -> italic; skip snake_case
    return note.replace("**", "").replace("*", "").replace("`", "")


def _release_notes(version=None, limit=4):
    """The current version's changelog bullets — plain text, truncated to the frame —
    parsed from CHANGELOG.md (the single source of release notes). Empty if none."""
    import re
    v = version or __version__
    try:
        text = (Path(__file__).resolve().parents[1] / "CHANGELOG.md").read_text(encoding="utf-8")
    except OSError:
        return []
    out, in_section, maxlen = [], False, W - 7
    for line in text.splitlines():
        s = line.strip()
        if s.startswith("## "):
            in_section = f"[{v}]" in s      # matches "## [2.3.0] - date"
            continue
        if in_section and s.startswith("- "):
            note = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", s[2:])   # [text](url) -> text
            note = _demarkup(note)                         # emphasis off, identifiers intact
            note = re.sub(r"\s+", " ", note).strip()
            if len(note) > maxlen:
                note = note[:maxlen - 1].rstrip() + "…"
            out.append(note)
            if len(out) >= limit:
                break
    return out


def render(version=None, tagline=DEFAULT_TAGLINE, systems=None, skills=None,
           agent=None, versioned=True) -> str:
    """Build the full launch screen as a string.

    ``agent`` (the vault's agent name) personalizes the footer; everything else is
    generic. ``systems``/``skills`` default to the module-level constants.

    ``versioned=False`` drops everything derived from ``__version__`` — the
    what's-new panel and the footer's version tag — leaving a screen that is stable
    across releases. That is the form the macOS installer embeds: its copy is a
    literal heredoc, and while it carried the release notes, every version bump
    silently desynced it unless the banner was regenerated in the right order.
    """
    systems = SYSTEMS if systems is None else systems
    skills = SKILLS if skills is None else skills

    out = []
    def bd(l, r): out.append(l + "─" * W + r)                       # noqa: E704
    def rule():   out.append("├" + "─" * W + "┤")                   # noqa: E704
    def row(s):   s = s[:W]; out.append("│" + s + " " * (W - len(s)) + "│")  # noqa: E704
    def blank():  out.append("│" + " " * W + "│")                   # noqa: E704
    def ctr(s):
        s = s[:W]; pad = W - len(s); l = pad // 2
        out.append("│" + " " * l + s + " " * (pad - l) + "│")
    def item(mark, label, detail, lw): row("   " + mark + "  " + label.ljust(lw) + "   " + detail)
    def loaded(name, status="ok"):
        left = "   ●  " + name
        dots = max(1, W - len(left) - len(status) - 3)   # -3 leaves a right gutter before the frame
        row(left + " " + "·" * dots + " " + status)

    bd("╭", "╮")
    blank()
    for r in ART:
        ctr(r)
    blank()
    ctr("· · ·    " + tagline + "    · · ·")
    blank()
    rule()
    blank()
    row("   loading systems …")
    for entry in systems:
        name, status = entry if isinstance(entry, tuple) else (entry, "ok")
        loaded(name, status)
    blank()
    rule()
    blank()
    row("   skills")
    for name, desc in skills:
        item("▸", name, desc, 18)
    blank()
    rule()
    blank()
    ctr("“ What you feed me, I remember.  What matters, I surface. ”")
    blank()
    ctr("▸ “good morning” — your brief        ▸ “settings” — tune me")
    blank()

    blank()
    ctr("© Keystone Context Systems, Inc.")
    blank()

    notes = _release_notes(version) if versioned else []
    if notes:
        rule()
        blank()
        row("   what’s new · v" + (version or __version__))
        for note in notes:
            row("   •  " + note)
        blank()

    if versioned:
        v = brand_version(version)
        tag = f" {agent} · {v} " if agent else f" {v} "
        if len(tag) > W - 6:        # a very long agent name — fall back to the version alone
            tag = f" {v} "
    else:
        tag = f" {agent} " if agent else ""
        if len(tag) > W - 6:        # a very long agent name — plain rule
            tag = ""
    out.append("╰" + "─" * (W - len(tag) - 3) + tag + "───╯")
    return "\n".join(out)


def _skills_from_vault(vault: Path):
    """The skills actually rendered into a vault (so module-added skills appear).

    Read from the neutral `Agent/skills/`, not the generated `.claude/` adapter —
    same set either way, but the neutral tree is the source of truth and the one
    a brain reached only over MCP still has."""
    d = Path(vault) / "Agent" / "skills"
    if not d.is_dir():
        return None
    found = [(p.stem, _SKILL_BLURB.get(p.stem, "in-vault skill"))
             for p in sorted(d.glob("*.md"))]
    return found or None


def from_vault(vault, version=None) -> str:
    """Personalize the screen from a vault's ``Agent/config.json`` + its skills.

    The systems panel is the generic baked-in list plus the automations with
    their honest, config-derived statuses — never a blanket "ok"."""
    from .config import load_config
    vault = Path(vault)
    cfg = load_config(str(vault)) or {}
    agent = (cfg.get("agent") or {}).get("name") or None
    return render(version=version, agent=agent, skills=_skills_from_vault(vault),
                  systems=SYSTEMS + _automation_systems(cfg))


def main(argv=None) -> int:
    print(render())
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
