/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  setInterval: "resource://gre/modules/Timer.sys.mjs",
  clearInterval: "resource://gre/modules/Timer.sys.mjs",
  MemoriesManager:
    "moz-src:///browser/components/aiwindow/models/memories/MemoriesManager.sys.mjs",
  getRecentChats:
    "moz-src:///browser/components/aiwindow/models/memories/MemoriesChatSource.sys.mjs",
  CONVERSATION:
    "moz-src:///browser/components/aiwindow/models/memories/MemoriesConstants.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "console", function () {
  return console.createInstance({
    prefix: "MemoriesConversationScheduler",
    maxLogLevelPref: "browser.smartwindow.memoriesLogLevel",
  });
});

// Generate memories if there have been at least 10 user messages since the last run
const MEMORIES_SCHEDULER_MESSAGES_THRESHOLD = 10;

// Conversation scheduler tick every 2 mins
const MEMORIES_SCHEDULER_INTERVAL_MS = 2 * 60 * 1000;
// Cooldown period - don't run more than once every 4 hours
//TODO: pref only for test purposes. will be later reverted
const MEMORIES_SCHEDULER_COOLDOWN_MS = Services.prefs.getIntPref(
  "browser.smartwindow.memoriesSchedulerCooldownInMs",
  4 * 60 * 60 * 1000
);

/**
 * Schedules periodic generation of conversation-based memories.
 * Triggers memories generation when number of user messages exceeds the configured threshold ({@link MEMORIES_SCHEDULER_MESSAGES_THRESHOLD})
 *
 * E.g. Usage: MemoriesConversationScheduler.maybeInit()
 */
export class MemoriesConversationScheduler {
  #intervalHandle = 0;
  #destroyed = false;
  #running = false;

  /** @type {MemoriesConversationScheduler | null} */
  static #instance = null;

  static maybeInit() {
    if (
      !lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
        lazy.CONVERSATION
      )
    ) {
      return null;
    }
    if (!this.#instance) {
      this.#instance = new MemoriesConversationScheduler();
    }
    return this.#instance;
  }

  constructor() {
    this.#startInterval();
    lazy.console.debug("Initialized");
  }

  /**
   * Starts the interval that periodically evaluates history drift and
   * potentially triggers memory generation.
   *
   * @throws {Error} If an interval is already running.
   */
  #startInterval() {
    if (this.#intervalHandle) {
      throw new Error(
        "Attempting to start an interval when one already existed"
      );
    }
    this.#intervalHandle = lazy.setInterval(
      this.#onInterval,
      MEMORIES_SCHEDULER_INTERVAL_MS
    );
  }

  /**
   * Stops the currently running interval, if any.
   */
  #stopInterval() {
    if (this.#intervalHandle) {
      lazy.clearInterval(this.#intervalHandle);
      this.#intervalHandle = 0;
    }
  }

  #onInterval = async () => {
    if (this.#destroyed) {
      lazy.console.warn("Interval fired after destroy; ignoring.");
      return;
    }

    // Re-check gating conditions on every tick (AIWindow may have closed, prefs may have changed).
    if (
      !lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
        lazy.CONVERSATION
      )
    ) {
      lazy.console.debug(
        "Memories from conversation scheduler no longer enabled; stopping conversation scheduler."
      );
      this.destroy();
      // Also clear singleton so it can be re-initialized later when conditions become true again.
      MemoriesConversationScheduler.#instance = null;
      return;
    }

    if (this.#running) {
      lazy.console.debug(
        "Skipping run because a previous run is still in progress."
      );
      return;
    }

    this.#running = true;
    this.#stopInterval();

    try {
      // Detect whether conversation memories were generated before.
      const lastMemoryTs =
        (await lazy.MemoriesManager.getLastConversationMemoryTimestamp()) ?? 0;

      const now = Date.now();

      // Cooldown check - don't run more than once every 4 hours.
      if (
        lastMemoryTs > 0 &&
        now - lastMemoryTs < MEMORIES_SCHEDULER_COOLDOWN_MS
      ) {
        lazy.console.debug(
          `Cooldown not met; last run was ${Math.floor(
            (now - lastMemoryTs) / (60 * 1000)
          )}m ago (<${Math.floor(
            MEMORIES_SCHEDULER_COOLDOWN_MS / (60 * 60 * 1000)
          )}h). Skipping.`
        );
        return;
      }

      // Get user chat messages
      const chatMessagesSinceLastMemory =
        await lazy.getRecentChats(lastMemoryTs);

      // Not enough new messages
      if (
        chatMessagesSinceLastMemory.length <
        MEMORIES_SCHEDULER_MESSAGES_THRESHOLD
      ) {
        return;
      }

      // Generate memories
      await lazy.MemoriesManager.generateMemoriesFromConversationHistory();
    } catch (error) {
      lazy.console.error("Failed to generate conversation memories", error);
    } finally {
      if (
        !this.#destroyed &&
        lazy.MemoriesManager.shouldEnableMemoriesFromSchedulers(
          lazy.CONVERSATION
        )
      ) {
        this.#startInterval();
      }
      this.#running = false;
    }
  };

  destroy() {
    this.#stopInterval();
    this.#destroyed = true;
    MemoriesConversationScheduler.#instance = null;
    lazy.console.debug("Destroyed");
  }

  async runNowForTesting() {
    await this.#onInterval();
  }
}
