/**
 * proxy.ts / middleware.ts runner
 *
 * Loads and executes the user's proxy.ts (Next.js 16) or middleware.ts file
 * before routing. Runs in Node (not Edge Runtime), per the vinext design.
 *
 * In Next.js 16, proxy.ts replaces middleware.ts:
 * - proxy.ts: default export function, runs on Node.js runtime
 * - middleware.ts: deprecated but still supported for Edge runtime use cases
 *
 * The proxy/middleware receives a NextRequest and can:
 * - Return NextResponse.next() to continue to the route
 * - Return NextResponse.redirect() to redirect
 * - Return NextResponse.rewrite() to rewrite the URL
 * - Set/modify headers and cookies
 * - Return a Response directly (e.g., for auth guards)
 *
 * Supports the `config.matcher` export for path filtering.
 */

import type { ViteDevServer } from "vite";
import fs from "node:fs";
import path from "node:path";
import { NextRequest } from "../shims/server.js";
import { safeRegExp } from "../config/config-matchers.js";

/**
 * Possible proxy/middleware file names.
 * proxy.ts (Next.js 16) is checked first, then middleware.ts (deprecated).
 */
const PROXY_FILES = [
  "proxy.ts",
  "proxy.js",
  "proxy.mjs",
  "src/proxy.ts",
  "src/proxy.js",
  "src/proxy.mjs",
];

const MIDDLEWARE_FILES = [
  "middleware.ts",
  "middleware.tsx",
  "middleware.js",
  "middleware.mjs",
  "src/middleware.ts",
  "src/middleware.tsx",
  "src/middleware.js",
  "src/middleware.mjs",
];

/**
 * Find the proxy or middleware file in the project root.
 * Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
 * If middleware.ts is found, logs a deprecation warning.
 */
export function findMiddlewareFile(root: string): string | null {
  // Check proxy.ts first (Next.js 16 replacement for middleware.ts)
  for (const file of PROXY_FILES) {
    const fullPath = path.join(root, file);
    if (fs.existsSync(fullPath)) {
      return fullPath;
    }
  }

  // Fall back to middleware.ts (deprecated in Next.js 16)
  for (const file of MIDDLEWARE_FILES) {
    const fullPath = path.join(root, file);
    if (fs.existsSync(fullPath)) {
      console.warn(
        "[vinext] middleware.ts is deprecated in Next.js 16. " +
          "Rename to proxy.ts and export a default function.",
      );
      return fullPath;
    }
  }
  return null;
}

/** Matcher pattern from middleware config export. */
type MatcherConfig =
  | string
  | string[]
  | { source: string; regexp?: string; locale?: boolean; has?: any[]; missing?: any[] }[];

/**
 * Check if a pathname matches the middleware matcher config.
 * If no matcher is configured, middleware runs on all paths
 * except static files and internal Next.js paths.
 */
export function matchesMiddleware(
  pathname: string,
  matcher: MatcherConfig | undefined,
): boolean {
  if (!matcher) {
    // Default: match all paths except static files, _next, and favicon
    return (
      !pathname.startsWith("/_next") &&
      !pathname.startsWith("/api") &&
      !pathname.includes(".") &&
      pathname !== "/favicon.ico"
    );
  }

  const patterns: string[] = [];
  if (typeof matcher === "string") {
    patterns.push(matcher);
  } else if (Array.isArray(matcher)) {
    for (const m of matcher) {
      if (typeof m === "string") {
        patterns.push(m);
      } else if (m && typeof m === "object" && "source" in m) {
        patterns.push(m.source);
      }
    }
  }

  return patterns.some((pattern) => matchPattern(pathname, pattern));
}

/**
 * Match a single pattern against a pathname.
 * Supports Next.js matcher patterns:
 *   /about          -> exact match
 *   /dashboard/:path*  -> prefix match with params
 *   /api/:path+     -> one or more segments
 *   /((?!api|_next).*)  -> regex patterns
 */
export function matchPattern(pathname: string, pattern: string): boolean {
  // Handle regex patterns (starts with /)
  if (pattern.includes("(") || pattern.includes("\\")) {
    const re = safeRegExp("^" + pattern + "$");
    if (re) return re.test(pathname);
    // Fall through to simple matching
  }

  // Convert Next.js path patterns to regex.
  // Escape dots FIRST (before replacements that produce regex metacharacters
  // like .* and .+ which must not be escaped).
  const regexStr = pattern
    // Escape dots in the literal path segments
    .replace(/\./g, "\\.")
    // /:path* -> optionally match slash + zero or more segments
    .replace(/\/:(\w+)\*/g, "(?:/.*)?")
    // /:path+ -> match slash + one or more segments
    .replace(/\/:(\w+)\+/g, "(?:/.+)")
    // :param -> match one segment
    .replace(/:(\w+)/g, "([^/]+)");

  const re = safeRegExp("^" + regexStr + "$");
  if (re) return re.test(pathname);
  return pathname === pattern;
}

