import { getDotenvReflectionsPath, getDotenvPath, resolveDotenvReflection, resolveDotenvProperties } from './chunk-7LQATK6V.js';
import { createLog } from './chunk-EAGALHEL.js';
import { generateSourceMap } from './chunk-4FDFE63H.js';
import { writeFile } from './chunk-V3VWCAQH.js';
import { getCache, setCache } from './chunk-665NTAJO.js';
import { getMagicString } from './chunk-J3TAATZP.js';
import { __name } from './chunk-6AXVLTU5.js';
import { declarationTransformer, transformer } from '@deepkit/type-compiler';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { hash } from '@stryke/hash/hash';
import { joinPaths } from '@stryke/path/join-paths';
import ts from 'typescript';
import { parseAsync, Lang } from '@ast-grep/napi';
import { Mutex } from '@stryke/helpers/mutex';
import { readJsonFile } from '@stryke/fs/read-file';
import { StormJSON } from '@stryke/json/storm-json';
import { existsSync } from '@stryke/path/exists';
import { deepClone } from '@stryke/helpers/deep-clone';
import { isEqual } from '@stryke/helpers/is-equal';
import defu from 'defu';
import { existsSync as existsSync$1 } from 'node:fs';

async function writeDotenvReflection(log, context, reflection, name) {
  let reflectionObjectLiteral = (void 0).from({
    kind: (void 0).objectLiteral,
    description: `An object containing the dotenv variables used by the ${context.name ? `${context.name} application` : "application"}.`,
    types: []
  });
  const varsFilePath = getDotenvReflectionsPath(context, name);
  if (existsSync(varsFilePath)) {
    reflectionObjectLiteral = (void 0)((void 0)(await readJsonFile(varsFilePath)));
  }
  reflection.getProperties().map((prop) => {
    if (!reflectionObjectLiteral.hasProperty(prop.getName())) {
      reflectionObjectLiteral.addProperty(prop.property);
    }
  });
  const serialized = reflectionObjectLiteral.serializeType();
  return writeFile(log, getDotenvReflectionsPath(context, name), StormJSON.stringify(serialized));
}
__name(writeDotenvReflection, "writeDotenvReflection");
async function writeDotenvProperties(log, context, name, properties) {
  const varsFilePath = getDotenvPath(context, name);
  let reflectionObjectLiteral = (void 0).from({
    kind: (void 0).objectLiteral,
    description: `An object containing the dotenv variables used by the ${context.name ? `${context.name} application` : "application"}.`,
    types: []
  });
  if (existsSync(varsFilePath)) {
    reflectionObjectLiteral = (void 0)((void 0)(await readJsonFile(varsFilePath)));
  }
  properties.map((prop) => {
    if (!reflectionObjectLiteral.hasProperty(prop.getName())) {
      reflectionObjectLiteral.addProperty(prop.property);
    }
  });
  await writeFile(log, varsFilePath, StormJSON.stringify(reflectionObjectLiteral.serializeType()));
}
__name(writeDotenvProperties, "writeDotenvProperties");

