"""Dispatch-time wiring for the deterministic HTML -> Nuxt conversion (static_to_nuxt.py).

When a caller asks to ``migrate`` or ``replace`` a Nuxt project's UI with a static HTML prototype, the pages
are converted here, mechanically and exactly, and committed with the run. The agent then starts from a project
that already renders the prototype identically and only adds what has to be functional, instead of retyping
eleven pages (lossy) or serving the files as they are (not a conversion). Off by default
(``CODEGEN_STATIC_CONVERSION_ENABLED``); see docs/STATIC_TO_NUXT.md.

This module is pure planning: it decides, from the snapshot files and the branch's current file list, which
files to write and which stale ones to delete, and writes the agent's instructions. The caller commits.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from src.domain.codegen.static_to_nuxt import convert_static_site, route_for

ELIGIBLE_ADOPTIONS = frozenset({"migrate", "replace"})
ELIGIBLE_PROJECT_TYPES = frozenset({"nuxt_app"})

# Routes that belong to the platform or to the project's own auth/admin: a prototype page with one of these names
# is reported, never converted, so it cannot replace them.
PROTECTED_NAMES = frozenset(
    {
        "admin", "login", "logout", "signin", "signup", "register", "auth", "api", "account", "settings",
        "reset-password", "forgot-password", "verify", "callback", "invite",
    }
)
_MAX_LISTED = 25


def protected_routes() -> set[str]:
    routes: set[str] = set()
    for name in PROTECTED_NAMES:
        routes.update({route_for(name), f"/{name}.html"})
    return routes


def is_eligible(settings: Any, project_type: str | None, plan_context: dict[str, Any] | None) -> bool:
    """Only a deliberate ``migrate``/``replace`` of a Nuxt project that carries imported source."""
    ctx = plan_context or {}
    return bool(
        getattr(settings, "codegen_static_conversion_enabled", False)
        and (project_type or "") in ELIGIBLE_PROJECT_TYPES
        and ctx.get("source_adoption") in ELIGIBLE_ADOPTIONS
        and isinstance(ctx.get("import_snapshot"), dict)
        and ctx["import_snapshot"].get("files")
    )


@dataclass
class Conversion:
    files: dict[str, str | None]  # path -> content; None deletes the path
    summary: dict[str, Any]
    appendix: str = ""
    deleted: list[str] = field(default_factory=list)


def _shown(items: list[str]) -> str:
    text = ", ".join(f"`{i}`" for i in items[:_MAX_LISTED])
    return text + (f" and {len(items) - _MAX_LISTED} more" if len(items) > _MAX_LISTED else "")


def build_appendix(pages: list[dict[str, Any]], skipped: list[dict[str, str]], deleted: list[str],
                   listing_known: bool) -> str:
    urls = [p["route"] for p in pages]
    lines = [
        "## Pages already converted by the platform",
        "The prototype's HTML pages have been converted, mechanically and exactly, into pages of this Nuxt project "
        "and are committed with this run. They render identically to the prototype; that is the baseline you keep.",
        f"Pages: {_shown(urls)}. Each keeps its original markup in `legacy/pages/<name>.html`, its original CSS and "
        "JavaScript under `public/assets/`, and loads the page's scripts, in order, through "
        "`composables/useLegacyScripts.ts` (the list is in `pages/<name>.html.vue`; the home page is `pages/index.vue`). "
        "A clean URL such as `/about` redirects to `/about.html`: the prototype's scripts read the file name.",
        "Do NOT re-template these pages into components, do NOT rewrite or restyle them, do NOT copy the imported HTML "
        "into `public/` and serve it as static files, and do NOT delete `legacy/`. The existing markup, CSS and scripts "
        "are the source of truth for the design.",
        "Your job is the functional part: server routes and persistence, forms, integrations, the admin area, "
        "configuration. Hook into the existing pages minimally: to add a script, append it to the page's "
        "`useLegacyScripts([...])` list; to change markup, edit `legacy/pages/<name>.html` in place.",
    ]
    if deleted:
        lines.append(
            f"The platform removed older static copies that would have shadowed these pages: {_shown(deleted)}."
        )
    elif not listing_known:
        lines.append(
            "If `public/<name>.html` already exists for a converted page, delete it: a static file is served before "
            "the page and would shadow it."
        )
    if skipped:
        lines.append("Not converted (handle these yourself, without replacing the routes above):")
        lines += [f"- `{s['path']}`: {s['reason']}" for s in skipped[:_MAX_LISTED]]
    return "\n".join(lines)


def plan_conversion(site_files: dict[str, str], existing_paths: set[str] | None) -> Conversion | None:
    """Files to commit for a converted site, or None when there is nothing to convert.

    ``existing_paths`` is the branch's current file list (None if it could not be read, in which case nothing is
    deleted and the agent is told to)."""
    result = convert_static_site(site_files, existing_routes=protected_routes())
    if not result.converted:
        return None
    files: dict[str, str | None] = dict(result.files)
    deleted: list[str] = []
    for page in result.pages:
        # A static copy from an earlier build (public/about.html) is served before the page and shadows it.
        stem = page["source"].rsplit(".", 1)[0]
        stale = f"public/{stem}.html"
        if existing_paths is not None and stale in existing_paths and stale not in files:
            files[stale] = None
            deleted.append(stale)
    summary = {
        "pages": [{"source": p["source"], "route": p["route"]} for p in result.pages],
        "skipped": result.skipped,
        "deleted": deleted,
        "files": sum(1 for v in files.values() if v is not None),
    }
    return Conversion(
        files=files,
        summary=summary,
        appendix=build_appendix(result.pages, result.skipped, deleted, existing_paths is not None),
        deleted=deleted,
    )
