/**
 * Numeric Validation Helpers
 *
 * Shared guards for numeric form fields that are persisted as free-form strings
 * (e.g. ranges like "25-34", "5-10 years", "$10M Series A", "15-20%"). These
 * reject NEGATIVE numeric values without rejecting valid range/text strings,
 * so existing records stay backward-compatible.
 *
 * Mirrors the frontend helper in
 * `src/frontend/src/utils/inputValidation.ts` (isNegativeNumericValue).
 */

/**
 * Returns true if the value represents (or contains) a negative number.
 *
 * - Pure numeric negatives  → "-5", "-5.5", -5              → true
 * - Free-form with a sign    → "-5 years", "$-10"             → true
 * - Hyphenated ranges        → "25-34", "5-10 years", "15-20%" → false
 *   (there the '-' is preceded by a digit, so it's a range separator)
 * - Non-numeric / empty       → "", "$10M Series A", null       → false
 */
export function isNegativeNumericValue(value: unknown): boolean {
  if (value === undefined || value === null) return false;
  const str = String(value).trim();
  if (!str) return false;

  // Pure numeric value (e.g. "-5", "5", "-5.5")
  const num = Number(str);
  if (!Number.isNaN(num)) return num < 0;

  // Free-form string: a '-' acting as a sign is one NOT preceded by a digit.
  return /(^|[^\d.])-\s*\d/.test(str);
}

// ============================================
// ICP / PERSONA NUMERIC FIELD RULES
// ============================================
//
// These mirror the frontend validators in
// `src/frontend/src/utils/fieldValidators.ts` (validateEmployeeCount,
// validateAgeRange, validateYearsOfExperience) — keep both sides in step.
//
// Each rule comes as a `normalise*` helper rather than a bare boolean test:
// AI generation and document extraction produce human-readable variants
// ("8-12 years", "25 to 34", "1,500 employees") that carry a perfectly valid
// value, so routes normalise first and only reject what cannot be salvaged
// (letters-only, negatives, inverted ranges, out-of-range numbers).

/** Employee Count bounds for an ICP. */
export const EMPLOYEE_COUNT_MIN = 1;
export const EMPLOYEE_COUNT_MAX = 10_000_000;

/** Lowest / highest age accepted in a Persona's Age Range. */
export const PERSONA_AGE_MIN = 1;
export const PERSONA_AGE_MAX = 120;

/** Highest Years of Experience accepted for a Persona. */
export const PERSONA_EXPERIENCE_MAX = 70;

/**
 * Normalise an ICP Employee Count to a positive whole number, or null when the
 * value cannot represent one.
 *
 * - 50, "50", "1,500", "1500 employees", "50+" → 50 / 1500 / 50
 * - "50-200" (a range) → 200, the upper bound, so a single count is still stored
 * - "-5", "0", "abc", "" → null (rejected by the caller)
 */
export function normaliseEmployeeCount(value: unknown): number | null {
  if (value === undefined || value === null) return null;
  const str = String(value).trim();
  if (!str) return null;

  // A leading sign means a negative count — never valid, never salvaged.
  if (isNegativeNumericValue(str)) return null;

  // Collect the whole numbers in the value (thousands separators removed first)
  // and take the largest, so a "50-200" range yields its upper bound.
  const numbers = str.replace(/,/g, '').match(/\d+(?:\.\d+)?/g);
  if (!numbers) return null;

  const count = Math.round(Math.max(...numbers.map(Number)));
  if (!Number.isFinite(count)) return null;
  if (count < EMPLOYEE_COUNT_MIN || count > EMPLOYEE_COUNT_MAX) return null;

  return count;
}

/**
 * Normalise a Persona Age Range to the canonical `min-max` form, or null when
 * the value cannot represent a valid range.
 *
 * - "20-30", "25 – 34", "25 to 34", "30-45 years old" → "20-30" / "25-34" / "30-45"
 * - "50-20" (inverted), "0-20" / "20-150" (out of bounds), "30" (not a range),
 *   "-20-30", "abc" → null
 */
export function normaliseAgeRange(value: unknown): string | null {
  if (value === undefined || value === null) return null;
  const str = String(value).trim();
  if (!str) return null;

  if (isNegativeNumericValue(str)) return null;

  // Accept hyphen, en/em dash or the word "to" as the separator.
  const match = str.match(/(\d{1,3})\s*(?:-|–|—|to)\s*(\d{1,3})/i);
  if (!match) return null;

  const minAge = Number(match[1]);
  const maxAge = Number(match[2]);

  if (minAge < PERSONA_AGE_MIN || maxAge < PERSONA_AGE_MIN) return null;
  if (minAge > PERSONA_AGE_MAX || maxAge > PERSONA_AGE_MAX) return null;
  if (minAge >= maxAge) return null;

  return `${minAge}-${maxAge}`;
}

/**
 * Normalise a Persona's Years of Experience to a whole number ("5") or a
 * whole-number range ("5-10"), or null when the value cannot represent either.
 *
 * - "5", "5 years", "8-12 years", "8 – 12", "10 to 15" → "5" / "8-12" / "10-15"
 * - "-5", "10-5" (inverted), "80" (out of range), "five" → null
 */
export function normaliseYearsOfExperience(value: unknown): string | null {
  if (value === undefined || value === null) return null;
  const str = String(value).trim();
  if (!str) return null;

  if (isNegativeNumericValue(str)) return null;

  const rangeMatch = str.match(/(\d{1,2})\s*(?:-|–|—|to)\s*(\d{1,2})/i);
  if (rangeMatch) {
    const from = Number(rangeMatch[1]);
    const to = Number(rangeMatch[2]);
    if (from >= to) return null;
    if (to > PERSONA_EXPERIENCE_MAX) return null;
    return `${from}-${to}`;
  }

  const singleMatch = str.match(/\d+(?:\.\d+)?/);
  if (!singleMatch) return null;

  const years = Math.round(Number(singleMatch[0]));
  if (!Number.isFinite(years) || years < 0 || years > PERSONA_EXPERIENCE_MAX) return null;

  return String(years);
}
