/**
 * Backend Package.json Generator
 *
 * Generates package.json for backend workspace in monorepo. Adapts deps +
 * scripts to the manifest's resolved ORM/storage so a manifest pinning
 * MongoDBNativeDriver doesn't get stuck with Prisma scripts and
 * `@prisma/client` in dependencies.
 */

import type { TemplateContext } from '@specverse/types';
import { predictAiBehaviorLibraries } from '@specverse/engines/ai';

// Version pins for the AI-behavior whitelist libs. Single source of
// truth so the runtime dep + the @types dep stay in sync. Listed
// here (not in `library-whitelist.ts`) because that module is shared
// with the validator + spec scanner — those don't care about npm
// version ranges, only library names.
const AI_LIBRARY_VERSIONS: Record<string, { runtime: string; types?: string }> = {
  'jsonwebtoken': { runtime: '^9.0.0', types: '^9.0.0' },
  'bcryptjs':     { runtime: '^2.4.3', types: '^2.4.0' },
  'uuid':         { runtime: '^9.0.0', types: '^9.0.0' },
  'crypto':       { runtime: '*' /* Node built-in */ },
  'expr-eval':    { runtime: '^2.0.2' },
};

/** Read the manifest's resolved orm name (e.g. "PrismaORM", "MongoDBNativeDriver"). */
function resolveOrmName(manifest: any): string {
  if (!manifest) return 'PrismaORM';
  const inner = manifest.manifests
    ? Object.values(manifest.manifests)[0] as any
    : manifest;
  if (!inner) return 'PrismaORM';
  // Prefer explicit capabilityMappings for orm.client; fall back to defaultMappings.orm.
  const caps = Array.isArray(inner.capabilityMappings) ? inner.capabilityMappings : [];
  const ormCap = caps.find((m: any) => m?.capability === 'orm.client') ||
                 caps.find((m: any) => m?.capability === 'orm.schema');
  if (ormCap) return ormCap.implementation || ormCap.instanceFactory || 'PrismaORM';
  return inner.defaultMappings?.orm || 'PrismaORM';
}

