import {
  dot,
  github,
  ndjson,
  spec
} from "./chunk-RFKFNXTE.js";
import {
  Group,
  Refiner,
  Test,
  TestContext,
  colors
} from "./chunk-L7YZLDZD.js";

// src/debug.ts
import { debuglog } from "util";
var debug_default = debuglog("japa:runner");

// src/validator.ts
var Validator = class {
  /**
   * Ensures the japa is configured. Otherwise raises an exception
   */
  ensureIsConfigured(config) {
    if (!config) {
      throw new Error(
        `Cannot run tests. Make sure to call "configure" method before the "run" method`
      );
    }
  }
  /**
   * Ensures the japa is in planning phase
   */
  ensureIsInPlanningPhase(phase) {
    if (phase !== "planning") {
      throw new Error(
        `Cannot import japa test file directly. It must be imported by calling the "japa.run" method`
      );
    }
  }
  /**
   * Ensures the suites filter uses a subset of the user configured suites.
   */
  validateSuitesFilter(config) {
    if (!config.filters.suites || !config.filters.suites.length) {
      return;
    }
    if (!("suites" in config) || !config.suites.length) {
      throw new Error(`Cannot apply suites filter. You have not configured any test suites`);
    }
    const suites = config.suites.map(({ name }) => name);
    const unknownSuites = config.filters.suites.filter((suite) => !suites.includes(suite));
    if (unknownSuites.length) {
      throw new Error(`Cannot apply suites filter. "${unknownSuites[0]}" suite is not configured`);
    }
  }
  /**
   * Ensure there are unique suites
   */
  validateSuitesForUniqueness(config) {
    if (!("suites" in config)) {
      return;
    }
    const suites = /* @__PURE__ */ new Set();
    config.suites.forEach(({ name }) => {
      if (suites.has(name)) {
        throw new Error(`Duplicate suite "${name}"`);
      }
      suites.add(name);
    });
    suites.clear();
  }
  /**
   * Ensure the activated reporters are in the list of defined
   * reporters
   */
  validateActivatedReporters(config) {
    const reportersList = config.reporters.list.map(({ name }) => name);
    const unknownReporters = config.reporters.activated.filter(
      (name) => !reportersList.includes(name)
    );
    if (unknownReporters.length) {
      throw new Error(
        `Invalid reporter "${unknownReporters[0]}". Make sure to register it first inside the "reporters.list" array`
      );
    }
  }
};
var validator_default = new Validator();

// src/files_manager.ts
import slash from "slash";
import fastGlob from "fast-glob";
import { pathToFileURL } from "url";
var FILE_SUFFIX_EXPRESSION = /(\.spec|\.test)?\.[js|ts|jsx|tsx|mjs|mts|cjs|cts]+$/;
var FilesManager = class {
  /**
   * Returns a collection of files from the user defined
   * glob or the implementation function
   */
  async getFiles(cwd, files, excludes) {
    if (Array.isArray(files) || typeof files === "string") {
      const testFiles = await fastGlob(files, {
        absolute: true,
        onlyFiles: true,
        cwd,
        ignore: excludes
      });
      return testFiles.map((file) => pathToFileURL(file));
    }
    return await files();
  }
  /**
   * Applies file name filter on a collection of file
   * URLs
   */
  grep(files, filters) {
    return files.filter((file) => {
      const filename = slash(file.pathname);
      const filenameWithoutTestSuffix = filename.replace(FILE_SUFFIX_EXPRESSION, "");
      return !!filters.find((filter) => {
        if (filename.endsWith(filter)) {
          return true;
        }
        const filterSegments = filter.split("/").reverse();
        const fileSegments = filenameWithoutTestSuffix.split("/").reverse();
        return filterSegments.every((segment, index) => {
          return fileSegments[index] && (segment === "*" || fileSegments[index].endsWith(segment));
        });
      });
    });
  }
};

