#!/usr/bin/env node

import { promises as fs } from "node:fs";
import path from "node:path";

const ROOT = process.cwd();
const DOCS_DIR = path.join(ROOT, "docs");

const BACKEND_ROUTES_DIR = path.join(ROOT, "backend", "src", "routes");
const FRONTEND_PAGES_DIR = path.join(ROOT, "frontend", "pages");
const MOBILE_APP_DIR = path.join(ROOT, "mobile", "app");

const OUT_BACKEND = path.join(DOCS_DIR, "funktionen", "backend-api.md");
const OUT_FRONTEND = path.join(DOCS_DIR, "funktionen", "frontend-web.md");
const OUT_MOBILE = path.join(DOCS_DIR, "funktionen", "mobile-app.md");

const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];

async function walkFiles(dir, extension) {
  const result = [];

  async function walk(current) {
    const entries = await fs.readdir(current, { withFileTypes: true });

    for (const entry of entries) {
      const full = path.join(current, entry.name);
      if (entry.isDirectory()) {
        await walk(full);
      } else if (entry.isFile() && full.endsWith(extension)) {
        result.push(full);
      }
    }
  }

  await walk(dir);
  return result.sort();
}

function normalizePosix(p) {
  return p.split(path.sep).join("/");
}

function toRelative(filePath) {
  return normalizePosix(path.relative(ROOT, filePath));
}

function extractBackendEndpoints(sourceText) {
  const endpoints = [];
  const regex = /\b(?:server|subApp|m2mApp|devicesApp)\.(get|post|put|patch|delete|options|head)\(\s*["'`](.+?)["'`]/g;

  let match;
  while ((match = regex.exec(sourceText)) !== null) {
    const method = String(match[1] || "").toUpperCase();
    const routePath = String(match[2] || "");
    if (!HTTP_METHODS.includes(method.toLowerCase())) {
      continue;
    }
    endpoints.push({ method, path: routePath });
  }

  return endpoints.sort((a, b) => {
    if (a.path === b.path) {
      return a.method.localeCompare(b.method);
    }
    return a.path.localeCompare(b.path);
  });
}

function sortAndUnique(items) {
  return [...new Set(items)].sort((a, b) => a.localeCompare(b));
}

function filePathToNuxtRoute(filePath, baseDir) {
  const relative = normalizePosix(path.relative(baseDir, filePath));
  let route = relative.replace(/\.vue$/, "");
  route = route.replace(/\.client$/, "").replace(/\.server$/, "");

  route = route
    .replace(/\[\[\.\.\.(.+?)\]\]/g, ":$1*?")
    .replace(/\[\.\.\.(.+?)\]/g, ":$1*")
    .replace(/\[\[(.+?)\]\]/g, ":$1?")
    .replace(/\[(.+?)\]/g, ":$1");

  route = route.replace(/\/index$/g, "");
  if (route === "index") {
    route = "";
  }

  if (!route.startsWith("/")) {
    route = `/${route}`;
  }

  if (route === "") {
    return "/";
  }

  return route || "/";
}

function filePathToExpoRoute(filePath, baseDir) {
  const relative = normalizePosix(path.relative(baseDir, filePath));
  const baseName = path.basename(relative);

  if (baseName.startsWith("_")) {
    return null;
  }

  let route = relative.replace(/\.tsx$/, "");

  route = route
    .replace(/\[\[(.+?)\]\]/g, ":$1?")
    .replace(/\[(.+?)\]/g, ":$1");

  route = route.replace(/\/index$/g, "");
  if (route === "index") {
    route = "";
  }

  if (!route.startsWith("/")) {
    route = `/${route}`;
  }

  if (route === "") {
    return "/";
  }

  return route;
}

async function generateBackendDoc() {
  const files = await walkFiles(BACKEND_ROUTES_DIR, ".ts");

  let output = "# Backend API Funktionskatalog\n\n";
  output += "Automatisch generiert (deterministisch, ohne Zeitstempel).\n\n";
  output += "Hinweis: Diese Datei wird durch `docs/scripts/sync-funktionsdoku.mjs` erzeugt.\n\n";

  const allEndpoints = [];

  for (const file of files) {
    const source = await fs.readFile(file, "utf-8");
    const endpoints = extractBackendEndpoints(source);
    const relativeFile = toRelative(file);

    if (endpoints.length === 0) {
      continue;
    }

    output += `## ${relativeFile}\n\n`;
    output += "| Methode | Pfad |\n";
    output += "|---|---|\n";

    for (const endpoint of endpoints) {
      output += `| ${endpoint.method} | \`${endpoint.path}\` |\n`;
      allEndpoints.push(`${endpoint.method} ${endpoint.path}`);
    }

    output += "\n";
  }

  const uniqueCount = sortAndUnique(allEndpoints).length;
  output += `Gesamtzahl erkannter Endpunkte: **${uniqueCount}**\n`;

  await fs.writeFile(OUT_BACKEND, output, "utf-8");
}

async function generateFrontendDoc() {
  const files = await walkFiles(FRONTEND_PAGES_DIR, ".vue");

  const rows = files.map((file) => {
    const route = filePathToNuxtRoute(file, FRONTEND_PAGES_DIR);
    return { route, file: toRelative(file) };
  });

  rows.sort((a, b) => a.route.localeCompare(b.route));

  let output = "# Frontend Web Funktionskatalog\n\n";
  output += "Automatisch generiert (deterministisch, ohne Zeitstempel).\n\n";
  output += "Hinweis: Diese Datei wird durch `docs/scripts/sync-funktionsdoku.mjs` erzeugt.\n\n";
  output += "| Route (Nuxt) | Datei |\n";
  output += "|---|---|\n";

  for (const row of rows) {
    output += `| \`${row.route}\` | \`${row.file}\` |\n`;
  }

  output += `\nGesamtzahl erkannter Web-Routen: **${rows.length}**\n`;

  await fs.writeFile(OUT_FRONTEND, output, "utf-8");
}

async function generateMobileDoc() {
  const files = await walkFiles(MOBILE_APP_DIR, ".tsx");

  const rows = files
    .map((file) => {
      const route = filePathToExpoRoute(file, MOBILE_APP_DIR);
      if (!route) {
        return null;
      }
      return { route, file: toRelative(file) };
    })
    .filter(Boolean);

  rows.sort((a, b) => a.route.localeCompare(b.route));

  let output = "# Mobile App Funktionskatalog\n\n";
  output += "Automatisch generiert (deterministisch, ohne Zeitstempel).\n\n";
  output += "Hinweis: Diese Datei wird durch `docs/scripts/sync-funktionsdoku.mjs` erzeugt.\n\n";
  output += "| Route (Expo Router) | Datei |\n";
  output += "|---|---|\n";

  for (const row of rows) {
    output += `| \`${row.route}\` | \`${row.file}\` |\n`;
  }

  output += `\nGesamtzahl erkannter Mobile-Screens: **${rows.length}**\n`;

  await fs.writeFile(OUT_MOBILE, output, "utf-8");
}

async function main() {
  await generateBackendDoc();
  await generateFrontendDoc();
  await generateMobileDoc();
  console.log("Funktionsdokumentation erfolgreich synchronisiert.");
}

main().catch((error) => {
  console.error("Fehler bei der Doku-Synchronisierung:", error);
  process.exit(1);
});
