/**
 * GemShell API - TypeScript Definitions
 *
 * Native desktop features for HTML5 games packaged with GemShell.
 * Available on `window.gemshell` at runtime when running in a packaged build.
 *
 * Docs:    https://gemshell.dev/api/overview
 * Repo:    https://github.com/eddime/gemshell
 *
 * Usage (no bundler):
 *   /// <reference path="./gemshell.d.ts" />
 *
 * Usage (tsconfig.json):
 *   { "compilerOptions": { "types": ["./gemshell.d.ts"] } }
 */

declare global {
  interface Window {
    /** GemShell native API. Only present in packaged builds. */
    gemshell?: GemShellAPI;
  }

  /** GemShell native API. Only present in packaged builds. */
  const gemshell: GemShellAPI | undefined;
}

// =============================================================================
// Root API
// =============================================================================

export interface GemShellAPI {
  /**
   * Your game's version (from `app.version` in `gemshell.config.json`).
   * Synchronous shortcut for `await gemshell.app.getVersion()`.
   */
  readonly version: string;

  /** Operating-system info, locale, opening external URLs/folders. */
  os: OsAPI;
  /** Application controls: name, version, paths, quit, relaunch. */
  app: AppAPI;
  /** Window controls: size, position, fullscreen, frameless, opacity, etc. */
  window: WindowAPI;
  /** Display info and cursor position. */
  screen: ScreenAPI;
  /** Per-app key-value file storage in the user's data directory. */
  file: FileAPI;
  /** Read/write the system clipboard (text). */
  clipboard: ClipboardAPI;
  /** Native dialogs (open/save file, message, confirm). */
  dialog: DialogAPI;
  /** System notifications. */
  notification: NotificationAPI;
  /** Pointer lock (FPS-style mouse capture). */
  input: InputAPI;
  /**
   * External asset overrides. Loose files in `<exe-dir>/mods/` and subscribed
   * Steam Workshop items are loaded transparently before the encrypted bundle.
   */
  assets: AssetsAPI;

  /** Steamworks API. Only present when `steamworks.enabled` is true in config. */
  steam?: SteamAPI;
  /** Steam in-game overlay info (ImGui-based). Only present with steamworks. */
  overlay?: OverlayAPI;
}

// =============================================================================
// OS
// =============================================================================

export type OsName = 'macOS' | 'Windows' | 'Linux' | string;

export interface OsAPI {
  /** Returns 'macOS', 'Windows' or 'Linux'. */
  getName(): Promise<OsName>;
  /** OS release/version string (e.g. '23.4.0'). */
  getVersion(): Promise<string>;
  /** Locale with underscore separator (e.g. 'en_US'). */
  getLocale(): Promise<string>;
  /** Just the language part of the locale (e.g. 'en'). */
  getLocaleLanguage(): Promise<string>;
  /** Open a URL in the user's default browser. */
  openURL(url: string): Promise<boolean>;
  /** Reveal a file or folder in the OS file manager. */
  showInFolder(path: string): Promise<boolean>;
}

// =============================================================================
// App
// =============================================================================

export type AppPathName =
  | 'home'
  | 'appData'
  | 'userData'
  | 'temp'
  | 'desktop'
  | 'documents'
  | 'downloads'
  | 'music'
  | 'pictures'
  | 'videos'
  | 'logs';

export interface AppAPI {
  /** Configured application name (from `build.app_name`). */
  getName(): Promise<string>;
  /** Application version (from `app.version`). */
  getVersion(): Promise<string>;
  /** True if running as a packaged build (production). */
  isPackaged(): Promise<boolean>;
  /**
   * Look up a system path. Defaults to `'userData'`.
   * Returns an empty string if the name is invalid.
   */
  getPath(name?: AppPathName | string): Promise<string>;
  /** Quit the application gracefully. */
  quit(): Promise<boolean>;
  /** Restart the application (current instance exits, new one starts). */
  relaunch(): Promise<boolean>;
}

// =============================================================================
// Window
// =============================================================================

export interface Size {
  width: number;
  height: number;
}

export interface Point {
  x: number;
  y: number;
}

export interface Rect extends Point, Size {}

export interface WindowAPI {
  // ---------- Title ----------
  setTitle(title: string): Promise<boolean>;
  getTitle(): Promise<string>;

