/**
 * An Effect-native module for working with child processes.
 *
 * This module uses an AST-based approach where commands are built first
 * using `make` and `pipeTo`, then executed using `spawn`.
 *
 * **Example** (Spawning and piping commands)
 *
 * ```ts
 * import { Effect, Stream } from "effect"
 * import { NodeServices } from "@effect/platform-node"
 * import { ChildProcess } from "effect/unstable/process"
 *
 * // Build a command
 * const command = ChildProcess.make`echo "hello world"`
 *
 * // Spawn and collect output
 * const program = Effect.gen(function*() {
 *   // You can `yield*` a command, which calls `ChildProcess.spawn`
 *   const handle = yield* command
 *   const chunks = yield* Stream.runCollect(handle.stdout)
 *   const exitCode = yield* handle.exitCode
 *   return { chunks, exitCode }
 * }).pipe(Effect.scoped, Effect.provide(NodeServices.layer))
 *
 * // With options
 * const withOptions = ChildProcess.make({ cwd: "/tmp" })`ls -la`
 *
 * // Piping commands
 * const pipeline = ChildProcess.make`cat package.json`.pipe(
 *   ChildProcess.pipeTo(ChildProcess.make`grep name`)
 * )
 *
 * // Spawn the pipeline
 * const pipelineProgram = Effect.gen(function*() {
 *   const handle = yield* pipeline
 *   const chunks = yield* Stream.runCollect(handle.stdout)
 *   return chunks
 * }).pipe(Effect.scoped, Effect.provide(NodeServices.layer))
 * ```
 *
 * @since 4.0.0
 */
import * as Context from "../../Context.js";
import * as Effectable from "../../Effectable.js";
import { dual } from "../../Function.js";
import * as Predicate from "../../Predicate.js";
import { ChildProcessSpawner } from "./ChildProcessSpawner.js";
const TypeId = "~effect/unstable/process/ChildProcess";
// =============================================================================
// Constructors
// =============================================================================
const Proto = {
  ... /*#__PURE__*/Effectable.Prototype({
    label: "Command",
    evaluate(fiber) {
      return Context.getUnsafe(fiber.context, ChildProcessSpawner).spawn(this);
    }
  }),
  [TypeId]: TypeId
};
/**
 * Checks whether a value is a `Command`.
 *
 * @category guards
 * @since 4.0.0
 */
export const isCommand = u => Predicate.hasProperty(u, TypeId);
/**
 * Checks whether a command is a `StandardCommand`.
 *
 * @category guards
 * @since 4.0.0
 */
export const isStandardCommand = command => command._tag === "StandardCommand";
/**
 * Checks whether a command is a `PipedCommand`.
 *
 * @category guards
 * @since 4.0.0
 */
