/**
 * VLMetadata Tool - extracts structured metadata from VL project files
 * Integrates the vl-metadata-extractor.js concepts
 * Provides: file manifest, dependency graph, component/service catalogs
 *
 * Also: "extract-from-specs" — reads 9-file workflow spec files (Process/Specs/*)
 * and builds a complete ProjectMeta.json from them.
 */
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';

export function createVLMetadataTool(projectContext) {
  return {
    description: 'Extract structured metadata from VL project files or 9-file spec files. Actions: "manifest" (file list), "dependencies" (dep graph), "components" (all components), "services" (all services), "summary" (full overview), "extract-from-specs" (build ProjectMeta from 9-file workflow specs in Process/Specs/).',
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['manifest', 'dependencies', 'components', 'services', 'summary', 'extract-from-specs'],
          description: 'What metadata to extract',
        },
      },
      required: ['action'],
    },
    execute: async (input) => {
      switch (input.action) {
        case 'manifest':
          return getManifest(projectContext);
        case 'dependencies':
          return getDependencies(projectContext);
        case 'components':
          return getComponents(projectContext);
        case 'services':
          return getServices(projectContext);
        case 'summary':
          return getSummary(projectContext);
        case 'extract-from-specs':
          return extractFromSpecs(projectContext);
        default:
          return 'Unknown action. Use: manifest, dependencies, components, services, summary, extract-from-specs.';
      }
    },
  };
}

function getManifest(ctx) {
  const manifest = {
    projectName: ctx.projectName,
    vlVersion: ctx.vlVersion,
    files: ctx.getAllFiles().map(f => ({
      path: f.path,
      type: f.type,
      name: f.name,
    })),
  };
  return JSON.stringify(manifest, null, 2);
}

async function getDependencies(ctx) {
  const nodes = [];
  const edges = [];

  for (const file of ctx.getAllFiles()) {
    nodes.push({ id: file.path, type: file.type, label: file.name });

    // Parse file for references
    try {
      const content = await fs.readFile(file.fullPath, 'utf-8');

      // Find Section references in App files
      if (file.type === 'app') {
        const sectionRefs = content.matchAll(/<Section-(\w+)\s+"[^"]+"/g);
        for (const m of sectionRefs) {
          const target = ctx.getFilesByType('section').find(f => f.name === m[1]);
          if (target) edges.push({ from: file.path, to: target.path, type: 'hosts' });
        }
        const compRefs = content.matchAll(/<Component-(\w+)\s+"[^"]+"/g);
        for (const m of compRefs) {
          const target = ctx.getFilesByType('component').find(f => f.name === m[1]);
          if (target) edges.push({ from: file.path, to: target.path, type: 'uses' });
        }
      }

      // Find Service references in Section files
      if (file.type === 'section') {
        const serviceRefs = content.matchAll(/<ServiceDomain-(\w+)>/g);
        for (const m of serviceRefs) {
          const target = ctx.getFilesByType('service').find(f => f.name === m[1]);
          if (target) edges.push({ from: file.path, to: target.path, type: 'calls' });
        }
        const compRefs = content.matchAll(/<Component-(\w+)\s+"[^"]+"/g);
        for (const m of compRefs) {
          const target = ctx.getFilesByType('component').find(f => f.name === m[1]);
          if (target) edges.push({ from: file.path, to: target.path, type: 'uses' });
        }
      }
    } catch {
      continue;
    }
  }

  return JSON.stringify({ nodes, edges }, null, 2);
}

async function getComponents(ctx) {
  const components = [];
  for (const file of ctx.getFilesByType('component')) {
    try {
      const content = await fs.readFile(file.fullPath, 'utf-8');
      const props = [];
      const events = [];

      // Extract public props
      const propMatches = content.matchAll(/\$(\w+)\(([^)]+)\)\s*=\s*(.+)/g);
      for (const m of propMatches) {
        props.push({ name: m[1], type: m[2], default: m[3].trim() });
      }

      // Extract events
      const eventMatches = content.matchAll(/EVENT\s+@(\w+)\(([^)]*)\)/g);
      for (const m of eventMatches) {
        events.push({ name: m[1], params: m[2] });
      }

      components.push({ id: file.name, path: file.path, props, events });
    } catch {
      components.push({ id: file.name, path: file.path, props: [], events: [] });
    }
  }

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

