// Telegram Mini App SDK access. The official telegram-web-app.js (loaded in
// index.html) exposes window.Telegram.WebApp; this wraps the subset the app uses:
// launch detection, initData (for auth.telegram), the deep-link start parameter,
// theme params, and ready()/expand(). Every helper is safe to call outside Telegram.

import type { TelegramThemeParams } from './theme';

interface TelegramWebApp {
  initData: string;
  initDataUnsafe?: { start_param?: string };
  platform?: string;
  themeParams?: TelegramThemeParams;
  colorScheme?: 'light' | 'dark';
  isFullscreen?: boolean;
  safeAreaInset?: { top: number; bottom: number; left: number; right: number };
  contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number };
  ready?: () => void;
  expand?: () => void;
  requestFullscreen?: () => void;
  openTelegramLink?: (url: string) => void;
  openLink?: (url: string) => void;
  onEvent?: (event: string, handler: () => void) => void;
  setHeaderColor?: (color: string) => void;
  setBackgroundColor?: (color: string) => void;
  setBottomBarColor?: (color: string) => void;
  disableVerticalSwipes?: () => void;
  enableClosingConfirmation?: () => void;
  disableClosingConfirmation?: () => void;
  HapticFeedback?: {
    impactOccurred?: (style: string) => void;
    notificationOccurred?: (type: string) => void;
    selectionChanged?: () => void;
  };
  BackButton?: {
    show?: () => void;
    hide?: () => void;
    onClick?: (cb: () => void) => void;
    offClick?: (cb: () => void) => void;
  };
}

function webApp(): TelegramWebApp | undefined {
  if (typeof window === 'undefined') return undefined;
  return (window as unknown as { Telegram?: { WebApp?: TelegramWebApp } }).Telegram?.WebApp;
}

/**
 * insideTelegram reports whether the app launched as a Telegram Mini App — the SDK
 * is present and carries non-empty initData (an ordinary browser tab has neither).
 */
export function insideTelegram(): boolean {
  const w = webApp();
  return !!w && typeof w.initData === 'string' && w.initData.length > 0;
}

/**
 * telegramOpenLink opens a t.me link through the Mini App SDK, so Telegram navigates to
 * it natively (e.g. a bot chat) rather than spawning an in-app browser tab. Returns false
 * outside Telegram or when the SDK lacks the method, so the caller can fall back to a plain
 * window.open.
 */
export function telegramOpenLink(url: string): boolean {
  const w = webApp();
  if (!w?.openTelegramLink) return false;
  w.openTelegramLink(url);
  return true;
}

/**
 * telegramOpenExternalLink opens an arbitrary external URL through the Mini App SDK's openLink,
 * so Telegram opens it directly in its in-app browser instead of the WebView's generic "open
 * this link?" confirmation that a plain target=_blank navigation triggers. Returns false
 * outside Telegram or when the SDK lacks the method, so the caller can fall back to a normal
 * anchor.
 */
export function telegramOpenExternalLink(url: string): boolean {
  const w = webApp();
  if (!w?.openLink) return false;
  w.openLink(url);
  return true;
}

/**
 * isExternalHttpUrl reports whether href is an absolute http(s) URL on a different origin than the
 * app — so a same-origin or root-relative in-app (SPA) link is left to normal navigation, only a
 * genuinely external link is routed through Telegram's browser.
 */
function isExternalHttpUrl(href: string): boolean {
  try {
    const u = new URL(href, typeof location === 'undefined' ? undefined : location.href);
    if (u.protocol !== 'http:' && u.protocol !== 'https:') return false;
    return typeof location === 'undefined' || u.origin !== location.origin;
  } catch {
    return false;
  }
}

/**
 * routeExternalLinkInTelegram decides whether a clicked anchor should be opened through the Mini
 * App SDK rather than the WebView's default navigation: only inside Telegram, and only for an
 * external http(s) link opened in a new tab (target=_blank) that is not a t.me link (those use
 * telegramOpenLink/openTelegramLink). Returns true when it opened the link through openLink — the
 * caller should then preventDefault — and false to let the browser handle the click normally.
 */