export default function generateBackendPackageJson(context: TemplateContext): string {
  const { spec, manifest } = context as any;
  const appName = (spec.metadata?.component || 'app').toLowerCase().replace(/\s+/g, '-');
  const orm = resolveOrmName(manifest);
  const isMongoNative = orm === 'MongoDBNativeDriver';
  const isPgNative = orm === 'PostgresNativeDriver';

  // Database scripts are ORM-specific.
  //   - Prisma:        generate / push / migrate / studio / seed
  //   - MongoDB native: collections are dynamic — no DDL step
  //   - Postgres native: ship a schema.sql; db:setup pipes it through psql
  //     (works against any postgres URL the env exposes; users can swap in
  //     their preferred migrator later — node-pg-migrate, sqitch, etc.)
  const dbScripts: Record<string, string> =
    isMongoNative ? {} :
    isPgNative ? {
      'db:setup': 'psql "$POSTGRES_URL" -f src/db/schema.sql',
    } : {
      'db:setup': 'prisma generate && prisma db push',
      'db:generate': 'prisma generate',
      'db:push': 'prisma db push',
      'db:migrate': 'prisma migrate dev',
      'db:studio': 'prisma studio',
      'db:seed': 'tsx prisma/seed.ts',
    };

  const ormDeps: Record<string, string> =
    isMongoNative ? { 'mongodb': '^6.3.0' } :
    isPgNative ? { 'pg': '^8.11.0' } :
    { '@prisma/client': '^5.7.0' };

  const ormDevDeps: Record<string, string> =
    isMongoNative ? {} :
    isPgNative ? { '@types/pg': '^8.11.0' } :
    { 'prisma': '^5.7.0' };

  const pkg: Record<string, any> = {
    name: `${appName}-backend`,
    version: spec.metadata?.version || '1.0.0',
    description: `Backend API for ${spec.metadata?.component || 'application'}`,
    type: 'module',
    bin: {
      [appName]: './bin/cli.mjs'
    },

    scripts: {
      // Development
      'dev': 'tsx watch src/main.ts',
      'dev:debug': 'tsx watch --inspect src/main.ts',

      // Build
      'build': 'tsc',
      'build:watch': 'tsc --watch',

      // Production
      'start': 'node dist/main.js',

      // Database (ORM-specific; empty for native driver)
      ...dbScripts,

      // Testing
      'test': 'vitest run --passWithNoTests',
      'test:watch': 'vitest watch',
      'test:coverage': 'vitest --coverage',

      // Linting
      'lint': 'eslint src --ext .ts',
      'lint:fix': 'eslint src --ext .ts --fix',

      // Type checking
      'typecheck': 'tsc --noEmit'
    },

    dependencies: {
      ...ormDeps,
      'fastify': '^5.8.3',
      '@fastify/cors': '^10.0.0',
      '@fastify/helmet': '^12.0.0',
      '@fastify/rate-limit': '^10.0.0',
      '@fastify/websocket': '^11.0.0',
      'eventemitter3': '^5.0.0',
      'zod': '^3.22.0',
      'dotenv': '^16.3.0',
      'commander': '^13.0.0',
      // AI-behavior whitelist deps — only included when the spec's step
      // text actually triggers them (predicted via predictAiBehaviorLibraries).
      // Pre-fix every backend installed all 5 libs unconditionally even
      // though 97% of generated bodies imported nothing. (#43K-B-review)
      ...buildAiLibraryDeps(spec, 'runtime')
    },

    devDependencies: {
      'typescript': '^5.3.0',
      '@types/node': '^20.10.0',
      'tsx': '^4.7.0',
      ...ormDevDeps,
      // vitest 3.2.5 is a BROKEN upstream publish (2026-06-01): it pins
      // vite-node@3.2.5 exactly, but that version was never published (npm
      // tops out at vite-node@3.2.4), so a fresh install ETARGETs. Exclude
      // exactly 3.2.5 while staying in 3.x — self-heals to 3.2.6+ once the
      // vitest team ships a matching vite-node.
      'vitest': '^3.0.0 <3.2.5 || >3.2.5 <4.0.0',
      '@vitest/coverage-v8': '^3.0.0 <3.2.5 || >3.2.5 <4.0.0',
      'eslint': '^9.0.0',
      '@typescript-eslint/eslint-plugin': '^8.0.0',
      '@typescript-eslint/parser': '^8.0.0',
      // Type definitions for whichever AI-behavior whitelist libs are
      // included as runtime deps above. Same per-spec gating.
      ...buildAiLibraryDeps(spec, 'types')
    },

    engines: {
      node: '>=20.0.0'
    }
  };

  return JSON.stringify(pkg, null, 2);
}

/**
 * Build the AI-library deps block for the given spec. `kind = 'runtime'`
 * yields the runtime deps (jsonwebtoken etc.); `kind = 'types'` yields
 * the matching @types/* devDeps. Empty object when no whitelist trigger
 * fires in the spec — the realized backend then ships without any of
 * these deps installed (the validator already prevented bodies from
 * importing them, so nothing references them at runtime).
 *
 * `crypto` is a Node built-in, so it never produces a runtime dep
 * entry; if a body uses it, no install is needed.
 */
function buildAiLibraryDeps(spec: any, kind: 'runtime' | 'types'): Record<string, string> {
  const out: Record<string, string> = {};
  const predicted = predictAiBehaviorLibraries(spec);
  for (const lib of predicted) {
    const versions = AI_LIBRARY_VERSIONS[lib];
    if (!versions) continue;
    if (kind === 'runtime') {
      // Skip Node built-ins (crypto) — they don't go in package.json deps.
      if (versions.runtime === '*') continue;
      out[lib] = versions.runtime;
    } else if (kind === 'types' && versions.types) {
      out[`@types/${lib}`] = versions.types;
    }
  }
  return out;
}
