#!/usr/bin/env node

'use strict';

/**
 * Build script: generates _index.yaml from primitive filesystem scan + router files.
 *
 * Modes:
 *   node scripts/build-index.js           # Generate mode (default)
 *   node scripts/build-index.js --verify   # Verify mode (CI gate)
 *
 * Generate mode writes scaffold/project/primitives/_index.yaml.
 * Verify mode exits non-zero if the index is stale.
 *
 * Run via: npm run build:index
 */

const fs = require('fs');
const path = require('path');

const PRIMITIVES_DIR = path.resolve(__dirname, '..', 'scaffold', 'project', 'primitives');
const INDEX_PATH = path.join(PRIMITIVES_DIR, '_index.yaml');

// ─── Domain Configuration ───────────────────────────────────────────────────

/**
 * Domain definitions: maps domain names to their backends and routing config.
 * Each domain specifies:
 *   - resolve_by: the project-constants key used for dispatch
 *   - backends: map of backend dir name → condition value
 *   - sub_groups (optional): AWS-style sub-grouping by prefix
 */
const DOMAINS = {
  tracker: {
    resolve_by: 'TRACKER_TYPE',
    backends: {
      'jira-official':   { condition: 'JIRA' },
      'gh-cli':          { condition: 'GitHub Issues' },
      'linear-official': { condition: 'Linear' },
      'linear-cli':      { condition: 'Linear' },
      'local':           { condition: 'local' },
    },
  },
  docs: {
    resolve_by: 'DOC_PLATFORM',
    backends: {
      'confluence-official': { condition: 'Confluence' },
      'local':          { condition: 'Local markdown' },
    },
  },
  cloud: {
    resolve_by: 'CLOUD_PROVIDER',
    backends: {
      'aws': {
        condition: 'AWS',
        sub_grouped: true, // primitives are in sub-group subdirectories
        sub_groups: {
          compute:       ['ec2', 'lambda', 'ecs', 'eks'],
          storage:       ['s3', 'dynamodb', 'rds', 'elasticache'],
          networking:    ['vpc', 'route53', 'cloudfront', 'elb'],
          security:      ['iam', 'kms', 'secrets', 'guardduty', 'waf'],
          observability: ['cloudwatch', 'cloudtrail', 'xray'],
          messaging:     ['sqs', 'sns', 'ses', 'eventbridge'],
          deployment:    ['cloudformation', 'cdk', 'ecr', 'codebuild', 'codepipeline'],
          management:    ['ssm', 'sts', 'organizations'],
          'ai-ml':       ['bedrock', 'sagemaker'],
        },
      },
    },
  },
  vcs: {
    resolve_by: null, // always git
    backends: {
      'git': { condition: 'always' },
    },
  },
};

/**
 * Backward-compat alias mapping: old flat namespace name → { domain, backend }.
 * Used to generate aliases from old names to new domain paths.
 */
const LEGACY_ALIASES = {
  'local-issues': { domain: 'tracker', backend: 'local' },
  'local-docs':   { domain: 'docs',    backend: 'local' },
  'aws-cli':      { domain: 'cloud',   backend: 'aws' },
};

/**
 * Core namespaces: always deployed, no routing needed.
 */
const CORE_NAMESPACES = [
  'agent', 'codebase', 'mode', 'ops', 'output',
  'planning', 'review', 'traceability', 'validation',
];

// ─── Helpers ────────────────────────────────────────────────────────────────

/**
 * List .md files in a directory (excluding README.md, _router.md, _schema.md).
 * Returns array of basenames without extension.
 * If recursive=true, scans subdirectories too (for sub-grouped backends like AWS).
 */
function listPrimitives(dirPath, recursive = false) {
  if (!fs.existsSync(dirPath)) return [];

  if (recursive) {
    const results = [];
    const subDirs = fs.readdirSync(dirPath, { withFileTypes: true })
      .filter(d => d.isDirectory());
    for (const sub of subDirs) {
      const subPath = path.join(dirPath, sub.name);
      const files = fs.readdirSync(subPath)
        .filter(f => f.endsWith('.md') && f !== 'README.md' && !f.startsWith('_'))
        .map(f => f.replace(/\.md$/, ''));
      results.push(...files);
    }
    return results.sort();
  }

  return fs.readdirSync(dirPath)
    .filter(f => f.endsWith('.md') && f !== 'README.md' && !f.startsWith('_'))
    .map(f => f.replace(/\.md$/, ''))
    .sort();
}

/**
 * Classify an AWS primitive into a sub-group by prefix matching.
 */