  // ---------- Size & Position ----------
  setSize(width: number, height: number): Promise<boolean>;
  getSize(): Promise<Size>;
  setPosition(x: number, y: number): Promise<boolean>;
  getPosition(): Promise<Point>;
  setBounds(bounds: Rect): Promise<boolean>;
  getBounds(): Promise<Rect>;
  setMinSize(width: number, height: number): Promise<boolean>;
  getMinSize(): Promise<Size>;

  // ---------- Fullscreen ----------
  setFullscreen(enabled: boolean): Promise<boolean>;
  isFullscreen(): Promise<boolean>;
  toggleFullscreen(): Promise<boolean>;

  // ---------- Frameless ----------
  /**
   * Toggle the native window frame at runtime.
   * The window is recreated internally, preserving bounds, title, fullscreen
   * and maximized state.
   */
  setFrameless(enabled: boolean): Promise<boolean>;
  isFrameless(): Promise<boolean>;
  toggleFrameless(): Promise<boolean>;

  // ---------- Resizable / Always-on-top ----------
  setResizable(enabled: boolean): Promise<boolean>;
  isResizable(): Promise<boolean>;
  setAlwaysOnTop(enabled: boolean): Promise<boolean>;
  isAlwaysOnTop(): Promise<boolean>;
  toggleAlwaysOnTop(): Promise<boolean>;

  // ---------- Opacity ----------
  /** Opacity from 0 (transparent) to 1 (opaque). Values are clamped. */
  setOpacity(opacity: number): Promise<boolean>;
  getOpacity(): Promise<number>;

  // ---------- State ----------
  isMaximized(): Promise<boolean>;
  isMinimized(): Promise<boolean>;
  isVisible(): Promise<boolean>;
  isFocused(): Promise<boolean>;

  // ---------- Visibility / Window controls ----------
  show(): Promise<boolean>;
  hide(): Promise<boolean>;
  minimize(): Promise<boolean>;
  /** Maximize the window, or unmaximize if already maximized. */
  maximize(): Promise<boolean>;
  restore(): Promise<boolean>;
  focus(): Promise<boolean>;
  center(): Promise<boolean>;
}

// =============================================================================
// Screen
// =============================================================================

export interface Display {
  id: number;
  bounds: Rect;
  workArea: Rect;
  size: Size;
  workAreaSize: Size;
  /** DPI scale factor (1, 1.5, 2, ...). */
  scaleFactor: number;
  /** Rotation in degrees: 0, 90, 180, 270. */
  rotation: number;
  /** True for built-in displays (e.g. laptop screens). */
  internal: boolean;
  monochrome: boolean;
  colorDepth: number;
  colorSpace: string;
  depthPerComponent: number;
  displayFrequency: number;
}

export interface ScreenAPI {
  getPrimaryDisplay(): Promise<Display>;
  getAllDisplays(): Promise<Display[]>;
  /** Global cursor position in screen coordinates. */
  getCursorScreenPoint(): Promise<Point>;
  /** Display containing or closest to the given screen point. */
  getDisplayNearestPoint(point: Point): Promise<Display>;
}

// =============================================================================
// File (key-value save store)
// =============================================================================

/** Any JSON-serializable value. */
export type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

export interface FileAPI {
  /** Save a JSON-serializable value under `key`. Files are stored as `<key>.json`. */
  save(key: string, data: JsonValue): Promise<boolean>;
  /** Load and parse the file for `key`, or `null` if it does not exist. */
  load<T = JsonValue>(key: string): Promise<T | null>;
  /** Delete the file for `key`. Returns true if a file was removed. */
  delete(key: string): Promise<boolean>;
  /** Check whether a file exists for `key`. */
  exists(key: string): Promise<boolean>;
  /** List all save keys (without `.json` extension). */
  list(): Promise<string[]>;
  /** Absolute path of the saves directory for this app. */
  getPath(): Promise<string>;
}

// =============================================================================
// Clipboard
// =============================================================================

export interface ClipboardAPI {
  /** Read text from the system clipboard. */
  get(): Promise<string>;
  /** Write text to the system clipboard. */
  set(text: string): Promise<boolean>;
  /** Whether the clipboard currently contains text. */
  has(): Promise<boolean>;
}

// =============================================================================
// Dialog
// =============================================================================

export interface DialogFilter {
  name: string;
  /** Extensions without the dot, e.g. ['png', 'jpg']. */
  extensions: string[];
}

export interface DialogOpenOptions {
  title?: string;
  defaultPath?: string;
  filters?: DialogFilter[];
}

export interface DialogSaveOptions extends DialogOpenOptions {}

