/**
 * Bash runner script generation.
 * The runner checks Node.js, loads .env, and runs the bundle.
 */

import { type FrontmcpExecConfig } from './config';

/**
 * Defense-in-depth: scrub anything outside `[a-zA-Z0-9._+-]` from values that
 * the runner / installer scripts interpolate into bash. The user owns
 * `frontmcp.config`, so a malicious value would be self-inflicted, but the
 * generated scripts are committed into repos and downloaded by end-users —
 * so we keep them safe to copy/paste regardless of upstream config hygiene.
 */
export function sanitizeShellLiteral(value: string): string {
  // `-` placed last in the character class is literal, so no escape needed.
  return value.replace(/[^A-Za-z0-9._+-]/g, '_');
}

export function generateRunnerScript(config: FrontmcpExecConfig, cliMode?: boolean, seaMode?: boolean): string {
  const name = config.name;
  const version = sanitizeShellLiteral(config.version || '0.0.0');

  // #377 — `--target node` runner used to silently exec the bundle for any
  // flag, so `./frontegg-bin --help` quietly booted the HTTP server. Intercept
  // help/version here so server-mode runners behave like a normal CLI for
  // those flags. CLI-mode runners pass everything through to the bundle's
  // own commander parser.
  const helpInterceptor = !cliMode
    ? `
# Intercept standard CLI flags before booting the long-running server.
case "\${1:-}" in
  -h|--help)
    cat <<EOF
${name} v${version} — FrontMCP server

This binary starts a long-running MCP HTTP server, or an MCP stdio server with --stdio.

Usage:
  ${name}                       Start the HTTP server
  ${name} --stdio               Serve over stdio (stdin/stdout JSON-RPC); binds no TCP port
  ${name} --help                Show this help
  ${name} --version             Show version
  ${name} --print-manifest      Print the deployment manifest as JSON

Configure via environment variables, .env, or frontmcp.config.

Use --stdio for local MCP clients (Claude Desktop, Cursor). Example config:
  { "command": "${name}", "args": ["--stdio"] }

For a CLI-style binary that exposes tools/resources/prompts as subcommands,
build with: frontmcp build --target cli
EOF
    exit 0
    ;;
  --version)
    echo "${name} ${version}"
    exit 0
    ;;
  --print-manifest)
    cat "\${SCRIPT_DIR}/${name}.manifest.json"
    exit 0
    ;;
  --stdio)
    # Serve over stdio (stdin/stdout JSON-RPC) instead of starting the HTTP
    # server. FRONTMCP_STDIO=1 makes the @FrontMcp decorator connect the stdio
    # transport and bind no TCP port (#448, #451); logs go to stderr and
    # ~/.frontmcp/logs. Drop the flag and fall through to the exec below.
    export FRONTMCP_STDIO=1
    shift
    ;;
  --*)
    echo "Error: unsupported flag '\${1}' on the server runner."
    echo "This binary is a long-running HTTP server; flag-style invocation is reserved (except --stdio)." >&2
    echo "Run with no args to start, --stdio to serve over stdio, or build with --target cli for a CLI binary." >&2
    exit 2
    ;;
esac
`
    : '';

  // SEA mode: binary is self-contained, no Node.js required
  if (seaMode) {
    const binary = cliMode ? `${name}-cli-bin` : `${name}-bin`;
    const comment = cliMode
      ? `# ${name} — FrontMCP CLI (single executable)`
      : `# ${name} — FrontMCP Server (single executable)`;

    return `#!/usr/bin/env bash
set -euo pipefail

${comment}
# Generated by frontmcp build --target ${cliMode ? 'cli' : 'node'}

SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
BINARY="\${SCRIPT_DIR}/${binary}"

if [ ! -f "\${BINARY}" ]; then
  echo "Error: Binary not found at \${BINARY}"
  exit 1
fi
${helpInterceptor}
# Load .env if present
ENV_FILE="\${SCRIPT_DIR}/.env"
if [ -f "\${ENV_FILE}" ]; then
  set -a
  # shellcheck disable=SC1090
  source "\${ENV_FILE}"
  set +a
fi

exec "\${BINARY}" "$@"
`;
  }

  const nodeVersion = config.nodeVersion || '>=22.0.0';
  const minNodeMajor = extractMinMajor(nodeVersion);

  const bundle = cliMode
    ? `\${SCRIPT_DIR}/${name}-cli.bundle.js`
    : `\${SCRIPT_DIR}/${name}.bundle.js`;

  const comment = cliMode
    ? `# ${name} — FrontMCP CLI Executable`
    : `# ${name} — FrontMCP Server Runner`;

  return `#!/usr/bin/env bash
set -euo pipefail

${comment}
# Generated by frontmcp build --target ${cliMode ? 'cli --js' : 'node'}

SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
BUNDLE="${bundle}"
${helpInterceptor}
# Check Node.js
if ! command -v node &> /dev/null; then
  echo "Error: Node.js is required but not installed."
  echo "Install Node.js ${nodeVersion}: https://nodejs.org"
  exit 1
fi

NODE_MAJOR=$(node -e "console.log(process.versions.node.split('.')[0])")
if [ "\${NODE_MAJOR}" -lt "${minNodeMajor}" ]; then
  echo "Error: Node.js ${nodeVersion} required, found v$(node -v)"
  exit 1
fi

# Check bundle exists
if [ ! -f "\${BUNDLE}" ]; then
  echo "Error: Bundle not found at \${BUNDLE}"
  echo "Run 'frontmcp build --target ${cliMode ? 'cli --js' : 'node'}' to create it."
  exit 1
fi

# Load .env if present
ENV_FILE="\${SCRIPT_DIR}/.env"
if [ -f "\${ENV_FILE}" ]; then
  set -a
  # shellcheck disable=SC1090
  source "\${ENV_FILE}"
  set +a
fi

# Enable Node.js compile cache for faster startup on warm runs
COMPILE_CACHE_DIR="\${HOME}/.cache/frontmcp/${name}"
mkdir -p "\${COMPILE_CACHE_DIR}" 2>/dev/null || true
export NODE_COMPILE_CACHE="\${COMPILE_CACHE_DIR}"

# Run
exec node "\${BUNDLE}" "$@"
`;
}

function extractMinMajor(version: string): number {
  const match = version.match(/(\d+)/);
  return match ? parseInt(match[1], 10) : 22;
}