function classifyAws(basename, subGroups) {
  for (const [group, prefixes] of Object.entries(subGroups)) {
    for (const prefix of prefixes) {
      if (basename.startsWith(prefix + '-') || basename === prefix) {
        return group;
      }
    }
  }
  return 'other';
}

/**
 * Get today's date as YYYY-MM-DD.
 */
function todayISO() {
  return new Date().toISOString().slice(0, 10);
}

// ─── Index Builder ──────────────────────────────────────────────────────────

/**
 * Build the complete index data structure by scanning the filesystem.
 */
function buildIndex() {
  const domains = {};
  const capabilities = {};
  const aliases = {};
  let totalPrimitives = 0;

  // Process routed domains
  for (const [domainName, domainConfig] of Object.entries(DOMAINS)) {
    const domainEntry = {
      router: `${domainName}/_router.md`,
      resolve_by: domainConfig.resolve_by,
      backends: {},
    };

    // Add schema if it exists
    const schemaPath = path.join(PRIMITIVES_DIR, domainName, '_schema.md');
    if (fs.existsSync(schemaPath)) {
      domainEntry.schema = `${domainName}/_schema.md`;
    }

    for (const [backendName, backendConfig] of Object.entries(domainConfig.backends)) {
      // Backends are now at PRIMITIVES_DIR/{domain}/{backend}
      const backendDir = path.join(PRIMITIVES_DIR, domainName, backendName);
      const isSubGrouped = !!backendConfig.sub_grouped;
      const primitives = listPrimitives(backendDir, isSubGrouped);
      const count = primitives.length;
      totalPrimitives += count;

      const backendEntry = {
        condition: backendConfig.condition,
        count,
      };

      // Handle sub-groups (AWS) — count files per sub-directory
      if (backendConfig.sub_groups) {
        backendEntry.sub_groups = {};
        if (isSubGrouped) {
          // Files are physically in sub-directories
          const subDirs = fs.readdirSync(backendDir, { withFileTypes: true })
            .filter(d => d.isDirectory());
          for (const sub of subDirs) {
            const subFiles = listPrimitives(path.join(backendDir, sub.name));
            backendEntry.sub_groups[sub.name] = subFiles.length;
          }
        } else {
          // Classify by prefix
          const groups = {};
          for (const prim of primitives) {
            const group = classifyAws(prim, backendConfig.sub_groups);
            if (!groups[group]) groups[group] = [];
            groups[group].push(prim);
          }
          for (const [groupName, groupPrims] of Object.entries(groups)) {
            backendEntry.sub_groups[groupName] = groupPrims.length;
          }
        }
      }

      domainEntry.backends[backendName] = backendEntry;

      // Build capability cross-reference and aliases
      for (const prim of primitives) {
        // Current name: domain/backend:operation
        aliases[`${domainName}/${backendName}:${prim}`] = `${domainName}/${backendName}/${prim}`;

        // Capability: operation → { domain, backends[] }
        if (!capabilities[prim]) {
          capabilities[prim] = { domain: domainName, backends: [] };
        }
        capabilities[prim].backends.push(backendName);
      }

      // Legacy aliases: old flat name → new domain path
      // For renamed backends (local-issues→local, local-docs→local, aws-cli→aws)
      const legacyName = Object.entries(LEGACY_ALIASES)
        .find(([, v]) => v.domain === domainName && v.backend === backendName);
      if (legacyName) {
        for (const prim of primitives) {
          aliases[`${legacyName[0]}:${prim}`] = `${domainName}/${backendName}/${prim}`;
        }
      }
      // For backends that kept their name (jira-mcp, github-mcp, etc.)
      if (!legacyName) {
        for (const prim of primitives) {
          aliases[`${backendName}:${prim}`] = `${domainName}/${backendName}/${prim}`;
        }
      }
    }

    domains[domainName] = domainEntry;
  }

  // Process core namespaces — now at PRIMITIVES_DIR/core/{namespace}
  let coreCount = 0;
  for (const ns of CORE_NAMESPACES) {
    const nsDir = path.join(PRIMITIVES_DIR, 'core', ns);
    const primitives = listPrimitives(nsDir);
    coreCount += primitives.length;
    totalPrimitives += primitives.length;

    // Core aliases (old name still works)
    for (const prim of primitives) {
      aliases[`${ns}:${prim}`] = `core/${ns}/${prim}`;
    }
  }

  domains.core = {
    router: null,
    namespaces: CORE_NAMESPACES,
    count: coreCount,
  };

  return {
    version: require('../package.json').version,
    generated_at: todayISO(),
    total_primitives: totalPrimitives,
    domains,
    capabilities,
    aliases,
  };
}

