"""Template strings for the ``mld init`` scaffolding command.

All templates use ``__PLACEHOLDER__`` delimiters so they don't conflict
with curly braces in Python, TypeScript, JSON, or CSS.
"""

# ---------------------------------------------------------------------------
# Backend templates
# ---------------------------------------------------------------------------

PYPROJECT_TOML = """\
[project]
name = "__PACKAGE_NAME__"
version = "0.1.0"
description = "__DESCRIPTION__"
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
authors = [{ name = "__AUTHOR__", email = "__EMAIL__" }]
keywords = ["mld", "plugin", "__PLUGIN_TYPE__"]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Framework :: FastAPI",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3.12",
]

dependencies = [
    "mld-sdk>=__SDK_VERSION__",
    "fastapi>=0.109.0",
    "pydantic>=2.0.0",
    "uvicorn>=0.27.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0.0",
    "pytest-asyncio>=0.23.0",
    "httpx>=0.27.0",
]

[dependency-groups]
dev = [
    "ruff>=0.4.0",
    "pytest>=8.0.0",
    "pytest-asyncio>=0.23.0",
    "httpx>=0.27.0",
]

[project.entry-points."mld.plugins"]
__SLUG__ = "__MODULE_NAME__.plugin:__CLASS_NAME__"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/__MODULE_NAME__"]

[tool.hatch.build.targets.wheel.force-include]
"frontend/dist" = "__MODULE_NAME__/frontend"

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]

[tool.ruff]
target-version = "py312"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
"""

INIT_PY = """\
try:
    from __MODULE_NAME__._version import __version__
except ImportError:
    __version__ = "0.1.0"
"""

PLUGIN_PY = """\
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any

from fastapi import APIRouter
from mld_sdk import (
    AnalysisPlugin,
    HealthStatus,
    PlatformContext,
    PluginCapabilities,
    PluginHealth,
    PluginMetadata,
    PluginType,
)

from __MODULE_NAME__ import __version__
from __MODULE_NAME__.routers import analysis

PLUGIN_NAME = "__SLUG__"
PLUGIN_VERSION = __version__
PLUGIN_DESCRIPTION = "__DESCRIPTION__"
PLUGIN_ROUTES_PREFIX = "__ROUTES_PREFIX__"
PLUGIN_FRONTEND_ROUTE = "__ROUTES_PREFIX__"
PLUGIN_ANALYSIS_TYPE = "custom"
PLUGIN_TYPE = PluginType.__PLUGIN_TYPE_ENUM__


class __CLASS_NAME__(AnalysisPlugin):

    _instance: "__CLASS_NAME__ | None" = None

    def __new__(cls) -> "__CLASS_NAME__":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self) -> None:
        if hasattr(self, "_initialized"):
            return
        self._initialized = True

    @property
    def metadata(self) -> PluginMetadata:
        return PluginMetadata(
            name=PLUGIN_NAME,
            version=PLUGIN_VERSION,
            description=PLUGIN_DESCRIPTION,
            analysis_type=PLUGIN_ANALYSIS_TYPE,
            routes_prefix=PLUGIN_ROUTES_PREFIX,
            plugin_type=PLUGIN_TYPE,
            capabilities=PluginCapabilities(
                requires_auth=False,
                requires_database=False,
                requires_experiments=False,
            ),
            author="__AUTHOR__",
            license="MIT",
        )

    async def initialize(self, context: PlatformContext | None = None) -> None:
        self._context = context
        mode = "integrated" if context else "standalone"
        print(f"[{PLUGIN_NAME}] Initialized in {mode} mode")

    async def shutdown(self) -> None:
        print(f"[{PLUGIN_NAME}] Shutting down...")

    def get_routers(self) -> list[tuple[APIRouter, str]]:
        return [
            (analysis.router, ""),
        ]

    def get_frontend_dir(self) -> str | None:
        frontend_dist = Path(__file__).parent / "frontend"
        if frontend_dist.exists():
            return str(frontend_dist)
        frontend_dist = Path(__file__).parent.parent.parent / "frontend" / "dist"
        if frontend_dist.exists():
            return str(frontend_dist)
        return None

    def get_frontend_config(self) -> dict[str, Any]:
        return {
            "plugin_name": PLUGIN_NAME,
            "version": PLUGIN_VERSION,
            "navItems": [
                {
                    "path": "/",
                    "label": "Home",
                    "icon": "home",
                    "description": PLUGIN_DESCRIPTION,
                },
                {
                    "path": "/analysis",
                    "label": "Analysis",
                    "icon": "chart",
                    "description": "Run analysis on your data",
                },
            ],
        }

    async def check_health(self) -> PluginHealth:
        return PluginHealth(
            status=HealthStatus.HEALTHY,
            message="Plugin is running normally",
            details={
                "mode": "standalone" if self.is_standalone else "integrated",
                "version": PLUGIN_VERSION,
            },
        )


def create_standalone_app():
    from fastapi import FastAPI
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi.staticfiles import StaticFiles

    plugin = __CLASS_NAME__()

    @asynccontextmanager
    async def lifespan(app: FastAPI):
        await plugin.initialize(context=None)
        yield
        await plugin.shutdown()

    app = FastAPI(
        title=f"{PLUGIN_NAME} (Standalone)",
        version=PLUGIN_VERSION,
        description=PLUGIN_DESCRIPTION,
        lifespan=lifespan,
    )

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    for router, prefix in plugin.get_routers():
        app.include_router(router, prefix=f"/api{PLUGIN_ROUTES_PREFIX}{prefix}")

    frontend_dir = plugin.get_frontend_dir()
    if frontend_dir:

        class SPAStaticFiles(StaticFiles):
            async def get_response(self, path: str, scope):
                try:
                    return await super().get_response(path, scope)
                except Exception:
                    return await super().get_response("index.html", scope)

        app.mount(
            PLUGIN_FRONTEND_ROUTE,
            SPAStaticFiles(directory=frontend_dir, html=True),
            name="frontend",
        )

    return app


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "__MODULE_NAME__.plugin:create_standalone_app",
        factory=True,
        host="0.0.0.0",
        port=8003,
        reload=True,
    )
"""

