export class SnowflakeGenerator {
  private epoch: bigint;
  private workerId: bigint;
  private processId: bigint;
  private sequence: bigint;
  private lastTimestamp: bigint;

  constructor(workerId: bigint = 1n, processId: bigint = 1n, epoch: bigint = 1766617200000n) {
    // dec 1 2025 (creation of this)
    this.epoch = epoch;
    this.workerId = workerId;
    this.processId = processId;
    this.sequence = 0n;
    this.lastTimestamp = -1n;
  }

  private getCurrentTimestamp(): bigint {
    return BigInt(Date.now());
  }

  private waitForNextMillisecond(lastTimestamp: bigint): bigint {
    let timestamp = this.getCurrentTimestamp();
    while (timestamp <= lastTimestamp) {
      timestamp = this.getCurrentTimestamp();
    }
    return timestamp;
  }

  public generateSnowflake(): number {
    const timestamp = this.getCurrentTimestamp() - this.epoch;

    if (timestamp === this.lastTimestamp) {
      this.sequence += 1n;
      if (this.sequence > 4095n) {  // max sequence value (12 bits)
        // Wait for the next millisecond if we've reached the max sequence value
        const newTimestamp = this.waitForNextMillisecond(this.lastTimestamp);
        this.sequence = 0n;
        return Number.parseInt(String(this.createSnowflake(newTimestamp)));
      }
    } else {
      this.sequence = 0n;
    }

    this.lastTimestamp = timestamp;
    return Number.parseInt(String(this.createSnowflake(timestamp)));
  }

  private createSnowflake(timestamp: bigint): bigint {
    return (timestamp << 22n) | (BigInt(this.workerId) << 18n) | (BigInt(this.processId) << 12n) | BigInt(this.sequence);
  }

    public decodeSnowflake(id: bigint | number) {
    const snowflake = BigInt(id);

    const sequence  = snowflake & (1n << 6n) - 1n;
    const processId = (snowflake >> 6n)  & ((1n << 6n) - 1n);
    const workerId  = (snowflake >> 12n) & ((1n << 10n) - 1n);
    const timestamp = (snowflake >> 22n) + this.epoch;

    return {
      timestamp,                // bigint (ms since Unix epoch)
      date: new Date(Number(timestamp)),
      workerId,
      processId,
      sequence,
    };
  }
}