/**
 * VSCode Extension Generator
 *
 * Generates a complete VSCode extension package from the SpecVerse spec.
 * - package.json with commands derived from CLI spec + entity types
 * - Ships static assets (tmLanguage, themes, schemas, extension.ts)
 *
 * The spec's ToolsSupport component describes WHAT the extension provides.
 * This generator decides HOW to package it.
 */

import type { TemplateContext } from '@specverse/types';
import { readFileSync, existsSync, readdirSync, statSync, mkdirSync, writeFileSync, copyFileSync } from 'fs';
import { join, dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

const __generatorDir = dirname(fileURLToPath(import.meta.url));

export default function generateVSCodeExtension(context: TemplateContext): string {
  const { spec, outputDir } = context;

  const extensionDir = join(outputDir || '.', 'tools', 'vscode-extension');
  if (!existsSync(extensionDir)) mkdirSync(extensionDir, { recursive: true });

  // 1. Check for distribution spec (new entity type)
  const distribution = extractDistribution(spec);

  // 2. Extract CLI commands — from distribution, spec, or standard defaults
  let cliCommands: Array<{ command: string; title: string; category: string }> = [];
  if (distribution?.commands) {
    cliCommands = distribution.commands.map((cmd: any) => {
      const parts = (cmd.from || cmd.command || '').split('.');
      const name = parts[parts.length - 1] || 'unknown';
      return {
        command: `specverse.${name}`,
        title: cmd.title || `${capitalize(name)}: ${cmd.description || ''}`,
        category: 'SpecVerse',
      };
    });
  }
  if (cliCommands.length === 0) {
    cliCommands = extractCLICommands(spec);
  }
  if (cliCommands.length === 0) {
    cliCommands = getStandardCommands();
  }

  // 3. Extract entity types for syntax highlighting keywords
  const entityTypes = extractEntityTypes(spec);

  // 4. Generate package.json — use distribution metadata if available
  const specVersion = distribution?.version || spec?.metadata?.version || spec?.version || '4.0.0';
  const packageJson = generatePackageJson(cliCommands, entityTypes, specVersion);

  // Override package.json with distribution-specific values
  if (distribution) {
    if (distribution.displayName) packageJson.displayName = distribution.displayName;
    if (distribution.publisher) packageJson.publisher = distribution.publisher;
    if (distribution.description) packageJson.description = distribution.description;

    // Languages from distribution
    if (distribution.languages && distribution.languages.length > 0) {
      packageJson.contributes.languages = distribution.languages.map((lang: any) => ({
        id: lang.id,
        aliases: lang.aliases || [lang.id],
        extensions: lang.extensions || [],
        configuration: `./language-configuration.json`,
      }));
      packageJson.contributes.grammars = distribution.languages
        .filter((lang: any) => lang.grammar)
        .map((lang: any) => ({
          language: lang.id,
          scopeName: lang.grammar,
          path: `./syntaxes/${lang.id}.tmLanguage.json`,
        }));
    }

    // Themes from distribution
    if (distribution.themes && distribution.themes.length > 0) {
      packageJson.contributes.themes = distribution.themes.map((theme: any) => ({
        label: theme.name,
        uiTheme: theme.type === 'dark' ? 'vs-dark' : theme.type === 'light' ? 'vs' : 'hc-black',
        path: `./themes/${theme.name.toLowerCase().replace(/\s+/g, '-')}-theme.json`,
      }));
    }
  }
  writeFileSync(join(extensionDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n');

  // 4. Copy static assets — check both source and compiled locations
  let staticDir = join(__generatorDir, 'static');
  if (!existsSync(staticDir)) {
    staticDir = join(__generatorDir.replace('/dist/libs/', '/libs/'), 'static');
  }
  if (existsSync(staticDir)) {
    copyRecursive(staticDir, extensionDir);
  }

  // 5. Create src directory and copy extension.ts
  const srcDir = join(extensionDir, 'src');
  if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
  const extTs = join(staticDir, 'extension.ts');
  if (existsSync(extTs)) {
    copyFileSync(extTs, join(srcDir, 'extension.ts'));
  }

  // 6. Generate build script
  const buildScript = `const esbuild = require('esbuild');
esbuild.build({
  entryPoints: ['src/extension.ts'],
  bundle: true,
  outfile: 'dist/extension.js',
  external: ['vscode'],
  format: 'cjs',
  platform: 'node',
}).catch(() => process.exit(1));
`;
  const scriptsDir = join(extensionDir, 'scripts');
  if (!existsSync(scriptsDir)) mkdirSync(scriptsDir, { recursive: true });
  writeFileSync(join(scriptsDir, 'build.js'), buildScript);

  return `VSCode extension generated in: ${extensionDir}\n  ${cliCommands.length} commands, ${entityTypes.length} entity keywords`;
}

function extractDistribution(spec: any): any | null {
  const allDistributions: any[] = [];

  // Check top-level distributions (AI-optimized spec format)
  if (spec?.distributions) {
    const entries = Array.isArray(spec.distributions)
      ? spec.distributions
      : Object.entries(spec.distributions).map(([name, data]) => ({ name, ...(data as any) }));
    allDistributions.push(...entries);
  }

  // Search components for distributions sections
  const components = spec?.components || {};
  const componentList = Array.isArray(components) ? components : Object.values(components);

  for (const comp of componentList) {
    const distributions = (comp as any)?.distributions;
    if (!distributions) continue;
    const distEntries = Array.isArray(distributions)
      ? distributions
      : Object.entries(distributions).map(([name, data]) => ({ name, ...(data as any) }));
    allDistributions.push(...distEntries);
  }

  // Prefer IDE type
  for (const dist of allDistributions) {
    if ((dist as any).type === 'ide') return dist;
  }
  return allDistributions.length > 0 ? allDistributions[0] : null;
}

function extractCLICommands(spec: any): Array<{ command: string; title: string; category: string }> {
  const commands: Array<{ command: string; title: string; category: string }> = [];

  // Walk components looking for CLI commands section
  // Handles both object form (raw spec: components.CLI.commands) and array form (inferred)
  const components = spec?.components || {};
  const componentList = Array.isArray(components) ? components : Object.entries(components).map(([name, data]) => ({ name, ...(data as any) }));

  for (const comp of componentList) {
    const cliCommands = (comp as any)?.commands;
    if (!cliCommands) continue;

    for (const [rootName, rootDef] of Object.entries(cliCommands as Record<string, any>)) {
      const subcommands = (rootDef as any)?.subcommands || {};
      for (const [subName, subDef] of Object.entries(subcommands as Record<string, any>)) {
        const sub = subDef as any;
        const nestedSubs = sub?.subcommands;
        if (nestedSubs) {
          for (const [nestedName, nestedDef] of Object.entries(nestedSubs as Record<string, any>)) {
            commands.push({
              command: `specverse.${subName}.${nestedName}`,
              title: `${capitalize(subName)} ${capitalize(nestedName)}: ${(nestedDef as any).description || ''}`,
              category: 'SpecVerse',
            });
          }
        } else {
          commands.push({
            command: `specverse.${subName}`,
            title: `${capitalize(subName)}: ${sub.description || ''}`,
            category: 'SpecVerse',
          });
        }
      }
    }
  }

  return commands;
}

function extractEntityTypes(spec: any): string[] {
  const types = new Set<string>();
  const components = spec?.components || [];
  for (const component of Array.isArray(components) ? components : Object.values(components)) {
    const comp = component as any;
    const models = comp?.models;
    if (models) {
      if (Array.isArray(models)) {
        models.forEach((m: any) => types.add(m.name));
      } else {
        Object.keys(models).forEach(name => types.add(name));
      }
    }
  }
  return [...types];
}

function generatePackageJson(
  commands: Array<{ command: string; title: string; category: string }>,
  _entityTypes: string[],
  specVersion: string = '1.0.0'
) {
  return {
    name: 'specverse',
    displayName: 'SpecVerse',
    description: 'SpecVerse specification language support — syntax highlighting, validation, IntelliSense',
    version: specVersion,
    publisher: 'specverse',
    engines: { vscode: '^1.80.0' },
    categories: ['Programming Languages', 'Linters', 'Snippets'],
    activationEvents: ['onLanguage:specverse'],
    main: './dist/extension.js',
    contributes: {
      languages: [{
        id: 'specverse',
        aliases: ['SpecVerse', 'specly'],
        extensions: ['.specly', '.specverse'],
        configuration: './language-configuration.json',
      }],
      grammars: [{
        language: 'specverse',
        scopeName: 'source.specverse',
        path: './syntaxes/specverse.tmLanguage.json',
      }],
      jsonValidation: [{
        fileMatch: ['*.specly', '*.specverse'],
        url: './schemas/specverse-v3-schema.json',
      }],
      themes: [
        { label: 'SpecVerse Dark', uiTheme: 'vs-dark', path: './themes/specverse-complete-theme.json' },
        { label: 'SpecVerse Basic', uiTheme: 'vs-dark', path: './themes/specverse-basic-theme.json' },
      ],
      commands: commands.map(c => ({
        command: c.command,
        title: c.title,
        category: c.category,
      })),
    },
    scripts: {
      'vscode:prepublish': 'npm run build',
      build: 'node scripts/build.js',
      package: 'npx @vscode/vsce package',
    },
    devDependencies: {
      '@types/vscode': '^1.80.0',
      '@vscode/vsce': '^3.0.0',
      esbuild: '^0.25.0',
    },
    overrides: {
      // Force the patched version of lodash through @vscode/vsce →
      // @secretlint → @textlint transitive chain (4.17.23 has a
      // code-injection advisory in `_.template`).
      lodash: '^4.18.1',
    },
  };
}

function copyRecursive(src: string, dest: string) {
  for (const entry of readdirSync(src)) {
    if (entry === 'extension.ts') continue; // Handled separately into src/
    const srcPath = join(src, entry);
    const destPath = join(dest, entry);
    if (statSync(srcPath).isDirectory()) {
      if (!existsSync(destPath)) mkdirSync(destPath, { recursive: true });
      copyRecursive(srcPath, destPath);
    } else {
      copyFileSync(srcPath, destPath);
    }
  }
}

function getStandardCommands(): Array<{ command: string; title: string; category: string }> {
  return [
    { command: 'specverse.validate', title: 'Validate specification', category: 'SpecVerse' },
    { command: 'specverse.infer', title: 'Infer full architecture', category: 'SpecVerse' },
    { command: 'specverse.realize', title: 'Generate code from specification', category: 'SpecVerse' },
    { command: 'specverse.init', title: 'Initialize new project', category: 'SpecVerse' },
    { command: 'specverse.gen.diagrams', title: 'Generate Mermaid diagrams', category: 'SpecVerse' },
    { command: 'specverse.gen.docs', title: 'Generate documentation', category: 'SpecVerse' },
    { command: 'specverse.gen.uml', title: 'Generate UML diagrams', category: 'SpecVerse' },
    { command: 'specverse.dev.format', title: 'Format .specly file', category: 'SpecVerse' },
    { command: 'specverse.dev.watch', title: 'Watch and validate on change', category: 'SpecVerse' },
    { command: 'specverse.dev.quick', title: 'Quick validation', category: 'SpecVerse' },
    { command: 'specverse.cache', title: 'Manage import cache', category: 'SpecVerse' },
    { command: 'specverse.ai.docs', title: 'Generate AI implementation prompt', category: 'SpecVerse' },
    { command: 'specverse.ai.suggest', title: 'Get spec improvement suggestions', category: 'SpecVerse' },
    { command: 'specverse.ai.template', title: 'Load AI prompt template', category: 'SpecVerse' },
  ];
}

function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
