/**
 * Strict numeric parsers for CLI flags.
 *
 * `Number.parseInt`/`Number.parseFloat` silently truncate trailing
 * garbage ("10abc" → 10), which we don't want for user-facing flags
 * like `--limit`, `--fps`, `--poll-interval`. These wrappers reject
 * anything that isn't a complete numeric literal.
 */

import { errorBox } from "../ui/format.js";

const INTEGER_RE = /^-?\d+$/;
const NUMERIC_RE = /^-?\d+(\.\d+)?$/;

export interface IntFlagOptions {
  flag: string;
  min?: number;
  max?: number;
}

/**
 * Parse an integer flag, exit(1) with an errorBox on any non-integer
 * input (including trailing garbage like "10abc" and decimals like
 * "10.5"). Returns `undefined` when `raw === undefined` so callers can
 * supply their own defaults.
 */
export function parseIntFlag(raw: string | undefined, opts: IntFlagOptions): number | undefined {
  if (raw === undefined) return undefined;
  if (!INTEGER_RE.test(raw)) {
    errorBox(`Invalid ${opts.flag}`, `Got "${raw}". Must be an integer${rangeSuffix(opts)}.`);
    process.exit(1);
  }
  const n = Number.parseInt(raw, 10);
  enforceRange(opts.flag, raw, n, opts);
  return n;
}

export interface FloatFlagOptions {
  flag: string;
  min?: number;
  max?: number;
}

/**
 * Parse a non-NaN finite numeric flag, exit(1) on any non-numeric
 * input. Accepts integers and decimals.
 */
export function parseNumericFlag(
  raw: string | undefined,
  opts: FloatFlagOptions,
): number | undefined {
  if (raw === undefined) return undefined;
  if (!NUMERIC_RE.test(raw)) {
    errorBox(`Invalid ${opts.flag}`, `Got "${raw}". Must be a number${rangeSuffix(opts)}.`);
    process.exit(1);
  }
  const n = Number.parseFloat(raw);
  if (!Number.isFinite(n)) {
    errorBox(`Invalid ${opts.flag}`, `Got "${raw}". Must be a finite number.`);
    process.exit(1);
  }
  enforceRange(opts.flag, raw, n, opts);
  return n;
}

function enforceRange(
  flag: string,
  raw: string,
  value: number,
  bounds: { min?: number; max?: number },
): void {
  if (bounds.min !== undefined && value < bounds.min) {
    errorBox(`Invalid ${flag}`, `Got "${raw}". Minimum is ${bounds.min}.`);
    process.exit(1);
  }
  if (bounds.max !== undefined && value > bounds.max) {
    errorBox(`Invalid ${flag}`, `Got "${raw}". Maximum is ${bounds.max}.`);
    process.exit(1);
  }
}

/** Parse an enum-typed flag against a closed set of allowed values. */
export function parseEnumFlag<T extends string>(
  raw: string | undefined,
  allowed: readonly T[],
  opts: { flag: string },
): T | undefined {
  if (raw === undefined) return undefined;
  if ((allowed as readonly string[]).includes(raw)) return raw as T;
  errorBox(`Invalid ${opts.flag}`, `Got "${raw}". Must be one of: ${allowed.join(", ")}.`);
  process.exit(1);
}

function rangeSuffix(opts: { min?: number; max?: number }): string {
  if (opts.min !== undefined && opts.max !== undefined) return ` (${opts.min}-${opts.max})`;
  if (opts.min !== undefined) return ` (>= ${opts.min})`;
  if (opts.max !== undefined) return ` (<= ${opts.max})`;
  return "";
}
