// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: GPL-3.0-or-later

import { execSync } from "node:child_process";

/**
 * Resolve a configuration value (API key, header value, etc.) to a concrete string.
 *
 * Resolution order, following pi-coding-agent's convention:
 * - `"!command"` — executes the rest as a shell command, uses trimmed stdout
 * - `"$VAR"` or `"${VAR}"` — treats as an env var reference (with sigil)
 * - Otherwise checks `process.env[value]` — bare name is tried as env var
 * - If no env var matches, the string is used as a literal value
 *
 * Returns `undefined` only when a shell command fails or produces empty output.
 */
export function resolveConfigValue(value: string): string | undefined {
  if (value.startsWith("!")) {
    return executeShellCommand(value.slice(1));
  }

  // Explicit $VAR or ${VAR} reference
  const envRef = value.match(/^\$\{(.+)\}$|^\$([A-Za-z_][A-Za-z0-9_]*)$/);
  if (envRef) {
    const name = envRef[1] ?? envRef[2]!;
    return process.env[name] ?? undefined;
  }

  // Bare name — check as env var first, then use as literal
  const envValue = process.env[value];
  return envValue || value;
}

/**
 * Expand `$VAR` and `${VAR}` references embedded within a larger string.
 * Unlike `resolveConfigValue`, this handles mixed literal + env-var strings
 * like `"Bearer $API_KEY"`.
 */
export function expandEnvVars(value: string): string {
  return value.replace(
    /\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
    (_, braced, bare) => {
      const name = braced ?? bare;
      return process.env[name] ?? "";
    },
  );
}

/**
 * Resolve all values in a headers record using `resolveConfigValue`.
 * Drops entries whose values resolve to `undefined`.
 */
export function resolveHeaders(
  headers: Record<string, string> | undefined,
): Record<string, string> | undefined {
  if (!headers) return undefined;
  const resolved: Record<string, string> = {};
  for (const [key, value] of Object.entries(headers)) {
    const resolvedValue = resolveConfigValue(value);
    if (resolvedValue) {
      resolved[key] = resolvedValue;
    }
  }
  return Object.keys(resolved).length > 0 ? resolved : undefined;
}

function executeShellCommand(command: string): string | undefined {
  try {
    const output = execSync(command, {
      encoding: "utf-8",
      timeout: 10_000,
      stdio: ["ignore", "pipe", "ignore"],
    });
    return output.trim() || undefined;
  } catch {
    return undefined;
  }
}
