import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { config } from '../../config.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { writeStaticHtmlTemplate } from './templates/staticHtml.js';
import { writeExpressApiTemplate } from './templates/expressApi.js';

const TEMPLATES = [
  'vite-react',
  'vite-vue',
  'next',
  'express-api',
  'static-html',
  'laravel',
  'codeigniter',
  'laravel-react',
  'codeigniter-react',
] as const;

type Template = (typeof TEMPLATES)[number];

export function registerScaffoldProjectTool(server: McpServer): void {
  server.registerTool(
    'scaffold_project',
    {
      title: 'Scaffold Project',
      description: `Create a new project from a template. Supported templates: ${TEMPLATES.join(', ')}. "laravel-react" and "codeigniter-react" create a PHP backend in "<name>/backend" and a separate Vite+React frontend in "<name>/frontend".`,
      inputSchema: {
        template: z.enum(TEMPLATES),
        name: z.string().min(1).describe('Project/folder name to create.'),
        directory: z
          .string()
          .optional()
          .describe('Parent directory (relative to the workspace root) to create the project in. Defaults to the workspace root.'),
      },
    },
    async ({ template, name, directory }) => {
      try {
        const parentDir = resolveWorkspacePath(config.workspaceRoot, directory ?? '.');
        fs.mkdirSync(parentDir, { recursive: true });

        const targetDir = path.join(parentDir, name);
        if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
          return errorResult(`"${path.relative(config.workspaceRoot, targetDir)}" already exists and is not empty.`);
        }

        const result = await runScaffold(template, name, parentDir);
        return jsonResult(result);
      } catch (error) {
        return errorResult(toErrorMessage(error));
      }
    },
  );
}

async function runScaffold(template: Template, name: string, parentDir: string) {
  switch (template) {
    case 'vite-react':
      return scaffoldVite(name, parentDir, 'react-ts');
    case 'vite-vue':
      return scaffoldVite(name, parentDir, 'vue-ts');
    case 'next':
      return scaffoldNext(name, parentDir);
    case 'express-api':
      return { template, path: writeExpressApiTemplate(path.join(parentDir, name), name) };
    case 'static-html':
      return { template, path: writeStaticHtmlTemplate(path.join(parentDir, name), name) };
    case 'laravel':
      return scaffoldComposerProject('laravel/laravel', name, parentDir, template);
    case 'codeigniter':
      return scaffoldComposerProject('codeigniter4/appstarter', name, parentDir, template);
    case 'laravel-react':
      return scaffoldCombined('laravel/laravel', name, parentDir, template);
    case 'codeigniter-react':
      return scaffoldCombined('codeigniter4/appstarter', name, parentDir, template);
    default: {
      const exhaustive: never = template;
      throw new Error(`Unhandled template: ${exhaustive as string}`);
    }
  }
}

async function scaffoldVite(name: string, parentDir: string, viteTemplate: 'react-ts' | 'vue-ts') {
  const result = await runCommand('npm', ['create', 'vite@latest', name, '--', '--template', viteTemplate], {
    cwd: parentDir,
    timeoutMs: config.scaffoldCommandTimeoutMs,
  });
  return { template: viteTemplate === 'react-ts' ? 'vite-react' : 'vite-vue', path: path.join(parentDir, name), ...result };
}

async function scaffoldNext(name: string, parentDir: string) {
  const result = await runCommand(
    'npx',
    [
      '--yes',
      'create-next-app@latest',
      name,
      '--ts',
      '--eslint',
      '--app',
      '--src-dir',
      '--import-alias',
      '@/*',
      '--use-npm',
      '--no-tailwind',
      '--yes',
    ],
    { cwd: parentDir, timeoutMs: config.scaffoldCommandTimeoutMs },
  );
  return { template: 'next', path: path.join(parentDir, name), ...result };
}

async function scaffoldComposerProject(composerPackage: string, name: string, parentDir: string, template: Template) {
  const result = await runCommand('composer', ['create-project', composerPackage, name], {
    cwd: parentDir,
    timeoutMs: config.scaffoldCommandTimeoutMs,
  });
  return { template, path: path.join(parentDir, name), ...result };
}

async function scaffoldCombined(composerPackage: string, name: string, parentDir: string, template: Template) {
  const projectDir = path.join(parentDir, name);
  fs.mkdirSync(projectDir, { recursive: true });

  const backendResult = await runCommand('composer', ['create-project', composerPackage, 'backend'], {
    cwd: projectDir,
    timeoutMs: config.scaffoldCommandTimeoutMs,
  });
  const frontendResult = await runCommand(
    'npm',
    ['create', 'vite@latest', 'frontend', '--', '--template', 'react-ts'],
    { cwd: projectDir, timeoutMs: config.scaffoldCommandTimeoutMs },
  );

  return {
    template,
    path: projectDir,
    backend: { path: path.join(projectDir, 'backend'), ...backendResult },
    frontend: { path: path.join(projectDir, 'frontend'), ...frontendResult },
    note: 'Backend and frontend were scaffolded as independent siblings (not wired together via Inertia). Point the frontend at the backend API base URL as a next step.',
  };
}