// src/helpers/transform/transform-env.ts
var mutex = new Mutex();
async function transformEnv(log, source, context) {
  const varsReflection = await resolveDotenvReflection(context, "variables");
  const dotenvProperties = await resolveDotenvProperties(log, context, "variables");
  const originalLength = dotenvProperties.length;
  const ast = await parseAsync(Lang.TypeScript, source.code.toString());
  const root = ast.root();
  const nodes = root.findAll({
    rule: {
      kind: "member_expression",
      any: [
        {
          pattern: "$storm.env.$ENV_VALUE"
        },
        {
          pattern: "process.env.$ENV_VALUE"
        },
        {
          pattern: "input.meta.$ENV_VALUE"
        }
      ]
    }
  });
  if (!nodes) {
    return source;
  }
  for (const node of nodes) {
    const name = node.getMatch("ENV_VALUE")?.text()?.replace("STORM_", "");
    if (name && varsReflection.hasProperty(name)) {
      const reflectionProperty = varsReflection.getProperty(name);
      if (context.resolvedDotenv.replace) {
        const value = context.resolvedDotenv.values?.[name] ?? context.resolvedDotenv.values?.[`STORM_${name}`] ?? reflectionProperty.getDefaultValue();
        if (reflectionProperty.isValueRequired() && value === void 0) {
          throw new Error(`Environment variable \`${name}\` is not defined in the .env configuration files`);
        }
        source.code = source.code.replaceAll(node.text(), String(value));
      }
      dotenvProperties.push(reflectionProperty);
    } else {
      throw new Error(`Environment variable \`${name}\` is not defined in the dotenv type definition but is used in the code. 

The following variable names are defined in the dotenv type definition: 
${Object.keys(varsReflection).map((typeDef) => ` - ${typeDef} `).join("\n")} 

Please check your \`dotenv\` configuration option. If you are using a custom dotenv type definition, please make sure that the variable names match the ones in the code. 

`);
    }
  }
  if (dotenvProperties.length !== originalLength) {
    log(LogLevelLabel.INFO, `Adding environment variables from ${source.id} to variables.json.`);
    await mutex.acquire();
    try {
      await writeDotenvProperties(log, context, "variables", dotenvProperties);
    } catch (e) {
      console.error(e);
    } finally {
      mutex.release();
    }
  }
  return source;
}
__name(transformEnv, "transformEnv");
var mutex2 = new Mutex();
function getLatestErrorCode(type = "general", errorCodes = {}) {
  if (!errorCodes[type]) {
    return "1";
  }
  const keys = Object.keys(errorCodes[type]);
  if (keys.length === 0) {
    return "1";
  }
  const errorCode = Number.parseInt(keys.sort((a, b) => b.localeCompare(a))[0]);
  if (Number.isNaN(errorCode)) {
    return "1";
  }
  return String(errorCode + 1);
}
__name(getLatestErrorCode, "getLatestErrorCode");
async function transformErrors(log, source, context) {
  if (!context.workspaceConfig.error.codesFile) {
    log(LogLevelLabel.WARN, `No error codes file path configured in \`storm-workspace.json\`. Skipping error transformation processing.`);
    return source;
  }
  let errorCodes = {};
  if (existsSync$1(joinPaths(context.workspaceConfig.workspaceRoot, context.workspaceConfig.error.codesFile))) {
    errorCodes = await readJsonFile(joinPaths(context.workspaceConfig.workspaceRoot, context.workspaceConfig.error.codesFile));
  }
  const originalErrorCodes = deepClone(errorCodes);
  const ast = await parseAsync(Lang.TypeScript, source.code.toString());
  const root = ast.root();
  const nodes = root.findAll({
    rule: {
      kind: "new_expression",
      any: [
        {
          pattern: "new Error($$$ARGS)"
        },
        {
          pattern: "new TypeError($$$ARGS)"
        },
        {
          pattern: "new ReferenceError($$$ARGS)"
        },
        {
          pattern: "new SyntaxError($$$ARGS)"
        },
        {
          pattern: "new RangeError($$$ARGS)"
        },
        {
          pattern: "new EvalError($$$ARGS)"
        },
        {
          pattern: "new URIError($$$ARGS)"
        },
        {
          pattern: "new AggregateError($$$ARGS)"
        },
        {
          pattern: "new InternalError($$$ARGS)"
        },
        {
          pattern: "new SystemError($$$ARGS)"
        },
        {
          pattern: "new NotImplementedError($$$ARGS)"
        },
        {
          pattern: "new StormError($$$ARGS)"
        }
      ]
    }
  });
  if (!nodes || nodes.length === 0) {
    log(LogLevelLabel.TRACE, `No error messages require transformation in ${source.id}.`);
    return source;
  }
  for (const node of nodes) {
    const args = node.getMultipleMatches("ARGS").filter((arg) => arg.kind() === "string" || arg.kind() === "template_string");
    if (args.length >= 1 && args[0]) {
      let message = "";
      const params = [];
      if (args[0].kind() === "string") {
        message = args[0].text().slice(1, -1);
      } else if (args[0].kind() === "template_string") {
        for (const node2 of args[0].children()) {
          if (node2.kind() === "string_fragment") {
            message += node2.text();
          } else if (node2.kind() === "template_substitution") {
            const param = node2.text().slice(2, -1);
            if (param) {
              params.push(param);
              message += "%s";
            }
          }
        }
      }
      if (message) {
        let type = "general";
        if (args.length > 1 && args[1]) {
          const text = args[1].text();
          if (text) {
            type = text;
          }
        }
        errorCodes[type] ??= {};
        let code = Object.keys(errorCodes[type]).find((key) => errorCodes[type][key] === message);
        if (!code) {
          code = getLatestErrorCode(type, errorCodes);
          log(LogLevelLabel.INFO, `Found unlisted error message "${message}" in ${source.id}. Assigning it to "${type}" code #${code}.`);
          errorCodes[type][code] = message;
        }
        source.code = source.code.replaceAll(node.text(), `new StormError({ type: "${type}", code: ${code}${params.length > 0 ? ` params: [${params.join(", ")}] ` : ""} })`);
      }
    }
  }
  if (!isEqual(originalErrorCodes, errorCodes)) {
    log(LogLevelLabel.INFO, `Adding error messages from ${source.id} to ${context.workspaceConfig.error.codesFile}.`);
    await mutex2.acquire();
    try {
      await writeFile(log, joinPaths(context.workspaceConfig.workspaceRoot, context.workspaceConfig.error.codesFile), StormJSON.stringify(defu(errorCodes, await readJsonFile(joinPaths(context.workspaceConfig.workspaceRoot, context.workspaceConfig.error.codesFile)))));
    } finally {
      mutex2.release();
    }
  }
  return source;
}
__name(transformErrors, "transformErrors");

