let OBSWebSocket = null;
let EventSubscription = {};
let obsWebSocketLoadError = null;
try {
  ({ OBSWebSocket, EventSubscription } = require("obs-websocket-js"));
} catch (error) {
  obsWebSocketLoadError = error;
}
const { publishWebEvent } = require("./web-events");
const { createLogger } = require("./logger");
const {
  getObsSettings,
  getOverlay,
  listEnabledObsSettings,
  onOverlayChanged,
  setActiveScene
} = require("./overlays");

const providers = new Map();
const browserBridgeConnectors = new Map();
const connectorLog = createLogger("core:obs-connectors", { category: "integration" });

const OBS_BROWSER_CONTROL_LEVELS = [
  "No OBS access",
  "Read OBS status",
  "Read scenes and transitions",
  "Basic OBS actions",
  "Advanced OBS control",
  "Full OBS control"
];

function cleanNames(values) {
  return Array.isArray(values)
    ? values.slice(0, 250).map((value) => {
      const candidate = value && typeof value === "object"
        ? (value.name || value.sceneName || value.transitionName || value.profileName || value.sceneCollectionName)
        : value;
      return String(candidate || "").trim().slice(0, 200);
    }).filter(Boolean)
    : [];
}

function registerOverlayConnectorProvider(provider) {
  if (!provider?.id || typeof provider.create !== "function") {
    throw new Error("Connector providers require an id and create(settings) function.");
  }
  providers.set(provider.id, provider);
  return () => providers.delete(provider.id);
}

function listOverlayConnectorProviders() {
  return Array.from(providers.values()).map(({ id, label, available = true, description }) => ({
    id,
    label,
    available,
    description
  }));
}

class LocalObsWebSocketConnector {
  constructor(settings) {
    if (!OBSWebSocket) {
      throw new Error("OBS WebSocket support is unavailable until Lumi dependencies are synchronized and the server is restarted.");
    }
    this.settings = settings;
    this.client = new OBSWebSocket();
    this.state = "disconnected";
    this.error = null;
    this.currentScene = null;
    this.scenes = [];
    this.transitions = [];
    this.currentTransition = null;
    this.canvasWidth = null;
    this.canvasHeight = null;
    this.profiles = [];
    this.currentProfile = null;
    this.sceneCollections = [];
    this.currentSceneCollection = null;
    this.outputs = {};
    this.sceneListeners = new Set();
    this.closedListeners = new Set();
    this.statusListeners = new Set();
    this.client.on("CurrentProgramSceneChanged", ({ sceneName }) => {
      this.currentScene = sceneName || null;
      this.sceneListeners.forEach((listener) => listener(this.currentScene));
    });
    this.client.on("ConnectionClosed", (error) => {
      this.state = "disconnected";
      this.error = error?.message || null;
      this.closedListeners.forEach((listener) => listener(error));
    });
    this.client.on("ConnectionError", (error) => {
      this.state = "error";
      this.error = error?.message || "OBS connection failed.";
    });
    const refreshStatus = () => this.refreshSetupState().then(() => {
      this.statusListeners.forEach((listener) => listener(this.status()));
    }).catch(() => {});
    this.client.on("CurrentProfileChanged", ({ profileName }) => {
      this.currentProfile = profileName || null;
      refreshStatus();
    });
    this.client.on("ProfileListChanged", refreshStatus);
    this.client.on("CurrentSceneCollectionChanged", ({ sceneCollectionName }) => {
      this.currentSceneCollection = sceneCollectionName || null;
      refreshStatus();
    });
    this.client.on("SceneCollectionListChanged", refreshStatus);
    this.client.on("CurrentSceneTransitionChanged", ({ transitionName }) => {
      this.currentTransition = transitionName || null;
      refreshStatus();
    });
    this.client.on("SceneTransitionListChanged", refreshStatus);
    for (const eventName of [
      "StreamStateChanged", "RecordStateChanged", "ReplayBufferStateChanged", "VirtualcamStateChanged"
    ]) this.client.on(eventName, refreshStatus);
  }

  async connect() {
    this.state = "connecting";
    this.error = null;
    try {
      await this.client.connect(this.settings.endpoint, this.settings.password || undefined, {
        eventSubscriptions: EventSubscription.Scenes | EventSubscription.Transitions | EventSubscription.Config | EventSubscription.Outputs
      });
      this.state = "connected";
      const current = await this.client.call("GetCurrentProgramScene");
      this.currentScene = current.currentProgramSceneName || null;
      await this.refreshSetupState();
      return this.status();
    } catch (error) {
      this.state = "error";
      this.error = error?.message || "OBS connection failed.";
      try { await this.client.disconnect(); } catch {}
      throw error;
    }
  }

