import streamDeck, { SingletonAction } from "@elgato/streamdeck";
import { wsManager } from "../websocket-manager.js";
import { generateButtonImage, generateDeckButton, generateCancelButton } from "../image-generator.js";

/**
 * Toggle Deck Action
 *
 * States:
 * - IDLE: Shows deck name, tap to enter selection mode (if no track playing)
 * - PLAYING: Shows current track, tap to stop
 * - SELECTING: Shows track options (last played, next unplayed)
 */
export class ToggleDeckAction extends SingletonAction {
  manifestId = "com.djliveplaylist.toggle-deck";

  // Store state per action instance (keyed by ev.action.id)
  actionStates = new Map();

  // Track which buttons are in selection mode
  selectionMode = new Map();

  // Pending candidates for selection
  pendingCandidates = new Map();

  constructor() {
    super();
    this.setupWebSocket();
  }

  setupWebSocket() {
    wsManager.connect();

    // Listen for playing updates
    wsManager.on("PLAYING_UPDATE", (payload) => {
      this.handlePlayingUpdate(payload.tracks);
    });

    // Listen for deck candidates (response to selection request)
    wsManager.on("DECK_CANDIDATES", (payload) => {
      this.handleDeckCandidates(payload);
    });

    // Listen for decks configuration update
    wsManager.on("DECKS_CONFIG", (payload) => {
      this.handleDecksConfig(payload.decks);
    });

    // Handle connection state
    wsManager.on("connected", () => {
      this.updateAllButtons();
    });

    wsManager.on("disconnected", () => {
      this.updateAllButtons();
    });
  }

  /**
   * Handle decks configuration received from the app
   */
  handleDecksConfig(decks) {
    // Update state for each action with matching deck info
    for (const [actionId, state] of this.actionStates.entries()) {
      const deckInfo = decks.find((d) => d.id === state.deckId);
      if (deckInfo) {
        // Update deck name and image from app config
        state.deckName = deckInfo.name;
        state.deckImage = deckInfo.image;
        this.updateButton(actionId);
      }
    }
  }

  /**
   * Called when the action appears on the Stream Deck
   */
  onWillAppear(ev) {
    const actionId = ev.action.id;
    const settings = ev.payload.settings || {};
    const deckId = settings.deckId || "deck-1";

    // Try to get deck info from cached configuration
    const deckInfo = wsManager.getDeckById(deckId);

    // Initialize state, store the action reference for later setImage calls
    this.actionStates.set(actionId, {
      action: ev.action,
      deckId,
      deckName: deckInfo?.name || settings.deckName || "Deck 1",
      deckImage: deckInfo?.image || null,
      currentTrack: null,
      mode: "IDLE", // IDLE, PLAYING, SELECTING
    });

    streamDeck.logger.info(`Action appeared: ${actionId}, deck=${deckId}`);
    this.updateButton(actionId);
  }

  /**
   * Called when settings are updated via the Property Inspector
   */
  onDidReceiveSettings(ev) {
    const actionId = ev.action.id;
    const settings = ev.payload.settings || {};
    const state = this.actionStates.get(actionId);

    if (state) {
      const deckId = settings.deckId || "deck-1";
      const deckInfo = wsManager.getDeckById(deckId);

      state.deckId = deckId;
      state.deckName = deckInfo?.name || settings.deckName || "Deck 1";
      state.deckImage = deckInfo?.image || null;
      state.currentTrack = null;
      state.mode = "IDLE";

      streamDeck.logger.info(`Settings updated for ${actionId}: deck=${deckId}`);
      this.updateButton(actionId);
    }
  }

  /**
   * Called when the action disappears from the Stream Deck
   */
  onWillDisappear(ev) {
    const actionId = ev.action.id;
    this.actionStates.delete(actionId);
    this.selectionMode.delete(actionId);
    this.pendingCandidates.delete(actionId);
  }

  /**
   * Called when the key is pressed
   */
  async onKeyDown(ev) {
    const actionId = ev.action.id;
    const state = this.actionStates.get(actionId);

    if (!state) return;

    // Check if we're in selection mode
    if (this.selectionMode.has(actionId)) {
      const selectionInfo = this.selectionMode.get(actionId);
      await this.handleSelectionPress(actionId, selectionInfo);
      return;
    }

    // Normal mode
    if (state.currentTrack) {
      // Track is playing - stop it
      wsManager.send("STOP_DECK", { deck_id: state.deckId });
    } else {
      // No track playing - request candidates and enter selection mode
      wsManager.send("GET_DECK_CANDIDATES", { deck_id: state.deckId });

      // Store that we're waiting for candidates
      this.pendingCandidates.set(actionId, {
        deckId: state.deckId,
        timestamp: Date.now(),
      });
    }
  }

  /**
   * Handle a press during selection mode
   */
  async handleSelectionPress(actionId, selectionInfo) {
    const { type, trackId } = selectionInfo;

    if (type === "cancel") {
      // Cancel selection mode
      this.exitSelectionMode(actionId);
    } else if (type === "track" && trackId) {
      // Play the selected track
      wsManager.send("PLAY_TRACK", { track_id: trackId });
      this.exitSelectionMode(actionId);
    }
  }

