"""Minimum runtime substrate for new generated Pages apps.

This is not a product. It is the toolchain skeleton a Starter overlays.
Imported and connected repositories must not call these writers.
"""

from __future__ import annotations

import json
from pathlib import Path

from src.domain.projects.bootstrap_contract import (
    BOOTSTRAP_MODE_STARTER_ASSEMBLY,
    NEW_PROJECT_BOOTSTRAP_MODE,
)
from src.domain.starter_library.assembler import assemble_starter_files, assert_thin_shell

BOOTSTRAP_VERSION = "starter_assembly"
PAGES_SUBSTRATE_TYPES = frozenset({"html", "react_app", "nuxt_app"})

_PRODUCT_PATH_MARKERS = (
    "components/starter/",
    "src/components/starter/",
    "src/pages/Document",
    "src/pages/Documents",
    "pages/documents/",
    "p2hs.platform.json",
)
_PRODUCT_TEXT_MARKERS = (
    "NUXT_PUBLIC_P2HS_GATEWAY_URL",
    "VITE_P2HS_GATEWAY_URL",
    "NUXT_PUBLIC_P2HS_CLOUD_PUBLIC_URL",
    "VITE_P2HS_CLOUD_PUBLIC_URL",
    "NUXT_PUBLIC_P2HS_CORE_PUBLIC_URL",
    "VITE_P2HS_CORE_PUBLIC_URL",
    "gatewayPublicUrl",
    "cloudgate.p2hs.com",
)
_GITIGNORE = """\
node_modules/
.nuxt/
dist/
.output/
.env
*.local
.DS_Store
# P2HS control plane — never commit
.p2hs.json
.p2hs/
.p2hs-control/
.cursor/
.cursorrules
CLAUDE.md
AGENTS.md
p2hs.platform.json
p2hs/
*.p2hs-local.bak
.p2hs/local-bak/
"""


def uses_starter_assembly(project_type: str) -> bool:
    return (
        project_type in PAGES_SUBSTRATE_TYPES
        and NEW_PROJECT_BOOTSTRAP_MODE == BOOTSTRAP_MODE_STARTER_ASSEMBLY
    )


def assemble_runtime_substrate(project_type: str) -> dict[str, str]:
    """Return the minimum toolchain files for a Pages framework."""
    if project_type not in PAGES_SUBSTRATE_TYPES:
        raise ValueError(f"no runtime substrate for {project_type}")
    if project_type == "html":
        files = _html_substrate()
    elif project_type == "react_app":
        files = _react_substrate()
    else:
        files = _nuxt_substrate()
    files[".gitignore"] = _GITIGNORE
    files["README.md"] = _readme()
    _assert_not_product(files)
    return files


def assemble_generated_project(
    project_type: str,
    *,
    starter_id: str | None = None,
) -> dict[str, str]:
    """Substrate plus optional thin Starter overlay. Does not emit BFF routes."""
    files = dict(assemble_runtime_substrate(project_type))
    if starter_id:
        overlay = assemble_starter_files(starter_id, framework=project_type)
        assert_thin_shell(overlay)
        files.update(overlay)
    _assert_not_product(files)
    return files


def write_generated_project_tree(
    repo_path: str,
    project_type: str,
    starter_id: str | None = None,
) -> str:
    """Write assembled files onto an empty generated-create tree.

    Callers must not use this for import, template-clone, or connected-repo adopt.
    """
    root = Path(repo_path)
    root.mkdir(parents=True, exist_ok=True)
    files = assemble_generated_project(project_type, starter_id=starter_id)
    for relative, content in files.items():
        target = root / relative
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(content, encoding="utf-8", newline="\n")
    return BOOTSTRAP_VERSION


def _assert_not_product(files: dict[str, str]) -> None:
    for relative in files:
        normalized = relative.replace("\\", "/")
        for marker in _PRODUCT_PATH_MARKERS:
            if marker in normalized:
                raise ValueError(f"substrate must not include product path {relative}")
    joined = "\n".join(files.values())
    for marker in _PRODUCT_TEXT_MARKERS:
        if marker in joined:
            raise ValueError(f"substrate leaked {marker}")


def _readme() -> str:
    return (
        "Generated by P2HS.\n\n"
        "The browser calls relative `/api`. Server-side BFF adapters are installed "
        "by the platform; do not embed Cloud or Core origins in client code.\n"
    )


def _package(name: str, payload: dict) -> str:
    body = {"name": name, "private": True, "version": "0.0.1", "type": "module", **payload}
    return json.dumps(body, indent=2) + "\n"


def _nuxt_substrate() -> dict[str, str]:
    return {
        "package.json": _package(
            "p2hs-nuxt-app",
            {
                "scripts": {
                    "build": "nuxt build",
                    "generate": "nuxt generate",
                    "dev": "nuxt dev",
                    "preview": "nuxt preview",
                },
                "engines": {"node": ">=22"},
                "dependencies": {
                    "nuxt": "^3.15.0",
                    "vue": "^3.5.0",
                    "vue-router": "^4.5.0",
                },
            },
        ),
        "nuxt.config.ts": """export default defineNuxtConfig({
  nitro: {
    preset: process.env.NITRO_PRESET,
    cloudflareModuleWorker: true,
  },
  runtimeConfig: {
    cloudGatewayUrl: process.env.CLOUD_GATEWAY_URL || "",
    cloudServiceToken: process.env.CLOUD_SERVICE_TOKEN || "",
    coreUrl: process.env.P2HS_CORE_URL || "",
    projectId: process.env.PROJECT_ID || "",
    public: {
      projectId: process.env.PROJECT_ID || "",
    },
  },
});
""",
        "app.vue": """<template>
  <div>
    <NuxtPage />
  </div>
</template>
""",
        "pages/index.vue": """<template>
  <main>
    <p>Application source. Data goes through /api.</p>
  </main>
</template>
""",
    }


def _react_substrate() -> dict[str, str]:
    return {
        "package.json": _package(
            "p2hs-react-app",
            {
                "scripts": {
                    "dev": "vite",
                    "build": "vite build",
                    "preview": "vite preview",
                },
                "engines": {"node": ">=22"},
                "dependencies": {
                    "react": "^19.0.0",
                    "react-dom": "^19.0.0",
                    "react-router": "^7.1.0",
                    "react-router-dom": "^7.1.0",
                },
                "devDependencies": {
                    "@vitejs/plugin-react": "^4.3.4",
                    "typescript": "^5.7.2",
                    "vite": "^6.0.0",
                },
            },
        ),
        "vite.config.ts": """import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
});
""",
        "tsconfig.json": json.dumps(
            {
                "compilerOptions": {
                    "target": "ES2022",
                    "module": "ESNext",
                    "moduleResolution": "bundler",
                    "jsx": "react-jsx",
                    "strict": True,
                    "skipLibCheck": True,
                    "noEmit": True,
                },
                "include": ["src"],
            },
            indent=2,
        )
        + "\n",
        "index.html": """<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
""",
        "src/vite-env.d.ts": """/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly DEV: boolean;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
""",
        "src/main.tsx": """import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
""",
        "src/App.tsx": """export default function App() {
  return (
    <main>
      <p>Application source. Data goes through /api.</p>
    </main>
  );
}
""",
    }


def _html_substrate() -> dict[str, str]:
    return {
        "index.html": """<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
  </head>
  <body>
    <main>
      <p>Application source. Data goes through /api.</p>
    </main>
  </body>
</html>
""",
    }