  async disconnect() {
    try { await this.client.disconnect(); } catch {}
    this.state = "disconnected";
  }

  onSceneChanged(listener) {
    this.sceneListeners.add(listener);
    return () => this.sceneListeners.delete(listener);
  }

  onClosed(listener) {
    this.closedListeners.add(listener);
    return () => this.closedListeners.delete(listener);
  }

  onStatusChanged(listener) {
    this.statusListeners.add(listener);
    return () => this.statusListeners.delete(listener);
  }

  async listScenes() {
    const result = await this.client.call("GetSceneList");
    this.scenes = (result.scenes || []).map((scene) => scene.sceneName).filter(Boolean);
    return this.scenes;
  }

  async getActiveScene() {
    const result = await this.client.call("GetCurrentProgramScene");
    this.currentScene = result.currentProgramSceneName || null;
    return this.currentScene;
  }

  async setActiveScene(sceneName) {
    await this.client.call("SetCurrentProgramScene", { sceneName });
    this.currentScene = sceneName;
  }

  async saveReplay() {
    if (this.state !== "connected") throw new Error("OBS WebSocket is not connected.");
    if (!this.outputs.replay_buffer) throw new Error("The OBS replay buffer is not running.");
    await this.client.call("SaveReplayBuffer");
    return { queued: true };
  }

  async refreshSetupState() {
    const results = await Promise.allSettled([
      this.listScenes(),
      this.client.call("GetTransitionList"),
      this.client.call("GetVideoSettings"),
      this.client.call("GetProfileList"),
      this.client.call("GetSceneCollectionList"),
      this.client.call("GetStreamStatus"),
      this.client.call("GetRecordStatus"),
      this.client.call("GetReplayBufferStatus"),
      this.client.call("GetVirtualCamStatus")
    ]);
    if (results[1].status === "fulfilled") {
      this.transitions = (results[1].value.transitions || []).map((item) => item.transitionName).filter(Boolean);
      this.currentTransition = results[1].value.currentSceneTransitionName || null;
    }
    if (results[2].status === "fulfilled") {
      this.canvasWidth = Number(results[2].value.baseWidth) || null;
      this.canvasHeight = Number(results[2].value.baseHeight) || null;
    }
    if (results[3].status === "fulfilled") {
      this.profiles = cleanNames(results[3].value.profiles);
      this.currentProfile = results[3].value.currentProfileName || null;
    }
    if (results[4].status === "fulfilled") {
      this.sceneCollections = cleanNames(results[4].value.sceneCollections);
      this.currentSceneCollection = results[4].value.currentSceneCollectionName || null;
    }
    this.outputs = {
      streaming: results[5].status === "fulfilled" && Boolean(results[5].value.outputActive),
      recording: results[6].status === "fulfilled" && Boolean(results[6].value.outputActive),
      recording_paused: results[6].status === "fulfilled" && Boolean(results[6].value.outputPaused),
      replay_buffer: results[7].status === "fulfilled" && Boolean(results[7].value.outputActive),
      virtual_camera: results[8].status === "fulfilled" && Boolean(results[8].value.outputActive)
    };
  }

  status() {
    return {
      state: this.state,
      error: this.error,
      current_scene: this.currentScene,
      scenes: this.scenes,
      transitions: this.transitions,
      current_transition: this.currentTransition,
      canvas_width: this.canvasWidth,
      canvas_height: this.canvasHeight,
      profiles: this.profiles,
      current_profile: this.currentProfile,
      scene_collections: this.sceneCollections,
      current_scene_collection: this.currentSceneCollection,
      outputs: this.outputs,
      profiles_supported: true,
      scene_collections_supported: true
    };
  }
}