export interface DialogAPI {
  /** Pick a single file. Returns the path or null when cancelled. */
  openFile(options?: DialogOpenOptions): Promise<string | null>;
  /** Pick multiple files. Returns an empty array when cancelled. */
  openFiles(options?: DialogOpenOptions): Promise<string[]>;
  /** Pick a save destination. Returns the path or null when cancelled. */
  saveFile(options?: DialogSaveOptions): Promise<string | null>;
  /** Pick a folder. Returns the path or null when cancelled. */
  selectFolder(): Promise<string | null>;
  /** Show an informational message box. */
  message(title: string, message: string): Promise<boolean>;
  /** Show a Yes/No confirmation. Returns true for Yes. */
  confirm(title: string, message: string): Promise<boolean>;
}

// =============================================================================
// Notification
// =============================================================================

export interface NotificationAPI {
  /** Show a system notification. Returns true if it was displayed. */
  show(title: string, body: string): Promise<boolean>;
  /** Whether system notifications are supported. */
  isSupported(): Promise<boolean>;
}

// =============================================================================
// Input
// =============================================================================

export interface InputAPI {
  /** Request pointer lock on the document body. */
  requestPointerLock(): Promise<boolean>;
  /** Release pointer lock. */
  exitPointerLock(): Promise<boolean>;
  /** Whether pointer lock is currently active. */
  isPointerLocked(): Promise<boolean>;
}

// =============================================================================
// Assets (External Overrides + Workshop)
// =============================================================================

/**
 * External asset loading. GemShell looks up `gemshell://` requests in this
 * order:
 *
 *   1. Loose files in `<exe-dir>/mods/` (auto-mounted at startup)
 *   2. Subscribed Steam Workshop items (auto-mounted after `steam.init()`)
 *   3. Programmatically added folders via `addFolder()`
 *   4. The encrypted asset bundle (original game assets)
 *
 * For game devs this means: drop a `mods/` folder next to your executable
 * with files mirroring your asset paths (e.g. `mods/sprites/hero.png`),
 * and they will replace the bundled originals - no rebuild required.
 *
 * Code files (`.html`, `.js`, `.mjs`, `.wasm`, `.css`) are intentionally
 * excluded from override to prevent third-party mods from replacing game
 * logic. Asset files (images, audio, video, fonts, JSON, text) are allowed.
 */
export interface AssetsAPI {
  /** Currently mounted override folders, in lookup priority order. */
  listFolders(): Promise<string[]>;
  /**
   * Add a custom folder to the asset lookup chain. Returns true if the
   * folder exists and was added (or was already present). Useful for
   * loading mod packs from arbitrary paths.
   */
  addFolder(absolutePath: string): Promise<boolean>;
  /** Remove a previously added folder from the lookup chain. */
  removeFolder(absolutePath: string): Promise<boolean>;
  /**
   * Re-scan subscribed Steam Workshop items and mount any new ones.
   * Workshop items also auto-mount in the background once Steam finishes
   * downloading them; this method is a manual trigger for cases where you
   * want to force an immediate scan (e.g. right after the user clicks
   * "subscribe" in an in-game browser).
   * Returns the number of newly mounted folders.
   */
  refreshWorkshop(): Promise<number>;
  /**
   * Absolute path of the local `mods/` folder (next to the executable).
   * Useful for showing the user where to drop mod files.
   */
  getLocalModsDir(): Promise<string>;
  /**
   * Subscribe to background notifications about Workshop items that just
   * finished downloading and were auto-mounted into the asset chain.
   * Returns an unsubscribe function.
   *
   * Useful for showing toasts ("New mod available!") or for hot-reloading
   * sprites/audio without a game restart.
   */
  onWorkshopChanged(
    listener: (items: Array<{ id: string; folder: string }>) => void
  ): () => void;
}

// =============================================================================
// Steamworks
// =============================================================================

/** Steam stats loading state. Mirrors `gemshell.steam.STATS_*` constants. */
export type StatsState = 0 | 1 | 2 | 3;

export interface FriendInfo {
  steamId: string;
  name: string;
}

export interface FriendGamePlayed {
  appId: number;
  ipAddress: string;
  port: number;
  queryPort: number;
  lobbyId: string;
}

export interface VoiceAvailable {
  result: number;
  compressedSize: number;
}

export interface DigitalActionData {
  state: boolean;
  active: boolean;
}

export interface AnalogActionData {
  mode: number;
  x: number;
  y: number;
  active: boolean;
}