// ─── YAML Serializer ────────────────────────────────────────────────────────

/**
 * Serialize the index to YAML format.
 * Hand-rolled to avoid adding a YAML dependency (D1: Minimal Dependencies).
 */
function toYaml(index) {
  const lines = [];
  lines.push('# Auto-generated by: npm run build:index');
  lines.push('# Do not edit manually — regenerate with: node scripts/build-index.js');
  lines.push('');
  lines.push(`version: "${index.version}"`);
  lines.push(`generated_at: "${index.generated_at}"`);
  lines.push(`total_primitives: ${index.total_primitives}`);
  lines.push('');

  // Domains
  lines.push('domains:');
  for (const [name, domain] of Object.entries(index.domains)) {
    lines.push(`  ${name}:`);

    if (domain.router !== null && domain.router !== undefined) {
      lines.push(`    router: "${domain.router}"`);
    } else {
      lines.push('    router: null');
    }

    if (domain.schema) {
      lines.push(`    schema: "${domain.schema}"`);
    }

    if (domain.resolve_by !== undefined) {
      if (domain.resolve_by) {
        lines.push(`    resolve_by: ${domain.resolve_by}`);
      } else {
        lines.push('    resolve_by: null');
      }
    }

    if (domain.backends) {
      lines.push('    backends:');
      for (const [bName, bInfo] of Object.entries(domain.backends)) {
        lines.push(`      ${bName}:`);
        lines.push(`        condition: "${bInfo.condition}"`);
        lines.push(`        count: ${bInfo.count}`);
        if (bInfo.sub_groups) {
          lines.push('        sub_groups:');
          for (const [sgName, sgCount] of Object.entries(bInfo.sub_groups)) {
            lines.push(`          ${sgName}: ${sgCount}`);
          }
        }
      }
    }

    if (domain.namespaces) {
      lines.push(`    namespaces: [${domain.namespaces.join(', ')}]`);
    }

    if (domain.count !== undefined) {
      lines.push(`    count: ${domain.count}`);
    }
  }
  lines.push('');

  // Capabilities (sorted by operation name)
  lines.push('# Capability cross-reference: operation → domain + backends');
  lines.push('capabilities:');
  const sortedCaps = Object.entries(index.capabilities).sort(([a], [b]) => a.localeCompare(b));
  for (const [op, info] of sortedCaps) {
    lines.push(`  ${op}: { domain: ${info.domain}, backends: [${info.backends.join(', ')}] }`);
  }
  lines.push('');

  // Aliases (sorted)
  lines.push('# Backward-compat aliases: old flat name -> new domain path');
  lines.push('aliases:');
  const sortedAliases = Object.entries(index.aliases).sort(([a], [b]) => a.localeCompare(b));
  for (const [oldName, newPath] of sortedAliases) {
    lines.push(`  "${oldName}": "${newPath}"`);
  }
  lines.push('');

  return lines.join('\n');
}

// ─── Main ───────────────────────────────────────────────────────────────────

function main() {
  const verify = process.argv.includes('--verify');
  const index = buildIndex();
  const yaml = toYaml(index);

  if (verify) {
    console.log('\n  Primitive index verification...\n');
    if (!fs.existsSync(INDEX_PATH)) {
      console.error('  [FAIL] _index.yaml does not exist. Run: npm run build:index\n');
      process.exit(1);
    }
    const existing = fs.readFileSync(INDEX_PATH, 'utf-8');
    // Compare ignoring generated_at (which changes daily)
    const normalize = s => s.replace(/generated_at: "[^"]*"/, 'generated_at: "DATE"');
    if (normalize(existing) !== normalize(yaml)) {
      console.error('  [FAIL] _index.yaml is stale. Run: npm run build:index\n');
      process.exit(1);
    }
    console.log(`  [OK] _index.yaml is current (${index.total_primitives} primitives).\n`);
  } else {
    console.log('\n  Generating primitive index...\n');
    fs.writeFileSync(INDEX_PATH, yaml, 'utf-8');
    console.log(`  Written: ${INDEX_PATH}`);
    console.log(`  Total primitives: ${index.total_primitives}`);
    console.log(`  Domains: ${Object.keys(index.domains).length}`);
    console.log(`  Capabilities: ${Object.keys(index.capabilities).length}`);
    console.log(`  Aliases: ${Object.keys(index.aliases).length}\n`);
  }
}

if (require.main === module) {
  main();
}

module.exports = {
  DOMAINS,
  CORE_NAMESPACES,
  buildIndex,
  toYaml,
  listPrimitives,
  classifyAws,
};