class ObsBrowserBridgeConnector {
  constructor(settings) {
    this.settings = settings;
    this.overlayId = settings.overlay_id;
    this.state = "waiting";
    this.error = "Waiting for this overlay to load inside OBS.";
    this.currentScene = null;
    this.scenes = [];
    this.transitions = [];
    this.currentTransition = null;
    this.outputs = {};
    this.controlLevel = 0;
    this.pluginVersion = null;
    this.canvasWidth = null;
    this.canvasHeight = null;
    this.sourceActive = null;
    this.sourceVisible = null;
    this.instanceId = null;
    this.lastReportAt = 0;
    this.sceneListeners = new Set();
    this.closedListeners = new Set();
    this.statusListeners = new Set();
    this.staleTimer = null;
  }

  async connect() {
    browserBridgeConnectors.set(this.overlayId, this);
    return this.status();
  }

  async disconnect() {
    if (browserBridgeConnectors.get(this.overlayId) === this) browserBridgeConnectors.delete(this.overlayId);
    clearTimeout(this.staleTimer);
    this.state = "disconnected";
  }

  onSceneChanged(listener) {
    this.sceneListeners.add(listener);
    return () => this.sceneListeners.delete(listener);
  }

  onClosed(listener) {
    this.closedListeners.add(listener);
    return () => this.closedListeners.delete(listener);
  }

  onStatusChanged(listener) {
    this.statusListeners.add(listener);
    return () => this.statusListeners.delete(listener);
  }

  notifyStatus() {
    this.statusListeners.forEach((listener) => listener(this.status()));
  }

  acceptReport(report = {}) {
    const now = Date.now();
    const instanceId = String(report.instance_id || "").slice(0, 100);
    if (!instanceId) throw new Error("Browser Bridge instance ID is missing.");
    const currentIsFresh = this.instanceId && now - this.lastReportAt < 15000;
    const shouldTakeOver = !currentIsFresh || this.instanceId === instanceId || (report.source_active === true && this.sourceActive !== true);
    if (!shouldTakeOver) return { accepted: false, primary_instance_id: this.instanceId, ...this.status() };

    const previousScene = this.currentScene;
    this.instanceId = instanceId;
    this.lastReportAt = now;
    this.controlLevel = Math.min(5, Math.max(0, Math.round(Number(report.control_level) || 0)));
    this.pluginVersion = String(report.plugin_version || "").slice(0, 80) || null;
    this.currentScene = String(report.current_scene || "").slice(0, 200) || null;
    this.scenes = cleanNames(report.scenes);
    this.transitions = cleanNames(report.transitions);
    this.currentTransition = String(report.current_transition || "").slice(0, 200) || null;
    this.canvasWidth = Math.min(7680, Math.max(0, Math.round(Number(report.canvas_width) || 0))) || null;
    this.canvasHeight = Math.min(7680, Math.max(0, Math.round(Number(report.canvas_height) || 0))) || null;
    this.sourceActive = typeof report.source_active === "boolean" ? report.source_active : null;
    this.sourceVisible = typeof report.source_visible === "boolean" ? report.source_visible : null;
    this.outputs = report.outputs && typeof report.outputs === "object" ? {
      streaming: Boolean(report.outputs.streaming),
      recording: Boolean(report.outputs.recording),
      recording_paused: Boolean(report.outputs.recordingPaused || report.outputs.recording_paused),
      replay_buffer: Boolean(report.outputs.replaybuffer || report.outputs.replay_buffer),
      virtual_camera: Boolean(report.outputs.virtualcam || report.outputs.virtual_camera)
    } : {};
    this.state = "connected";
    this.error = this.controlLevel < 2
      ? "Choose at least “Read access to user information” in the Browser Source page permissions."
      : (["overlay_to_obs", "bidirectional"].includes(this.settings.sync_direction) && this.controlLevel < 4
        ? "Advanced OBS access is required when Lumi should change OBS scenes."
        : null);

    clearTimeout(this.staleTimer);
    this.staleTimer = setTimeout(() => {
      this.state = "waiting";
      this.error = "The OBS Browser Source stopped reporting. Keep it loaded and disable shutdown when hidden for persistent control.";
      this.notifyStatus();
    }, 20000);
    if (this.currentScene && this.currentScene !== previousScene) {
      this.sceneListeners.forEach((listener) => listener(this.currentScene));
    }
    this.notifyStatus();
    return { accepted: true, primary_instance_id: this.instanceId, ...this.status() };
  }

  async listScenes() {
    return this.scenes;
  }

  async getActiveScene() {
    return this.currentScene;
  }