export type LobbyType = 0 | 1 | 2 | 3;
export type LobbyComparison = -2 | -1 | 0 | 1 | 2 | 3;
export type LobbyDistanceFilter = 0 | 1 | 2 | 3;
export type P2PSendType = 0 | 1 | 2 | 3;
export type LeaderboardUploadMethod = 0 | 1 | 2;
export type LeaderboardDataRequest = 0 | 1 | 2 | 3;

export interface WorkshopItemInstallInfo {
  /** True if the item is downloaded and ready on disk. */
  installed: boolean;
  /** Absolute path to the item's content folder, or "" if not installed. */
  folder: string;
  /** Total size in bytes, or 0 if not installed. */
  sizeOnDisk: number;
  /** Unix timestamp of last update, or 0 if not installed. */
  timestamp: number;
}

export interface LeaderboardEntry {
  steamId: string;
  globalRank: number;
  score: number;
  details: number[];
}

export interface SteamEvent {
  type: string;
  [key: string]: unknown;
}

/**
 * Steamworks API. Only present when `steamworks.enabled` is true in the
 * GemShell config. Most methods require `init()` to have been called first.
 */
export interface SteamAPI {
  // ---------- Lifecycle ----------
  init(): Promise<boolean>;
  shutdown(): Promise<void>;
  isAvailable(): Promise<boolean>;
  isInitialized(): Promise<boolean>;
  /** Run pending Steam callbacks. Call this regularly (e.g. once per frame). */
  runCallbacks(): Promise<void>;

  // ---------- User ----------
  getSteamID(): Promise<string>;
  getPersonaName(): Promise<string>;
  getAppID(): Promise<number>;
  getPlayerSteamLevel(): Promise<number>;

  // ---------- Achievements ----------
  unlockAchievement(id: string): Promise<boolean>;
  getAchievement(id: string): Promise<boolean>;
  clearAchievement(id: string): Promise<boolean>;

  // ---------- Stats ----------
  storeStats(): Promise<boolean>;
  setStatInt(name: string, value: number): Promise<boolean>;
  getStatInt(name: string): Promise<number>;
  setStatFloat(name: string, value: number): Promise<boolean>;
  getStatFloat(name: string): Promise<number>;
  requestStats(): Promise<boolean>;
  /**
   * Request stats and wait until they are loaded (or until `timeoutMs` elapses).
   * Use this before reading/writing stats to avoid races on startup.
   */
  requestStatsAndWait(timeoutMs?: number): Promise<boolean>;
  areStatsReady(): Promise<boolean>;
  getStatsState(): Promise<StatsState>;
  readonly STATS_NOT_REQUESTED: 0;
  readonly STATS_PENDING: 1;
  readonly STATS_READY: 2;
  readonly STATS_FAILED: 3;

  // ---------- Friends ----------
  getFriendCount(): Promise<number>;
  getFriendByIndex(index: number, flags?: number): Promise<string>;
  getFriendPersonaName(index: number): Promise<string>;
  getFriendPersonaState(steamId: string): Promise<number>;
  getFriendGamePlayed(steamId: string): Promise<FriendGamePlayed | null>;
  requestUserInformation(steamId: string): Promise<boolean>;
  /** Convenience: returns up to `max` friends with id and name. */
  getFriends(max?: number): Promise<FriendInfo[]>;
  getSmallFriendAvatar(steamId: string): Promise<number>;
  getMediumFriendAvatar(steamId: string): Promise<number>;
  getLargeFriendAvatar(steamId: string): Promise<number>;
  /** Returns RGBA pixel data and dimensions for an avatar handle. */
  getImageRGBA(handle: number): Promise<{ width: number; height: number; data: number[] } | null>;

  // ---------- Overlay ----------
  activateOverlay(dialog: string): Promise<boolean>;
  activateOverlayToWebPage(url: string): Promise<boolean>;
  isOverlayEnabled(): Promise<boolean>;

  // ---------- Rich Presence ----------
  setRichPresence(key: string, value: string): Promise<boolean>;
  clearRichPresence(): Promise<boolean>;

  // ---------- DLC / Apps ----------
  isDlcInstalled(appId: number): Promise<boolean>;
  getDLCCount(): Promise<number>;
  isSubscribedApp(appId: number): Promise<boolean>;
  isAppInstalled(appId: number): Promise<boolean>;
  getAppInstallDir(appId: number): Promise<string>;