export function routeExternalLinkInTelegram(anchor: { href: string; target: string }): boolean {
  if (!insideTelegram()) return false;
  if (anchor.target !== '_blank') return false;
  if (anchor.href.startsWith('https://t.me/')) return false;
  if (!isExternalHttpUrl(anchor.href)) return false;
  return telegramOpenExternalLink(anchor.href);
}

/**
 * onExternalLinkClick is the anchor click handler that applies routeExternalLinkInTelegram. It
 * resolves the clicked anchor with closest(), so it works both attached directly on an <a> and
 * delegated on a container that renders links (e.g. {@html} markdown). Attach it as onclick on an
 * external link or its container; outside Telegram it is a no-op and the anchor's own
 * target=_blank handles the click.
 */
export function onExternalLinkClick(e: MouseEvent): void {
  const a = (e.target as HTMLElement | null)?.closest('a');
  if (a && routeExternalLinkInTelegram(a)) e.preventDefault();
}

/**
 * shareTelegramLink opens Telegram's native "share to a chat" picker for url with a
 * caption, through the Mini App SDK (https://t.me/share/url). Returns false outside
 * Telegram or when the SDK lacks the method, so the caller can fall back to the Web
 * Share API. Works consistently on iOS and Android within Telegram.
 */
export function shareTelegramLink(url: string, text: string): boolean {
  return telegramOpenLink(`https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`);
}

/** TelegramLaunch is the data a Mini App launch carries. */
export interface TelegramLaunch {
  initData: string;
  startParam: string;
  theme: TelegramThemeParams | undefined;
}

/**
 * telegramLaunch readies the Mini App (full-height, ready signal) and returns its
 * launch data: the raw initData (for auth.telegram), the deep-link start parameter
 * (from the SDK or, for a bot web_app button, the page URL), and the theme params.
 */
export function telegramLaunch(): TelegramLaunch {
  const w = webApp();
  if (!w) return { initData: '', startParam: startParamFromURL(), theme: undefined };
  w.ready?.();
  w.expand?.();
  const startParam = w.initDataUnsafe?.start_param ?? startParamFromURL();
  return { initData: w.initData, startParam, theme: w.themeParams };
}

/**
 * telegramOnEvent subscribes to a Telegram WebApp lifecycle event (e.g. 'activated' /
 * 'deactivated', added in Bot API 8.0). It is a no-op outside Telegram or on a client
 * that predates the event, so callers can register defensively.
 */
export function telegramOnEvent(event: string, handler: () => void): void {
  webApp()?.onEvent?.(event, handler);
}

/**
 * telegramColorScheme returns Telegram's active colour scheme ('light' | 'dark'),
 * or undefined outside Telegram. Inside the Mini App this — not the OS
 * prefers-color-scheme — is the authoritative theme: on some clients (Telegram
 * Desktop) the OS scheme leaks into the webview and fights Telegram's own setting,
 * so the app forces this value on launch.
 */
export function telegramColorScheme(): 'light' | 'dark' | undefined {
  return webApp()?.colorScheme;
}

/**
 * telegramSetChrome paints Telegram's own header, background and bottom bar to match the
 * app's colours, so the surrounding Telegram chrome does not clash with the UI. No-op
 * outside Telegram or on a client predating a given setter.
 */
export function telegramSetChrome(header: string, background: string, bottom: string): void {
  const w = webApp();
  if (header) w?.setHeaderColor?.(header);
  if (background) w?.setBackgroundColor?.(background);
  if (bottom) w?.setBottomBarColor?.(bottom);
}

/**
 * telegramContentSafeAreaTop returns the height (px) Telegram's own UI overlays at the top of
 * the viewport in fullscreen (its nav band; the content-safe area, Bot API 8.0). It is 0
 * outside Telegram or on clients predating it, so callers can pad/position defensively.
 */