  async setActiveScene(sceneName) {
    if (this.state !== "connected") throw new Error("The OBS Browser Bridge is not connected.");
    if (this.controlLevel < 4) throw new Error("Advanced OBS page permission is required to change scenes.");
    const commandId = `${Date.now()}:${Math.random().toString(16).slice(2)}`;
    const delivered = publishWebEvent("overlay:obs-browser-command", {
      command_id: commandId,
      target_instance_id: this.instanceId,
      action: "set_current_scene",
      scene_name: String(sceneName || "").slice(0, 200)
    }, { scope: `overlay:${this.overlayId}` });
    if (!delivered) throw new Error("The OBS Browser Bridge is not listening for commands.");
  }

  async saveReplay() {
    if (this.state !== "connected") throw new Error("The OBS Browser Bridge is not connected.");
    if (this.controlLevel < 3) throw new Error("Basic OBS page permission is required to save the replay buffer.");
    if (!this.outputs.replay_buffer) throw new Error("The OBS replay buffer is not running.");
    const commandId = `${Date.now()}:${Math.random().toString(16).slice(2)}`;
    const delivered = publishWebEvent("overlay:obs-browser-command", {
      command_id: commandId,
      target_instance_id: this.instanceId,
      action: "save_replay_buffer"
    }, { scope: `overlay:${this.overlayId}` });
    if (!delivered) throw new Error("The OBS Browser Bridge is not listening for commands.");
    return { queued: true };
  }

  status() {
    return {
      state: this.state,
      error: this.error,
      current_scene: this.currentScene,
      scenes: this.scenes,
      transitions: this.transitions,
      current_transition: this.currentTransition,
      outputs: this.outputs,
      canvas_width: this.canvasWidth,
      canvas_height: this.canvasHeight,
      control_level: this.controlLevel,
      control_level_label: OBS_BROWSER_CONTROL_LEVELS[this.controlLevel],
      plugin_version: this.pluginVersion,
      source_active: this.sourceActive,
      source_visible: this.sourceVisible,
      bridge_instance_id: this.instanceId,
      last_report_at: this.lastReportAt || null,
      profiles: [],
      current_profile: null,
      scene_collections: [],
      current_scene_collection: null,
      profiles_supported: false,
      scene_collections_supported: false
    };
  }
}

registerOverlayConnectorProvider({
  id: "local_obs_websocket",
  label: "OBS WebSocket",
  description: OBSWebSocket
    ? "Connect from Lumi to OBS on the streaming computer or private local network."
    : `Unavailable because obs-websocket-js could not load${obsWebSocketLoadError?.code === "MODULE_NOT_FOUND" ? "; restart Lumi through run.js to synchronize dependencies" : ""}.`,
  available: Boolean(OBSWebSocket),
  create: (settings) => new LocalObsWebSocketConnector(settings)
});

registerOverlayConnectorProvider({
  id: "obs_browser_bridge",
  label: "OBS Browser Bridge",
  description: "Use the Lumi Browser Source itself to read and synchronize OBS without a WebSocket password.",
  create: (settings) => new ObsBrowserBridgeConnector(settings)
});

registerOverlayConnectorProvider({
  id: "trusted_lumi_client",
  label: "Trusted Lumi client (future)",
  description: "Reserved provider interface for a future trusted client running beside OBS.",
  available: false,
  create() {
    throw new Error("The trusted Lumi client provider is an extension point and is not implemented yet.");
  }
});

class OverlayConnectorManager {
  constructor() {
    this.connections = new Map();
    this.syncSuppressed = new Set();
    this.stopped = false;
    this.removeOverlayListener = null;
  }

  async start() {
    this.stopped = false;
    if (!this.removeOverlayListener) {
      this.removeOverlayListener = onOverlayChanged((change) => this.handleOverlayChange(change));
    }
    for (const settings of listEnabledObsSettings()) {
      await this.restart(settings.overlay_id).catch(() => {});
    }
  }

  async stop() {
    this.stopped = true;
    this.removeOverlayListener?.();
    this.removeOverlayListener = null;
    const entries = Array.from(this.connections.values());
    this.connections.clear();
    await Promise.all(entries.map(async (entry) => {
      clearTimeout(entry.reconnectTimer);
      await entry.connector.disconnect().catch(() => {});
    }));
  }