// src/planner.ts
var Planner = class {
  #config;
  #fileManager = new FilesManager();
  constructor(config) {
    validator_default.validateActivatedReporters(config);
    validator_default.validateSuitesFilter(config);
    validator_default.validateSuitesForUniqueness(config);
    this.#config = config;
  }
  /**
   * Returns a list of reporters based upon the activated
   * reporters list.
   */
  #getActivatedReporters() {
    return this.#config.reporters.activated.map((activated) => {
      return this.#config.reporters.list.find(({ name }) => activated === name);
    });
  }
  /**
   * A generic method to collect files from the user defined
   * files glob and apply the files filter
   */
  async #collectFiles(files) {
    let filesURLs = await this.#fileManager.getFiles(this.#config.cwd, files, this.#config.exclude);
    if (this.#config.filters.files && this.#config.filters.files.length) {
      filesURLs = this.#fileManager.grep(filesURLs, this.#config.filters.files);
    }
    return filesURLs;
  }
  /**
   * Returns a collection of suites and their associated
   * test files by applying all the filters
   */
  async #getSuites() {
    let suites = [];
    let suitesFilters = this.#config.filters.suites || [];
    if ("files" in this.#config) {
      suites.push({
        name: "default",
        files: this.#config.files,
        timeout: this.#config.timeout,
        retries: this.#config.retries,
        filesURLs: await this.#collectFiles(this.#config.files)
      });
    }
    if ("suites" in this.#config) {
      for (let suite of this.#config.suites) {
        if (!suitesFilters.length || suitesFilters.includes(suite.name)) {
          suites.push({
            ...suite,
            filesURLs: await this.#collectFiles(suite.files)
          });
        }
      }
    }
    return suites;
  }
  /**
   * Returns a list of filters to the passed to the refiner
   */
  #getRefinerFilters() {
    return Object.keys(this.#config.filters).reduce(
      (result, layer) => {
        if (layer === "tests" || layer === "tags" || layer === "groups") {
          result.push({ layer, filters: this.#config.filters[layer] });
        }
        return result;
      },
      []
    );
  }
  /**
   * Creates a plan for running the tests
   */
  async plan() {
    const suites = await this.#getSuites();
    const reporters = this.#getActivatedReporters();
    const refinerFilters = this.#getRefinerFilters();
    return {
      reporters,
      suites,
      refinerFilters,
      config: this.#config
    };
  }
};