export function telegramContentSafeAreaTop(): number {
  return webApp()?.contentSafeAreaInset?.top ?? 0;
}

/**
 * telegramSafeAreaTop returns the device safe-area top inset (px) — the notch / status bar
 * (Bot API 8.0). Telegram's own nav controls sit in the band between it and
 * telegramContentSafeAreaTop, so aligning our header to that band lines it up with them. 0
 * outside Telegram or on older clients.
 */
export function telegramSafeAreaTop(): number {
  return webApp()?.safeAreaInset?.top ?? 0;
}

/**
 * telegramDisableVerticalSwipes turns off Telegram's swipe-down-to-minimise gesture so
 * it does not fight tile drag-and-drop or the board's vertical scroll.
 */
export function telegramDisableVerticalSwipes(): void {
  webApp()?.disableVerticalSwipes?.();
}

/** Haptic is the set of feedbacks the app triggers. */
export type Haptic = 'select' | 'success' | 'error' | 'warning' | 'light' | 'medium' | 'heavy';

/** telegramHaptic fires a Telegram haptic; a no-op outside Telegram or on older clients. */
export function telegramHaptic(kind: Haptic): void {
  const h = webApp()?.HapticFeedback;
  if (!h) return;
  if (kind === 'select') h.selectionChanged?.();
  else if (kind === 'success' || kind === 'error' || kind === 'warning') h.notificationOccurred?.(kind);
  else h.impactOccurred?.(kind);
}

/**
 * isMobilePlatform reports whether the Mini App runs on a Telegram mobile client — iOS or
 * Android (the latter reported as 'android' by Telegram for Android and 'android_x' by
 * Telegram X). Desktop clients (tdesktop, macOS, web) report other values. Used to limit
 * mobile-only chrome such as the close guard and immersive fullscreen.
 */
function isMobilePlatform(): boolean {
  const p = webApp()?.platform;
  return p === 'ios' || p === 'android' || p === 'android_x';
}

/**
 * telegramRequestFullscreen asks Telegram to open the Mini App in fullscreen (Bot API 8.0+),
 * but only on mobile clients — mirroring how Telegram's own Mini Apps go immersive on phones
 * while staying a standard window on desktop (where the bot's full-size setting already fills
 * the window). A no-op outside Telegram, on desktop, or on clients predating the method.
 */
export function telegramRequestFullscreen(): void {
  if (isMobilePlatform()) webApp()?.requestFullscreen?.();
}

/**
 * telegramClosingConfirmation toggles the confirmation Telegram shows when the user swipes
 * the Mini App closed — enabled during an active game so it is not lost by accident. The
 * guard is only armed on mobile clients: on desktop, closing a window is deliberate and
 * Telegram surfaces a "changes may not be saved" dialog that is just noise here (drafts
 * auto-save), so the confirmation is skipped there.
 */
export function telegramClosingConfirmation(on: boolean): void {
  const w = webApp();
  if (on) {
    if (isMobilePlatform()) w?.enableClosingConfirmation?.();
  } else {
    w?.disableClosingConfirmation?.();
  }
}

let backHandler: (() => void) | null = null;

/**
 * telegramBackButton shows or hides Telegram's native header back button, wiring its
 * click to onClick (replacing any previous handler). The app hides its own back chevron
 * inside Telegram so only the native control shows.
 */
export function telegramBackButton(show: boolean, onClick?: () => void): void {
  const b = webApp()?.BackButton;
  if (!b) return;
  if (backHandler) b.offClick?.(backHandler);
  backHandler = null;
  if (show) {
    if (onClick) {
      backHandler = onClick;
      b.onClick?.(onClick);
    }
    b.show?.();
  } else {
    b.hide?.();
  }
}

/**
 * startParamFromURL reads a startapp parameter from the page URL — a bot web_app
 * launch button carries the deep-link there rather than in initDataUnsafe.
 */
