/**
 * @typedef {import("./types.js").NarrativeSequence} NarrativeSequence
 * @typedef {import("./types.js").NarrativeNode} NarrativeNode
 */

/**
 * NarrativeManager.js
 * Manages the flow of story events, dialogue, and tutorials.
 * Extends EventTarget to broadcast UI updates to the DialogueOverlay.
 * @class
 */
export class NarrativeManager extends EventTarget {
  constructor() {
    super();
    /** @type {NarrativeSequence | null} */
    this.currentSequence = null;
    /** @type {NarrativeNode | null} */
    this.currentNode = null;
    /** @type {Set<string>} */
    this.history = new Set(); // Track IDs of played sequences
  }

  /**
   * Loads and starts a narrative sequence.
   * @param {NarrativeSequence} sequenceData - The JSON object of the conversation (from assets/data/narrative/).
   */
  startSequence(sequenceData) {
    if (!sequenceData || !sequenceData.nodes) {
      console.error("NarrativeManager: Invalid sequence data", sequenceData);
      return;
    }

    console.log(`NarrativeManager: Starting Sequence '${sequenceData.id}'`);
    this.currentSequence = sequenceData;
    this.history.add(sequenceData.id);

    // Find first node (usually index 0 or has explicit start flag, here we use index 0)
    this.currentNode = sequenceData.nodes[0];

    // Process entry triggers for the first node
    this._processNodeTriggers(this.currentNode);

    this.broadcastUpdate();
  }

  /**
   * Advances to the next node in the linear sequence.
   * If the current node has choices, this method should strictly be blocked by the UI,
   * but we include a check here for safety.
   */
  next() {
    if (!this.currentNode) return;
    if (this.currentNode.type === "CHOICE") {
      console.warn(
        "NarrativeManager: Cannot call next() on a CHOICE node. User must select option."
      );
      return;
    }

    const nextId = this.currentNode.next;
    
    // If the next node is "END", we still need to wait for user to click again
    // to actually close the dialogue. This gives them time to read the last message.
    if (!nextId || nextId === "END") {
      // User clicked on the last message - now close it
      this.endSequence();
      return;
    }
    
    this._advanceToNode(nextId);
  }

  /**
   * Handles player choice selection from a branching node.
   * @param {number} choiceIndex - The index of the chosen option in the `choices` array.
   */
  makeChoice(choiceIndex) {
    if (!this.currentNode || !this.currentNode.choices) return;

    const choice = this.currentNode.choices[choiceIndex];
    if (!choice) return;

    // Process Choice-specific triggers (e.g., immediate reputation gain)
    if (choice.trigger) {
      this.dispatchEvent(
        new CustomEvent("narrative-trigger", {
          detail: { action: choice.trigger },
        })
      );
    }

    this._advanceToNode(choice.next);
  }

  /**
   * Internal helper to handle transition logic.
   */
  _advanceToNode(nextId) {
    if (!nextId || nextId === "END") {
      this.endSequence();
      return;
    }

    const nextNode = this.currentSequence.nodes.find((n) => n.id === nextId);

    if (!nextNode) {
      console.error(
        `NarrativeManager: Node '${nextId}' not found in sequence.`
      );
      this.endSequence();
      return;
    }

    this.currentNode = nextNode;
    this._processNodeTriggers(this.currentNode);

    // If it's an ACTION node (invisible), execute trigger and auto-advance
    if (this.currentNode.type === "ACTION") {
      // Triggers are already processed above, just move to next
      // Use setTimeout to allow event loop to breathe if needed, or sync recursion
      this._advanceToNode(this.currentNode.next);
    } else {
      this.broadcastUpdate();
    }
  }

  _processNodeTriggers(node) {
    if (node && node.trigger) {
      console.log("NarrativeManager: Dispatching Trigger", node.trigger);
      this.dispatchEvent(
        new CustomEvent("narrative-trigger", {
          detail: { action: node.trigger },
        })
      );
    }
  }

  endSequence() {
    console.log("NarrativeManager: Sequence Ended");
    // Only end if we actually have a sequence active
    if (!this.currentSequence) {
      console.warn("NarrativeManager: endSequence called but no active sequence");
      return;
    }
    this.currentSequence = null;
    this.currentNode = null;
    this.dispatchEvent(new CustomEvent("narrative-end"));
  }

  /**
   * Sends the current node data to the UI (DialogueOverlay).
   */
  broadcastUpdate() {
    this.dispatchEvent(
      new CustomEvent("narrative-update", {
        detail: {
          node: this.currentNode,
          active: !!this.currentNode,
        },
      })
    );
  }
}

// Export singleton for global access
export const narrativeManager = new NarrativeManager();
