import { bsonType } from './bson_value';
import { BSONError } from './error';
import type { Int32 } from './int_32';
import { Long } from './long';
import { type InspectFn, defaultInspect } from './parser/utils';

/**
 * @public
 * @deprecated This type is no longer used internally and will be removed in a future version.
 */
export type TimestampOverrides =
  | '_bsontype'
  | 'toExtendedJSON'
  | 'fromExtendedJSON'
  | 'inspect'
  | typeof bsonType;

/**
 * @public
 *
 * Inherited `Long` members surfaced on `Timestamp`.
 * When `Long` gains a new member: add it here if it should pass through, or
 * add a `@deprecated declare` line on the `Timestamp` class if it should be
 * surfaced as deprecated. Anything left unclassified is silently dropped from
 * the public type — preventing accidental behavioral bleed-through.
 */
type TimestampKept =
  | 'toBigInt'
  | 'toString'
  | 'compare'
  | 'equals'
  | 'eq'
  | 'notEquals'
  | 'neq'
  | 'ne'
  | 'lessThan'
  | 'lt'
  | 'lessThanOrEqual'
  | 'lte'
  | 'le'
  | 'greaterThan'
  | 'gt'
  | 'greaterThanOrEqual'
  | 'gte'
  | 'ge';

/** @public */
export type LongWithoutOverrides = new (
  low: unknown,
  high?: number | boolean,
  unsigned?: boolean
) => {
  [P in TimestampKept]: Long[P];
};
/** @public */
export const LongWithoutOverridesClass: LongWithoutOverrides =
  Long as unknown as LongWithoutOverrides;

/** @public */
export interface TimestampExtended {
  $timestamp: {
    t: number;
    i: number;
  };
}

/**
 * @public
 * @category BSONType
 *
 * A special type for _internal_ MongoDB use and is **not** associated with the regular Date type.
 */
export class Timestamp extends LongWithoutOverridesClass {
  get _bsontype(): 'Timestamp' {
    return 'Timestamp';
  }
  get [bsonType](): 'Timestamp' {
    return 'Timestamp';
  }

  /**
   * @deprecated Use `Long.MAX_UNSIGNED_VALUE` instead.
   */
  static readonly MAX_VALUE = Long.MAX_UNSIGNED_VALUE;

  /**
   * An incrementing ordinal for operations within a given second.
   */
  get i(): number {
    return this.low >>> 0;
  }

  /**
   * A `time_t` value measuring seconds since the Unix epoch
   */
  get t(): number {
    return this.high >>> 0;
  }

  /**
   * @param int - A 64-bit bigint representing the Timestamp.
   */
  constructor(int: bigint);
  /**
   * @param long - A 64-bit Long representing the Timestamp.
   */
  constructor(long: Long);
  /**
   * @param value - A pair of two values indicating timestamp and increment.
   */
  constructor(value: { t: number; i: number });
  constructor(low?: bigint | Long | { t: number | Int32; i: number | Int32 }) {
    if (low == null) {
      super(0, 0, true);
    } else if (typeof low === 'bigint') {
      super(low, true);
    } else if (Long.isLong(low)) {
      super(low.low, low.high, true);
    } else if (typeof low === 'object' && 't' in low && 'i' in low) {
      if (typeof low.t !== 'number' && (typeof low.t !== 'object' || low.t._bsontype !== 'Int32')) {
        throw new BSONError('Timestamp constructed from { t, i } must provide t as a number');
      }
      if (typeof low.i !== 'number' && (typeof low.i !== 'object' || low.i._bsontype !== 'Int32')) {
        throw new BSONError('Timestamp constructed from { t, i } must provide i as a number');
      }
      const t = Number(low.t);
      const i = Number(low.i);
      if (t < 0 || Number.isNaN(t)) {
        throw new BSONError('Timestamp constructed from { t, i } must provide a positive t');
      }
      if (i < 0 || Number.isNaN(i)) {
        throw new BSONError('Timestamp constructed from { t, i } must provide a positive i');
      }
      if (t > 0xffff_ffff) {
        throw new BSONError(
          'Timestamp constructed from { t, i } must provide t equal or less than uint32 max'
        );
      }
      if (i > 0xffff_ffff) {
        throw new BSONError(
          'Timestamp constructed from { t, i } must provide i equal or less than uint32 max'
        );
      }

      super(i, t, true);
    } else {
      throw new BSONError(
        'A Timestamp can only be constructed with: bigint, Long, or { t: number; i: number }'
      );
    }
  }

  toJSON(): { $timestamp: string } {
    return {
      // eslint-disable-next-line @typescript-eslint/no-base-to-string
      $timestamp: this.toString()
    };
  }

  /**
   * Returns a Timestamp represented by the given (32-bit) integer value.
   * @deprecated Stores `value` in the low 32 bits (increment), leaving t = 0. Use `new Timestamp({ t, i })` or `Timestamp.fromBits(lowBits, highBits)` for explicit construction.
   */
  static fromInt(value: number): Timestamp {
    return new Timestamp(Long.fromInt(value, true));
  }

  /**
   * Returns a Timestamp representing the given number value, provided that it is a finite number. Otherwise, zero is returned.
   * @deprecated Splits `value` across (t, i) as a uint64; the result rarely matches user intent. Use `new Timestamp({ t, i })` or `Timestamp.fromBits(lowBits, highBits)` for explicit construction.
   */
  static fromNumber(value: number): Timestamp {
    return new Timestamp(Long.fromNumber(value, true));
  }