export const isPipedCommand = command => command._tag === "PipedCommand";
const makeStandardCommand = (command, args, options) => Object.assign(Object.create(Proto), {
  _tag: "StandardCommand",
  command,
  args,
  options
});
const makePipedCommand = (left, right, options = {}) => Object.assign(Object.create(Proto), {
  _tag: "PipedCommand",
  left,
  right,
  options
});
/**
 * Create a command from a template literal, options + template, or array form.
 *
 * **Details**
 *
 * This function supports three calling conventions:
 * 1. Template literal: `make\`npm run build\``
 * 2. Options + template literal: `make({ cwd: "/app" })\`npm run build\``
 * 3. Array form: `make("npm", ["run", "build"], options?)`
 *
 * Template literals are not parsed until execution time, allowing parsing
 * errors to flow through Effect's error channel.
 *
 * **Example** (Creating commands)
 *
 * ```ts
 * import { ChildProcess } from "effect/unstable/process"
 *
 * // Template literal form
 * const cmd1 = ChildProcess.make`echo "hello"`
 *
 * // With options
 * const cmd2 = ChildProcess.make({ cwd: "/tmp" })`ls -la`
 *
 * // Array form
 * const cmd3 = ChildProcess.make("git", ["status"])
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export const make = function make(...args) {
  // Template literal form: make`command`
  if (isTemplateString(args[0])) {
    const [templates, ...expressions] = args;
    const tokens = parseTemplates(templates, expressions);
    return makeStandardCommand(tokens[0] ?? "", tokens.slice(1), {});
  }
  // Options form: make({ cwd: "/tmp" })`command`
  if (typeof args[0] === "object" && !Array.isArray(args[0]) && !isTemplateString(args[0])) {
    const options = args[0];
    return function (templates, ...expressions) {
      const tokens = parseTemplates(templates, expressions);
      return makeStandardCommand(tokens[0] ?? "", tokens.slice(1), options);
    };
  }
  // Standard form without arguments: make("command", options?)
  if (typeof args[0] === "string" && !Array.isArray(args[1])) {
    const [command, options = {}] = args;
    return makeStandardCommand(command, [], options);
  }
  // Standard form with arguments: make("command", ["arg1", "arg2"], options?)
  const [command, cmdArgs = [], options = {}] = args;
  return makeStandardCommand(command, cmdArgs, options);
};
/**
 * Pipes the output of one command to the input of another.
 *
 * **Details**
 *
 * By default, pipes `stdout` from the source to `stdin` of the destination.
 * Use the `options` parameter to customize which streams are connected.
 *
 * **Example** (Piping command output)
 *
 * ```ts
 * import { ChildProcess } from "effect/unstable/process"
 *
 * // Pipe stdout (default)
 * const pipeline1 = ChildProcess.make`cat file.txt`.pipe(
 *   ChildProcess.pipeTo(ChildProcess.make`grep pattern`)
 * )
 *
 * // Pipe stderr instead of stdout
 * const pipeline2 = ChildProcess.make`my-program`.pipe(
 *   ChildProcess.pipeTo(ChildProcess.make`grep error`, { from: "stderr" })
 * )
 *
 * // Pipe combined stdout and stderr
 * const pipeline3 = ChildProcess.make`my-program`.pipe(
 *   ChildProcess.pipeTo(ChildProcess.make`tee output.log`, { from: "all" })
 * )
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export const pipeTo = /*#__PURE__*/dual(args => isCommand(args[0]) && isCommand(args[1]), (self, that, options) => makePipedCommand(self, that, options ?? {}));
/**
 * Prepends another command to a command.
 *
 * **Details**
 *
 * For pipelines, only the leftmost command is prefixed.
 *
 * **Example** (Prefixing commands)
 *
 * ```ts
 * import { ChildProcess } from "effect/unstable/process"
 *
 * const command = ChildProcess.make`echo "foo"`
 *
 * const prefixed = command.pipe(
 *   ChildProcess.prefix`time`
 * )
 *
 * // now prefixed will execute `time echo "foo"`
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export const prefix = function prefix(...args) {
  if (isCommand(args[0]) && args.length > 1) {
    const [self, ...rest] = args;
    const prefixSpec = parsePrefixArgs(rest);
    return applyPrefix(self, prefixSpec);
  }
  const prefixSpec = parsePrefixArgs(args);
  return self => applyPrefix(self, prefixSpec);
};
const parsePrefixArgs = args => {
  if (isTemplateString(args[0])) {
    const [templates, ...expressions] = args;
    const tokens = parseTemplates(templates, expressions);
    return {
      command: tokens[0] ?? "",
      args: tokens.slice(1)
    };
  }
  const [command, cmdArgs = []] = args;
  return {
    command,
    args: cmdArgs
  };
};
const applyPrefix = (self, prefixSpec) => {
  switch (self._tag) {
    case "StandardCommand":
      {
        return makeStandardCommand(prefixSpec.command, [...prefixSpec.args, self.command, ...self.args], self.options);
      }
    case "PipedCommand":
      {
        return makePipedCommand(applyPrefix(self.left, prefixSpec), self.right, self.options);
      }
  }
};
/**
 * Sets the current working directory for a command.
 *
 * **Details**
 *
 * For pipelines, applies to each command in the pipeline.
 *
 * **Example** (Setting command working directories)
 *
 * ```ts
 * import { ChildProcess } from "effect/unstable/process"
 *
 * const cmd = ChildProcess.make`ls -la`.pipe(
 *   ChildProcess.setCwd("/tmp")
 * )
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export const setCwd = /*#__PURE__*/dual(2, (self, cwd) => {
  switch (self._tag) {
    case "StandardCommand":
      {
        return makeStandardCommand(self.command, self.args, {
          ...self.options,
          cwd
        });
      }
    case "PipedCommand":
      {
        return makePipedCommand(setCwd(self.left, cwd), setCwd(self.right, cwd), self.options);
      }
  }
});
/**
 * Adds environment variables to a command, merging them with any existing
 * command environment and overriding duplicate keys.
 *
 * **Details**
 *
 * For pipelines, applies to each command in the pipeline.
 *
 * **Example** (Setting command environment variables)
 *
 * ```ts
 * import { ChildProcess } from "effect/unstable/process"
 *
 * const cmd = ChildProcess.make`node script.js`.pipe(
 *   ChildProcess.setEnv({ NODE_ENV: "test" })
 * )
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export const setEnv = /*#__PURE__*/dual(2, (self, env) => {
  switch (self._tag) {
    case "StandardCommand":
      {
        const nextEnv = self.options.env === undefined ? env : {
          ...self.options.env,
          ...env
        };
        return makeStandardCommand(self.command, self.args, {
          ...self.options,
          env: nextEnv
        });
      }
    case "PipedCommand":
      {
        return makePipedCommand(setEnv(self.left, env), setEnv(self.right, env), self.options);
      }
  }
});
const isTemplateString = u => Array.isArray(u) && "raw" in u && Array.isArray(u.raw);
// =============================================================================
// Utilities
// =============================================================================
/**
 * Parses an fd name like "fd3" to its numeric index.
 * Returns undefined if the name is invalid.
 *
 * @category converting
 * @since 4.0.0
 */