ROUTERS_INIT_PY = """\
"""

ROUTERS_ANALYSIS_PY = """\
from fastapi import APIRouter

router = APIRouter(tags=["analysis"])


@router.get("/health")
async def health():
    return {"status": "healthy", "plugin": "__SLUG__"}


@router.post("/analyze/{experiment_id}")
async def analyze(experiment_id: int):
    return {"experiment_id": experiment_id, "status": "not_implemented"}
"""

SCHEMAS_INIT_PY = """\
"""

SCHEMAS_REQUESTS_PY = """\
from pydantic import BaseModel


class AnalyzeRequest(BaseModel):
    experiment_id: int
"""

TEST_PLUGIN_PY = """\
import pytest
from __MODULE_NAME__.plugin import __CLASS_NAME__, create_standalone_app


class TestPluginMetadata:

    def test_metadata_name(self):
        # Reset singleton for clean test
        __CLASS_NAME__._instance = None
        plugin = __CLASS_NAME__()
        assert plugin.metadata.name == "__SLUG__"

    def test_metadata_routes_prefix(self):
        __CLASS_NAME__._instance = None
        plugin = __CLASS_NAME__()
        assert plugin.metadata.routes_prefix == "__ROUTES_PREFIX__"


class TestStandaloneApp:

    def test_create_standalone_app_returns_fastapi(self):
        __CLASS_NAME__._instance = None
        app = create_standalone_app()
        from fastapi import FastAPI
        assert isinstance(app, FastAPI)
"""

# ---------------------------------------------------------------------------
# Config / meta files
# ---------------------------------------------------------------------------

README_MD = """\
# __PLUGIN_NAME__

__DESCRIPTION__

## Development

```bash
# Install dependencies
uv sync

# Run backend dev server
uv run uvicorn __MODULE_NAME__.plugin:create_standalone_app --factory --reload --port 8003

# Run tests
uv run pytest -v
```

### Frontend

```bash
cd frontend
bun install
bun run dev
```

### Build

```bash
# Build .mld bundle for distribution
mld build .

# Or use the build script
bash scripts/build.sh
```
"""

CHANGELOG_MD = """\
# Changelog

## 0.1.0

- Initial release
"""

GITIGNORE = """\
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
dist/
build/
.eggs/
*.egg
.venv/
venv/
*.mld
uv.lock

# Frontend
frontend/node_modules/
frontend/dist/
frontend/bun.lock
frontend/bun.lockb

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db
"""

# ---------------------------------------------------------------------------
# Scripts
# ---------------------------------------------------------------------------