function startParamFromURL(): string {
  if (typeof location === 'undefined') return '';
  return new URLSearchParams(location.search).get('startapp') ?? '';
}

/**
 * onTelegramPath reports whether the app is served under the dedicated Telegram
 * entry path (/telegram/); outside Telegram on that path the app refuses to render.
 */
export function onTelegramPath(): boolean {
  if (typeof location === 'undefined') return false;
  return location.pathname.startsWith('/telegram/');
}

// --- Login Widget (web sign-in for account linking) ---

// The Login Widget is the web (non-Mini-App) Telegram sign-in. It is used only to
// attach a Telegram identity to an existing account from a browser; inside the Mini
// App the session is already a Telegram identity. It needs the bot id (numeric,
// VITE_TELEGRAM_BOT_ID) and, in production, the site domain registered with BotFather
// (/setdomain) — without that Telegram refuses to render. The connector validates the
// returned data (HMAC under SHA-256(bot_token)).

const widgetScriptSrc = 'https://telegram.org/js/telegram-widget.js?22';

interface telegramAuthUser {
  id: number;
  first_name?: string;
  last_name?: string;
  username?: string;
  photo_url?: string;
  auth_date: number;
  hash: string;
}

interface telegramLoginSDK {
  auth(opts: { bot_id: string; request_access?: string }, cb: (user: telegramAuthUser | false) => void): void;
}

function isMock(): boolean {
  return import.meta.env.MODE === 'mock';
}

function botID(): string {
  return (import.meta.env.VITE_TELEGRAM_BOT_ID as string | undefined) ?? '';
}

/**
 * loginWidgetAvailable reports whether the "Link Telegram" control should be shown:
 * not already inside the Mini App, and either the mock build or a configured bot id.
 */
export function loginWidgetAvailable(): boolean {
  if (insideTelegram()) return false;
  return isMock() || botID() !== '';
}

let widgetLoad: Promise<void> | null = null;

function loadWidget(): Promise<void> {
  if (typeof document === 'undefined') return Promise.reject(new Error('telegram: no document'));
  const sdk = (window as unknown as { Telegram?: { Login?: telegramLoginSDK } }).Telegram?.Login;
  if (sdk) return Promise.resolve();
  if (!widgetLoad) {
    widgetLoad = new Promise<void>((resolve, reject) => {
      const s = document.createElement('script');
      s.src = widgetScriptSrc;
      s.async = true;
      s.onload = () => resolve();
      s.onerror = () => reject(new Error('telegram: widget load failed'));
      document.head.appendChild(s);
    });
  }
  return widgetLoad;
}

/**
 * requestTelegramLogin drives the Login Widget popup and resolves with the auth data
 * serialized as a URL query string (id=...&auth_date=...&hash=...) — the form the
 * connector validates — or null when the user cancels. In the mock build it returns
 * a fixed payload without loading the real widget (telegram.org is blocked in tests).
 */
export async function requestTelegramLogin(): Promise<string | null> {
  if (isMock()) {
    return `id=42&first_name=Telegram&auth_date=${Math.floor(Date.now() / 1000)}&hash=mock`;
  }
  await loadWidget();
  const login = (window as unknown as { Telegram?: { Login?: telegramLoginSDK } }).Telegram?.Login;
  if (!login) throw new Error('telegram: login unavailable');
  const user = await new Promise<telegramAuthUser | false>((resolve) => {
    login.auth({ bot_id: botID(), request_access: 'write' }, resolve);
  });
  if (!user) return null;
  return serializeTelegramAuth(user);
}

function serializeTelegramAuth(u: telegramAuthUser): string {
  const params = new URLSearchParams();
  params.set('id', String(u.id));
  if (u.first_name) params.set('first_name', u.first_name);
  if (u.last_name) params.set('last_name', u.last_name);
  if (u.username) params.set('username', u.username);
  if (u.photo_url) params.set('photo_url', u.photo_url);
  params.set('auth_date', String(u.auth_date));
  params.set('hash', u.hash);
  return params.toString();
}