  /**
   * Handle deck candidates response
   */
  handleDeckCandidates(payload) {
    const { deckId, lastPlayed, nextUnplayed, currentlyPlaying } = payload;

    // Find the action waiting for this deck's candidates
    for (const [actionId, pending] of this.pendingCandidates.entries()) {
      if (pending.deckId === deckId) {
        this.pendingCandidates.delete(actionId);

        // If there's a currently playing track, just update the button
        if (currentlyPlaying) {
          const state = this.actionStates.get(actionId);
          if (state) {
            state.currentTrack = currentlyPlaying;
            state.mode = "PLAYING";
            this.updateButton(actionId);
          }
          return;
        }

        // Enter selection mode
        this.enterSelectionMode(actionId, { lastPlayed, nextUnplayed });
        break;
      }
    }
  }

  /**
   * Enter selection mode - show track options
   */
  async enterSelectionMode(actionId, candidates) {
    const state = this.actionStates.get(actionId);
    if (!state) return;

    state.mode = "SELECTING";

    const { lastPlayed, nextUnplayed } = candidates;

    // If only one candidate, play it directly
    if (lastPlayed && !nextUnplayed) {
      wsManager.send("PLAY_TRACK", { track_id: lastPlayed.id });
      return;
    }
    if (nextUnplayed && !lastPlayed) {
      wsManager.send("PLAY_TRACK", { track_id: nextUnplayed.id });
      return;
    }
    if (!lastPlayed && !nextUnplayed) {
      // No candidates available
      streamDeck.logger.info("No track candidates available for deck");
      return;
    }

    // We have both options - need to show selection
    this.selectionMode.set(actionId, {
      type: "track",
      trackId: nextUnplayed.id,
      options: { lastPlayed, nextUnplayed },
      currentOption: "next", // Start with next
    });

    // Show the next track option first (more common choice)
    const image = await generateButtonImage({
      title: nextUnplayed.title,
      artist: nextUnplayed.artist,
      image: nextUnplayed.image,
      label: "NEXT",
    });

    state.action.setImage(image);

    // Set a timeout to auto-cancel if no selection
    setTimeout(() => {
      if (this.selectionMode.has(actionId)) {
        // Toggle to the other option
        this.toggleSelectionOption(actionId);
      }
    }, 3000);
  }

  /**
   * Toggle between selection options
   */
  async toggleSelectionOption(actionId) {
    const selection = this.selectionMode.get(actionId);
    if (!selection || !selection.options) return;

    const state = this.actionStates.get(actionId);
    if (!state) return;

    const { lastPlayed, nextUnplayed } = selection.options;

    if (selection.currentOption === "next" && lastPlayed) {
      // Switch to prev
      selection.currentOption = "prev";
      selection.trackId = lastPlayed.id;

      const image = await generateButtonImage({
        title: lastPlayed.title,
        artist: lastPlayed.artist,
        image: lastPlayed.image,
        label: "PREV",
      });

      state.action.setImage(image);
    } else if (selection.currentOption === "prev" && nextUnplayed) {
      // Switch back to next
      selection.currentOption = "next";
      selection.trackId = nextUnplayed.id;

      const image = await generateButtonImage({
        title: nextUnplayed.title,
        artist: nextUnplayed.artist,
        image: nextUnplayed.image,
        label: "NEXT",
      });

      state.action.setImage(image);
    }

    // Set another toggle timeout
    setTimeout(() => {
      if (this.selectionMode.has(actionId)) {
        this.toggleSelectionOption(actionId);
      }
    }, 2000);
  }

  /**
   * Exit selection mode
   */
  exitSelectionMode(actionId) {
    this.selectionMode.delete(actionId);
    const state = this.actionStates.get(actionId);
    if (state) {
      state.mode = "IDLE";
    }
    this.updateButton(actionId);
  }

  /**
   * Handle playing tracks update
   */
  handlePlayingUpdate(tracks) {
    // Update each action's state based on playing tracks
    for (const [actionId, state] of this.actionStates.entries()) {
      // Find track for this deck by matching deck name
      const deckTrack = tracks.find((t) => t.deckName.toLowerCase().includes(state.deckName.toLowerCase()));

      if (deckTrack) {
        state.currentTrack = deckTrack;
        state.mode = "PLAYING";
        this.exitSelectionMode(actionId);
      } else {
        state.currentTrack = null;
        state.mode = "IDLE";
      }

      this.updateButton(actionId);
    }
  }

  /**
   * Update a single button's image
   */
  async updateButton(actionId) {
    const state = this.actionStates.get(actionId);
    if (!state) return;

    // Don't update if in selection mode (handled separately)
    if (this.selectionMode.has(actionId)) return;

    const image = await generateDeckButton({
      deckName: state.deckName,
      isConnected: wsManager.isConnected,
      currentTrack: state.currentTrack,
      deckImage: state.deckImage,
    });

    state.action.setImage(image);
  }

  /**
   * Update all buttons
   */
  updateAllButtons() {
    for (const actionId of this.actionStates.keys()) {
      this.updateButton(actionId);
    }
  }
}