// src/compiler.ts
var Compiler = class {
  static {
    __name(this, "Compiler");
  }
  #cache = /* @__PURE__ */ new WeakMap();
  /**
  * The logger function to use
  */
  log;
  /**
  * The cache directory
  */
  cacheDir;
  /**
  * A callback function to be called before the source file is compiled
  */
  onTransformCallback;
  constructor(context, onTransformCallback = async () => {
  }) {
    this.log = createLog("compiler", context);
    this.onTransformCallback = onTransformCallback;
    this.cacheDir = joinPaths(context.envPaths.cache, hash(context.resolvedTsconfig.options));
  }
  /**
  * Transpile the module.
  *
  * @param context - The compiler context.
  * @param id - The name of the file to compile.
  * @param code - The source code to compile.
  * @returns The transpiled module.
  */
  async transpile(context, id, code, options = {}) {
    const source = this.getSourceFile(id, code);
    return this.transpileModule(context, source, options);
  }
  /**
  * Compile the source code.
  *
  * @param context - The compiler context.
  * @param id - The name of the file to compile.
  * @param code - The source code to compile.
  * @returns The compiled source code and source map.
  */
  async compile(context, id, code, options = {}) {
    this.log(LogLevelLabel.TRACE, `Compiling ${id}`);
    const source = this.getSourceFile(id, code);
    let transpiled;
    if (!options.skipCache) {
      transpiled = await this.getCache(context, source);
      if (transpiled) {
        this.log(LogLevelLabel.TRACE, `Cache hit: ${source.id}`);
      } else {
        this.log(LogLevelLabel.TRACE, `Cache miss: ${source.id}`);
      }
    }
    if (!transpiled) {
      transpiled = await this.transpileModule(context, source, options);
      await this.setCache(context, source, transpiled);
    }
    return transpiled;
  }
  /**
  * Get the source file.
  *
  * @param id - The name of the file.
  * @param code - The source code.
  * @returns The source file.
  */
  getSourceFile(id, code) {
    return {
      id,
      code: getMagicString(code),
      env: []
    };
  }
  /**
  * Get the result of the compiler.
  *
  * @param sourceFile - The source file.
  * @param transpiled - The transpiled source code.
  * @returns The result of the compiler.
  */
  getResult(sourceFile, transpiled) {
    return generateSourceMap(sourceFile.id, sourceFile.code, transpiled);
  }
  async getCache(context, sourceFile) {
    let cache = this.#cache.get(sourceFile);
    if (cache) {
      return cache;
    }
    if (context.skipCache) {
      return;
    }
    cache = await getCache(sourceFile, this.cacheDir);
    if (cache) {
      this.#cache.set(sourceFile, cache);
    }
    return cache;
  }
  async setCache(context, sourceFile, transpiled) {
    if (transpiled) {
      this.#cache.set(sourceFile, transpiled);
    } else {
      this.#cache.delete(sourceFile);
    }
    if (context.skipCache) {
      return;
    }
    return setCache(sourceFile, this.cacheDir, transpiled);
  }
  /**
  * Transpile the module.
  *
  * @param context - The compiler context.
  * @param source - The compiler source file.
  * @returns The transpiled module.
  */
  async transpileModule(context, source, options = {}) {
    this.log(LogLevelLabel.TRACE, `Transpiling ${source.id} module with TypeScript compiler`);
    let transformed = source;
    if (!options.skipTransform) {
      if (!options.skipEnvTransform) {
        transformed = await transformEnv(this.log, source, context);
      }
      if (!options.skipErrorsTransform) {
        transformed = await transformErrors(this.log, transformed, context);
      }
      await this.onTransformCallback(context, transformed);
    }
    if (context.unimport && !transformed.id.replaceAll("\\", "/").includes(context.runtimeDir)) {
      transformed = await context.unimport.injectImports(transformed);
    }
    const transpiled = ts.transpileModule(transformed.code.toString(), {
      compilerOptions: {
        ...context.resolvedTsconfig.options,
        configFilePath: context.tsconfig
      },
      fileName: transformed.id,
      transformers: {
        before: [
          transformer
        ],
        after: [
          declarationTransformer
        ]
      }
    });
    if (transpiled === null) {
      this.log(LogLevelLabel.ERROR, `Transform is null: ${transformed.id}`);
      throw new Error(`Transform is null: ${transformed.id}`);
    } else {
      this.log(LogLevelLabel.TRACE, `Transformed: ${transformed.id}`);
    }
    return transpiled.outputText;
  }
};

export { Compiler, writeDotenvReflection };
//# sourceMappingURL=chunk-X3FMRUBA.js.map
//# sourceMappingURL=chunk-X3FMRUBA.js.map