import streamDeck, { action, type DidReceiveSettingsEvent, type KeyDownEvent, type SendToPluginEvent, type WillAppearEvent, SingletonAction } from "@elgato/streamdeck";
import type { JsonValue } from "@elgato/utils";
import { FilyxClient, normalizeFilyxBaseUrl } from "../api/filyxClient";
import { FilyxAutomaticPairing } from "../api/automaticPairing";
import type { FilyxGlobalSettings, FilyxSettings } from "../types";

abstract class FilyxRemoteAction extends SingletonAction<FilyxSettings> {
  protected abstract actionId: string;
  protected abstract defaultTitle: string;

  private async resolvedSettings(settings: FilyxSettings): Promise<FilyxSettings> {
    const global = await streamDeck.settings.getGlobalSettings<FilyxGlobalSettings>();
    const automaticKey = await FilyxAutomaticPairing.apiKey();
    return {
      ...settings,
      apiUrl: normalizeFilyxBaseUrl(settings.apiUrl || global.apiUrl),
      apiKey: String(settings.apiKey || global.apiKey || automaticKey || ""),
    };
  }
  private async rememberConnection(settings: FilyxSettings): Promise<void> {
    const apiKey = String(settings.apiKey || "").trim();
    if (!apiKey.startsWith("fxsd_")) return;
    const global = await streamDeck.settings.getGlobalSettings<FilyxGlobalSettings>();
    await streamDeck.settings.setGlobalSettings<FilyxGlobalSettings>({
      ...global,
      apiUrl: normalizeFilyxBaseUrl(settings.apiUrl || global.apiUrl),
      apiKey,
    });
  }

  protected async applyButtonTitle(action: { setTitle(title: string): Promise<void> }, settings: FilyxSettings): Promise<void> {
    const title = String(settings.buttonTitle || "").trim();
    if (title) await action.setTitle(title);
  }
  override async onWillAppear(ev: WillAppearEvent<FilyxSettings>): Promise<void> {
    void FilyxAutomaticPairing.ensure();
    await this.rememberConnection(ev.payload.settings);
    await this.applyButtonTitle(ev.action, ev.payload.settings);
  }
  override async onDidReceiveSettings(ev: DidReceiveSettingsEvent<FilyxSettings>): Promise<void> {
    await this.rememberConnection(ev.payload.settings);
    await this.applyButtonTitle(ev.action, ev.payload.settings);
  }
  override async onSendToPlugin(ev: SendToPluginEvent<JsonValue, FilyxSettings>): Promise<void> {
    const payload = ev.payload as { type?: string; settings?: FilyxSettings };
    if (payload?.type !== "filyx.saveSettings") return;
    const settings = payload.settings || {};
    await ev.action.setSettings(settings);
    await this.rememberConnection(settings);
    await this.applyButtonTitle(ev.action, settings);
    await streamDeck.ui.sendToPropertyInspector({ type: "filyx.settings.saved" });
  }

  override async onKeyDown(ev: KeyDownEvent<FilyxSettings>): Promise<void> {
    // Feedback belongs to the physical key, not to the round-trip to OVH.
    // The command is still verified server-side; an eventual refusal replaces
    // the immediate checkmark with an alert a moment later.
    void ev.action.showOk();
    void this.dispatch(ev);
  }

  private async dispatch(ev: KeyDownEvent<FilyxSettings>): Promise<void> {
    try {
      const settings = await this.resolvedSettings(ev.payload.settings);
      // Keep the per-key chat preference with the action itself.  The server
      // can then apply the module's saved FilyX message, or a deliberate
      // per-key override, exactly like it does for chat commands.
      const serverSettings = {
        ...this.serverSettings(settings),
        announceInChat: settings.announceInChat !== false,
        chatMessage: String(settings.chatMessage || "").trim(),
      };
      const response = await new FilyxClient(settings).action(this.actionId, serverSettings);
      streamDeck.logger.info(`[FilyX] ${this.actionId}: ${response.message || "ok"}`);
    } catch (error) {
      streamDeck.logger.error(`[FilyX] ${this.actionId}: ${String(error)}`);
      await ev.action.showAlert();
    }
  }