// src/hooks.ts
import Hooks from "@poppinss/hooks";
var GlobalHooks = class {
  #hooks = new Hooks();
  #setupRunner;
  #teardownRunner;
  /**
   * Apply hooks from the config
   */
  apply(config) {
    config.setup.forEach((hook) => this.#hooks.add("setup", hook));
    config.teardown.forEach((hook) => this.#hooks.add("teardown", hook));
  }
  /**
   * Perform setup
   */
  async setup(runner) {
    this.#setupRunner = this.#hooks.runner("setup");
    this.#teardownRunner = this.#hooks.runner("teardown");
    await this.#setupRunner.run(runner);
  }
  /**
   * Perform cleanup
   */
  async teardown(error, runner) {
    if (this.#setupRunner) {
      await this.#setupRunner.cleanup(error, runner);
    }
    if (this.#teardownRunner) {
      if (!error) {
        await this.#teardownRunner.run(runner);
      }
      await this.#teardownRunner.cleanup(error, runner);
    }
  }
};

// src/cli_parser.ts
import getopts from "getopts";
var OPTIONS = {
  string: ["tests", "groups", "tags", "files", "timeout", "retries", "reporters", "bailLayer"],
  boolean: ["help", "matchAll", "failed", "bail", "listPinned"],
  alias: {
    forceExit: "force-exit",
    matchAll: "match-all",
    listPinned: "list-pinned",
    bailLayer: "bail-layer",
    help: "h"
  }
};
var GET_HELP = () => `
${colors.yellow("@japa/runner v2.3.0")}

${colors.green("--tests")}                     ${colors.dim("Filter tests by the test title")}
${colors.green("--groups")}                    ${colors.dim("Filter tests by the group title")}
${colors.green("--tags")}                      ${colors.dim("Filter tests by tags")}
${colors.green("--match-all")}                 ${colors.dim("Run tests that matches all the supplied tags")}
${colors.green("--list-pinned")}               ${colors.dim("List pinned tests")}
${colors.green("--files")}                     ${colors.dim("Filter tests by the file name")}
${colors.green("--force-exit")}                ${colors.dim("Forcefully exit the process")}
${colors.green("--timeout")}                   ${colors.dim("Define default timeout for all tests")}
${colors.green("--retries")}                   ${colors.dim("Define default retries for all tests")}
${colors.green("--reporters")}                 ${colors.dim("Activate one or more test reporters")}
${colors.green("--failed")}                    ${colors.dim("Run tests failed during the last run")}
${colors.green("--bail")}                      ${colors.dim("Exit early when a test fails")}
${colors.green("--bail-layer")}                ${colors.dim('Specify at which layer to enable the bail mode. Can be "group" or "suite"')}
${colors.green("-h, --help")}                  ${colors.dim("View help")}

${colors.yellow("Examples:")}
${colors.dim('node bin/test.js --tags="@github"')}
${colors.dim('node bin/test.js --tags="~@github"')}
${colors.dim('node bin/test.js --tags="@github,@slow,@integration" --match-all')}
${colors.dim("node bin/test.js --force-exit")}
${colors.dim('node bin/test.js --files="user"')}
${colors.dim('node bin/test.js --files="functional/user"')}
${colors.dim('node bin/test.js --files="unit/user"')}
${colors.dim("node bin/test.js --failed")}
${colors.dim("node bin/test.js --bail")}
${colors.dim("node bin/test.js --bail=group")}

${colors.yellow("Notes:")}
- When groups and tests filters are applied together. We will first filter the
  tests by group title and then apply the tests filter.
- The timeout defined on test object takes precedence over the ${colors.green("--timeout")} flag.
- The retries defined on test object takes precedence over the ${colors.green("--retries")} flag.
- The ${colors.green("--files")} flag checks for the file names ending with the filter substring.
- The ${colors.green("--tags")} filter runs tests that has one or more of the supplied tags.
- You can use the ${colors.green("--match-all")} flag to run tests that has all the supplied tags.
`;
var CliParser = class {
  /**
   * Parses command-line arguments
   */
  parse(argv) {
    return getopts(argv, OPTIONS);
  }
  /**
   * Returns the help string
   */
  getHelp() {
    return GET_HELP();
  }
};

// src/config_manager.ts
var NOOP = () => {
};
var DEFAULTS = {
  files: [],
  timeout: 2e3,
  retries: 0,
  forceExit: false,
  plugins: [],
  reporters: {
    activated: ["spec"].concat(process.env.GITHUB_ACTIONS === "true" ? ["github"] : []),
    list: [spec(), ndjson(), dot(), github()]
  },
  importer: (filePath) => import(filePath.href),
  configureSuite: () => {
  }
};
var ConfigManager = class {
  #config;
  #cliArgs;
  constructor(config, cliArgs) {
    this.#config = config;
    this.#cliArgs = cliArgs;
  }
  /**
   * Processes a CLI argument and converts it to an
   * array of strings
   */
  #processAsArray(value, splitByComma) {
    return Array.isArray(value) ? value : splitByComma ? value.split(",").map((item) => item.trim()) : [value];
  }
  /**
   * Returns a copy of filters based upon the CLI
   * arguments.
   */
  #getCLIFilters() {
    const filters = {};
    if (this.#cliArgs.tags) {
      filters.tags = this.#processAsArray(this.#cliArgs.tags, true);
    }
    if (this.#cliArgs.tests) {
      filters.tests = this.#processAsArray(this.#cliArgs.tests, false);
    }
    if (this.#cliArgs.files) {
      filters.files = this.#processAsArray(this.#cliArgs.files, true);
    }
    if (this.#cliArgs.groups) {
      filters.groups = this.#processAsArray(this.#cliArgs.groups, false);
    }
    if (this.#cliArgs._ && this.#cliArgs._.length) {
      filters.suites = this.#processAsArray(this.#cliArgs._, true);
    }
    return filters;
  }
  /**
   * Returns the timeout from the CLI args
   */
  #getCLITimeout() {
    if (this.#cliArgs.timeout) {
      const value = Number(this.#cliArgs.timeout);
      if (!Number.isNaN(value)) {
        return value;
      }
    }
  }
  /**
   * Returns the retries from the CLI args
   */
  #getCLIRetries() {
    if (this.#cliArgs.retries) {
      const value = Number(this.#cliArgs.retries);
      if (!Number.isNaN(value)) {
        return value;
      }
    }
  }
  /**
   * Returns the forceExit property from the CLI args
   */
  #getCLIForceExit() {
    if (this.#cliArgs.forceExit) {
      return true;
    }
  }
  /**
   * Returns reporters selected using the commandline
   * --reporter flag
   */
  #getCLIReporters() {
    if (this.#cliArgs.reporters) {
      return this.#processAsArray(this.#cliArgs.reporters, true);
    }
  }
  /**
   * Hydrates the config with user defined options and the
   * command-line flags.
   */
  hydrate() {
    const cliFilters = this.#getCLIFilters();
    const cliRetries = this.#getCLIRetries();
    const cliTimeout = this.#getCLITimeout();
    const cliReporters = this.#getCLIReporters();
    const cliForceExit = this.#getCLIForceExit();
    debug_default("filters applied using CLI flags %O", cliFilters);
    const baseConfig = {
      cwd: this.#config.cwd ?? process.cwd(),
      exclude: this.#config.exclude || ["node_modules/**", ".git/**", "coverage/**"],
      filters: Object.assign({}, this.#config.filters ?? {}, cliFilters),
      importer: this.#config.importer ?? DEFAULTS.importer,
      refiner: this.#config.refiner ?? new Refiner(),
      retries: cliRetries ?? this.#config.retries ?? DEFAULTS.retries,
      timeout: cliTimeout ?? this.#config.timeout ?? DEFAULTS.timeout,
      plugins: this.#config.plugins ?? DEFAULTS.plugins,
      forceExit: cliForceExit ?? this.#config.forceExit ?? DEFAULTS.forceExit,
      reporters: this.#config.reporters ? {
        activated: this.#config.reporters.activated,
        list: this.#config.reporters.list || DEFAULTS.reporters.list
      } : DEFAULTS.reporters,
      configureSuite: this.#config.configureSuite ?? DEFAULTS.configureSuite,
      setup: this.#config.setup || [],
      teardown: this.#config.teardown || []
    };
    if (cliReporters) {
      baseConfig.reporters.activated = cliReporters;
    }
    if ("files" in this.#config) {
      return {
        files: this.#config.files,
        ...baseConfig
      };
    }
    return {
      suites: this.#config.suites.map((suite) => {
        return {
          name: suite.name,
          files: suite.files,
          timeout: cliTimeout ?? suite.timeout ?? baseConfig.timeout,
          retries: cliRetries ?? suite.retries ?? baseConfig.retries,
          configure: suite.configure || NOOP
        };
      }),
      ...baseConfig
    };
  }
};

