"""App factory — wiring ONLY. No routes, no business logic, no schema.

Routers register here as they land. The SPA (frontend/dist) is mounted last
so /api and /notes take precedence; unknown paths fall back to index.html
for client-side routing.
"""
from __future__ import annotations

import os

from fastapi import FastAPI
from fastapi.responses import FileResponse
from starlette.staticfiles import StaticFiles

from .auth import AuthMiddleware
from .errors import install_handlers
from .routers import (
    archive,
    artifacts,
    auth,
    chat,
    featured_images,
    areas,
    catalog,
    edit,
    export,
    inventory,
    layout,
    media,
    meta,
    notes,
    plantings,
    plants,
    timeline,
    years,
)
from .version import APP_VERSION

DIST_DIR = os.environ.get("FRONTEND_DIST", "/app/dist")


def create_app() -> FastAPI:
    app = FastAPI(
        title="Garden",
        version=APP_VERSION,
        docs_url=None,          # no public API explorer on a personal app
        redoc_url=None,
        openapi_url="/api/openapi.json",  # behind auth via middleware
    )
    install_handlers(app)
    app.add_middleware(AuthMiddleware)

    app.include_router(auth.router)  # /login, /auth/callback, /logout (public)
    app.include_router(meta.router)
    app.include_router(areas.router)
    app.include_router(layout.router)
    app.include_router(plantings.router)
    app.include_router(plants.router)
    app.include_router(catalog.router)
    app.include_router(inventory.router)
    app.include_router(years.router)
    app.include_router(notes.router)
    app.include_router(timeline.router)
    app.include_router(media.router)
    app.include_router(edit.router)
    app.include_router(archive.router)
    app.include_router(export.router)
    app.include_router(artifacts.router)
    app.include_router(featured_images.router)
    app.include_router(chat.router)
    # P4+: assistant persona, reminders, notifications, semantic search

    if os.path.isdir(DIST_DIR):
        assets = os.path.join(DIST_DIR, "assets")
        if os.path.isdir(assets):
            app.mount("/assets", StaticFiles(directory=assets), name="assets")

        @app.get("/{full_path:path}", include_in_schema=False)
        def spa_fallback(full_path: str):
            # Serve real files at the dist root (manifest, sw.js, icons…);
            # everything else falls back to the SPA shell. The SILENT auto-
            # signin probe is CLIENT-triggered (AppShell → /auth/probe) rather
            # than done here, because the PWA service worker serves navigations
            # (including "/") from its cache offline, so a server-side probe on
            # the document request would never run once the SW is installed.
            candidate = os.path.normpath(os.path.join(DIST_DIR, full_path))
            if candidate.startswith(DIST_DIR) and os.path.isfile(candidate):
                return FileResponse(candidate)
            return FileResponse(os.path.join(DIST_DIR, "index.html"))

    return app


app = create_app()