  protected serverSettings(settings: FilyxSettings): Record<string, unknown> { return { channelId: settings.channelId }; }
}

@action({ UUID: "fr.filyx.streamdeck.alert.test" })
export class AlertTestAction extends FilyxRemoteAction { protected actionId = "filyx.alert.test"; protected defaultTitle = "ALERTE\nTEST"; protected override serverSettings(s: FilyxSettings) { return { channelId: s.channelId, alert_type: s.alertType || "follow" }; } }
@action({ UUID: "fr.filyx.streamdeck.soundrequest.playpause" })
export class SoundRequestPlayPauseAction extends FilyxRemoteAction { protected actionId = "filyx.soundrequest.playpause"; protected defaultTitle = "SR\nPLAY"; }
@action({ UUID: "fr.filyx.streamdeck.soundrequest.skip" })
export class SoundRequestSkipAction extends FilyxRemoteAction { protected actionId = "filyx.soundrequest.skip"; protected defaultTitle = "SR\nSUIVANT"; }
@action({ UUID: "fr.filyx.streamdeck.quiz.start" })
export class QuizStartAction extends FilyxRemoteAction { protected actionId = "filyx.quiz.start"; protected defaultTitle = "MINI\nJEU"; protected override serverSettings(s: FilyxSettings) { return { channelId: s.channelId, game: s.game || "quiz" }; } }
@action({ UUID: "fr.filyx.streamdeck.quiz.stop" })
export class QuizStopAction extends FilyxRemoteAction { protected actionId = "filyx.quiz.stop"; protected defaultTitle = "STOP\nJEU"; }
@action({ UUID: "fr.filyx.streamdeck.eventlog.marker" })
export class EventLogMarkerAction extends FilyxRemoteAction { protected actionId = "filyx.eventlog.marker"; protected defaultTitle = "MARQUEUR"; protected override serverSettings(s: FilyxSettings) { return { channelId: s.channelId, label: s.message || "Marqueur Stream Deck" }; } }
@action({ UUID: "fr.filyx.streamdeck.subathon.control" })
export class SubathonControlAction extends FilyxRemoteAction {
  protected actionId = "filyx.subathon.control";
  protected defaultTitle = "SUBATHON";

  override async onWillAppear(ev: WillAppearEvent<FilyxSettings>): Promise<void> {
    await super.onWillAppear(ev);
    // A newly dropped action has no stored inspector values yet.  Its visible
    // default is “Démarrer / reprendre”, so the server default must be the
    // same — never the former pause fallback.
    if (!String(ev.payload.settings.control || "").trim()) {
      await ev.action.setSettings({ ...ev.payload.settings, control: "start" });
    }
  }

  protected override serverSettings(s: FilyxSettings) {
    return { channelId: s.channelId, control: s.control || "start", seconds: Number(s.seconds || 0) };
  }
}
@action({ UUID: "fr.filyx.streamdeck.widget.toggle" })
export class WidgetToggleAction extends FilyxRemoteAction { protected actionId = "filyx.widget.toggle"; protected defaultTitle = "WIDGET"; protected override serverSettings(s: FilyxSettings) { return { channelId: s.channelId, widget: s.widget || "alerts", enabled: Boolean(s.enabled) }; } }
@action({ UUID: "fr.filyx.streamdeck.discord.announce" })
export class DiscordAnnounceAction extends FilyxRemoteAction { protected actionId = "filyx.discord.announce"; protected defaultTitle = "DISCORD"; protected override serverSettings(s: FilyxSettings) { return { channelId: s.channelId, title: s.title || "Annonce FilyX", message: s.message || "Le live continue sur FilyX." }; } }
@action({ UUID: "fr.filyx.streamdeck.multiaction" })
export class MultiAction extends FilyxRemoteAction {
  protected actionId = "filyx.multiaction"; protected defaultTitle = "MULTI\nACTION";
  protected override serverSettings(s: FilyxSettings) {
    let steps: unknown[] = [];
    try { steps = JSON.parse(String(s.multiSteps || "[]")); } catch (_) { throw new Error("Le JSON MultiAction est invalide."); }
    return { channelId: s.channelId, steps };
  }
}
