import streamDeck, { action, type DidReceiveSettingsEvent, type KeyDownEvent, type SendToPluginEvent, type WillAppearEvent, SingletonAction } from "@elgato/streamdeck";
import type { JsonValue } from "@elgato/utils";
import { ObsAutoConnector } from "../obs/obsAutoConnector";
import { getSavedObsConnection, resolveObsConnection, saveObsConnection } from "../obs/obsConnectionSettings";
import type { FilyxSettings } from "../types";

abstract class ObsLocalAction extends SingletonAction<FilyxSettings> {
  protected abstract defaultTitle: string;
  protected readonly obs = new ObsAutoConnector();
  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> {
    await this.applyButtonTitle(ev.action, ev.payload.settings);
  }
  override async onDidReceiveSettings(ev: DidReceiveSettingsEvent<FilyxSettings>): Promise<void> {
    await this.applyButtonTitle(ev.action, ev.payload.settings);
  }
  override async onPropertyInspectorDidAppear(): Promise<void> {
    const saved = await getSavedObsConnection();
    await streamDeck.ui.sendToPropertyInspector({ type: "obs.connection.status", saved: saved ? { host: saved.obsHost, port: saved.obsPort, savedAt: saved.savedAt } : null });
    // A healthy FilyX Companion is local and requires no credential.  Detect
    // it as soon as the inspector opens so a user never has to repeat the
    // installation step or press a separate connection button.
    try {
      const discovery = await this.obs.discover({});
      await streamDeck.ui.sendToPropertyInspector({ type: "obs.detect.result", ok: true, discovery, companion: discovery.transport === "filyx-companion" });
    } catch (_) {
      // Keep the manual recovery control available when OBS is genuinely not
      // running; the inspector's existing helper message handles that case.
    }
  }
  override async onSendToPlugin(ev: SendToPluginEvent<JsonValue, FilyxSettings>): Promise<void> {
    const payload = ev.payload as { type?: string; settings?: FilyxSettings };
    if (payload?.type === "filyx.saveSettings") {
      const settings = payload.settings || {};
      await ev.action.setSettings(settings);
      await this.applyButtonTitle(ev.action, settings);
      await streamDeck.ui.sendToPropertyInspector({ type: "filyx.settings.saved" });
      return;
    }
    if (payload?.type !== "obs.detect") return;
    try {
      const requested = payload.settings || {};
      const shared = String(requested.obsConnectionMode || "shared") === "shared";
      // The normal button reuses the already detected local connection. The
      // manual fields are only a recovery option for a non-standard OBS setup.
      const connection = shared ? await resolveObsConnection(requested) : requested;
      const discovery = await this.obs.discover(connection);
      const saved = shared && discovery.transport === "obs-websocket" ? await saveObsConnection({ ...connection, obsHost: discovery.host }) : null;
      await streamDeck.ui.sendToPropertyInspector({ type: "obs.detect.result", ok: true, discovery, companion: discovery.transport === "filyx-companion", saved: saved ? { host: saved.obsHost, port: saved.obsPort } : null });
    } catch (error) {
      const message = String(error instanceof Error ? error.message : error);
      await streamDeck.ui.sendToPropertyInspector({ type: "obs.detect.result", ok: false, error: message });
    }
  }
  protected async run(ev: KeyDownEvent<FilyxSettings>, task: (settings: FilyxSettings) => Promise<void>): Promise<void> {
    try { await task(await resolveObsConnection(ev.payload.settings)); await ev.action.showOk(); }
    catch (error) { streamDeck.logger.error(`[FilyX OBS] ${String(error)}`); await ev.action.showAlert(); }
  }
}

@action({ UUID: "fr.filyx.streamdeck.obs.scene.auto" })
export class ObsSceneAction extends ObsLocalAction { protected defaultTitle = "OBS\nSCÈNE"; override onKeyDown(ev: KeyDownEvent<FilyxSettings>) { return this.run(ev, (settings) => this.obs.setScene(settings)); } }
@action({ UUID: "fr.filyx.streamdeck.obs.source.auto" })
export class ObsSourceAction extends ObsLocalAction { protected defaultTitle = "OBS\nSOURCE"; override onKeyDown(ev: KeyDownEvent<FilyxSettings>) { return this.run(ev, (settings) => this.obs.setSource(settings)); } }
@action({ UUID: "fr.filyx.streamdeck.obs.browser.refresh" })
export class ObsBrowserRefreshAction extends ObsLocalAction { protected defaultTitle = "RAFRAÎCHIR"; override onKeyDown(ev: KeyDownEvent<FilyxSettings>) { return this.run(ev, (settings) => this.obs.refreshBrowser(settings)); } }

abstract class DoublePressObsAction extends ObsLocalAction {
  protected abstract operation(settings: FilyxSettings): Promise<void>;
  protected abstract armedTitle: string;
  override async onKeyDown(ev: KeyDownEvent<FilyxSettings>): Promise<void> {
    const now = Date.now();
    const settings = ev.payload.settings;
    if (Number(settings.confirmUntil || 0) < now) {
      await ev.action.setSettings({ ...settings, confirmUntil: now + 5000 });
      await ev.action.setTitle(this.armedTitle);
      return;
    }
    await ev.action.setSettings({ ...settings, confirmUntil: 0 });
    await ev.action.setTitle(String(settings.buttonTitle || this.defaultTitle));
    await this.run(ev, (resolved) => this.operation(resolved));
  }
}

@action({ UUID: "fr.filyx.streamdeck.obs.record.toggle" })
export class ObsRecordAction extends DoublePressObsAction { protected defaultTitle = "REC\nOBS"; protected armedTitle = "CONFIRMER\nREC"; protected operation(settings: FilyxSettings) { return this.obs.toggleRecord(settings); } }
@action({ UUID: "fr.filyx.streamdeck.obs.stream.toggle" })
export class ObsStreamAction extends DoublePressObsAction { protected defaultTitle = "LIVE\nOBS"; protected armedTitle = "CONFIRMER\nLIVE"; protected operation(settings: FilyxSettings) { return this.obs.toggleStream(settings); } }