  async restart(overlayId) {
    await this.disconnect(overlayId);
    const settings = getObsSettings(overlayId, { includeSecret: true });
    if (!settings.enabled) return this.status(overlayId);
    const provider = providers.get(settings.provider);
    if (!provider || provider.available === false) {
      const error = provider ? "Connector provider is not implemented." : "Unknown connector provider.";
      this.connections.set(overlayId, { settings, connector: null, error, state: "unavailable" });
      this.publishStatus(overlayId);
      return this.status(overlayId);
    }
    const entry = {
      settings,
      connector: provider.create(settings),
      state: "connecting",
      error: null,
      reconnectAttempts: 0,
      reconnectTimer: null,
      lastOutboundScene: null,
      lastOutboundAt: 0
    };
    this.connections.set(overlayId, entry);
    entry.connector.onSceneChanged((sceneName) => this.handleObsScene(overlayId, sceneName));
    entry.connector.onClosed(() => this.scheduleReconnect(overlayId));
    entry.connector.onStatusChanged?.((status) => {
      entry.state = status.state || entry.state;
      entry.error = status.error || null;
      this.publishStatus(overlayId);
    });
    this.publishStatus(overlayId);
    try {
      const connectedStatus = await entry.connector.connect();
      entry.state = connectedStatus?.state || "connected";
      entry.error = connectedStatus?.error || null;
      entry.reconnectAttempts = 0;
      this.publishStatus(overlayId);
      if (entry.state === "connected") {
        await this.handleObsScene(overlayId, await entry.connector.getActiveScene());
      }
      return this.status(overlayId, { includeScenes: true });
    } catch (error) {
      entry.state = "error";
      entry.error = error?.message || "OBS connection failed.";
      this.publishStatus(overlayId);
      this.scheduleReconnect(overlayId);
      return this.status(overlayId);
    }
  }

  async disconnect(overlayId) {
    const entry = this.connections.get(overlayId);
    if (!entry) return;
    this.connections.delete(overlayId);
    clearTimeout(entry.reconnectTimer);
    if (entry.connector) await entry.connector.disconnect().catch(() => {});
  }

  scheduleReconnect(overlayId) {
    const entry = this.connections.get(overlayId);
    if (!entry || this.stopped || !entry.settings.reconnect || entry.reconnectTimer) return;
    entry.state = "disconnected";
    entry.reconnectAttempts += 1;
    const delay = Math.min(30000, 1000 * (2 ** Math.min(entry.reconnectAttempts, 5)));
    entry.reconnectTimer = setTimeout(() => {
      entry.reconnectTimer = null;
      this.restart(overlayId).catch(() => {});
    }, delay);
    this.publishStatus(overlayId);
  }

  async handleObsScene(overlayId, obsSceneName) {
    const entry = this.connections.get(overlayId);
    if (!entry || !obsSceneName) return;
    if (entry.lastOutboundScene === obsSceneName && Date.now() - entry.lastOutboundAt < 2500) return;
    entry.lastOutboundScene = null;
    if (!["obs_to_overlay", "bidirectional"].includes(entry.settings.sync_direction)) return;
    const overlay = getOverlay(overlayId);
    const match = overlay?.scenes.find((scene) => scene.enabled && scene.obs_scene_name === obsSceneName);
    if (match && overlay.active_scene_id !== match.id) {
      setActiveScene(overlayId, match.id, { source: "obs" });
    }
    this.publishStatus(overlayId);
  }

  async handleOverlayChange({ overlayId, source, deleted }) {
    if (deleted) return this.disconnect(overlayId);
    if (this.syncSuppressed.has(overlayId)) return;
    const entry = this.connections.get(overlayId);
    if (!entry?.connector || entry.connector.status?.().state !== "connected" || source === "obs") return;
    if (!["overlay_to_obs", "bidirectional"].includes(entry.settings.sync_direction)) return;
    const overlay = getOverlay(overlayId);
    const scene = overlay?.scenes.find((item) => item.id === overlay.active_scene_id);
    const target = scene?.obs_scene_name;
    if (!target || entry.lastOutboundScene === target) return;
    entry.lastOutboundScene = target;
    entry.lastOutboundAt = Date.now();
    try {
      await entry.connector.setActiveScene(target);
      entry.error = null;
    } catch (error) {
      entry.error = error?.message || "Could not change the OBS scene.";
    }
    this.publishStatus(overlayId);
  }

