import fs from "fs";
import path from "path";
import {
  dedupeBackendImports,
  dedupeDataImports,
  dedupeEdges,
  dedupeNodes,
  escapeRegExp,
  hasWord,
  isBackendSpecifier,
  isDataSpecifier,
  modulePathKey,
} from "./shared.mjs";
import { resolveImportToSourceFile } from "./fs-walk.mjs";

export function extractBackendImports({
  serviceName,
  serviceRoot,
  filePath,
  content,
  resolveImport = resolveImportToSourceFile,
  resolveImportToSourceFile: resolveImportAlias,
}) {
  const resolveImportFn = resolveImportAlias || resolveImport;
  const imports = [];
  const importRegex = /import\s+\{([^}]+)\}\s+from\s+["'`]([^"'`]+)["'`]/gu;
  for (const match of content.matchAll(importRegex)) {
    const rawNames = match[1]
      .split(",")
      .map((entry) => entry.trim())
      .map((entry) => entry.split(/\s+as\s+/u)[0]?.trim())
      .filter(Boolean);
    const specifier = match[2].trim();
    if (!isBackendSpecifier(specifier)) continue;
    const resolvedFilePath = resolveImportFn(serviceRoot, filePath, specifier);
    for (const importName of rawNames) {
      imports.push({
        importName,
        node: {
          id: `server_capability:${modulePathKey(resolvedFilePath || filePath)}#${importName}`,
          kind: "server_capability",
          service: serviceName,
          label: importName,
          filePath: resolvedFilePath || null,
          metadata: {
            specifier,
          },
        },
      });
    }
  }
  return dedupeBackendImports(imports);
}

export function discoverDataCapabilities({ serviceName, serviceRoot, nodes, readFile = defaultReadFile }) {
  const dataNodes = [];
  const dataEdges = [];
  const serverCapabilities = nodes.filter((node) => node.kind === "server_capability" && node.filePath);

  for (const capability of serverCapabilities) {
    const absolutePath = path.join(serviceRoot, capability.filePath);
    const content = readFile(absolutePath);
    if (!content) continue;
    const dataImports = extractDataImports({ serviceName, serviceRoot, filePath: capability.filePath, content });
    const exportedBody = extractExportedFunctionBody(content, capability.label);
    if (!exportedBody) continue;

    const matchedDataImports = dataImports.filter((entry) => hasWord(exportedBody, entry.importName));
    for (const dataImport of matchedDataImports) {
      dataNodes.push(dataImport.node);
      dataEdges.push({
        id: `delegates_to:${capability.id}:${dataImport.node.id}`,
        kind: "delegates_to",
        from: capability.id,
        to: dataImport.node.id,
        confidence: "high",
      });
    }
  }

  return {
    nodes: dedupeNodes(dataNodes),
    edges: dedupeEdges(dataEdges),
  };
}

export function extractDataImports({
  serviceName,
  serviceRoot,
  filePath,
  content,
  resolveImport = resolveImportToSourceFile,
  resolveImportToSourceFile: resolveImportAlias,
}) {
  const resolveImportFn = resolveImportAlias || resolveImport;
  const imports = [];
  const importRegex = /import\s+\{([^}]+)\}\s+from\s+["'`]([^"'`]+)["'`]/gu;
  for (const match of content.matchAll(importRegex)) {
    const rawNames = match[1]
      .split(",")
      .map((entry) => entry.trim())
      .map((entry) => entry.split(/\s+as\s+/u)[0]?.trim())
      .filter(Boolean);
    const specifier = match[2].trim();
    if (!isDataSpecifier(specifier)) continue;
    const resolvedFilePath = resolveImportFn(serviceRoot, filePath, specifier);
    for (const importName of rawNames) {
      imports.push({
        importName,
        node: {
          id: `data_capability:${modulePathKey(resolvedFilePath || filePath)}#${importName}`,
          kind: "data_capability",
          service: serviceName,
          label: importName,
          filePath: resolvedFilePath || null,
          metadata: {
            specifier,
          },
        },
      });
    }
  }
  return dedupeDataImports(imports);
}

export function extractExportedMethodBodies(content, methods) {
  const bodies = [];
  for (const method of methods) {
    const body = extractExportedFunctionBody(content, method);
    if (body) bodies.push([method, body]);
  }
  return bodies;
}

export function extractExportedFunctions(content) {
  const exported = [];
  const functionRegex = /export\s+(?:async\s+)?function\s+([A-Za-z0-9_]+)\s*\(/gu;
  for (const match of content.matchAll(functionRegex)) {
    const name = match[1];
    const body = extractExportedFunctionBody(content, name);
    if (!body) continue;
    exported.push({ name, body });
  }
  return exported;
}

export function extractExportedFunctionBody(content, exportName) {
  const functionStart = new RegExp(`export\\s+(?:async\\s+)?function\\s+${escapeRegExp(exportName)}\\s*\\(`, "u");
  const startMatch = functionStart.exec(content);
  if (!startMatch) return null;
  const afterSignatureIndex = content.indexOf("{", startMatch.index);
  if (afterSignatureIndex === -1) return null;
  return readBalancedBlock(content, afterSignatureIndex);
}

export function readBalancedBlock(content, startIndex) {
  let depth = 0;
  let inSingle = false;
  let inDouble = false;
  let inTemplate = false;

  for (let index = startIndex; index < content.length; index += 1) {
    const char = content[index];
    const previous = content[index - 1];
    if (char === "'" && !inDouble && !inTemplate && previous !== "\\") inSingle = !inSingle;
    if (char === '"' && !inSingle && !inTemplate && previous !== "\\") inDouble = !inDouble;
    if (char === "`" && !inSingle && !inDouble && previous !== "\\") inTemplate = !inTemplate;
    if (inSingle || inDouble || inTemplate) continue;
    if (char === "{") depth += 1;
    if (char === "}") {
      depth -= 1;
      if (depth === 0) {
        return content.slice(startIndex, index + 1);
      }
    }
  }
  return null;
}

function defaultReadFile(filePath) {
  if (!fs.existsSync(filePath)) return null;
  return fs.readFileSync(filePath, "utf8");
}