/** Result of running middleware. */
export interface MiddlewareResult {
  /** Whether to continue to the route handler. */
  continue: boolean;
  /** If set, redirect to this URL. */
  redirectUrl?: string;
  /** HTTP status for redirect (default 307). */
  redirectStatus?: number;
  /** If set, rewrite to this URL (internal). */
  rewriteUrl?: string;
  /** HTTP status for rewrite (e.g. 403 from NextResponse.rewrite(url, { status: 403 })). */
  rewriteStatus?: number;
  /** Headers to set on the response. */
  responseHeaders?: Headers;
  /** If the middleware returned a full Response, use it directly. */
  response?: Response;
}

/**
 * Load and execute middleware for a given request.
 *
 * @param server - Vite dev server (for SSR module loading)
 * @param middlewarePath - Absolute path to the middleware file
 * @param request - The incoming Request object
 * @returns Middleware result describing what action to take
 */
export async function runMiddleware(
  server: ViteDevServer,
  middlewarePath: string,
  request: Request,
): Promise<MiddlewareResult> {
  // Load the middleware module via Vite's SSR module loader
  const mod = await server.ssrLoadModule(middlewarePath);

  // Accept: default export, named "proxy" (Next.js 16), or named "middleware"
  const middlewareFn = mod.default ?? mod.proxy ?? mod.middleware;
  if (typeof middlewareFn !== "function") {
    // No proxy/middleware function exported — continue as normal
    return { continue: true };
  }

  // Check matcher config
  const config = mod.config;
  const matcher = config?.matcher;
  const url = new URL(request.url);

  if (!matchesMiddleware(url.pathname, matcher)) {
    return { continue: true };
  }

  // Wrap in NextRequest so middleware gets .nextUrl, .cookies, .geo, .ip, etc.
  const nextRequest = request instanceof NextRequest ? request : new NextRequest(request);

  // Execute the middleware
  let response: Response | undefined;
  try {
    response = await middlewareFn(nextRequest);
  } catch (e: any) {
    console.error("[vinext] Middleware error:", e);
    return {
      continue: false,
      response: new Response("Middleware Error: " + (e?.message ?? String(e)), {
        status: 500,
      }),
    };
  }

  // No response = continue
  if (!response) {
    return { continue: true };
  }

  // Check for x-middleware-next header (NextResponse.next())
  if (response.headers.get("x-middleware-next") === "1") {
    // Continue to the route, but apply any headers the middleware set
    const responseHeaders = new Headers();
    for (const [key, value] of response.headers) {
      if (
        key !== "x-middleware-next" &&
        key !== "x-middleware-rewrite"
      ) {
        responseHeaders.set(key, value);
      }
    }
    return { continue: true, responseHeaders };
  }

  // Check for redirect (3xx status)
  if (response.status >= 300 && response.status < 400) {
    const location = response.headers.get("Location") ?? response.headers.get("location");
    if (location) {
      return {
        continue: false,
        redirectUrl: location,
        redirectStatus: response.status,
      };
    }
  }

  // Check for rewrite (x-middleware-rewrite header)
  const rewriteUrl = response.headers.get("x-middleware-rewrite");
  if (rewriteUrl) {
    // Continue to the route but with a rewritten URL
    const responseHeaders = new Headers();
    for (const [key, value] of response.headers) {
      if (key !== "x-middleware-rewrite") {
        responseHeaders.set(key, value);
      }
    }
    // Parse the rewrite URL — may be absolute or relative
    let rewritePath: string;
    try {
      const rewriteParsed = new URL(rewriteUrl, request.url);
      rewritePath = rewriteParsed.pathname + rewriteParsed.search;
    } catch {
      rewritePath = rewriteUrl;
    }
    return {
      continue: true,
      rewriteUrl: rewritePath,
      rewriteStatus: response.status !== 200 ? response.status : undefined,
      responseHeaders,
    };
  }

  // Middleware returned a full Response (e.g., blocking, custom body)
  return { continue: false, response };
}