export const parseFdName = name => {
  const match = /^fd(\d+)$/.exec(name);
  if (match === null) return undefined;
  const fd = parseInt(match[1], 10);
  return fd >= 3 ? fd : undefined;
};
/**
 * Create an fd name from its numeric index.
 *
 * @category converting
 * @since 4.0.0
 */
export const fdName = fd => `fd${fd}`;
// =============================================================================
// Template Parsing
// =============================================================================
const parseTemplates = (templates, expressions) => {
  let tokens = [];
  for (const [index, template] of templates.entries()) {
    tokens = parseTemplate(templates, expressions, tokens, template, index);
  }
  return tokens;
};
const parseTemplate = (templates, expressions, prevTokens, template, index) => {
  const rawTemplate = templates.raw[index];
  if (rawTemplate === undefined) {
    throw new Error(`Invalid backslash sequence: ${templates.raw[index]}`);
  }
  const {
    hasLeadingWhitespace,
    hasTrailingWhitespace,
    tokens
  } = splitByWhitespaces(template, rawTemplate);
  const nextTokens = concatTokens(prevTokens, tokens, hasLeadingWhitespace);
  if (index === expressions.length) {
    return nextTokens;
  }
  const expression = expressions[index];
  const expressionTokens = Array.isArray(expression) ? expression.map(expression => parseExpression(expression)) : [parseExpression(expression)];
  return concatTokens(nextTokens, expressionTokens, hasTrailingWhitespace);
};
/**
 * Convert valid expressions defined in a template string command (i.e. using
 * `${expression}` into strings.
 */
const parseExpression = expression => {
  const type = typeof expression;
  if (type === "string") {
    return expression; // Return strings directly
  }
  return String(expression); // Convert numbers to strings
};
const DELIMITERS = /*#__PURE__*/new Set([" ", "\t", "\r", "\n"]);
/**
 * Number of characters in backslash escape sequences: \0 \xXX or \uXXXX
 * \cX is allowed in RegExps but not in strings
 * Octal sequences are not allowed in strict mode
 */
