/**
 * tauri-bridge.js – Frontend bridge for Tauri v2 IPC.
 *
 * Provides a unified API for the webmail client to communicate with the
 * Tauri Rust backend.  This is the single module that covers desktop AND
 * mobile via Tauri's unified IPC layer.
 *
 * All Tauri JS imports are lazily loaded so this module can be safely
 * imported in a plain browser context (the functions simply become no-ops
 * when `window.__TAURI_INTERNALS__` is absent).
 *
 * Hardening:
 *   - All inputs are validated before being sent over IPC.
 *   - Deep-link URLs are validated against an allowlist of schemes.
 *   - Badge counts are bounds-checked.
 *   - Event payloads are type-checked before dispatch.
 */

import { isTauriMobile } from './platform.js';

let _invoke;
let _listen;
let _emit;

async function ensureTauriApi() {
  if (_invoke) return;
  try {
    const core = await import('@tauri-apps/api/core');
    const event = await import('@tauri-apps/api/event');
    _invoke = core.invoke;
    _listen = event.listen;
    _emit = event.emit;
  } catch {
    // Not running inside Tauri – provide silent no-ops.
    _invoke = async () => {};
    _listen = async () => () => {};
    _emit = async () => {};
  }
}

// ── Validation helpers ──────────────────────────────────────────────────────

const ALLOWED_DEEP_LINK_SCHEMES = ['mailto:', 'forwardemail:'];

function isValidDeepLink(url) {
  if (typeof url !== 'string') return false;
  const trimmed = url.trim().toLowerCase();
  return ALLOWED_DEEP_LINK_SCHEMES.some((scheme) => trimmed.startsWith(scheme));
}

function sanitizeString(value, maxLength = 1024) {
  if (typeof value !== 'string') return '';
  // Truncate to max length and strip control characters (except newline/tab)
  // eslint-disable-next-line no-control-regex
  return value.slice(0, maxLength).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
}

// ── Public API ──────────────────────────────────────────────────────────────

/**
 * Returns true when the app is running inside a Tauri webview.
 */
export function isTauri() {
  return Boolean(typeof window !== 'undefined' && window.__TAURI_INTERNALS__);
}

/**
 * Invoke a Tauri command (Rust backend).
 * The command name is validated against a known set.
 */
const ALLOWED_COMMANDS = new Set([
  'get_app_version',
  'get_platform',
  'get_build_info',
  'set_badge_count',
  'toggle_window_visibility',
  'is_default_mailto_handler',
  'set_default_mailto_handler',
  'get_pending_deep_links',
  'get_log_path',
  'read_recent_logs',
  'clear_logs',
]);

export async function invoke(cmd, args) {
  if (typeof cmd !== 'string' || !ALLOWED_COMMANDS.has(cmd)) {
    console.warn('[tauri-bridge] Blocked unknown IPC command:', cmd);
    return undefined;
  }

  await ensureTauriApi();
  return _invoke(cmd, args);
}

/**
 * Listen to a Tauri event emitted from Rust.
 * Returns an unlisten function.
 */
export async function listen(eventName, handler) {
  if (typeof eventName !== 'string' || typeof handler !== 'function') {
    return () => {};
  }

  await ensureTauriApi();
  return _listen(eventName, handler);
}

/**
 * Emit a Tauri event to the Rust backend.
 */
export async function emit(eventName, payload) {
  if (typeof eventName !== 'string') return;
  await ensureTauriApi();
  return _emit(eventName, payload);
}

/**
 * Get the app version from the Rust backend.
 */
export async function getAppVersion() {
  return invoke('get_app_version');
}

/**
 * Get the platform string (e.g. "macos-aarch64").
 */
export async function getPlatform() {
  return invoke('get_platform');
}

/**
 * Get build metadata for the About dialog.
 * Returns { version, buildDate, os, arch, license }.
 */
export async function getBuildInfo() {
  return invoke('get_build_info');
}

/**
 * Read the tail of the latest native log file (Phase 2 diagnostics).
 * Returns the empty string when no log file exists yet or when not running
 * inside Tauri. Maximum 256 KB regardless of the requested size.
 */
export async function readRecentLogs(maxBytes = 65536) {
  if (!isTauri()) return '';
  const result = await invoke('read_recent_logs', { maxBytes });
  return typeof result === 'string' ? result : '';
}

/**
 * Path to the app's log directory, for surfacing in the Settings UI.
 */
export async function getLogPath() {
  if (!isTauri()) return '';
  const result = await invoke('get_log_path');
  return typeof result === 'string' ? result : '';
}

/**
 * Delete every .log file in the log directory.
 */
export async function clearLogs() {
  if (!isTauri()) return;
  await invoke('clear_logs');
}

/**
 * Set the dock/taskbar badge count (macOS).
 * Input is validated: must be a non-negative integer <= 99999.
 */
export async function setBadgeCount(count) {
  const n = Number(count);
  if (!Number.isInteger(n) || n < 0 || n > 99_999) {
    console.warn('[tauri-bridge] Invalid badge count:', count);
    return;
  }

  return invoke('set_badge_count', { count: n });
}

/**
 * Toggle main window visibility (for tray icon).
 */
export async function toggleWindowVisibility() {
  return invoke('toggle_window_visibility');
}

/**
 * Check if this app is the default mailto: handler.
 *
 * Returns { status: 'default' | 'not_default' | 'unknown', currentHandler: string }
 *
 * - macOS: Uses CoreServices LSCopyDefaultHandlerForURLScheme
 * - Windows: Uses registry via deep-link plugin
 * - Linux: Uses xdg-mime via deep-link plugin
 */