SCRIPT_DEV_SH = """\
#!/usr/bin/env bash
set -e

# Start backend and frontend dev servers concurrently
echo "Starting __PLUGIN_NAME__ development servers..."

# Backend
uv run uvicorn __MODULE_NAME__.plugin:create_standalone_app \\
    --factory --reload --port 8003 &
BACKEND_PID=$!

# Frontend
if [ -d "frontend" ]; then
    cd frontend
    bun run dev &
    FRONTEND_PID=$!
    cd ..
fi

cleanup() {
    kill $BACKEND_PID 2>/dev/null || true
    [ -n "$FRONTEND_PID" ] && kill $FRONTEND_PID 2>/dev/null || true
}
trap cleanup EXIT

wait
"""

SCRIPT_BUILD_SH = """\
#!/usr/bin/env bash
set -e
echo "Building __PACKAGE_NAME__..."
mld build .
echo "Done! Check dist/ for the .mld bundle."
"""

SCRIPT_RELEASE_SH = """\
#!/usr/bin/env bash
set -e

BUMP_TYPE="${1:-patch}"
CURRENT=$(grep -oP 'version\\s*=\\s*"\\K[0-9]+\\.[0-9]+\\.[0-9]+' pyproject.toml | head -1)

MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)

case "$BUMP_TYPE" in
    major) NEW_VERSION="$((MAJOR + 1)).0.0" ;;
    minor) NEW_VERSION="$MAJOR.$((MINOR + 1)).0" ;;
    patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" ;;
    *) echo "Usage: $0 [patch|minor|major]"; exit 1 ;;
esac

echo "Bumping version: $CURRENT -> $NEW_VERSION"

# Update pyproject.toml
sed -i '' "s/version = \\"$CURRENT\\"/version = \\"$NEW_VERSION\\"/" pyproject.toml

# Commit and tag
git add pyproject.toml
git commit -m "chore: release v$NEW_VERSION"
git tag "v$NEW_VERSION"
git push origin HEAD --tags

echo "Released v$NEW_VERSION"
"""

# ---------------------------------------------------------------------------
# CI workflows
# ---------------------------------------------------------------------------

CI_YML = """\
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Set up Python
        run: uv python install 3.12

      - name: Install dependencies
        run: uv sync

      - name: Lint
        run: uv run ruff check .

      - name: Test
        run: uv run pytest -v

      - name: Check for frontend
        id: frontend
        run: |
          if [[ -f frontend/package.json ]]; then
            echo "HAS_FRONTEND=true" >> $GITHUB_OUTPUT
          else
            echo "HAS_FRONTEND=false" >> $GITHUB_OUTPUT
          fi

      - name: Setup Bun
        if: steps.frontend.outputs.HAS_FRONTEND == 'true'
        uses: oven-sh/setup-bun@v2

      - name: Frontend install and typecheck
        if: steps.frontend.outputs.HAS_FRONTEND == 'true'
        run: |
          cd frontend
          bun install
          bun run type-check
"""

# ---------------------------------------------------------------------------
# Frontend templates
# ---------------------------------------------------------------------------

FRONTEND_PACKAGE_JSON = """\
{
  "name": "__PACKAGE_NAME__-frontend",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc -b && VITE_API_PREFIX=/api__ROUTES_PREFIX__ vite build",
    "preview": "vite preview",
    "type-check": "vue-tsc --noEmit"
  },
  "dependencies": {
    "@morscherlab/mld-sdk": "^__SDK_VERSION__",
    "pinia": "^2.1.7",
    "vue": "^3.4.0",
    "vue-router": "^4.2.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.0",
    "@types/node": "^25.1.0",
    "@vitejs/plugin-vue": "^5.0.0",
    "autoprefixer": "^10.4.16",
    "postcss": "^8.4.32",
    "tailwindcss": "^4.1.0",
    "typescript": "^5.3.0",
    "vite": "^5.0.0",
    "vue-tsc": "^2.0.0"
  }
}
"""

FRONTEND_VITE_CONFIG_TS = """\
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      // CRITICAL: Dedupe Vue/Pinia to avoid multiple instances when SDK is bundled
      'vue': resolve(__dirname, 'node_modules/vue'),
      'pinia': resolve(__dirname, 'node_modules/pinia'),
    },
    dedupe: ['vue', 'pinia', 'vue-router'],
  },
  optimizeDeps: {
    include: ['vue', 'vue-router', 'pinia'],
  },
  server: {
    port: 5175,
    proxy: {
      '/api': {
        target: 'http://localhost:8003',
        changeOrigin: true,
      },
    },
  },
  base: '__ROUTES_PREFIX__/',
  build: {
    outDir: 'dist',
    emptyOutDir: true,
  },
})
"""

