import { finalize } from './chunk-7BAKVZ2Q.js';
import { init } from './chunk-SVE2Q6ZT.js';
import { lint } from './chunk-DMWNM3YP.js';
import { prepare } from './chunk-YTC7QPYX.js';
import { loadConfig } from './chunk-OJC4FSPU.js';
import { getTsconfigFilePath } from './chunk-JRP7TVNX.js';
import { createLog } from './chunk-5S5JGJML.js';
import { build } from './chunk-2PGIUBQM.js';
import { clean } from './chunk-2SY6WRSH.js';
import { docs } from './chunk-S6TMD2NR.js';
import { __name } from './chunk-6AXVLTU5.js';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { STORM_DEFAULT_ERROR_CODES_FILE } from '@storm-software/config/constants';
import { getEnvPaths } from '@stryke/env/get-env-paths';
import { install } from '@stryke/fs/install';
import { isPackageExists } from '@stryke/fs/package-fns';
import { hash } from '@stryke/hash/hash';
import { existsSync } from '@stryke/path/exists';
import { joinPaths } from '@stryke/path/join-paths';
import { isNumber } from '@stryke/type-checks/is-number';
import defu from 'defu';
import { createJiti } from 'jiti';

// ../../node_modules/.pnpm/hookable@5.5.3/node_modules/hookable/dist/index.mjs
function flatHooks(configHooks, hooks = {}, parentName) {
  for (const key in configHooks) {
    const subHook = configHooks[key];
    const name = parentName ? `${parentName}:${key}` : key;
    if (typeof subHook === "object" && subHook !== null) {
      flatHooks(subHook, hooks, name);
    } else if (typeof subHook === "function") {
      hooks[name] = subHook;
    }
  }
  return hooks;
}
__name(flatHooks, "flatHooks");
var defaultTask = { run: /* @__PURE__ */ __name((function_) => function_(), "run") };
var _createTask = /* @__PURE__ */ __name(() => defaultTask, "_createTask");
var createTask = typeof console.createTask !== "undefined" ? console.createTask : _createTask;
function serialTaskCaller(hooks, args) {
  const name = args.shift();
  const task = createTask(name);
  return hooks.reduce(
    (promise, hookFunction) => promise.then(() => task.run(() => hookFunction(...args))),
    Promise.resolve()
  );
}
__name(serialTaskCaller, "serialTaskCaller");
function parallelTaskCaller(hooks, args) {
  const name = args.shift();
  const task = createTask(name);
  return Promise.all(hooks.map((hook) => task.run(() => hook(...args))));
}
__name(parallelTaskCaller, "parallelTaskCaller");
function callEachWith(callbacks, arg0) {
  for (const callback of [...callbacks]) {
    callback(arg0);
  }
}
__name(callEachWith, "callEachWith");
var Hookable = class {
  static {
    __name(this, "Hookable");
  }
  constructor() {
    this._hooks = {};
    this._before = void 0;
    this._after = void 0;
    this._deprecatedMessages = void 0;
    this._deprecatedHooks = {};
    this.hook = this.hook.bind(this);
    this.callHook = this.callHook.bind(this);
    this.callHookWith = this.callHookWith.bind(this);
  }
  hook(name, function_, options = {}) {
    if (!name || typeof function_ !== "function") {
      return () => {
      };
    }
    const originalName = name;
    let dep;
    while (this._deprecatedHooks[name]) {
      dep = this._deprecatedHooks[name];
      name = dep.to;
    }
    if (dep && !options.allowDeprecated) {
      let message = dep.message;
      if (!message) {
        message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
      }
      if (!this._deprecatedMessages) {
        this._deprecatedMessages = /* @__PURE__ */ new Set();
      }
      if (!this._deprecatedMessages.has(message)) {
        console.warn(message);
        this._deprecatedMessages.add(message);
      }
    }
    if (!function_.name) {
      try {
        Object.defineProperty(function_, "name", {
          get: /* @__PURE__ */ __name(() => "_" + name.replace(/\W+/g, "_") + "_hook_cb", "get"),
          configurable: true
        });
      } catch {
      }
    }
    this._hooks[name] = this._hooks[name] || [];
    this._hooks[name].push(function_);
    return () => {
      if (function_) {
        this.removeHook(name, function_);
        function_ = void 0;
      }
    };
  }
  hookOnce(name, function_) {
    let _unreg;
    let _function = /* @__PURE__ */ __name((...arguments_) => {
      if (typeof _unreg === "function") {
        _unreg();
      }
      _unreg = void 0;
      _function = void 0;
      return function_(...arguments_);
    }, "_function");
    _unreg = this.hook(name, _function);
    return _unreg;
  }
  removeHook(name, function_) {
    if (this._hooks[name]) {
      const index = this._hooks[name].indexOf(function_);
      if (index !== -1) {
        this._hooks[name].splice(index, 1);
      }
      if (this._hooks[name].length === 0) {
        delete this._hooks[name];
      }
    }
  }
  deprecateHook(name, deprecated) {
    this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
    const _hooks = this._hooks[name] || [];
    delete this._hooks[name];
    for (const hook of _hooks) {
      this.hook(name, hook);
    }
  }
  deprecateHooks(deprecatedHooks) {
    Object.assign(this._deprecatedHooks, deprecatedHooks);
    for (const name in deprecatedHooks) {
      this.deprecateHook(name, deprecatedHooks[name]);
    }
  }
  addHooks(configHooks) {
    const hooks = flatHooks(configHooks);
    const removeFns = Object.keys(hooks).map(
      (key) => this.hook(key, hooks[key])
    );
    return () => {
      for (const unreg of removeFns.splice(0, removeFns.length)) {
        unreg();
      }
    };
  }
  removeHooks(configHooks) {
    const hooks = flatHooks(configHooks);
    for (const key in hooks) {
      this.removeHook(key, hooks[key]);
    }
  }
  removeAllHooks() {
    for (const key in this._hooks) {
      delete this._hooks[key];
    }
  }
  callHook(name, ...arguments_) {
    arguments_.unshift(name);
    return this.callHookWith(serialTaskCaller, name, ...arguments_);
  }
  callHookParallel(name, ...arguments_) {
    arguments_.unshift(name);
    return this.callHookWith(parallelTaskCaller, name, ...arguments_);
  }
  callHookWith(caller, name, ...arguments_) {
    const event = this._before || this._after ? { name, args: arguments_, context: {} } : void 0;
    if (this._before) {
      callEachWith(this._before, event);
    }
    const result = caller(
      name in this._hooks ? [...this._hooks[name]] : [],
      arguments_
    );
    if (result instanceof Promise) {
      return result.finally(() => {
        if (this._after && event) {
          callEachWith(this._after, event);
        }
      });
    }
    if (this._after && event) {
      callEachWith(this._after, event);
    }
    return result;
  }
  beforeEach(function_) {
    this._before = this._before || [];
    this._before.push(function_);
    return () => {
      if (this._before !== void 0) {
        const index = this._before.indexOf(function_);
        if (index !== -1) {
          this._before.splice(index, 1);
        }
      }
    };
  }
  afterEach(function_) {
    this._after = this._after || [];
    this._after.push(function_);
    return () => {
      if (this._after !== void 0) {
        const index = this._after.indexOf(function_);
        if (index !== -1) {
          this._after.splice(index, 1);
        }
      }
    };
  }
};
function createHooks() {
  return new Hookable();
}
__name(createHooks, "createHooks");
var Engine = class {
  static {
    __name(this, "Engine");
  }
  #initialized = false;
  /**
  * The engine hooks - these allow the plugins to hook into the engines processing
  */
  #hooks;
  /**
  * The plugins provided in the options
  */
  #plugins = [];
  /**
  * The options provided to Storm Stack
  */
  options;
  /**
  * The resolved options provided to Storm Stack
  */
  context;
  /**
  * The logger for the plugin
  */
  log;
  constructor(options, workspaceConfig) {
    this.options = options;
    this.log = createLog("engine", options);
    this.context = {
      options: {
        original: options,
        ...options
      },
      override: {},
      installs: {},
      runtime: {
        logs: [],
        storage: [],
        init: []
      }
    };
    this.context.workspaceConfig = workspaceConfig ?? {
      workspaceRoot: process.cwd()
    };
    this.context.envPaths = getEnvPaths({
      orgId: "storm-software",
      appId: "storm-stack",
      workspaceRoot: this.context.workspaceConfig?.workspaceRoot
    });
    if (!this.context.envPaths.cache) {
      throw new Error("The cache directory could not be determined.");
    }
    this.context.envPaths.cache = joinPaths(this.context.envPaths.cache, hash(options.projectRoot));
    this.context.resolver = createJiti(this.context.workspaceConfig.workspaceRoot, {
      interopDefault: true,
      fsCache: joinPaths(this.context.envPaths.cache, "jiti"),
      moduleCache: true
    });
  }
  /**
  * Initialize the engine
  */
  async init() {
    this.log(LogLevelLabel.TRACE, "Initializing Storm Stack engine");
    this.#hooks = createHooks();
    const config = await loadConfig(this.options.projectRoot, this.options.mode || "production", joinPaths(this.context.envPaths.cache, "jiti"));
    this.context.options = defu({
      original: this.options
    }, this.options, config, {
      platform: "node",
      mode: "production",
      projectType: "application",
      outputPath: joinPaths("dist", this.options.projectRoot),
      tsconfig: getTsconfigFilePath(this.context),
      errorsFile: this.context.workspaceConfig.error?.codesFile || STORM_DEFAULT_ERROR_CODES_FILE
    });
    for (const preset of this.context.options.presets ?? []) {
      await this.addPlugin(preset, true);
    }
    for (const plugin of this.context.options.plugins ?? []) {
      await this.addPlugin(plugin, false);
    }
    if (this.#plugins.length === 0) {
      this.log(LogLevelLabel.WARN, "No Storm Stack plugins or presets were specified in the options. Please ensure this is correct, as it is generally not recommended.");
    } else {
      for (const plugin of this.#plugins) {
        await Promise.resolve(plugin.addHooks(this.#hooks));
      }
    }
    await init(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack engine has been initialized");
    this.#initialized = true;
    return this.context;
  }
  /**
  * Clean any previously prepared artifacts
  */
  async clean() {
    if (!this.#initialized) {
      await this.init();
    }
    this.log(LogLevelLabel.INFO, "\u{1F9F9} Cleaning the previous Storm Stack artifacts");
    await clean(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack - Clean command completed");
  }
  /**
  * Prepare the Storm Stack project prior to building
  *
  * @remarks
  * This method will create the necessary directories, and write the artifacts files to the project.
  *
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async prepare(autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (existsSync(joinPaths(this.context.options.projectRoot, this.context.artifactsDir)) && autoClean) {
      await this.clean();
    }
    this.log(LogLevelLabel.INFO, "Preparing the Storm Stack project");
    await prepare(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack preparation completed");
  }
  /**
  * Lint the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async lint(autoPrepare = true, autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to lint the project.");
      }
    }
    this.log(LogLevelLabel.INFO, "Linting the Storm Stack project");
    await lint(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack linting completed");
  }
  /**
  * Build the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async build(autoPrepare = true, autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to build the project.");
      }
    }
    this.log(LogLevelLabel.INFO, "Building the Storm Stack project");
    await build(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack build completed");
  }
  /**
  * Generate the documentation for the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async docs(autoPrepare = true, autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to build the project.");
      }
    }
    this.log(LogLevelLabel.INFO, "Generating documentation for the Storm Stack project");
    await docs(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack documentation generation completed");
  }
  /**
  * Finalization process
  *
  * @remarks
  * This step includes any final processes or clean up required by Storm Stack. It will be run after each Storm Stack command.
  */
  async finalize() {
    this.log(LogLevelLabel.TRACE, "Storm Stack finalize execution started");
    await finalize(this.log, this.context, this.#hooks);
    this.log(LogLevelLabel.TRACE, "Storm Stack finalize execution completed");
  }
  /**
  * Add a Storm Stack plugin or preset to the build process
  *
  * @param config - The import path of the plugin or preset to add
  * @param isPreset - Whether the plugin is a preset
  */
  async addPlugin(config, isPreset = false) {
    if (config) {
      const instance = await this.initPlugin(config);
      if (instance.dependencies) {
        for (const dependency of instance.dependencies) {
          await this.addPlugin(dependency, false);
        }
      }
      this.log(LogLevelLabel.INFO, `Successfully loaded the "${instance.name}" ${isPreset ? "preset" : "plugin"}`);
      this.#plugins.push(instance);
    }
  }
  /**
  * Initialize a Storm Stack plugin
  *
  * @param plugin - The import path of the plugin to add
  */
  async initPlugin(plugin) {
    const pluginConfig = typeof plugin === "string" ? [
      plugin,
      {}
    ] : plugin;
    let installPath = pluginConfig[0];
    if (installPath.startsWith("@") && installPath.split("/").filter(Boolean).length > 2) {
      const splits = installPath.split("/").filter(Boolean);
      installPath = `${splits[0]}/${splits[1]}`;
    }
    const isInstalled = isPackageExists(installPath, {
      paths: [
        this.context.workspaceConfig.workspaceRoot,
        this.context.options.projectRoot
      ]
    });
    if (!isInstalled && this.context.options.skipInstalls !== true) {
      this.log(LogLevelLabel.WARN, `The plugin package "${installPath}" is not installed. It will be installed automatically.`);
      const result = await install(installPath, {
        cwd: this.context.options.projectRoot
      });
      if (isNumber(result.exitCode) && result.exitCode > 0) {
        this.log(LogLevelLabel.ERROR, result.stderr);
        throw new Error(`An error occurred while installing the build plugin package "${installPath}" `);
      }
    }
    let pluginInstance;
    try {
      const module = await this.context.resolver.import(this.context.resolver.esmResolve(pluginConfig[0]));
      const PluginConstructor = module.default;
      pluginInstance = new PluginConstructor(pluginConfig[1]);
    } catch (error) {
      if (!isInstalled) {
        throw new Error(`The plugin package "${pluginConfig[0]}" is not installed. Please install the package using the command: "npm install ${pluginConfig[0]} --save-dev"`);
      } else {
        throw new Error(`An error occurred while importing the build plugin package "${pluginConfig[0]}":
${error.message}

Note: Please ensure the plugin package's default export is a class that extends \`Plugin\` with a constructor that excepts zero arguments.`);
      }
    }
    if (!pluginInstance) {
      throw new Error(`The plugin package "${pluginConfig[0]}" does not export a valid module.`);
    }
    if (!pluginInstance.name) {
      throw new Error(`The module in the build plugin package "${pluginConfig[0]}" must export a \`name\` string value.`);
    }
    if (!pluginInstance.addHooks) {
      throw new Error(`The build plugin "${pluginInstance.name}" must export an \`addHooks\` function.`);
    }
    this.log(LogLevelLabel.INFO, `Successfully initialized the "${pluginInstance.name}" plugin`);
    return pluginInstance;
  }
};

export { Engine };
//# sourceMappingURL=chunk-ENVF4MIS.js.map
//# sourceMappingURL=chunk-ENVF4MIS.js.map