  // ---------- Utils ----------
  isSteamDeck(): Promise<boolean>;
  isSteamInBigPictureMode(): Promise<boolean>;
  getCurrentGameLanguage(): Promise<string>;
  getAvailableGameLanguages(): Promise<string>;
  getServerRealTime(): Promise<number>;
  getIPCountry(): Promise<string>;
  getBuildId(): Promise<number>;
  triggerScreenshot(): Promise<boolean>;

  // ---------- Steam Deck keyboard ----------
  /** Opens the floating on-screen keyboard over a text field. mode: 0=SingleLine 1=MultiLine 2=Email 3=Numeric */
  showFloatingGamepadTextInput(mode: 0 | 1 | 2 | 3, x: number, y: number, width: number, height: number): Promise<boolean>;
  /** Opens the Big Picture modal text input dialog. inputMode: 0=Normal 1=Password. lineMode: 0=SingleLine 1=MultiLine */
  showGamepadTextInput(inputMode: 0 | 1, lineMode: 0 | 1, description: string, maxChars: number, existingText: string): Promise<boolean>;
  /** Returns the text submitted via showGamepadTextInput(). Call after GamepadTextInputDismissed fires. */
  getEnteredGamepadTextInput(): Promise<string>;

  // ---------- Steam Cloud (Remote Storage) ----------
  fileWrite(fileName: string, data: string): Promise<boolean>;
  fileRead(fileName: string): Promise<string>;
  fileExists(fileName: string): Promise<boolean>;
  fileDelete(fileName: string): Promise<boolean>;
  fileGetSize(fileName: string): Promise<number>;
  isCloudEnabledForAccount(): Promise<boolean>;
  isCloudEnabledForApp(): Promise<boolean>;
  setCloudEnabledForApp(enabled: boolean): Promise<boolean>;

  // ---------- Event polling (low-level) ----------
  pollEvents(): Promise<boolean>;
  popEvent(): Promise<SteamEvent | null>;

  // ---------- Leaderboards ----------
  findLeaderboard(name: string): Promise<{ handle: string; name: string } | null>;
  uploadLeaderboardScore(
    name: string,
    score: number,
    method?: LeaderboardUploadMethod
  ): Promise<boolean>;
  downloadLeaderboardEntries(
    name: string,
    type: LeaderboardDataRequest,
    start: number,
    end: number
  ): Promise<LeaderboardEntry[]>;

  // ---------- Lobbies ----------
  createLobby(type: LobbyType, maxMembers: number): Promise<string | null>;
  joinLobby(lobbyId: string): Promise<boolean>;
  leaveLobby(): Promise<boolean>;
  getLobbyMembers(): Promise<string[]>;
  setLobbyData(key: string, value: string): Promise<boolean>;
  getLobbyData(key: string): Promise<string>;
  inviteUserToLobby(steamId: string): Promise<boolean>;
  getLobbyOwner(): Promise<string>;
  getLobbyMemberLimit(): Promise<number>;
  setLobbyJoinable(joinable: boolean): Promise<boolean>;
  requestLobbyList(): Promise<number>;
  addRequestLobbyListStringFilter(
    key: string,
    value: string,
    comparison?: LobbyComparison
  ): Promise<boolean>;
  addRequestLobbyListNumericalFilter(
    key: string,
    value: number,
    comparison?: LobbyComparison
  ): Promise<boolean>;
  addRequestLobbyListDistanceFilter(distance?: LobbyDistanceFilter): Promise<boolean>;
  addRequestLobbyListFilterSlotsAvailable(slots?: number): Promise<boolean>;
  addRequestLobbyListResultCountFilter(maxResults?: number): Promise<boolean>;
  getLobbyByIndex(index: number): Promise<string | null>;

  // ---------- P2P ----------
  isP2PPacketAvailable(channel: number): Promise<boolean>;
  readP2PPacket(
    channel: number,
    maxSize: number
  ): Promise<{ data: number[]; steamId: string } | null>;
  sendP2PPacket(
    steamId: string,
    data: number[] | Uint8Array,
    sendType: P2PSendType,
    channel: number
  ): Promise<boolean>;
  acceptP2PSessionWithUser(steamId: string): Promise<boolean>;
  closeP2PSessionWithUser(steamId: string): Promise<boolean>;