const ESCAPE_LENGTH = {
  x: 3,
  u: 5
};
/**
 * Splits a template string by whitespace while also properly handling escape
 * sequences.
 *
 * As an example, let's review the following valid commands:
 *
 * ```ts
 * ChildProcess.exec`echo foo\n bar`
 * // We should run `["echo", "foo\n", "bar"]`
 *
 * ChildProcess.exec`echo foo
 *  bar`
 * // We should run `["echo", "foo", "bar]`
 * ```
 *
 * The problem is that when we evaluate the template string for both of the above
 * commands, we will end up with the same string "echo foo\n bar".
 *
 * What we really want is to include the escaped character in the arguments for
 * the first command, since it was written explicitly by the user.
 *
 * This is why also having access to the raw template string is useful - in a
 * template string, there are two representations of the same string:
 * 1. `template`     - The processed string (escape sequences are evaluated).
 * 2. `template.raw` - The raw string (escape sequences are literal).
 */
const splitByWhitespaces = (template, rawTemplate) => {
  if (rawTemplate.length === 0) {
    return {
      tokens: [],
      hasLeadingWhitespace: false,
      hasTrailingWhitespace: false
    };
  }
  const hasLeadingWhitespace = DELIMITERS.has(rawTemplate[0]);
  const tokens = [];
  // Given that escape sequences will have different lengths in the template
  // versus the raw template, we must maintain two indices:
  // - One for the index into the template string
  // - One for the index into the raw template string
  // We also maintain the current cursor position for where we are in the template
  let templateCursor = 0;
  for (let templateIndex = 0, rawIndex = 0; templateIndex < template.length; templateIndex += 1, rawIndex += 1) {
    // Use the raw template character to check for actual whitespace
    const rawCharacter = rawTemplate[rawIndex];
    if (DELIMITERS.has(rawCharacter)) {
      // Whitespace found, extract token from template if necessary
      if (templateCursor !== templateIndex) {
        tokens.push(template.slice(templateCursor, templateIndex));
      }
      // Advance the template start index to the current position
      templateCursor = templateIndex + 1;
    } else if (rawCharacter === "\\") {
      // Escape sequence detected, check the next raw character
      const nextRawCharacter = rawTemplate[rawIndex + 1];
      if (nextRawCharacter === "\n") {
        // Handle `\` character followed by a newline (i.e. a line continuation) by:
        // - Reversing the template index (backslash-newline is erased in template)
        // - Advancing the raw template index past the line continuation
        templateIndex -= 1;
        rawIndex += 1;
      } else if (nextRawCharacter === "u" && rawTemplate[rawIndex + 2] === "{") {
        // Handle variable-length unicode escape sequences (i.e. `\u{1F600}`) by:
        // - Advancing the raw template index past the unicode escape sequence
        rawIndex = rawTemplate.indexOf("}", rawIndex + 3);
      } else {
        // Advance raw template index past fixed-length escape sequences:
        // - \n    → 2 chars (backslash + n)
        // - \t    → 2 chars (backslash + t)
        // - \xHH  → 4 chars (backslash + x + H + H)
        // - \uHHHH → 6 chars (backslash + u + H + H + H + H)
        rawIndex += ESCAPE_LENGTH[nextRawCharacter] ?? 1;
      }
    }
  }
  // Trailing whitespace only exists if the template cursor is equivalent to the
  // length of the template
  const hasTrailingWhitespace = templateCursor === template.length;
  // If we did not end with trailing whitespace, ensure the final token is added
  if (!hasTrailingWhitespace) {
    tokens.push(template.slice(templateCursor));
  }
  return {
    tokens,
    hasLeadingWhitespace,
    hasTrailingWhitespace
  };
};
/**
 * Concatenates two separate sets of string tokens together.
 *
 * If either set is empty or `isSeparated=false`, the last element of `prevTokens`
 * and the first element of `nextTokens` will be joined into a single token.
 */
const concatTokens = (prevTokens, nextTokens, isSeparated) => isSeparated || prevTokens.length === 0 || nextTokens.length === 0
// Keep the previous and next tokens separate from one another
? [...prevTokens, ...nextTokens]
// Join the last token from the previous set and the first token from the next set
: [...prevTokens.slice(0, -1), `${prevTokens.at(-1)}${nextTokens.at(0)}`, ...nextTokens.slice(1)];
//# sourceMappingURL=ChildProcess.js.map