/**
 * @typedef {import("./types.js").RunData} RunData
 * @typedef {import("../units/types.js").RosterSaveData} RosterSaveData
 */

/**
 * Persistence.js
 * Handles asynchronous saving and loading using IndexedDB.
 * Manages both Active Runs and Persistent Roster data.
 */
const DB_NAME = "AetherShardsDB";
const RUN_STORE = "Runs";
const ROSTER_STORE = "Roster";
const MARKET_STORE = "Market";
const CAMPAIGN_STORE = "Campaign";
const HUB_STASH_STORE = "HubStash";
const UNLOCKS_STORE = "Unlocks";
const RESEARCH_STORE = "Research";
const VERSION = 7; // Bumped version to add Research store

/**
 * Handles game data persistence using IndexedDB.
 * @class
 */
export class Persistence {
  constructor() {
    /** @type {IDBDatabase | null} */
    this.db = null;
  }

  /**
   * Initializes the IndexedDB database.
   * @returns {Promise<void>}
   */
  async init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, VERSION);

      request.onerror = (e) => reject("DB Error: " + e.target.error);

      request.onupgradeneeded = (e) => {
        const db = e.target.result;

        // Create Runs Store if missing
        if (!db.objectStoreNames.contains(RUN_STORE)) {
          db.createObjectStore(RUN_STORE, { keyPath: "id" });
        }

        // Create Roster Store if missing
        if (!db.objectStoreNames.contains(ROSTER_STORE)) {
          db.createObjectStore(ROSTER_STORE, { keyPath: "id" });
        }

        // Create Market Store if missing
        if (!db.objectStoreNames.contains(MARKET_STORE)) {
          db.createObjectStore(MARKET_STORE, { keyPath: "id" });
        }

        // Create Campaign Store if missing
        if (!db.objectStoreNames.contains(CAMPAIGN_STORE)) {
          db.createObjectStore(CAMPAIGN_STORE, { keyPath: "id" });
        }

        // Create Hub Stash Store if missing
        if (!db.objectStoreNames.contains(HUB_STASH_STORE)) {
          db.createObjectStore(HUB_STASH_STORE, { keyPath: "id" });
        }

        // Create Unlocks Store if missing
        if (!db.objectStoreNames.contains(UNLOCKS_STORE)) {
          db.createObjectStore(UNLOCKS_STORE, { keyPath: "id" });
        }

        // Create Research Store if missing
        if (!db.objectStoreNames.contains(RESEARCH_STORE)) {
          db.createObjectStore(RESEARCH_STORE, { keyPath: "id" });
        }
      };

      request.onsuccess = (e) => {
        this.db = e.target.result;
        resolve();
      };
    });
  }

  // --- RUN DATA ---

  /**
   * Saves run data.
   * @param {RunData} runData - Run data to save
   * @returns {Promise<void>}
   */
  async saveRun(runData) {
    if (!this.db) await this.init();
    return this._put(RUN_STORE, { ...runData, id: "active_run" });
  }

  /**
   * Loads run data.
   * @returns {Promise<RunData | undefined>}
   */
  async loadRun() {
    if (!this.db) await this.init();
    return this._get(RUN_STORE, "active_run");
  }

  /**
   * Clears saved run data.
   * @returns {Promise<void>}
   */
  async clearRun() {
    if (!this.db) await this.init();
    return this._delete(RUN_STORE, "active_run");
  }

  // --- ROSTER DATA ---

  /**
   * Saves roster data.
   * @param {RosterSaveData} rosterData - Roster data to save
   * @returns {Promise<void>}
   */
  async saveRoster(rosterData) {
    if (!this.db) await this.init();
    // Wrap the raw data object in an ID for storage
    return this._put(ROSTER_STORE, { id: "player_roster", data: rosterData });
  }

  /**
   * Loads roster data.
   * @returns {Promise<RosterSaveData | null>}
   */
  async loadRoster() {
    if (!this.db) await this.init();
    const result = await this._get(ROSTER_STORE, "player_roster");
    return result ? result.data : null;
  }

  // --- MARKET DATA ---

  /**
   * Saves market state.
   * @param {import("../managers/MarketManager.js").MarketState} marketState - Market state to save
   * @returns {Promise<void>}
   */
  async saveMarketState(marketState) {
    if (!this.db) await this.init();
    return this._put(MARKET_STORE, { id: "market_state", data: marketState });
  }

  /**
   * Loads market state.
   * @returns {Promise<import("../managers/MarketManager.js").MarketState | null>}
   */
  async loadMarketState() {
    if (!this.db) await this.init();
    const result = await this._get(MARKET_STORE, "market_state");
    return result ? result.data : null;
  }

  // --- CAMPAIGN DATA ---

  /**
   * Saves campaign data (completed missions, active mission, etc.).
   * @param {import("../managers/types.js").MissionSaveData} campaignData - Campaign data to save
   * @returns {Promise<void>}
   */
  async saveCampaign(campaignData) {
    if (!this.db) await this.init();
    console.log("Persistence: Saving campaign data to Campaign store:", campaignData);
    try {
      await this._put(CAMPAIGN_STORE, { id: "campaign_data", data: campaignData });
      console.log("Persistence: Campaign data saved successfully");
    } catch (error) {
      console.error("Persistence: Error saving campaign data:", error);
      throw error;
    }
  }

  /**
   * Loads campaign data.
   * @returns {Promise<import("../managers/types.js").MissionSaveData | null>}
   */
  async loadCampaign() {
    if (!this.db) await this.init();
    console.log("Persistence: Loading campaign data from Campaign store");
    try {
      const result = await this._get(CAMPAIGN_STORE, "campaign_data");
      console.log("Persistence: Loaded campaign data result:", result);
      return result ? result.data : null;
    } catch (error) {
      console.error("Persistence: Error loading campaign data:", error);
      return null;
    }
  }

  // --- HUB STASH DATA ---

  /**
   * Saves hub stash data (currency and items).
   * @param {Object} hubStashData - Hub stash data with currency and items
   * @returns {Promise<void>}
   */
  async saveHubStash(hubStashData) {
    if (!this.db) await this.init();
    return this._put(HUB_STASH_STORE, { id: "hub_stash", data: hubStashData });
  }

  /**
   * Loads hub stash data.
   * @returns {Promise<Object | null>} - Hub stash data with currency and items, or null
   */
  async loadHubStash() {
    if (!this.db) await this.init();
    const result = await this._get(HUB_STASH_STORE, "hub_stash");
    return result ? result.data : null;
  }

  // --- UNLOCKS DATA ---

  /**
   * Saves unlocked classes.
   * @param {string[]} unlocks - Array of unlocked class IDs
   * @returns {Promise<void>}
   */
  async saveUnlocks(unlocks) {
    if (!this.db) await this.init();
    return this._put(UNLOCKS_STORE, { id: "unlocks", data: unlocks });
  }

  /**
   * Loads unlocked classes.
   * @returns {Promise<string[]>} - Array of unlocked class IDs
   */
  async loadUnlocks() {
    if (!this.db) await this.init();
    const result = await this._get(UNLOCKS_STORE, "unlocks");
    return result ? result.data : [];
  }

  // --- RESEARCH DATA ---

  /**
   * Saves research state.
   * @param {import("../managers/ResearchManager.js").ResearchState} researchState - Research state to save
   * @returns {Promise<void>}
   */
  async saveResearchState(researchState) {
    if (!this.db) await this.init();
    return this._put(RESEARCH_STORE, { id: "research_state", data: researchState });
  }

  /**
   * Loads research state.
   * @returns {Promise<import("../managers/ResearchManager.js").ResearchState | null>}
   */
  async loadResearchState() {
    if (!this.db) await this.init();
    const result = await this._get(RESEARCH_STORE, "research_state");
    return result ? result.data : null;
  }

  // --- INTERNAL HELPERS ---

  /**
   * Internal helper to put data into a store.
   * @param {string} storeName - Store name
   * @param {unknown} item - Item to store
   * @returns {Promise<void>}
   * @private
   */
  _put(storeName, item) {
    return new Promise((resolve, reject) => {
      const tx = this.db.transaction([storeName], "readwrite");
      const store = tx.objectStore(storeName);
      const req = store.put(item);
      req.onsuccess = () => resolve();
      req.onerror = () => reject(req.error);
    });
  }

  /**
   * Internal helper to get data from a store.
   * @param {string} storeName - Store name
   * @param {string} key - Key to retrieve
   * @returns {Promise<unknown>}
   * @private
   */
  _get(storeName, key) {
    return new Promise((resolve, reject) => {
      const tx = this.db.transaction([storeName], "readonly");
      const store = tx.objectStore(storeName);
      const req = store.get(key);
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }

  /**
   * Internal helper to delete data from a store.
   * @param {string} storeName - Store name
   * @param {string} key - Key to delete
   * @returns {Promise<void>}
   * @private
   */
  _delete(storeName, key) {
    return new Promise((resolve, reject) => {
      const tx = this.db.transaction([storeName], "readwrite");
      const store = tx.objectStore(storeName);
      const req = store.delete(key);
      req.onsuccess = () => resolve();
      req.onerror = () => reject(req.error);
    });
  }
}
