```ts
/**
 * Converts a canonical Roman numeral string to its integer value.
 * Throws an error for invalid inputs or non-canonical representations.
 */
export function romanToInt(input: string): number {
  if (typeof input !== 'string' || input.length === 0) {
    throw new Error('Input must be a non-empty string');
  }

  const romanValues: Record<string, number> = {
    I: 1,
    V: 5,
    X: 10,
    L: 50,
    C: 100,
    D: 500,
    M: 1000
  };

  // Validate all characters are valid uppercase Roman numerals
  for (const char of input) {
    if (!(char in romanValues)) {
      throw new Error('Invalid character in Roman numeral');
    }
  }

  // Calculate the integer value using subtractive rules
  let total = 0;
  for (let i = 0; i < input.length; i++) {
    const current = romanValues[input[i]];
    const next = i + 1 < input.length ? romanValues[input[i + 1]] : 0;

    if (current < next) {
      total -= current;
    } else {
      total += current;
    }
  }

  // Validate range
  if (total < 1 || total > 3999) {
    throw new Error('Roman numeral value must be between 1 and 3999');
  }

  // Convert back to canonical form and compare with input
  function intToRoman(num: number): string {
    const values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
    const symbols = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'];

    let result = '';
    let remaining = num;

    for (let i = 0; i < values.length; i++) {
      const count = Math.floor(remaining / values[i]);
      if (count > 0) {
        result += symbols[i].repeat(count);
        remaining -= values[i] * count;
      }
    }

    return result;
  }

  const canonicalForm = intToRoman(total);

  if (canonicalForm !== input) {
    throw new Error('Non-canonical Roman numeral representation');
  }

  return total;
}
```