class Timekeeper {
  startTime;
  currentTime;
  endTime;
  running;

  // speed ratio (1 second in real time => x seconds in simulation time)
  speed;

  timerFunc;
  updateInterval = 50; // update canvas clock every 50 ms

  clockTickObservable;
  newTimeObservable;

  clock;
  revealDates;

  constructor(startTime, endTime, speed, revealDates) {
    const { Subject } = rxjs;

    this.startTime = luxon.DateTime.fromISO(startTime);
    this.currentTime = this.startTime;
    this.endTime = luxon.DateTime.fromISO(endTime);

    this.speed = speed;
    this.running = false;

    this.clockTickObservable = new Subject();
    this.newTimeObservable = new Subject();

    // Load saved state if exists
    const savedState = localStorage.getItem('timekeeper_state');
    if (savedState) {
      const state = JSON.parse(savedState);
      if (state.startTime === startTime && state.endTime === endTime) {
        this.currentTime = luxon.DateTime.fromISO(state.currentTime);
        this.running = state.running;
      } else {
        localStorage.removeItem('timekeeper_state');
      }
    }

    this.initClock(this.currentTime.toUTC());
    this.revealDates = revealDates;

    // Handle page visibility changes
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        // Save state when page is hidden
        if (this.running) {
          this.saveState();
        }
      } else {
        // Resume from saved state when page becomes visible
        const savedState = localStorage.getItem('timekeeper_state');
        if (savedState) {
          const state = JSON.parse(savedState);
          if (state.running) {
            const now = Date.now();
            const timeDiff = now - state.timestamp;
            this.currentTime = luxon.DateTime.fromISO(state.currentTime)
              .plus({ milliseconds: timeDiff * this.speed });
            this.start();
          }
        }
      }
    });

    this.updateDateTimeAdditionalInfo();
  }

  initClock(startTime) {
    this.clock = new StationClock("clock");
    this.clock.body = StationClock.RoundBody;
    this.clock.dial = StationClock.AustriaStrokeDial;
    this.clock.hourHand = StationClock.BarHourHand;
    this.clock.minuteHand = StationClock.BarSecondHand;
    this.clock.secondHand = StationClock.BarSecondHand;
    this.clock.boss = StationClock.NoBoss;
    this.clock.minuteHandBehavoir = StationClock.BouncingMinuteHand;
    this.clock.secondHandBehavoir = StationClock.CreepingSecondHand;
    this.clock.draw(startTime);
  }

  saveState() {
    const state = {
      startTime: this.startTime.toISO(),
      currentTime: this.currentTime.toISO(),
      endTime: this.endTime.toISO(),
      running: this.running,
      timestamp: Date.now()
    };
    localStorage.setItem('timekeeper_state', JSON.stringify(state));
  }

  start() {
    if (!this.timerFunc) {
      let lastTick = Date.now();
      this.timerFunc = setInterval(() => {
        const now = Date.now();
        const elapsed = now - lastTick;
        lastTick = now;  // Update lastTick for next interval
        this.onTick(elapsed);
        this.clock.draw(this.currentTime.toUTC());
      }, this.updateInterval);

      this.running = true;
      this.saveState();
    }
  }

  onTick(elapsed) {
    this.currentTime = this.currentTime.plus({
      milliseconds: this.speed * elapsed,
    });
    this.clockTickObservable.next(this.currentTime);

    if (this.currentTime >= this.endTime) {
      this.pause();
    }

    this.updateDateTimeAdditionalInfo();
    this.saveState();
  }

  pause() {
    if (this.timerFunc) {
      clearInterval(this.timerFunc);
      this.timerFunc = null;
      this.running = false;
      this.saveState();
    }
  }

  reset() {
    this.pause();
    this.currentTime = this.startTime;
    this.saveState();
  }

  setSimulationTime(date) {
    this.currentTime = luxon.DateTime.fromISO(date);
    this.newTimeObservable.next(this.currentTime);
    this.saveState();
  }

  updateDateTimeAdditionalInfo() {
    if (this.revealDates) {
      $("#dateUnderClock").html(
        this.currentTime.toUTC().toFormat("dd/MM/yyyy a")
      );
    }
  }
}
