// VK Bridge storage sync for the client display preferences that VK's desktop iframe drops between
// reloads — theme, interface language, reduce-motion, board labels and the board zoom, plus the
// first-run coachmark flags. In VK's cross-origin desktop iframe (notably in Firefox) the browser
// partitions or blocks IndexedDB and localStorage, so the app's local store is empty on every reload
// and these settings reset; a plain browser tab / PWA keeps them (first-party storage) and Telegram
// has its own CloudStorage sync (cloudprefs.ts). VKWebAppStorageSet/Get travel over the VK Bridge
// postMessage channel to vk.com rather than browser storage, so they survive; the VK identity is
// re-derived from the signed launch parameters on every load, so the stored values reload onto the
// same account. The pure encode/decode is kept free of the SDK and the DOM so it unit-tests in the
// node environment; the VK storage transport wrappers live in vk.ts and the wiring (mirror on save,
// reconcile on launch) in app.svelte.ts.
//
// Unlike the Telegram CloudStorage bundle (cloudprefs.ts), this one DOES carry the locale: on VK the
// interface language has no other durable client home (the server preferred_language is written from
// the client but never read back into it), so it must ride the same store as the rest.

import type { ThemePref } from './theme';
import type { BoardLabelMode } from './boardlabels';
import type { Locale } from './i18n/catalog';

/** VKPrefs is the display-preference bundle synced to VK Bridge storage. */
export interface VKPrefs {
  theme: ThemePref;
  locale: Locale;
  reduceMotion: boolean;
  boardLabels: BoardLabelMode;
  zoomBoard: boolean;
}

/** VKOnboarding mirrors the first-run coachmark completion flags to VK Bridge storage. */
export interface VKOnboarding {
  lobbyDone: boolean;
  gameDone: boolean;
}

/** VK_PREFS_KEY is the VK Bridge storage key holding the JSON-encoded VKPrefs. */
export const VK_PREFS_KEY = 'prefs';

/** VK_ONBOARDING_KEY is the VK Bridge storage key holding the JSON-encoded VKOnboarding flags. */
export const VK_ONBOARDING_KEY = 'onboarding';

/** encodeVKPrefs serialises the VK-synced display prefs. */
export function encodeVKPrefs(p: VKPrefs): string {
  return JSON.stringify({
    theme: p.theme,
    locale: p.locale,
    reduceMotion: p.reduceMotion,
    boardLabels: p.boardLabels,
    zoomBoard: p.zoomBoard,
  });
}

/**
 * decodeVKPrefs parses a VK Bridge storage payload into a partial VKPrefs, keeping only valid fields
 * and dropping anything unknown, mistyped or malformed — so a value written by a newer or older
 * build, or a corrupt entry, never throws and never applies a bad setting. A missing field stays
 * absent, so the caller leaves the corresponding local value untouched.
 */
export function decodeVKPrefs(raw: string | null | undefined): Partial<VKPrefs> {
  const o = parseObject(raw);
  if (!o) return {};
  const out: Partial<VKPrefs> = {};
  if (o.theme === 'auto' || o.theme === 'light' || o.theme === 'dark') out.theme = o.theme;
  if (o.locale === 'en' || o.locale === 'ru') out.locale = o.locale;
  if (typeof o.reduceMotion === 'boolean') out.reduceMotion = o.reduceMotion;
  if (o.boardLabels === 'beginner' || o.boardLabels === 'classic' || o.boardLabels === 'none') {
    out.boardLabels = o.boardLabels;
  }
  if (typeof o.zoomBoard === 'boolean') out.zoomBoard = o.zoomBoard;
  return out;
}

/** encodeVKOnboarding serialises the first-run coachmark completion flags. */
export function encodeVKOnboarding(s: VKOnboarding): string {
  return JSON.stringify({ lobbyDone: s.lobbyDone, gameDone: s.gameDone });
}

/**
 * decodeVKOnboarding parses a VK Bridge storage payload into a partial VKOnboarding, keeping only the
 * boolean flags and dropping anything else — a missing flag stays absent so the caller keeps its
 * current value.
 */
export function decodeVKOnboarding(raw: string | null | undefined): Partial<VKOnboarding> {
  const o = parseObject(raw);
  if (!o) return {};
  const out: Partial<VKOnboarding> = {};
  if (typeof o.lobbyDone === 'boolean') out.lobbyDone = o.lobbyDone;
  if (typeof o.gameDone === 'boolean') out.gameDone = o.gameDone;
  return out;
}

/**
 * parseObject safely parses raw JSON into a plain object, returning null for empty, malformed or
 * non-object input (including a JSON array), so every decoder above starts from a clean record.
 */
function parseObject(raw: string | null | undefined): Record<string, unknown> | null {
  if (!raw) return null;
  try {
    const o = JSON.parse(raw) as unknown;
    return o && typeof o === 'object' && !Array.isArray(o) ? (o as Record<string, unknown>) : null;
  } catch {
    return null;
  }
}