// src/create_test.ts
var contextBuilder = (testInstance) => new TestContext(testInstance);
function createTest(title, emitter, refiner, debuggingError, options) {
  const testInstance = new Test(title, contextBuilder, emitter, refiner, options.group);
  testInstance.options.meta.suite = options.suite;
  testInstance.options.meta.group = options.group;
  testInstance.options.meta.fileName = options.file;
  testInstance.options.meta.abort = (message) => {
    debuggingError.message = message;
    throw debuggingError;
  };
  if (options.timeout !== void 0) {
    testInstance.timeout(options.timeout);
  }
  if (options.retries !== void 0) {
    testInstance.retry(options.retries);
  }
  if (options.group) {
    options.group.add(testInstance);
  } else if (options.suite) {
    options.suite.add(testInstance);
  }
  return testInstance;
}
function createTestGroup(title, emitter, refiner, options) {
  if (options.group) {
    throw new Error("Nested groups are not supported by Japa");
  }
  const group = new Group(title, emitter, refiner);
  group.options.meta.suite = options.suite;
  group.options.meta.fileName = options.file;
  if (options.suite) {
    options.suite.add(group);
  }
  return group;
}

export {
  debug_default,
  validator_default,
  Planner,
  GlobalHooks,
  CliParser,
  ConfigManager,
  createTest,
  createTestGroup
};
