import { Assertion } from '../smartexpect.classes.assertion.js';
import type { TExecutionType } from '../types.js';

/**
 * Namespace for number-specific matchers
 */
export class NumberMatchers<M extends TExecutionType> {
  constructor(private assertion: Assertion<number, M>) {}

  toBeGreaterThan(value: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) > value,
      `Expected number to be greater than ${value}`
    );
  }

  toBeLessThan(value: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) < value,
      `Expected number to be less than ${value}`
    );
  }

  toBeGreaterThanOrEqual(value: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) >= value,
      `Expected number to be greater than or equal to ${value}`
    );
  }

  toBeLessThanOrEqual(value: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) <= value,
      `Expected number to be less than or equal to ${value}`
    );
  }

  toBeCloseTo(value: number, precision?: number) {
    return this.assertion.customAssertion(
      (v) => {
        const num = v as number;
        const p = precision !== undefined ? precision : 2;
        const diff = Math.abs(num - value);
        const tolerance = 0.5 * Math.pow(10, -p);
        return diff <= tolerance;
      },
      `Expected number to be close to ${value} within precision ${precision ?? 2}`
    );
  }
  /** Equality check for numbers */
  toEqual(value: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) === value,
      `Expected number to equal ${value}`
    );
  }
  /**
   * Checks for NaN
   */
  toBeNaN() {
    return this.assertion.customAssertion(
      (v) => Number.isNaN(v as number),
      `Expected number to be NaN`
    );
  }
  /**
   * Checks for finite number
   */
  toBeFinite() {
    return this.assertion.customAssertion(
      (v) => Number.isFinite(v as number),
      `Expected number to be finite`
    );
  }
  /**
   * Checks if number is within inclusive range
   */
  toBeWithinRange(min: number, max: number) {
    return this.assertion.customAssertion(
      (v) => (v as number) >= min && (v as number) <= max,
      `Expected number to be within range ${min} - ${max}`
    );
  }
}