  // ---------- Workshop ----------
  getNumSubscribedItems(): Promise<number>;
  getSubscribedItems(): Promise<string[]>;
  /**
   * Install info for a Workshop item. `installed` is false (and other fields
   * are zero/empty) when the item has not been downloaded yet.
   */
  getItemInstallInfo(fileId: string): Promise<WorkshopItemInstallInfo>;
  /**
   * EItemState bitmap for a Workshop item. Bits:
   *   1  = subscribed         (user is subscribed to this item)
   *   4  = installed          (downloaded and ready on disk)
   *   8  = needs update       (installed but creator pushed an update)
   *   16 = downloading
   *   32 = download pending
   * Returns 0 if the item is unknown to Steam.
   */
  getItemState(fileId: string): Promise<number>;
  /**
   * Trigger a download (or update) of a Workshop item. Returns true if the
   * download was started. The actual file becomes available asynchronously
   * once Steam fires its DownloadItemResult callback - GemShell auto-mounts
   * it then and emits `assets.onWorkshopChanged`. You usually do not need
   * to call this manually: `assets` auto-triggers downloads for any
   * subscribed-but-not-installed item right after `steam.init()`.
   */
  downloadItem(fileId: string, highPriority?: boolean): Promise<boolean>;

  // ---------- Steam Input ----------
  initInput(explicitlyCallRunFrame?: boolean): Promise<boolean>;
  shutdownInput(): Promise<boolean>;
  inputRunFrame(reserved?: boolean): Promise<void>;
  getConnectedControllers(): Promise<string[]>;
  getActionSetHandle(name: string): Promise<string>;
  activateActionSet(controller: string, actionSet: string): Promise<void>;
  getDigitalActionHandle(name: string): Promise<string>;
  getAnalogActionHandle(name: string): Promise<string>;
  getDigitalActionData(controller: string, action: string): Promise<DigitalActionData>;
  getAnalogActionData(controller: string, action: string): Promise<AnalogActionData>;
  getInputTypeForHandle(controller: string): Promise<number>;

  // ---------- Voice ----------
  startVoiceRecording(): Promise<boolean>;
  stopVoiceRecording(): Promise<boolean>;
  getAvailableVoice(): Promise<VoiceAvailable>;
  getVoice(maxSize?: number): Promise<{ buffer: number[]; written: number } | null>;
  decompressVoice(
    data: number[] | Uint8Array,
    sampleRate?: number
  ): Promise<{ buffer: number[]; written: number } | null>;

  // ---------- Parental controls ----------
  isParentalLockEnabled(): Promise<boolean>;
  isAppBlocked(appId: number): Promise<boolean>;
  isFeatureBlocked(feature: number): Promise<boolean>;

  // ---------- Timeline (Steam Recording) ----------
  setTimelineStateDescription(description: string, delta?: number): Promise<boolean>;
  clearTimelineStateDescription(delta?: number): Promise<boolean>;
  addTimelineEvent(
    icon: string,
    title: string,
    description?: string,
    priority?: number,
    startOffset?: number,
    duration?: number,
    clipPriority?: number
  ): Promise<boolean>;
  setTimelineTooltip(description: string, delta?: number): Promise<boolean>;
  clearTimelineTooltip(delta?: number): Promise<boolean>;

  // ---------- Music ----------
  isMusicEnabled(): Promise<boolean>;
  isMusicPlaying(): Promise<boolean>;
  getMusicPlaybackStatus(): Promise<number>;
  playMusic(): Promise<boolean>;
  pauseMusic(): Promise<boolean>;
  playPrevious(): Promise<boolean>;
  playNext(): Promise<boolean>;
  setMusicVolume(volume: number): Promise<boolean>;
  getMusicVolume(): Promise<number>;

  // ---------- Inventory ----------
  loadItemDefinitions(): Promise<boolean>;
  getAllItems(): Promise<number>;
  addPromoItem(itemDef: number): Promise<number>;
  getResultStatus(handle: number): Promise<number>;
  destroyResult(handle: number): Promise<boolean>;
  triggerItemDrop(listDef: number): Promise<number>;

  /** Steam in-game overlay sub-API. */
  overlay: {
    /** Whether the GemShell overlay was successfully injected. */
    isAvailable(): Promise<boolean>;
    /** Whether the overlay is currently being shown. */
    isActive(): Promise<boolean>;
  };
}

// =============================================================================
// GemShell custom overlay
// =============================================================================

export interface OverlayAPI {
  /** Whether the overlay was successfully injected. */
  isAvailable(): Promise<boolean>;
  /** Same as isAvailable, kept for clarity. */
  isInjected(): Promise<boolean>;
  /** Whether the overlay is currently being rendered. */
  isShowing(): Promise<boolean>;
  /** Subscribe to overlay show/hide changes. */
  onStateChange(callback: (showing: boolean) => void): void;
}

export {};
