import fs from "fs";
import path from "path";
import { analyzeNextRouteTree } from "./next-static-analysis.mjs";
import { extractBackendImports, extractExportedFunctions, extractExportedMethodBodies } from "./backend-discovery.mjs";
import { resolveImportToSourceFile, walkFiles } from "./fs-walk.mjs";
import { routeFromApiFile, routeFromAppFile } from "./routing.mjs";
import {
  dedupeNodes,
  hasWord,
  HTTP_METHODS,
  isServerActionFile,
  normalizePath,
  normalizeRoute,
  pageLabelFromRoute,
  toApiRequestPath,
} from "./shared.mjs";

export function discoverPageViews({ serviceName, serviceRoot, nextAppRoot, exclude = [], resolveImportToSourceFile }) {
  const pageFiles = walkFiles(nextAppRoot, { baseDir: serviceRoot, exclude }).filter(
    (filePath) => filePath.endsWith("/page.tsx") || filePath.endsWith("/page.ts")
  );
  const nodes = [];
  const edges = [];
  const pageEntries = [];

  for (const absolutePath of pageFiles) {
    const relativePath = normalizePath(path.relative(serviceRoot, absolutePath));
    const route = routeFromAppFile(nextAppRoot, absolutePath);
    const content = fs.readFileSync(absolutePath, "utf8");
    const node = {
      id: `page_view:${serviceName}:${route}`,
      kind: "page_view",
      service: serviceName,
      label: pageLabelFromRoute(route),
      route,
      filePath: relativePath,
    };
    nodes.push(node);
    const routeRootFiles = collectRouteRootFiles(nextAppRoot, absolutePath)
      .map((absoluteRootPath) => ({
        filePath: normalizePath(path.relative(serviceRoot, absoluteRootPath)),
        content: fs.readFileSync(absoluteRootPath, "utf8"),
      }));

    const pageAnalysis = analyzeNextRouteTree({
      serviceName,
      serviceRoot,
      route,
      rootFiles: routeRootFiles,
      readSourceFile: (resolvedPath) => {
        const absoluteResolved = path.join(serviceRoot, resolvedPath);
        return fs.existsSync(absoluteResolved) ? fs.readFileSync(absoluteResolved, "utf8") : null;
      },
      resolveImportPath: (fromFilePath, specifier) => resolveImportToSourceFile(serviceRoot, fromFilePath, specifier),
      isServerActionFile,
      normalizeRoute,
    });

    for (const analysisNode of pageAnalysis.nodes) nodes.push(analysisNode);
    for (const analysisEdge of pageAnalysis.edges) edges.push(analysisEdge);

    pageEntries.push({
      node,
      route,
      filePath: relativePath,
      requests: pageAnalysis.requests,
      serverActionRefs: pageAnalysis.serverActionRefs,
      surfacesByTargetValue: pageAnalysis.surfacesByTargetValue,
    });
  }

  return { nodes, edges, pageEntries };
}

function collectRouteRootFiles(nextAppRoot, pageAbsolutePath) {
  const rootFiles = [];
  let currentDir = path.dirname(pageAbsolutePath);
  const appRoot = path.resolve(nextAppRoot);

  while (currentDir.startsWith(appRoot)) {
    const layoutFile = resolveRouteCompanion(currentDir, "layout");
    if (layoutFile) rootFiles.push(layoutFile);
    if (currentDir === appRoot) break;
    currentDir = path.dirname(currentDir);
  }

  rootFiles.reverse();
  rootFiles.push(pageAbsolutePath);
  return [...new Set(rootFiles)];
}

function resolveRouteCompanion(directoryPath, baseName) {
  const candidates = [
    path.join(directoryPath, `${baseName}.tsx`),
    path.join(directoryPath, `${baseName}.ts`),
    path.join(directoryPath, `${baseName}.jsx`),
    path.join(directoryPath, `${baseName}.js`),
  ];
  return candidates.find((candidate) => fs.existsSync(candidate)) || null;
}

export function discoverApiRoutes({ serviceName, serviceRoot, nextAppRoot, exclude = [], resolveImportToSourceFile }) {
  const routeFiles = walkFiles(path.join(nextAppRoot, "api"), { baseDir: serviceRoot, exclude }).filter(
    (filePath) => filePath.endsWith("/route.ts") || filePath.endsWith("/route.tsx") || filePath.endsWith("/route.js")
  );
  const nodes = [];
  const edges = [];
  const routeEntries = [];

  for (const absolutePath of routeFiles) {
    const filePath = normalizePath(path.relative(serviceRoot, absolutePath));
    const route = routeFromApiFile(nextAppRoot, absolutePath);
    const requestPath = toApiRequestPath(route);
    const content = fs.readFileSync(absolutePath, "utf8");
    const methodBodies = extractExportedMethodBodies(content, HTTP_METHODS);
    const backendImports = extractBackendImports({ serviceName, serviceRoot, filePath, content, resolveImportToSourceFile });

    for (const [method, body] of methodBodies) {
      const node = {
        id: `api_route:${serviceName}:${method}:${requestPath}`,
        kind: "api_route",
        service: serviceName,
        label: `${method} ${requestPath}`,
        route,
        method,
        path: requestPath,
        filePath,
      };
      nodes.push(node);

      const matchedCapabilities = backendImports.filter((entry) => hasWord(body, entry.importName));
      for (const capability of matchedCapabilities) {
        nodes.push(capability.node);
        edges.push({
          id: `delegates_to:${node.id}:${capability.node.id}`,
          kind: "delegates_to",
          from: node.id,
          to: capability.node.id,
          confidence: "high",
        });
      }

      routeEntries.push({ node, method, route, requestPath, filePath });
    }
  }

  return { nodes: dedupeNodes(nodes), edges, routeEntries };
}

export function discoverServerActions({ serviceName, serviceRoot, exclude = [], resolveImportToSourceFile }) {
  const appRoots = [path.join(serviceRoot, "app"), path.join(serviceRoot, "src", "app")].filter((candidate) =>
    fs.existsSync(candidate)
  );
  const nodes = [];
  const edges = [];
  const actionEntries = [];

  for (const appRoot of appRoots) {
    for (const absolutePath of walkFiles(appRoot, { baseDir: serviceRoot, exclude })) {
      if (!absolutePath.endsWith(".ts") && !absolutePath.endsWith(".tsx")) continue;
      const content = fs.readFileSync(absolutePath, "utf8");
      if (!isServerActionFile(content)) continue;

      const relativePath = normalizePath(path.relative(serviceRoot, absolutePath));
      const backendImports = extractBackendImports({
        serviceName,
        serviceRoot,
        filePath: relativePath,
        content,
        resolveImportToSourceFile,
      });
      const exportedFunctions = extractExportedFunctions(content);

      for (const exported of exportedFunctions) {
        const node = {
          id: `server_action:${serviceName}:${relativePath}#${exported.name}`,
          kind: "server_action",
          service: serviceName,
          label: exported.name,
          filePath: relativePath,
        };
        nodes.push(node);
        actionEntries.push({
          node,
          exportName: exported.name,
          sourceFile: relativePath,
        });

        const matchedCapabilities = backendImports.filter((entry) => hasWord(exported.body, entry.importName));
        for (const capability of matchedCapabilities) {
          nodes.push(capability.node);
          edges.push({
            id: `delegates_to:${node.id}:${capability.node.id}`,
            kind: "delegates_to",
            from: node.id,
            to: capability.node.id,
            confidence: "high",
          });
        }
      }
    }
  }

  return { nodes: dedupeNodes(nodes), edges, actionEntries };
}
