const fs = require('fs');
const path = require('path');
const Parser = require('tree-sitter');
const tsGrammar = require('tree-sitter-typescript').typescript;

/**
 * Phase 7B: Contract Extractor
 * Extracts interfaces, type aliases, and enums from TypeScript source files.
 * Uses tree-sitter AST parsing — zero inference.
 */

const parser = new Parser();
parser.setLanguage(tsGrammar);

/** Extract contracts from a single TypeScript source file */
function extractContracts(filePath, relFile) {
  const source = fs.readFileSync(filePath, 'utf8');
  const tree = parser.parse(source);
  const contracts = [];

  for (const node of tree.rootNode.children) {
    // Handle both exported and non-exported declarations
    let decl = node;
    let isExported = false;

    if (node.type === 'export_statement') {
      isExported = true;
      // The actual declaration is a child
      decl = node.namedChildren.find(c =>
        c.type === 'interface_declaration' ||
        c.type === 'type_alias_declaration' ||
        c.type === 'enum_declaration'
      );
      if (!decl) continue;
    }

    if (decl.type === 'interface_declaration') {
      const name = decl.childForFieldName('name')?.text;
      if (!name) continue;

      const contract = {
        id: `${relFile}:${name}`,
        type: 'Interface',
        name,
        visibility: isExported ? 'public' : 'private',
      };

      // Extract extends clause
      const heritage = decl.children.find(c => c.type === 'extends_type_clause');
      if (heritage) {
        contract.extends = [];
        for (const child of heritage.namedChildren) {
          const typeName = child.type === 'type_identifier' ? child.text : child.text;
          if (typeName && typeName !== 'extends') contract.extends.push(typeName);
        }
      }

      // Extract fields from interface body
      const body = decl.childForFieldName('body');
      if (body) {
        contract.fields = [];
        for (const member of body.namedChildren) {
          if (member.type === 'property_signature' || member.type === 'public_field_definition') {
            const fieldName = member.childForFieldName('name')?.text;
            const typeAnnotation = member.childForFieldName('type') || member.children.find(c => c.type === 'type_annotation');
            let fieldType = 'unknown';
            if (typeAnnotation) {
              // type_annotation wraps the actual type; strip the ': '
              fieldType = typeAnnotation.text.replace(/^:\s*/, '');
            }
            if (fieldName) {
              contract.fields.push({ name: fieldName, type: fieldType });
            }
          } else if (member.type === 'method_signature') {
            const methodName = member.childForFieldName('name')?.text;
            if (methodName) {
              contract.fields.push({ name: methodName, type: 'method' });
            }
          }
        }
      }

      contracts.push(contract);

    } else if (decl.type === 'type_alias_declaration') {
      const name = decl.childForFieldName('name')?.text;
      if (!name) continue;

      contracts.push({
        id: `${relFile}:${name}`,
        type: 'TypeAlias',
        name,
        visibility: isExported ? 'public' : 'private',
      });

    } else if (decl.type === 'enum_declaration') {
      const name = decl.childForFieldName('name')?.text;
      if (!name) continue;

      const contract = {
        id: `${relFile}:${name}`,
        type: 'Enum',
        name,
        visibility: isExported ? 'public' : 'private',
        members: [],
      };

      const body = decl.childForFieldName('body');
      if (body) {
        for (const member of body.namedChildren) {
          if (member.type === 'enum_assignment' || member.type === 'enum_member') {
            // enum_assignment: "Debug = 'debug'" — first named child is the identifier
            const nameNode = member.namedChildren[0];
            const memberName = nameNode?.text || member.childForFieldName('name')?.text;
            if (memberName) contract.members.push(memberName);
          } else if (member.type === 'property_identifier') {
            if (member.text) contract.members.push(member.text);
          }
        }
      }

      contracts.push(contract);
    }
  }

  return contracts;
}

/**
 * Extract contracts from all TypeScript files in a subsystem map.
 * @param {object} subsystemMap - Result from buildSubsystems
 * @param {string} srcRoot - Absolute path to source root
 * @returns {object} { contracts, bySubsystem }
 */
function extractAllContracts(subsystemMap, srcRoot) {
  const allContracts = [];
  const bySubsystem = {};
  const parseErrors = [];

  for (const sub of subsystemMap.subsystems) {
    bySubsystem[sub.name] = [];

    for (const relFile of sub.files) {
      if (!relFile.endsWith('.ts') && !relFile.endsWith('.tsx')) continue;

      const absPath = path.join(srcRoot, relFile);
      if (!fs.existsSync(absPath)) continue;

      try {
        const contracts = extractContracts(absPath, relFile);
        allContracts.push(...contracts);
        bySubsystem[sub.name].push(...contracts);
      } catch (err) {
        parseErrors.push({ file: relFile, error: err.message });
      }
    }
  }

  return { contracts: allContracts, bySubsystem, parseErrors };
}

/**
 * Build a cross-reference map: which contracts are used by which subsystems.
 * @param {Array} contracts - All extracted contracts
 * @param {object} graph - Graph from GraphStore
 * @param {function} relPathFn - relPath function from subsystem.js
 * @returns {object} Map of contract name → { definedIn, usedBy[] }
 */