FRONTEND_TSCONFIG_JSON = """\
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
"""

FRONTEND_TSCONFIG_NODE_JSON = """\
{
  "compilerOptions": {
    "composite": true,
    "tsBuildInfoFile": "./tsconfig.node.tsbuildinfo",
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true,
    "strict": true
  },
  "include": ["vite.config.ts"]
}
"""

FRONTEND_INDEX_HTML = """\
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>__PLUGIN_NAME__</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
"""

FRONTEND_POSTCSS_CONFIG_JS = """\
export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}
"""

FRONTEND_MAIN_TS = """\
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter, createWebHistory } from 'vue-router'

import '@morscherlab/mld-sdk/style'
import './style.css'

import App from './App.vue'

const routes = [
  {
    path: '/',
    name: 'dashboard',
    component: () => import('./views/DashboardView.vue'),
  },
  {
    path: '/analysis',
    name: 'analysis',
    component: () => import('./views/AnalysisView.vue'),
  },
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)

app.mount('#app')
"""

FRONTEND_STYLE_CSS = """\
@import "tailwindcss";
@import "@morscherlab/mld-sdk/style";
"""

FRONTEND_ENV_D_TS = """\
/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
"""

FRONTEND_APP_VUE = """\
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import {
  AppLayout,
  AppTopBar,
  AppContainer,
} from '@morscherlab/mld-sdk'
import type { TopBarPage } from '@morscherlab/mld-sdk'

const router = useRouter()
const route = useRoute()

const pages: TopBarPage[] = [
  { id: 'dashboard', label: 'Dashboard', to: '/' },
  { id: 'analysis', label: 'Analysis', to: '/analysis' },
]

const currentPageId = computed(() => {
  return pages.find(p => p.to === route.path)?.id ?? 'dashboard'
})

const currentPageTitle = computed(() => {
  return pages.find(p => p.id === currentPageId.value)?.label ?? 'Dashboard'
})

function handlePageSelect(page: TopBarPage) {
  if (page.to) router.push(page.to)
}
</script>

<template>
  <AppLayout floating>
    <template #topbar>
      <AppTopBar
        plugin-name="__PLUGIN_NAME__"
        :title="currentPageTitle"
        :pages="pages"
        :current-page-id="currentPageId"
        show-theme-toggle
        show-settings
        @page-select="handlePageSelect"
      />
    </template>

    <AppContainer scrollable>
      <router-view />
    </AppContainer>
  </AppLayout>
</template>
"""

FRONTEND_DASHBOARD_VIEW_VUE = """\
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { AlertBox, Skeleton, useApi, useToast } from '@morscherlab/mld-sdk'

const apiBaseUrl = import.meta.env.VITE_API_PREFIX || '/api__ROUTES_PREFIX__'
const api = useApi({ baseUrl: apiBaseUrl })
const { error } = useToast()

const healthStatus = ref<{ status: string; plugin: string } | null>(null)
const loading = ref(true)

onMounted(async () => {
  try {
    const response = await api.get<{ status: string; plugin: string }>('/health')
    healthStatus.value = response
  } catch {
    error('Failed to fetch health status')
  } finally {
    loading.value = false
  }
})
</script>

<template>
  <div class="flex flex-col gap-3">
    <p class="m-0 text-sm text-[var(--text-muted)]">
      Your plugin dashboard. Start building here.
    </p>

    <div v-if="loading">
      <Skeleton variant="rounded" height="48px" />
    </div>
    <AlertBox
      v-else
      :type="healthStatus?.status === 'healthy' ? 'success' : 'error'"
    >
      {{ healthStatus?.status === 'healthy' ? 'All systems operational' : 'Health check failed' }}
    </AlertBox>
  </div>
</template>
"""

FRONTEND_ANALYSIS_VIEW_VUE = """\
<script setup lang="ts">
import { EmptyState } from '@morscherlab/mld-sdk'
</script>

<template>
  <div class="flex flex-col gap-3">
    <p class="m-0 text-sm text-[var(--text-muted)]">
      Add your analysis forms, data inputs, and result displays here.
    </p>

    <EmptyState
      title="No Analysis Configured"
      description="Replace this placeholder with your analysis UI."
      icon-path="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
      size="sm"
    />
  </div>
</template>
"""