  async setObsScene(overlayId, sceneName) {
    const entry = this.connections.get(overlayId);
    if (!entry?.connector || entry.connector.status?.().state !== "connected") throw new Error("OBS is not connected.");
    entry.lastOutboundScene = String(sceneName || "");
    entry.lastOutboundAt = Date.now();
    await entry.connector.setActiveScene(entry.lastOutboundScene);
    this.publishStatus(overlayId);
    return this.status(overlayId, { includeScenes: true });
  }

  async saveReplay({ name = "" } = {}) {
    const candidates = Array.from(this.connections.entries())
      .filter(([, entry]) => entry?.connector?.status?.().state === "connected" && typeof entry.connector.saveReplay === "function")
      .sort((left, right) => Number(Boolean(right[1].connector.status().outputs?.replay_buffer)) - Number(Boolean(left[1].connector.status().outputs?.replay_buffer)));
    if (!candidates.length) throw new Error("No operational OBS connector is available.");
    const failures = [];
    for (const [overlayId, entry] of candidates) {
      try {
        await entry.connector.saveReplay({ name });
        connectorLog.info("OBS replay save requested", {
          overlay_id: overlayId,
          provider: entry.settings.provider,
          label: String(name || "").trim().slice(0, 120) || null
        }, { event: "obs_replay_saved" });
        return { ok: true, overlay_id: overlayId, provider: entry.settings.provider, name: String(name || "").trim() || null };
      } catch (error) {
        failures.push(error?.message || "OBS replay save failed.");
      }
    }
    throw new Error(failures[0] || "OBS replay save failed.");
  }

  async status(overlayId, { includeScenes = false } = {}) {
    const entry = this.connections.get(overlayId);
    if (!entry) {
      const settings = getObsSettings(overlayId);
      return { state: settings.enabled ? "disconnected" : "disabled", error: null, current_scene: null, scenes: [] };
    }
    const connectorStatus = entry.connector?.status?.() || {};
    const result = {
      ...connectorStatus,
      state: connectorStatus.state || entry.state || "disconnected",
      error: connectorStatus.error || entry.error || null,
      current_scene: connectorStatus.current_scene || null,
      scenes: Array.isArray(connectorStatus.scenes) ? connectorStatus.scenes : []
    };
    if (includeScenes && entry.connector && result.state === "connected") {
      try { result.scenes = await entry.connector.listScenes(); }
      catch (error) { result.error = error?.message || "Could not read OBS scenes."; }
    }
    return result;
  }

  publishStatus(overlayId) {
    Promise.resolve(this.status(overlayId)).then((status) => {
      publishWebEvent("overlay:obs-status", { overlay_id: overlayId, ...status }, { role: "admin" });
    });
  }

  async test(settings) {
    if (settings.provider === "obs_browser_bridge") {
      if (!settings.overlayId) throw new Error("Overlay ID is required for Browser Bridge testing.");
      const entry = this.connections.get(settings.overlayId);
      if (entry?.settings?.provider !== "obs_browser_bridge") {
        throw new Error("Save the Browser Bridge connection before testing it.");
      }
      const status = await this.status(settings.overlayId, { includeScenes: true });
      if (status.state !== "connected") throw new Error(status.error || "The Browser Bridge is waiting for OBS.");
      return { ok: true, ...status };
    }
    const provider = providers.get(settings.provider);
    if (!provider || provider.available === false) throw new Error("Connector provider is not available.");
    const connector = provider.create(settings);
    try {
      await connector.connect();
      return {
        ok: true,
        current_scene: await connector.getActiveScene(),
        scenes: await connector.listScenes()
      };
    } finally {
      await connector.disconnect().catch(() => {});
    }
  }

  reportBrowserBridge(overlayId, report) {
    const entry = this.connections.get(overlayId);
    if (!entry?.connector || entry.settings.provider !== "obs_browser_bridge" || typeof entry.connector.acceptReport !== "function") {
      return { accepted: false, enabled: false, state: "disabled" };
    }
    const result = entry.connector.acceptReport(report);
    entry.state = result.state || entry.state;
    entry.error = result.error || null;
    this.publishStatus(overlayId);
    return { enabled: true, ...result };
  }

  suppressSync(overlayId, suppressed = true) {
    if (suppressed) this.syncSuppressed.add(overlayId);
    else this.syncSuppressed.delete(overlayId);
  }
}

const overlayConnectorManager = new OverlayConnectorManager();

module.exports = {
  ObsBrowserBridgeConnector,
  listOverlayConnectorProviders,
  overlayConnectorManager,
  registerOverlayConnectorProvider
};