function buildContractXref(contracts, graph, relPathFn) {
  const xref = {};

  // Index contract names → definition location
  for (const c of contracts) {
    xref[c.name] = {
      id: c.id,
      type: c.type,
      definedIn: c.id.split(':')[0].split('/')[0],
      usedBy: new Set(),
    };
  }

  // Scan IMPORTS edges for contract references
  for (const e of graph.edges) {
    if (e.type !== 'IMPORTS') continue;
    const dep = e.target.replace('dep:', '');
    // Check if any contract is defined in the imported file
    for (const c of contracts) {
      const contractFile = c.id.split(':')[0];
      const contractFileNoExt = contractFile.replace(/\.\w+$/, '');
      if (dep === contractFile || dep === contractFileNoExt || dep.endsWith('/' + contractFile) || dep.endsWith('/' + contractFileNoExt)) {
        const sourceFile = relPathFn(e.source.split(':')[0]);
        const sourceSub = sourceFile.split('/')[0];
        if (sourceSub !== xref[c.name].definedIn) {
          xref[c.name].usedBy.add(sourceSub);
        }
      }
    }
  }

  // Convert Sets to Arrays
  for (const key of Object.keys(xref)) {
    xref[key].usedBy = Array.from(xref[key].usedBy);
  }

  return xref;
}

/**
 * Extract contracts from Helm chart data (from extract-helm.js).
 * Produces contracts in the same shape as TypeScript ones for diagram/xref compatibility.
 * @param {Array} helmCharts - From discoverCharts()
 * @returns {object} { contracts, bySubsystem }
 */
function extractHelmContracts(helmCharts) {
  const allContracts = [];
  const bySubsystem = {};

  for (const c of helmCharts) {
    const sub = c.dir.split('/')[0] || 'root';
    if (!bySubsystem[sub]) bySubsystem[sub] = [];

    // Chart itself as a contract (its values.yaml is the API surface)
    if (c.values.keys.length > 0) {
      const contract = {
        id: `${c.dir}/values.yaml:${c.chart.name}`,
        type: 'HelmValues',
        name: `${c.chart.name} (values)`,
        visibility: 'public',
        fields: c.values.keys.map(k => ({
          name: k.name,
          type: k.type || 'unknown',
        })),
      };
      allContracts.push(contract);
      bySubsystem[sub].push(contract);
    }

    // Services produced by this chart
    const services = c.templates.resources.filter(r => r.kind === 'Service');
    if (services.length > 0) {
      const contract = {
        id: `${c.dir}/templates:${c.chart.name}-services`,
        type: 'HelmServices',
        name: `${c.chart.name} (services)`,
        visibility: 'public',
        fields: services.map(s => ({
          name: s.name === '(templated)' ? `${c.chart.name}-svc` : s.name,
          type: 'Service',
        })),
      };
      // Add port info from interactions
      const ports = c.interactions.filter(i => i.type === 'port' && i.target !== '0');
      for (const p of ports) {
        contract.fields.push({ name: `port:${p.target}`, type: 'port' });
      }
      allContracts.push(contract);
      bySubsystem[sub].push(contract);
    }

    // Deployments/StatefulSets/DaemonSets as workload contracts
    const workloads = c.templates.resources.filter(r =>
      ['Deployment', 'StatefulSet', 'DaemonSet', 'Rollout', 'CronJob', 'Job'].includes(r.kind)
    );
    if (workloads.length > 0) {
      const contract = {
        id: `${c.dir}/templates:${c.chart.name}-workloads`,
        type: 'HelmWorkloads',
        name: `${c.chart.name} (workloads)`,
        visibility: 'public',
        fields: workloads.map(w => ({
          name: w.name === '(templated)' ? `${c.chart.name}-${w.kind.toLowerCase()}` : w.name,
          type: w.kind,
        })),
      };
      allContracts.push(contract);
      bySubsystem[sub].push(contract);
    }

    // External interactions as dependency contracts
    const extInteractions = c.interactions.filter(i => i.type === 'k8s-service' || i.type === 'config-ref');
    if (extInteractions.length > 0) {
      const contract = {
        id: `${c.dir}/templates:${c.chart.name}-deps`,
        type: 'HelmDependencies',
        name: `${c.chart.name} (external deps)`,
        visibility: 'public',
        fields: extInteractions.map(i => ({
          name: i.target,
          type: i.type,
        })),
      };
      allContracts.push(contract);
      bySubsystem[sub].push(contract);
    }
  }

  return { contracts: allContracts, bySubsystem };
}

if (require.main === module) {
  const srcRoot = process.argv[2];
  if (!srcRoot) {
    console.error('Usage: node contracts.js <srcRoot>');
    process.exit(1);
  }

  // Quick standalone mode: scan all .ts files under srcRoot
  const { buildSubsystems, relPath } = require('./subsystem.js');
  const GraphStore = require('./graph.js');

  // Need a snapshot for subsystem map
  const snapshotPath = process.argv[3];
  if (snapshotPath) {
    const graph = GraphStore.loadSnapshot(snapshotPath);
    const subs = buildSubsystems(graph);
    const result = extractAllContracts(subs, srcRoot);
    console.log(JSON.stringify(result, null, 2));
  } else {
    // Just scan the directory
    const files = [];
    function walk(dir, rel) {
      for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
        const full = path.join(dir, entry.name);
        const r = rel ? `${rel}/${entry.name}` : entry.name;
        if (entry.isDirectory()) walk(full, r);
        else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
          files.push({ abs: full, rel: r });
        }
      }
    }
    walk(srcRoot, '');
    const all = [];
    for (const f of files) {
      all.push(...extractContracts(f.abs, f.rel));
    }
    console.log(JSON.stringify({ contracts: all }, null, 2));
  }
}

module.exports = { extractContracts, extractAllContracts, buildContractXref, extractHelmContracts };