export async function isDefaultMailtoHandler() {
  return invoke('is_default_mailto_handler');
}

/**
 * Attempt to set this app as the default mailto: handler.
 *
 * Returns { method: 'registered' | 'open_mail_settings' | 'error', message: string }
 *
 * - macOS (sandbox): Opens Apple Mail settings with instructions
 * - macOS (non-sandbox): Calls LSSetDefaultHandlerForURLScheme directly
 * - Windows: Registers via Windows registry
 * - Linux: Registers via xdg-mime
 */
export async function setDefaultMailtoHandler() {
  return invoke('set_default_mailto_handler');
}

/**
 * Register a handler for deep-link URLs (mailto:, forwardemail://).
 * URLs are validated against the allowed scheme list before dispatch.
 * Returns an unlisten function.
 */
export async function onDeepLink(handler) {
  return listen('deep-link-received', (event) => {
    if (event.payload && Array.isArray(event.payload.urls)) {
      for (const url of event.payload.urls) {
        if (isValidDeepLink(url)) {
          handler(sanitizeString(url, 2048));
        }
      }
    }
  });
}

/**
 * Register a handler for single-instance arguments.
 * Arguments are sanitised before dispatch.
 * Returns an unlisten function.
 */
export async function onSingleInstance(handler) {
  return listen('single-instance', (event) => {
    if (!event.payload) return;
    const safePayload = {
      args: Array.isArray(event.payload.args)
        ? event.payload.args.map((a) => sanitizeString(String(a), 2048))
        : [],
      cwd: sanitizeString(String(event.payload.cwd || ''), 512),
    };
    handler(safePayload);
  });
}

/**
 * Register a handler for the Android hardware/gesture back button.
 * Returns an unlisten function.
 */
export async function onBackButton(handler) {
  return listen('app:back-button', () => {
    if (typeof handler === 'function') handler();
  });
}

/**
 * Trigger haptic feedback via the Vibration API (Android WebView).
 * Styles: 'light' (10ms), 'medium' (20ms), 'heavy' (30ms),
 *         'success' (pattern), 'error' (pattern)
 * No-op on platforms without navigator.vibrate.
 */
export function triggerHaptic(style = 'light') {
  // iOS WKWebView has no navigator.vibrate, so route through the native haptics
  // plugin on Tauri mobile (works on Android too, with proper iOS-style impact /
  // notification feedback). Fall back to navigator.vibrate on web/desktop.
  if (isTauriMobile) {
    void nativeHaptic(style);
    return;
  }
  if (typeof navigator === 'undefined' || !navigator.vibrate) return;
  const patterns = {
    light: 10,
    medium: 20,
    heavy: 30,
    success: [10, 50, 10],
    error: [20, 40, 20, 40, 20],
  };
  navigator.vibrate(patterns[style] || 10);
}

async function nativeHaptic(style) {
  try {
    const haptics = await import('@tauri-apps/plugin-haptics');
    if (style === 'success' || style === 'warning' || style === 'error') {
      await haptics.notificationFeedback(style);
    } else if (style === 'medium' || style === 'heavy') {
      await haptics.impactFeedback(style);
    } else {
      await haptics.impactFeedback('light');
    }
  } catch {
    // Plugin not registered in this binary (e.g. desktop) — best-effort no-op.
  }
}

/**
 * Register a handler for Android share intents (ACTION_SEND).
 * The Android MainActivity dispatches 'app:share-received' CustomEvents.
 * Returns an unsubscribe function.
 */
export function onShareReceived(handler) {
  if (typeof handler !== 'function') return () => {};
  const listener = (event) => {
    const detail = event?.detail;
    if (!detail) return;
    handler({
      subject: sanitizeString(String(detail.subject || ''), 1024),
      text: sanitizeString(String(detail.text || ''), 65536),
    });
  };
  window.addEventListener('app:share-received', listener);
  return () => window.removeEventListener('app:share-received', listener);
}

/**
 * Drain any deep-link URLs that arrived before the frontend was ready
 * (cold-start race condition).  Returns an array of URL strings.
 */
export async function getPendingDeepLinks() {
  return invoke('get_pending_deep_links');
}

/**
 * Initialize the Tauri bridge.
 * Call once during app bootstrap.
 */
export async function initTauriBridge() {
  if (!isTauri()) return;

  await ensureTauriApi();

  // Listen for deep-link URLs and dispatch a custom DOM event
  // so existing code can handle mailto: links.
  await onDeepLink((url) => {
    window.dispatchEvent(new CustomEvent('app:deep-link', { detail: { url } }));
  });

  // Listen for single-instance events (second launch with args).
  await onSingleInstance((payload) => {
    window.dispatchEvent(new CustomEvent('app:single-instance', { detail: payload }));
  });

  // Drain any deep-link URLs that arrived before listeners were ready
  // (cold-start: OS delivered the URL before the webview JS loaded).
  try {
    const pending = await getPendingDeepLinks();
    if (Array.isArray(pending)) {
      for (const url of pending) {
        if (isValidDeepLink(url)) {
          window.dispatchEvent(
            new CustomEvent('app:deep-link', { detail: { url: sanitizeString(url, 2048) } }),
          );
        }
      }
    }
  } catch (err) {
    console.warn('[tauri-bridge] Failed to drain pending deep links:', err);
  }

  console.log('[tauri-bridge] initialized');
}