  /**
   * Returns a Timestamp for the given high and low bits. Each is assumed to use 32 bits.
   *
   * @param lowBits - the low 32-bits.
   * @param highBits - the high 32-bits.
   */
  static fromBits(lowBits: number, highBits: number): Timestamp {
    return new Timestamp({ i: lowBits, t: highBits });
  }

  /**
   * Returns a Timestamp from the given string, optionally using the given radix.
   *
   * @param str - the textual representation of the Timestamp.
   * @param optRadix - the radix in which the text is written.
   */
  static fromString(str: string, optRadix: number): Timestamp {
    return new Timestamp(Long.fromString(str, true, optRadix));
  }

  /** @internal */
  toExtendedJSON(): TimestampExtended {
    return { $timestamp: { t: this.t, i: this.i } };
  }

  /** @internal */
  static fromExtendedJSON(doc: TimestampExtended): Timestamp {
    // The Long check is necessary because extended JSON has different behavior given the size of the input number
    const i = Long.isLong(doc.$timestamp.i)
      ? doc.$timestamp.i.getLowBitsUnsigned() // Need to fetch the least significant 32 bits
      : doc.$timestamp.i;
    const t = Long.isLong(doc.$timestamp.t)
      ? doc.$timestamp.t.getLowBitsUnsigned() // Need to fetch the least significant 32 bits
      : doc.$timestamp.t;
    return new Timestamp({ t, i });
  }

  inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
    inspect ??= defaultInspect;
    const t = inspect(this.t, options);
    const i = inspect(this.i, options);
    return `new Timestamp({ t: ${t}, i: ${i} })`;
  }

  // ********************
  // **** DEPRECATED ****
  // ********************
  // Declare used to avoid runtime effects for field declaration.
  // See: https://www.typescriptlang.org/docs/handbook/2/classes.html#type-only-field-declarations

  // Arithmetic
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare add: Long['add'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare subtract: Long['subtract'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare sub: Long['sub'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare multiply: Long['multiply'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare mul: Long['mul'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare divide: Long['divide'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare div: Long['div'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare modulo: Long['modulo'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare mod: Long['mod'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare rem: Long['rem'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned. */
  declare negate: Long['negate'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned. */
  declare neg: Long['neg'];

  // Bitwise / shifts
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare and: Long['and'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare or: Long['or'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare xor: Long['xor'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare not: Long['not'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shiftLeft: Long['shiftLeft'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shl: Long['shl'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shiftRight: Long['shiftRight'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shr: Long['shr'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shiftRightUnsigned: Long['shiftRightUnsigned'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shr_u: Long['shr_u'];
  /** @deprecated Not applicable to Timestamp; Timestamp is not intended to be used as a Long. */
  declare shru: Long['shru'];

  // Signing
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned. */
  declare toSigned: Long['toSigned'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned (this is a no-op). */
  declare toUnsigned: Long['toUnsigned'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned (is always false). */
  declare isNegative: Long['isNegative'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned (is always true). */
  declare isPositive: Long['isPositive'];
  /** @deprecated Not applicable to Timestamp as the underlying Long is always unsigned (is always true). */
  declare unsigned: Long['unsigned'];

  // Conversion
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch, or `.i` for the increment ordinal. */
  declare toInt: Long['toInt'];
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch, or `.i` for the increment ordinal. */
  declare toNumber: Long['toNumber'];
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch, or `.i` for the increment ordinal. */
  declare toBytes: Long['toBytes'];
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch, or `.i` for the increment ordinal. */
  declare toBytesLE: Long['toBytesLE'];
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch, or `.i` for the increment ordinal. */
  declare toBytesBE: Long['toBytesBE'];

  // Other
  /** @deprecated Incompatible with Timestamp: returns a signed integer, but Timestamp is always unsigned. Use `.t` for seconds since the Unix epoch. */
  declare getHighBits: Long['getHighBits'];
  /** @deprecated Not applicable to Timestamp; use `.t` for seconds since the Unix epoch. */
  declare getHighBitsUnsigned: Long['getHighBitsUnsigned'];
  /** @deprecated Incompatible with Timestamp: returns a signed integer, but Timestamp is always unsigned. Use `.i` for the increment ordinal. */
  declare getLowBits: Long['getLowBits'];
  /** @deprecated Not applicable to Timestamp; use `.i` for the increment ordinal. */
  declare getLowBitsUnsigned: Long['getLowBitsUnsigned'];

  /** @deprecated Not applicable to Timestamp; use `.i` instead. */
  declare low: Long['low'];
  /** @deprecated Not applicable to Timestamp; use `.t` instead. */
  declare high: Long['high'];

  /** @deprecated Use .compare() for general comparison, or .equals(), .lessThan(), .greaterThan() for specific cases. */
  declare comp: Long['comp'];
  /** @deprecated Compare `.t` and `.i` against `0` explicitly. */
  declare isZero: Long['isZero'];
  /** @deprecated Compare `.t` and `.i` against `0` explicitly. */
  declare eqz: Long['eqz'];

  /** @deprecated Not applicable to Timestamp. Use the bsonType symbol or _bsontype === 'Timestamp' to identify a Timestamp. */
  declare __isLong__: Long['__isLong__'];
  /** @deprecated Not applicable to Timestamp. */
  declare getNumBitsAbs: Long['getNumBitsAbs'];
  /** @deprecated Not applicable to Timestamp; tests parity of the increment only. */
  declare isEven: Long['isEven'];
  /** @deprecated Not applicable to Timestamp; tests parity of the increment only. */
  declare isOdd: Long['isOdd'];
}