async function getServices(ctx) {
  const services = [];
  for (const file of ctx.getFilesByType('service')) {
    try {
      const content = await fs.readFile(file.fullPath, 'utf-8');
      const methods = [];

      // Extract SERVICE and PUBLIC_SERVICE declarations
      const serviceMatches = content.matchAll(/(PUBLIC_)?SERVICE\s+(\w+)\(([^)]*)\);\s*RETURN\s+(\{[^}]*\}|\w+)/g);
      for (const m of serviceMatches) {
        methods.push({
          name: m[2],
          isPublic: !!m[1],
          params: m[3],
          returns: m[4],
        });
      }

      services.push({ domainId: file.name, path: file.path, methods });
    } catch {
      services.push({ domainId: file.name, path: file.path, methods: [] });
    }
  }

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

async function getSummary(ctx) {
  const summary = ctx.getSummary();
  if (!summary) return 'No VL project detected.';

  const files = ctx.getAllFiles();
  const lines = [
    `Project: ${summary.projectName || 'Unknown'}`,
    `VL Version: ${summary.vlVersion || 'Unknown'}`,
    `Files: ${summary.totalFiles} (${summary.breakdown})`,
    '',
    'File Inventory:',
  ];

  for (const type of ['app', 'section', 'component', 'service', 'database', 'theme']) {
    const typeFiles = files.filter(f => f.type === type);
    if (typeFiles.length > 0) {
      lines.push(`  ${type}: ${typeFiles.map(f => f.name).join(', ')}`);
    }
  }

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

/**
 * Extract ProjectMeta from 9-file workflow spec files in Process/Specs/.
 * Handles both valid JSON files and files with LLM commentary mixed in.
 */
async function extractFromSpecs(ctx) {
  const projectDir = ctx.workDir || ctx.projectDir || process.cwd();
  const specsDir = path.join(projectDir, 'Process', 'Specs');

  if (!fsSync.existsSync(specsDir)) {
    return `No Process/Specs/ directory found at ${specsDir}. Run 9-file workflow first.`;
  }

  const specFiles = [
    'IntentSpec.json',
    'DomainSchema.json',
    'ServiceContractMap.json',
    'UIContractMap.json',
    'ModuleManifest.json',
    'DependencyGraph.json',
    'QualityGateSpec.json',
  ];

  const meta = { extractedAt: new Date().toISOString(), projectDir, specs: {}, errors: [] };
  let validCount = 0;

  for (const filename of specFiles) {
    const fp = path.join(specsDir, filename);
    const key = filename.replace('.json', '');
    if (!fsSync.existsSync(fp)) {
      meta.errors.push(`Missing: ${filename}`);
      continue;
    }
    try {
      const raw = await fs.readFile(fp, 'utf-8');
      const parsed = extractJson(raw);
      if (parsed) {
        meta.specs[key] = parsed;
        validCount++;
      } else {
        meta.errors.push(`${filename}: no valid JSON found (file contains only text)`);
      }
    } catch (e) {
      meta.errors.push(`${filename}: ${e.message}`);
    }
  }

  // Build summary from parsed specs
  const summary = buildSummaryFromSpecs(meta.specs);
  meta.summary = summary;

  // Write ProjectMeta.json
  const vlDir = path.join(projectDir, '.vl-code');
  if (!fsSync.existsSync(vlDir)) fsSync.mkdirSync(vlDir, { recursive: true });
  const metaPath = path.join(vlDir, 'ProjectMeta.json');
  await fs.writeFile(metaPath, JSON.stringify(meta, null, 2));

  // Format response
  const lines = [
    `ProjectMeta extracted: ${validCount}/${specFiles.length} specs parsed`,
    `Saved to: .vl-code/ProjectMeta.json`,
  ];
  if (meta.errors.length) {
    lines.push('', 'Errors:', ...meta.errors.map(e => `  - ${e}`));
  }
  if (summary) {
    lines.push('', '--- Summary ---');
    if (summary.projectName) lines.push(`Project: ${summary.projectName}`);
    if (summary.platform) lines.push(`Platform: ${summary.platform}`);
    if (summary.entities?.length) lines.push(`Entities: ${summary.entities.join(', ')}`);
    if (summary.services?.length) lines.push(`Service Domains: ${summary.services.map(s => s.domain).join(', ')}`);
    if (summary.sections?.length) lines.push(`Sections: ${summary.sections.join(', ')}`);
    if (summary.components?.length) lines.push(`Components: ${summary.components.join(', ')}`);
    if (summary.apps?.length) lines.push(`Apps: ${summary.apps.join(', ')}`);
    if (summary.totalModules) lines.push(`Total Modules: ${summary.totalModules}`);
  }
  return lines.join('\n');
}

/** Extract JSON from text that may contain LLM commentary */
function extractJson(raw) {
  const trimmed = raw.trim();
  // Try direct parse first
  try { return JSON.parse(trimmed); } catch {}

  // Strip markdown fences
  const defenced = trimmed
    .replace(/^\s*```(?:json|JSON)?\s*\n?/, '')
    .replace(/\n?\s*```\s*$/, '')
    .trim();
  try { return JSON.parse(defenced); } catch {}

  // Find first { or [ and extract balanced JSON
  const idxObj = defenced.indexOf('{');
  const idxArr = defenced.indexOf('[');
  let start = -1, open = '{', close = '}';
  if (idxObj >= 0 && (idxArr < 0 || idxObj < idxArr)) {
    start = idxObj;
  } else if (idxArr >= 0) {
    start = idxArr; open = '['; close = ']';
  }
  if (start < 0) return null;

  let depth = 0, inStr = false, esc = false, end = -1;
  for (let i = start; i < defenced.length; i++) {
    const c = defenced[i];
    if (esc) { esc = false; continue; }
    if (c === '\\' && inStr) { esc = true; continue; }
    if (c === '"' && !esc) { inStr = !inStr; continue; }
    if (inStr) continue;
    if (c === open) depth++;
    else if (c === close) { depth--; if (depth === 0) { end = i; break; } }
  }
  if (end > start) {
    try { return JSON.parse(defenced.substring(start, end + 1)); } catch {}
  }
  return null;
}

/** Build a human-readable summary from parsed spec objects */
function buildSummaryFromSpecs(specs) {
  const s = {};

  // From IntentSpec
  if (specs.IntentSpec) {
    const is = specs.IntentSpec;
    s.projectName = is.projectName || is.project?.name || is.project?.projectName;
    s.platform = is.platform || is.targetDevice || is.project?.platform;
    s.resolution = is.resolution || is.project?.resolution;
    s.routeStrategy = is.routeStrategy || is.apps?.[0]?.routeStrategy;
    s.sections = is.views?.flatMap(v => v.sectionIds || []) || is.apps?.[0]?.views?.flatMap(v => v.sectionIds || []) || is.sectionIds || [];
    // Dedupe
    s.sections = [...new Set(s.sections)];
  }

  // From DomainSchema
  if (specs.DomainSchema) {
    const ds = specs.DomainSchema;
    s.entities = (ds.entities || []).map(e => `${e.entityId || e.entityName || e.name || e.tableName}(${(e.fields || []).length} fields)`);
    s.valueDomains = (ds.valueDomains || []).length;
  }

  // From ServiceContractMap
  if (specs.ServiceContractMap) {
    const sc = specs.ServiceContractMap;
    s.services = (sc.domains || sc.serviceDomains || []).map(d => ({
      domain: d.domainId || d.domain,
      count: (d.services || []).length,
    }));
  }

  // From ModuleManifest
  if (specs.ModuleManifest) {
    const mm = specs.ModuleManifest;
    // Support both flat modules[] and separate *Files arrays
    if (mm.modules?.length) {
      s.totalModules = mm.modules.length;
      s.apps = mm.modules.filter(m => m.type === 'app').map(m => m.name || m.appId);
      s.components = mm.modules.filter(m => m.type === 'component').map(m => m.name || m.componentId);
      const secs = mm.modules.filter(m => m.type === 'section').map(m => m.name || m.sectionId);
      if (secs.length) s.sections = secs;
    } else {
      const sectionFiles = mm.sectionFiles || [];
      const componentFiles = mm.componentFiles || [];
      const appFiles = mm.appFiles || [];
      const serviceFiles = mm.serviceFiles || [];
      s.totalModules = sectionFiles.length + componentFiles.length + appFiles.length + serviceFiles.length + (mm.databaseFile ? 1 : 0) + (mm.themeFile ? 1 : 0);
      s.apps = appFiles.map(m => m.appId || m.name?.replace(/\.vx$/, ''));
      s.components = componentFiles.map(m => m.componentId || m.name?.replace(/\.cp$/, ''));
      s.sections = sectionFiles.map(m => m.sectionId || m.name?.replace(/\.sc$/, ''));
    }
  }

  // From DependencyGraph
  if (specs.DependencyGraph) {
    const dg = specs.DependencyGraph;
    s.depNodes = (dg.nodes || []).length;
    s.depEdges = (dg.edges || []).length;
  }

  return Object.keys(s).length ? s : null;
}
