import fs from "fs";
import path from "path";

export const DEFAULT_DISCOVERY_EXCLUDES = Object.freeze([
  ".cache",
  ".git",
  ".hg",
  ".next",
  ".nuxt",
  ".playwright-browsers",
  ".svn",
  ".testkit",
  ".turbo",
  "build",
  "coverage",
  "dist",
  "node_modules",
  "playwright-report",
  "test-results",
]);

export function normalizeDiscoveryConfig(value, { allowRoots = true } = {}) {
  if (value == null) return {};
  if (typeof value !== "object" || Array.isArray(value)) {
    throw new Error("testkit discovery config must be an object");
  }

  const normalized = {};
  if (allowRoots && Object.prototype.hasOwnProperty.call(value, "roots")) {
    normalized.roots = normalizeDiscoveryPathList(value.roots, "testkit discovery roots");
  }
  if (Object.prototype.hasOwnProperty.call(value, "exclude")) {
    normalized.exclude = normalizeDiscoveryPathList(value.exclude, "testkit discovery exclude");
  }
  return normalized;
}

export function mergeDiscoveryConfigs(...configs) {
  const merged = {};
  for (const config of configs.filter(Boolean)) {
    if (Array.isArray(config.roots)) {
      merged.roots = [...config.roots];
    }
    if (Array.isArray(config.exclude) && config.exclude.length > 0) {
      merged.exclude = [...new Set([...(merged.exclude || []), ...config.exclude])];
    }
  }
  return merged;
}

export function resolveDiscoveryRoots(baseDir, roots, defaultRoot = ".") {
  const normalizedRoots = Array.isArray(roots) && roots.length > 0 ? roots : [defaultRoot];
  const seen = new Set();
  const resolved = [];

  for (const root of normalizedRoots) {
    const normalizedRoot = normalizeDiscoveryPath(root);
    if (!normalizedRoot) continue;
    const absolutePath = path.resolve(baseDir, normalizedRoot === "." ? "." : normalizedRoot);
    if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) continue;
    if (seen.has(absolutePath)) continue;
    seen.add(absolutePath);
    resolved.push(absolutePath);
  }

  return resolved.sort((left, right) => left.localeCompare(right));
}

export function shouldExcludeDiscoveryPath(relativePath, exclude = []) {
  const normalizedPath = normalizeDiscoveryPath(relativePath);
  if (!normalizedPath || normalizedPath === ".") return false;
  return exclude.some((pattern) => matchesDiscoveryPathPattern(normalizedPath, pattern));
}

export function normalizeDiscoveryPath(value) {
  const normalized = String(value || "")
    .trim()
    .split(path.sep)
    .join("/")
    .replace(/^\.\/+/u, "")
    .replace(/\/+/gu, "/")
    .replace(/\/+$/u, "");
  return normalized || ".";
}

function normalizeDiscoveryPathList(value, label) {
  if (!Array.isArray(value)) {
    throw new Error(`${label} must be an array of relative paths`);
  }
  const normalized = value
    .map((entry) => {
      if (typeof entry !== "string" || entry.trim().length === 0) {
        throw new Error(`${label} entries must be non-empty strings`);
      }
      if (path.isAbsolute(entry)) {
        throw new Error(`${label} entries must be relative paths`);
      }
      return normalizeDiscoveryPath(entry);
    })
    .filter(Boolean);
  return [...new Set(normalized)];
}

function matchesDiscoveryPathPattern(relativePath, pattern) {
  return relativePath === pattern || relativePath.startsWith(`${pattern}/`);
}
