import { getErrorMessage } from "./lib/error-message.js";
import axios, {
  AxiosError,
  type AxiosInstance,
  type AxiosResponse,
  type InternalAxiosRequestConfig,
} from "axios";
import { toast } from "sonner";
import { getBasePath } from "@/lib/base-path";
import { isElectron } from "@/lib/electron";
import { clearTermixSessionStorage } from "@/shell/TabContext";
import type { SSHHost } from "@/types/index";

// ============================================================================
// RBAC TYPE DEFINITIONS
// ============================================================================

export interface Role {
  id: number;
  name: string;
  displayName: string;
  description: string | null;
  isSystem: boolean;
  permissions: string[] | string | null;
  createdAt: string;
  updatedAt: string;
}

export interface UserRole {
  userId: string;
  roleId: number;
  roleName: string;
  roleDisplayName: string;
  grantedBy: string;
  grantedByUsername: string;
  grantedAt: string;
}

export interface AccessRecord {
  id: number;
  targetType: "user" | "role";
  userId: string | null;
  roleId: number | null;
  username: string | null;
  roleName: string | null;
  roleDisplayName: string | null;
  grantedBy: string;
  grantedByUsername: string;
  permissionLevel: "connect" | "view" | "edit" | "manage";
  expiresAt: string | null;
  createdAt: string;
}
import {
  apiLogger,
  authLogger,
  sshLogger,
  tunnelLogger,
  fileLogger,
  statsLogger,
  dashboardLogger,
  type LogContext,
} from "@/lib/frontend-logger";
import { dbHealthMonitor } from "@/lib/db-health-monitor";
import { asHttpError } from "@/lib/http-error";
import { getDeviceId } from "@/lib/device-id";

export type ServerStatus = {
  status: "online" | "reachable" | "offline";
  lastChecked: string;
};

export type SSHHostWithStatus = SSHHost & {
  status: "online" | "reachable" | "offline" | "unknown";
};

interface CpuMetrics {
  percent: number | null;
  cores: number | null;
  load: [number, number, number] | null;
}

interface MemoryMetrics {
  percent: number | null;
  usedGiB: number | null;
  totalGiB: number | null;
}

export interface DiskFilesystem {
  filesystem: string;
  type: string;
  mount: string;
  percent: number | null;
  usedHuman: string | null;
  totalHuman: string | null;
  availableHuman: string | null;
  usedBytes: number | null;
  totalBytes: number | null;
  availableBytes: number | null;
  label?: string;
}

interface DiskMetrics {
  percent: number | null;
  usedHuman: string | null;
  totalHuman: string | null;
  availableHuman?: string | null;
  mount?: string | null;
  filesystems?: DiskFilesystem[];
}

export interface NetworkInterface {
  name: string;
  ip: string;
  state: string;
  rx?: string | null;
  tx?: string | null;
  rxBytes?: string | null;
  txBytes?: string | null;
  rxRateBps?: number | null;
  txRateBps?: number | null;
}

export interface ProcessInfo {
  pid: string;
  user: string;
  cpu: string;
  mem: string;
  command: string;
}

export interface LoginRecord {
  user: string;
  ip: string;
  time: string;
  status: "success" | "failed";
}

export interface ListeningPort {
  protocol: "tcp" | "udp";
  localAddress: string;
  localPort: number;
  state?: string;
  pid?: number;
  process?: string;
}

export interface FirewallRule {
  chain: string;
  target: string;
  protocol: string;
  source: string;
  destination: string;
  dport?: string;
  sport?: string;
  state?: string;
  interface?: string;
  extra?: string;
}

export interface FirewallChain {
  name: string;
  policy: string;
  rules: FirewallRule[];
}

export type ServerMetrics = {
  cpu: CpuMetrics;
  memory: MemoryMetrics;
  disk: DiskMetrics;
  network?: { interfaces?: NetworkInterface[] };
  uptime?: { seconds?: number | null; formatted?: string | null };
  system?: {
    hostname?: string | null;
    os?: string | null;
    kernel?: string | null;
    arch?: string | null;
  };
  processes?: {
    total?: number | null;
    running?: number | null;
    top?: ProcessInfo[];
  };
  login_stats?: {
    recentLogins?: LoginRecord[];
    failedLogins?: LoginRecord[];
    totalLogins?: number;
    uniqueIPs?: number;
  };
  ports?: {
    source?: "ss" | "netstat" | "none";
    ports?: ListeningPort[];
  };
  firewall?: {
    type?: "iptables" | "nftables" | "none";
    status?: "active" | "inactive" | "unknown";
    chains?: FirewallChain[];
  };
  temperature?: {
    source?: "sysfs" | "sensors" | "none";
    highestCelsius?: number | null;
    sensors?: Array<{
      label: string;
      celsius: number;
    }>;
  };
  lastChecked: string;
};

export interface AuthResponse {
  success?: boolean;
  is_admin?: boolean;
  username?: string;
  userId?: string;
  is_oidc?: boolean;
  totp_enabled?: boolean;
  requires_totp?: boolean;
  temp_token?: string;
  rememberMe?: boolean;
  token?: string;
}

export interface UserInfo {
  totp_enabled: boolean;
  userId: string;
  username: string;
  is_admin: boolean;
  is_oidc: boolean;
  is_dual_auth?: boolean;
  password_hash?: string;
  data_unlocked?: boolean;
  show_donation_modal?: boolean;
}

export interface RemoteSyncUserInfo extends UserInfo {
  roles: UserRole[];
}

interface UserCount {
  count: number;
}

interface OIDCAuthorize {
  auth_url: string;
}

type ElectronApi = {
  isElectron?: boolean;
  getSetting?: (key: string) => Promise<string | null | undefined>;
  setSetting?: (key: string, value: string) => Promise<void>;
};

type ElectronWindow = Window &
  typeof globalThis & {
    IS_ELECTRON?: boolean;
    electronAPI?: ElectronApi;
    ReactNativeWebView?: unknown;
  };

// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================

export { isElectron };

function getLoggerForService(serviceName: string) {
  if (serviceName.includes("SSH") || serviceName.includes("ssh")) {
    return sshLogger;
  } else if (serviceName.includes("TUNNEL") || serviceName.includes("tunnel")) {
    return tunnelLogger;
  } else if (serviceName.includes("FILE") || serviceName.includes("file")) {
    return fileLogger;
  } else if (serviceName.includes("STATS") || serviceName.includes("stats")) {
    return statsLogger;
  } else if (serviceName.includes("AUTH") || serviceName.includes("auth")) {
    return authLogger;
  } else if (
    serviceName.includes("DASHBOARD") ||
    serviceName.includes("dashboard")
  ) {
    return dashboardLogger;
  } else {
    return apiLogger;
  }
}

const electronSettingsCache = new Map<string, string>();

if (isElectron()) {
  (async () => {
    try {
      const electronAPI = (window as ElectronWindow).electronAPI;

      if (electronAPI?.getSetting) {
        const settingsToLoad = ["rightClickCopyPaste"];
        for (const key of settingsToLoad) {
          const value = await electronAPI.getSetting(key);
          if (value !== null && value !== undefined) {
            // Only populate if not already set to prevent overwriting new values during login
            if (!localStorage.getItem(key)) {
              electronSettingsCache.set(key, value);
              localStorage.setItem(key, value);
              console.log(`[Electron] Loaded setting ${key} from main process`);
            } else {
              // Even if we don't overwrite localStorage, update the cache
              electronSettingsCache.set(key, localStorage.getItem(key)!);
            }
          }
        }
      }
    } catch (error) {
      console.error("[Electron] Failed to load settings cache:", error);
    }
  })();
}

export function setCookie(
  name: string,
  value: string,
  days = 7,
): void | Promise<void> {
  if (isElectron()) {
    try {
      if (name === "jwt") {
        return;
      }

      const electronAPI = (window as ElectronWindow).electronAPI;

      if (electronAPI?.setSetting) {
        electronSettingsCache.set(name, value);
        localStorage.setItem(name, value);
        electronAPI.setSetting(name, value).catch((err: Error) => {
          console.error(`[Electron] Failed to persist setting ${name}:`, err);
        });
      }

      console.log(`[Electron] Set setting: ${name}`);
    } catch (error) {
      console.error(`[Electron] Failed to set setting: ${name}`, error);
    }
  } else {
    const expires = new Date(Date.now() + days * 864e5).toUTCString();
    document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
  }
}

export function getCookie(name: string): string | undefined {
  if (isElectron()) {
    try {
      if (name === "jwt") {
        return undefined;
      }

      if (electronSettingsCache.has(name)) {
        return electronSettingsCache.get(name);
      }

      const token = localStorage.getItem(name) || undefined;
      if (token) {
        electronSettingsCache.set(name, token);
      }
      console.log(`[Electron] Get setting: ${name} = ${token}`);
      return token;
    } catch (error) {
      console.error(`[Electron] Failed to get setting: ${name}`, error);
      return undefined;
    }
  } else {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    const encodedToken =
      parts.length === 2 ? parts.pop()?.split(";").shift() : undefined;
    const token = encodedToken ? decodeURIComponent(encodedToken) : undefined;
    return token;
  }
}

let userWasAuthenticated = false;
let latestAuthSuccessAt = 0;

export function markUserAuthenticated(): void {
  userWasAuthenticated = true;
  latestAuthSuccessAt =
    typeof performance !== "undefined" ? performance.now() : Date.now();
}

export function isCurrentAuthInvalidationError(error: unknown): boolean {
  const authError = error as {
    __staleAuthInvalidation?: boolean;
  };

  if (authError.__staleAuthInvalidation) {
    return false;
  }

  const axiosError = error as AxiosError;
  const apiError = error as ApiError;
  const responseData = axiosError.response?.data as
    Record<string, unknown> | undefined;
  const errorCode = responseData?.code || apiError.code;
  const errorMessage = responseData?.error || apiError.message;
  const status = axiosError.response?.status || apiError.status;
  const isMissingAuthenticationToken =
    errorMessage === "Missing authentication token";

  return (
    status === 401 &&
    (errorCode === "SESSION_EXPIRED" ||
      errorCode === "SESSION_NOT_FOUND" ||
      (errorCode === "AUTH_REQUIRED" && userWasAuthenticated) ||
      errorMessage === "Invalid token" ||
      (errorMessage === "Authentication required" && userWasAuthenticated) ||
      (isMissingAuthenticationToken && userWasAuthenticated))
  );
}

function createApiInstance(
  baseURL: string,
  serviceName: string = "API",
): AxiosInstance {
  const instance = axios.create({
    baseURL,
    headers: { "Content-Type": "application/json" },
    timeout: 30000,
    withCredentials: true,
  });

  instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
    const startTime = performance.now();
    const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

    const configWithMetadata = config as AxiosRequestConfigExtended;
    configWithMetadata.startTime = startTime;
    configWithMetadata.requestId = requestId;

    const method = config.method?.toUpperCase() || "UNKNOWN";
    const url = config.url || "UNKNOWN";
    const fullUrl = `${config.baseURL}${url}`;

    const context: LogContext = {
      requestId,
      method,
      url: fullUrl,
      operation: "request_start",
    };

    const logger = getLoggerForService(serviceName);

    const isDevMode = process.env.NODE_ENV === "development";

    if (isDevMode) {
      logger.requestStart(method, fullUrl, context);
    }

    const deviceId = getDeviceId();
    if (deviceId) {
      if (config.headers.set) {
        config.headers.set("X-Termix-Device-ID", deviceId);
      } else {
        config.headers["X-Termix-Device-ID"] = deviceId;
      }
    }

    if (isElectron()) {
      if (config.headers.set) {
        config.headers.set("X-Electron-App", "true");
      } else {
        config.headers["X-Electron-App"] = "true";
      }
      const jwt = localStorage.getItem("jwt");
      if (jwt) {
        if (config.headers.set) {
          config.headers.set("Authorization", `Bearer ${jwt}`);
        } else {
          config.headers["Authorization"] = `Bearer ${jwt}`;
        }
      }
    }

    if (
      typeof window !== "undefined" &&
      (window as ElectronWindow).ReactNativeWebView
    ) {
      let platform = "Unknown";
      if (typeof navigator !== "undefined" && navigator.userAgent) {
        if (navigator.userAgent.includes("Android")) {
          platform = "Android";
        } else if (
          navigator.userAgent.includes("iPhone") ||
          navigator.userAgent.includes("iPad") ||
          navigator.userAgent.includes("iOS")
        ) {
          platform = "iOS";
        }
      }
      if (config.headers.set) {
        config.headers.set("User-Agent", `Termix-Mobile/${platform}`);
      } else {
        config.headers["User-Agent"] = `Termix-Mobile/${platform}`;
      }
    }

    return config;
  });

  instance.interceptors.response.use(
    (response: AxiosResponse) => {
      const endTime = performance.now();
      const responseConfig = response.config as AxiosRequestConfigExtended;
      const startTime = responseConfig.startTime;
      const requestId = responseConfig.requestId;
      const responseTime = Math.round(endTime - (startTime || endTime));

      const method = response.config.method?.toUpperCase() || "UNKNOWN";
      const url = response.config.url || "UNKNOWN";
      const fullUrl = `${response.config.baseURL}${url}`;

      const context: LogContext = {
        requestId,
        method,
        url: fullUrl,
        status: response.status,
        statusText: response.statusText,
        responseTime,
        operation: "request_success",
      };

      const logger = getLoggerForService(serviceName);

      if (process.env.NODE_ENV === "development") {
        logger.requestSuccess(
          method,
          fullUrl,
          response.status,
          responseTime,
          context,
        );
      }

      if (responseTime > 3000) {
        logger.warn(`🐌 Slow request: ${responseTime}ms`, context);
      }

      dbHealthMonitor.reportDatabaseSuccess();

      return response;
    },
    (error: AxiosErrorExtended) => {
      const endTime = performance.now();
      const startTime = error.config?.startTime;
      const requestId = error.config?.requestId;
      const responseTime = startTime
        ? Math.round(endTime - startTime)
        : undefined;

      const method = error.config?.method?.toUpperCase() || "UNKNOWN";
      const url = error.config?.url || "UNKNOWN";
      const fullUrl = error.config ? `${error.config.baseURL}${url}` : url;
      const status = error.response?.status;
      const message =
        (error.response?.data as { error?: string })?.error ||
        (error as Error).message ||
        "Unknown error";
      const errorCode =
        (error.response?.data as { code?: string })?.code || error.code;

      const context: LogContext = {
        requestId,
        method,
        url: fullUrl,
        status,
        responseTime,
        errorCode,
        errorMessage: message,
        operation: "request_error",
      };

      const logger = getLoggerForService(serviceName);
      // A caller can mark a request as a silent retry (see progressive /status
      // retry) so we don't spam error logs / health events on each attempt.
      const isSilentRetry = !!error.config?.__silentRetry;

      if (process.env.NODE_ENV === "development" && !isSilentRetry) {
        if (status === 401) {
          logger.authError(method, fullUrl, context);
        } else if (status === 0 || !status) {
          logger.networkError(method, fullUrl, message, context);
        } else {
          logger.requestError(
            method,
            fullUrl,
            status || 0,
            message,
            responseTime,
            context,
          );
        }
      }

      if (status === 401) {
        const errorCode = (error.response?.data as Record<string, unknown>)
          ?.code;
        const errorMessage = (error.response?.data as Record<string, unknown>)
          ?.error;
        const isSessionExpired = errorCode === "SESSION_EXPIRED";
        const isSessionNotFound = errorCode === "SESSION_NOT_FOUND";
        const isMissingAuthenticationToken =
          errorMessage === "Missing authentication token";
        const isInvalidToken =
          errorCode === "AUTH_REQUIRED" ||
          errorMessage === "Invalid token" ||
          errorMessage === "Authentication required" ||
          (isMissingAuthenticationToken && userWasAuthenticated);

        if (isSessionExpired || isSessionNotFound || isInvalidToken) {
          const requestStartedAt =
            typeof error.config?.startTime === "number"
              ? error.config.startTime
              : 0;
          const isStaleAuthInvalidation =
            latestAuthSuccessAt > 0 &&
            requestStartedAt > 0 &&
            requestStartedAt < latestAuthSuccessAt;

          if (isStaleAuthInvalidation) {
            (
              error as { __staleAuthInvalidation?: boolean }
            ).__staleAuthInvalidation = true;
            return Promise.reject(error);
          }

          if (isElectron()) {
            const electronAPI = (
              window as unknown as {
                electronAPI?: { clearSessionCookies?: () => Promise<void> };
              }
            ).electronAPI;
            electronAPI?.clearSessionCookies?.().catch(() => {});
          }

          if (typeof window !== "undefined") {
            document.cookie =
              "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
          }

          if (isSessionExpired && typeof window !== "undefined") {
            console.warn("Session expired - please log in again");
            toast.warning("Session expired. Please log in again.");
          }

          dbHealthMonitor.reportSessionExpired();

          userWasAuthenticated = false;
        }
      } else if (!isSilentRetry) {
        dbHealthMonitor.reportDatabaseError(error);
      }

      return Promise.reject(error);
    },
  );

  return instance;
}

// ============================================================================
// API INSTANCES
// ============================================================================

function isDev(): boolean {
  if (isElectron()) {
    return false;
  }

  return (
    process.env.NODE_ENV === "development" &&
    (window.location.port === "3000" ||
      window.location.port === "5173" ||
      window.location.port === "" ||
      window.location.hostname === "localhost" ||
      window.location.hostname === "127.0.0.1")
  );
}

const apiHost =
  import.meta.env.VITE_API_HOST ||
  (typeof window !== "undefined" ? window.location.hostname : "localhost");

interface AxiosRequestConfigExtended extends InternalAxiosRequestConfig {
  startTime?: number;
  requestId?: string;
  __silentRetry?: boolean;
}

interface AxiosErrorExtended extends AxiosError {
  config?: AxiosRequestConfigExtended;
}

export interface ElectronUpdateCheckResult {
  success: boolean;
  status?: "up_to_date" | "requires_update" | "beta";
  localVersion?: string;
  remoteVersion?: string;
  latest_release?: {
    tag_name: string;
    name: string;
    published_at: string;
    html_url: string;
    body: string;
  };
  cached?: boolean;
  cache_age?: number;
  error?: string;
}

export async function checkElectronUpdate(): Promise<ElectronUpdateCheckResult> {
  if (!isElectron())
    return { success: false, error: "Not in Electron environment" };

  try {
    const result = (await (
      window as Window &
        typeof globalThis & {
          IS_ELECTRON?: boolean;
          electronAPI?: { invoke?: (channel: string) => Promise<unknown> };
        }
    ).electronAPI?.invoke?.("check-electron-update")) as
      ElectronUpdateCheckResult | undefined;
    return result ?? { success: false, error: "Update check failed" };
  } catch (error) {
    console.error("Failed to check Electron update:", error);
    return { success: false, error: "Update check failed" };
  }
}

function getApiUrl(path: string, defaultPort: number): string {
  const devMode = isDev();
  const electronMode = isElectron();

  if (electronMode) {
    // The desktop app always runs its embedded local backend as the
    // source of truth. A configured remote sync server is a separate,
    // narrow connection used only by the sync engine (see
    // remote-sync-axios.ts), not by these shared instances.
    return `http://localhost:${defaultPort}${path}`;
  } else if (devMode) {
    if (!import.meta.env.VITE_API_HOST) {
      return `/__termix_api/${defaultPort}${path}`;
    }
    const protocol = window.location.protocol === "https:" ? "https" : "http";
    const sslPort = protocol === "https" ? 8443 : defaultPort;
    const url = `${protocol}://${apiHost}:${sslPort}${path}`;
    return url;
  } else {
    return getBasePath() + path;
  }
}

// ============================================================================
// PER-HOST ORIGIN ROUTING (Electron desktop only)
// ============================================================================
//
// hostApi/fileManagerApi/tunnelApi/statsApi above always point at the
// embedded local backend -- they're the shared, always-on instances. When a
// host's connection origin resolves to "remote" (see
// src/ui/lib/connection-origin.ts), the backend that actually holds that
// host's live SSH session is the connected remote server instead, so file
// manager, tunnel, and stats calls for that host must follow it there.
//
// These dynamically-baseURL'd instances resolve the remote server's URL and
// JWT fresh on every request (cheap, and correct even if the user
// connects/disconnects remote sync without an app reload).

function createRemoteOriginApiInstance(path: string): AxiosInstance {
  const instance = axios.create({
    headers: { "Content-Type": "application/json" },
    timeout: 30000,
  });

  instance.interceptors.request.use(
    async (config: InternalAxiosRequestConfig) => {
      const [remoteConfig, remoteJwt] = await Promise.all([
        window.electronAPI?.invoke?.("get-remote-sync-config") as Promise<{
          serverUrl?: string;
        } | null>,
        window.electronAPI?.invoke?.("get-remote-sync-jwt") as Promise<
          string | null
        >,
      ]);

      const baseUrl = (remoteConfig?.serverUrl || "").replace(/\/$/, "");
      config.baseURL = baseUrl
        ? `${baseUrl}${path}`
        : "http://no-server-configured";

      if (config.headers.set) {
        config.headers.set("X-Electron-App", "true");
        if (remoteJwt)
          config.headers.set("Authorization", `Bearer ${remoteJwt}`);
      } else {
        config.headers["X-Electron-App"] = "true";
        if (remoteJwt) config.headers["Authorization"] = `Bearer ${remoteJwt}`;
      }

      return config;
    },
  );

  return instance;
}

let remoteFileManagerApi: AxiosInstance | null = null;
let remoteTunnelApi: AxiosInstance | null = null;
let remoteStatsApi: AxiosInstance | null = null;
let remoteGuacamoleApi: AxiosInstance | null = null;

export function getRemoteFileManagerApi(): AxiosInstance {
  if (!remoteFileManagerApi) {
    remoteFileManagerApi = createRemoteOriginApiInstance("/ssh/file_manager");
  }
  return remoteFileManagerApi;
}

export function getRemoteTunnelApi(): AxiosInstance {
  if (!remoteTunnelApi) {
    remoteTunnelApi = createRemoteOriginApiInstance("/ssh");
  }
  return remoteTunnelApi;
}

export function getRemoteStatsApi(): AxiosInstance {
  if (!remoteStatsApi) {
    remoteStatsApi = createRemoteOriginApiInstance("");
  }
  return remoteStatsApi;
}

export function getRemoteGuacamoleApi(): AxiosInstance {
  if (!remoteGuacamoleApi) {
    remoteGuacamoleApi = createRemoteOriginApiInstance("");
  }
  return remoteGuacamoleApi;
}

// Maps a live SSH session (keyed by sessionId, which today is the host's
// numeric id as a string -- see ensureSSHSessionForHost) to the resolved
// origin it was connected through, so every subsequent file-manager call
// for that session reaches the backend that actually holds it.
const sessionOrigins = new Map<string, "local" | "remote">();

export function setSessionOrigin(
  sessionId: string,
  origin: "local" | "remote",
): void {
  sessionOrigins.set(sessionId, origin);
}

export function clearSessionOrigin(sessionId: string): void {
  sessionOrigins.delete(sessionId);
}

export function getFileManagerApiForSession(sessionId: string): AxiosInstance {
  return sessionOrigins.get(sessionId) === "remote"
    ? getRemoteFileManagerApi()
    : fileManagerApi;
}

export function getTunnelApiForOrigin(
  origin: "local" | "remote",
): AxiosInstance {
  return origin === "remote" ? getRemoteTunnelApi() : tunnelApi;
}

export function getStatsApiForOrigin(
  origin: "local" | "remote",
): AxiosInstance {
  return origin === "remote" ? getRemoteStatsApi() : statsApi;
}

function initializeApiInstances() {
  // Host Management API (port 30001) - supports SSH, RDP, VNC, Telnet
  hostApi = createApiInstance(getApiUrl("/host", 30001), "HOST");
  sshHostApi = hostApi;

  // Tunnel Management API (port 30003)
  tunnelApi = createApiInstance(getApiUrl("/ssh", 30003), "TUNNEL");

  // File Manager Operations API (port 30004)
  fileManagerApi = createApiInstance(
    getApiUrl("/ssh/file_manager", 30004),
    "FILE_MANAGER",
  );

  // Server Statistics API (port 30005)
  statsApi = createApiInstance(getApiUrl("", 30005), "STATS");

  // Authentication API (port 30001)
  authApi = createApiInstance(getApiUrl("", 30001), "AUTH");

  // Dashboard API (port 30006)
  dashboardApi = createApiInstance(getApiUrl("", 30006), "DASHBOARD");

  // RBAC API (port 30001)
  rbacApi = createApiInstance(getApiUrl("", 30001), "RBAC");

  // Docker Management API (port 30007)
  dockerApi = createApiInstance(getApiUrl("/docker", 30007), "DOCKER");

  // Tmux Monitor API (port 30010) --- tmux-monitor ---
  tmuxMonitorApi = createApiInstance(
    getApiUrl("/tmux_monitor", 30010),
    "TMUX_MONITOR",
  );

  // Homepage API (port 30012)
  homepageApi = createApiInstance(getApiUrl("/homepage", 30012), "HOMEPAGE");
}

// Host Management API (port 30001) - supports SSH, RDP, VNC, Telnet
export let hostApi: AxiosInstance;
// Backward compatibility
export let sshHostApi: AxiosInstance;

// Tunnel Management API (port 30003)
export let tunnelApi: AxiosInstance;

// File Manager Operations API (port 30004)
export let fileManagerApi: AxiosInstance;

// Server Statistics API (port 30005)
export let statsApi: AxiosInstance;

// Authentication API (port 30001)
export let authApi: AxiosInstance;

// Dashboard API (port 30006)
export let dashboardApi: AxiosInstance;

// RBAC API (port 30001)
export let rbacApi: AxiosInstance;

// Docker Management API (port 30007)
export let dockerApi: AxiosInstance;

// Tmux Monitor API (port 30010) --- tmux-monitor ---
export let tmuxMonitorApi: AxiosInstance;

// Homepage API (port 30012)
export let homepageApi: AxiosInstance;

// Pre-initialize with default values to avoid undefined errors during early mounting
initializeApiInstances();

let _resolveAppReady!: () => void;
export const appReadyPromise: Promise<void> = new Promise((resolve) => {
  _resolveAppReady = resolve;
});

function initializeApp() {
  initializeApiInstances();
  _resolveAppReady();
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", initializeApp);
} else {
  initializeApp();
}

// ============================================================================
// ERROR HANDLING
// ============================================================================

class ApiError extends Error {
  constructor(
    message: string,
    public status?: number,
    public code?: string,
  ) {
    super(message);
    this.name = "ApiError";
  }
}

export function handleApiError(error: unknown, operation: string): never {
  const context: LogContext = {
    operation: "error_handling",
    errorOperation: operation,
  };

  if (axios.isAxiosError(error)) {
    const status = error.response?.status;
    const message =
      error.response?.data?.message ||
      error.response?.data?.error ||
      error.message;
    const code = error.response?.data?.code || error.response?.data?.error;
    const url = error.config?.url;
    const method = error.config?.method?.toUpperCase();

    const errorContext: LogContext = {
      ...context,
      method,
      url,
      status,
      errorCode: code,
      errorMessage: message,
    };

    if (status === 401) {
      authLogger.warn(
        `Auth failed: ${method} ${url} - ${message}`,
        errorContext,
      );

      const isLoginEndpoint = url?.includes("/users/login");
      const errorMessage = isLoginEndpoint
        ? message
        : "Authentication required. Please log in again.";

      throw new ApiError(errorMessage, 401, code || "AUTH_REQUIRED");
    } else if (status === 403) {
      authLogger.warn(`Access denied: ${method} ${url}`, errorContext);
      const apiError = new ApiError(
        code === "TOTP_REQUIRED"
          ? message
          : "Access denied. You do not have permission to perform this action.",
        403,
        code || "ACCESS_DENIED",
      );
      (apiError as ApiError & { response?: unknown }).response = error.response;
      throw apiError;
    } else if (status === 404) {
      apiLogger.warn(`Not found: ${method} ${url}`, errorContext);
      throw new ApiError(
        "Resource not found. The requested item may have been deleted.",
        404,
        "NOT_FOUND",
      );
    } else if (status === 409) {
      apiLogger.warn(`Conflict: ${method} ${url}`, errorContext);
      throw new ApiError(
        "Conflict. The resource already exists or is in use.",
        409,
        "CONFLICT",
      );
    } else if (status === 422) {
      apiLogger.warn(
        `Validation error: ${method} ${url} - ${message}`,
        errorContext,
      );
      throw new ApiError(
        "Validation error. Please check your input and try again.",
        422,
        "VALIDATION_ERROR",
      );
    } else if (status && status >= 500) {
      apiLogger.error(
        `Server error: ${method} ${url} - ${message}`,
        error,
        errorContext,
      );
      throw new ApiError(
        "Server error occurred. Please try again later.",
        status,
        "SERVER_ERROR",
      );
    } else if (status === 0) {
      if (url.includes("no-server-configured")) {
        apiLogger.error(
          `No server configured: ${method} ${url}`,
          error,
          errorContext,
        );
        throw new ApiError(
          "No server configured. Please configure a Termix server first.",
          0,
          "NO_SERVER_CONFIGURED",
        );
      }
      apiLogger.error(
        `Network error: ${method} ${url} - ${message}`,
        error,
        errorContext,
      );
      throw new ApiError(
        "Network error. Please check your connection and try again.",
        0,
        "NETWORK_ERROR",
      );
    } else {
      apiLogger.error(
        `Request failed: ${method} ${url} - ${message}`,
        error,
        errorContext,
      );
      throw new ApiError(message || `Failed to ${operation}`, status, code);
    }
  }

  if (error instanceof ApiError) {
    throw error;
  }

  const errorMessage = getErrorMessage(error);
  apiLogger.error(
    `Unexpected error during ${operation}: ${errorMessage}`,
    error,
    context,
  );
  throw new ApiError(
    `Unexpected error during ${operation}: ${errorMessage}`,
    undefined,
    "UNKNOWN_ERROR",
  );
}

// ============================================================================

// ============================================================================
// HOST-TO-HOST TRANSFER
// ============================================================================

export type TransferMethodPreference = "auto" | "tar" | "item_sftp";

export interface TransferScanSummary {
  fileCount: number;
  totalBytes: number;
  largestFileBytes: number;
  incompressibleRatio: number;
}

export interface TransferMethodPreview {
  methodPreference: TransferMethodPreference;
  resolvedMethod: "tar" | "item_sftp";
  reasonKey: string;
  sourcePlatform: "unix" | "windows";
  destPlatform: "unix" | "windows";
  sourceHasTar: boolean;
  destHasTar: boolean;
  summary: TransferScanSummary;
}

export async function getTransferMethodPreview(
  sourceSessionId: string,
  sourcePaths: string[],
  destSessionId: string,
  destPath: string,
  methodPreference?: TransferMethodPreference,
): Promise<TransferMethodPreview> {
  try {
    const response = await fileManagerApi.post("/ssh/transferMethodPreview", {
      sourceSessionId,
      sourcePaths,
      destSessionId,
      destPath,
      methodPreference: methodPreference ?? "auto",
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "preview transfer method");
    throw error;
  }
}

export interface TransferHopMetrics {
  id: string;
  mbPerSec?: number;
}

export interface TransferTimings {
  prepareDestMs?: number;
  compressMs?: number;
  transferMs?: number;
  extractMs?: number;
  verifyMs?: number;
  directBenchmarkMs?: number;
  relayBenchmarkMs?: number;
  sourceDeleteMs?: number;
  totalMs?: number;
  transferBytes?: number;
  endToEndMbPerSec?: number;
  hops?: TransferHopMetrics[];
}

export function getTransferProgressPercent(
  status: TransferProgressResponse,
): number | undefined {
  if (
    status.bytesTransferred !== undefined &&
    status.totalBytes !== undefined &&
    status.totalBytes > 0
  ) {
    return Math.min(
      100,
      Math.round((status.bytesTransferred / status.totalBytes) * 100),
    );
  }
  if (
    status.itemsCompleted !== undefined &&
    status.totalItems !== undefined &&
    status.totalItems > 0
  ) {
    return Math.min(
      100,
      Math.round((status.itemsCompleted / status.totalItems) * 100),
    );
  }
  return undefined;
}

export function formatTransferMbPerSec(
  mbPerSec?: number,
  _bytes?: number,
  _ms?: number,
): string {
  if (mbPerSec === undefined || mbPerSec <= 0) return "";
  if (mbPerSec < 1) return `${(mbPerSec * 1024).toFixed(0)} KB/s`;
  return `${mbPerSec.toFixed(1)} MB/s`;
}

export function formatDurationMs(ms?: number): string {
  if (ms === undefined) return "";
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
  const minutes = Math.floor(ms / 60000);
  const seconds = Math.round((ms % 60000) / 1000);
  return `${minutes}m ${seconds}s`;
}

interface TransferProgressTracker {
  update(status: TransferProgressResponse): {
    rate: number | undefined;
    stalled: boolean;
  };
}

export function createTransferProgressTracker(): TransferProgressTracker {
  let lastBytes: number | undefined;
  let lastTime: number | undefined;
  let lastRate: number | undefined;
  let lastActivityTime: number | undefined;
  const STALL_THRESHOLD_MS = 5000;

  return {
    update(status) {
      const now = Date.now();
      const bytes = status.bytesTransferred;

      if (
        bytes !== undefined &&
        lastBytes !== undefined &&
        lastTime !== undefined
      ) {
        const deltaBytes = bytes - lastBytes;
        const deltaMs = now - lastTime;
        if (deltaMs > 0 && deltaBytes >= 0) {
          lastRate = (deltaBytes / deltaMs / 1024 / 1024) * 1000;
          if (deltaBytes > 0) lastActivityTime = now;
        }
      } else if (bytes !== undefined) {
        lastActivityTime = now;
      }

      lastBytes = bytes;
      lastTime = now;

      const stalled =
        lastActivityTime !== undefined &&
        now - lastActivityTime > STALL_THRESHOLD_MS &&
        status.status === "running" &&
        status.phase === "transferring";

      return { rate: lastRate, stalled };
    },
  };
}

export interface TransferProgressResponse {
  transferId: string;
  status: "running" | "success" | "partial" | "error" | "cancelled";
  phase:
    | "compressing"
    | "transferring"
    | "benchmarking"
    | "verifying"
    | "extracting"
    | "reconnecting";
  bytesTransferred?: number;
  totalBytes?: number;
  itemsCompleted?: number;
  totalItems?: number;
  failedPaths?: string[];
  message?: string;
  method?: "stream" | "tar" | "item_sftp" | "direct_rsync";
  sourcePaths?: string[];
  destPath?: string;
  sourceSessionId?: string;
  destSessionId?: string;
  startedAt?: number;
  timings?: TransferTimings;
  sourceDeleted?: boolean;
  moveRequested?: boolean;
  partialDestRemaining?: boolean;
  cleanupCompleted?: boolean;
  retryable?: boolean;
  integrityVerified?: boolean;
  parallelSegmentCount?: number;
}

export async function transferToHost(
  sourceSessionId: string,
  sourcePaths: string[],
  destSessionId: string,
  destPath: string,
  move?: boolean,
  methodPreference?: TransferMethodPreference,
  parallelSegmentCount?: number,
): Promise<{ transferId: string }> {
  try {
    fileLogger.info("Starting host transfer", {
      operation: "host_transfer",
      sourceSessionId,
      destSessionId,
      sourcePaths,
      destPath,
      move,
      methodPreference,
      parallelSegmentCount,
    });

    const response = await fileManagerApi.post("/ssh/transferToHost", {
      sourceSessionId,
      sourcePaths,
      destSessionId,
      destPath,
      move,
      methodPreference: methodPreference ?? "auto",
      parallelSegmentCount,
    });

    return response.data;
  } catch (error) {
    fileLogger.error("Failed to start host transfer", error, {
      operation: "host_transfer",
      sourceSessionId,
      destSessionId,
      sourcePaths,
    });
    handleApiError(error, "transfer to host");
    throw error;
  }
}

export async function getTransferStatus(
  transferId: string,
): Promise<TransferProgressResponse> {
  try {
    const response = await fileManagerApi.get(
      `/ssh/transferStatus/${transferId}`,
    );
    return response.data;
  } catch (error) {
    handleApiError(error, "get transfer status");
    throw error;
  }
}

export async function listActiveTransfers(): Promise<{
  transfers: TransferProgressResponse[];
}> {
  try {
    const response = await fileManagerApi.get("/ssh/activeTransfers");
    return response.data;
  } catch (error) {
    handleApiError(error, "list active transfers");
    throw error;
  }
}

export async function cancelTransferToHost(transferId: string): Promise<void> {
  try {
    await fileManagerApi.post(`/ssh/transferCancel/${transferId}`);
  } catch (error) {
    fileLogger.warn("Transfer cancel request failed (non-fatal)", {
      operation: "host_transfer",
      transferId,
      error,
    });
  }
}

export async function cleanupCancelledTransfer(
  transferId: string,
): Promise<{ removedPaths: string[]; failedPaths: string[] }> {
  try {
    const response = await fileManagerApi.post(
      `/ssh/transferCleanup/${transferId}`,
    );
    return response.data;
  } catch (error) {
    handleApiError(error, "clean up cancelled transfer");
    throw error;
  }
}

export async function retryTransferToHost(
  transferId: string,
): Promise<{ ok: boolean; transferId: string }> {
  try {
    const response = await fileManagerApi.post(
      `/ssh/transferRetry/${transferId}`,
    );
    return response.data;
  } catch (error) {
    handleApiError(error, "retry transfer");
    throw error;
  }
}

export async function pollTransferUntilComplete(
  transferId: string,
  onProgress?: (status: TransferProgressResponse) => void,
  intervalMs = 500,
): Promise<TransferProgressResponse> {
  return new Promise((resolve, reject) => {
    const poll = async () => {
      try {
        const status = await getTransferStatus(transferId);
        onProgress?.(status);

        if (
          status.status === "success" ||
          status.status === "partial" ||
          status.status === "error" ||
          status.status === "cancelled"
        ) {
          resolve(status);
          return;
        }

        setTimeout(poll, intervalMs);
      } catch (err) {
        reject(err);
      }
    };

    void poll();
  });
}

// ============================================================================
// FILE MANAGER DATA
// ============================================================================

export interface TransferDestination {
  id: number;
  userId: string;
  sourceHostId: number;
  destHostId: number;
  destPath: string;
  destPathLabel?: string;
  lastUsed?: string;
}

export async function getTransferRecent(
  sourceHostId: number,
): Promise<TransferDestination[]> {
  try {
    const response = await authApi.get("/host/transfer/recent", {
      params: { sourceHostId },
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "get transfer recent destinations");
    throw error;
  }
}

export async function addTransferRecent(
  sourceHostId: number,
  destHostId: number,
  destPath: string,
  destPathLabel?: string,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/host/transfer/recent", {
      sourceHostId,
      destHostId,
      destPath,
      destPathLabel,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "add transfer recent destination");
    throw error;
  }
}

export {
  getSSHHosts,
  createSSHHost,
  updateSSHHost,
  wakeOnLan,
  bulkImportSSHHosts,
  importSSHConfigHosts,
  discoverProxmoxGuests,
  discoverProxmoxGuestsStream,
  syncProxmoxGuests,
  bulkUpdateSSHHosts,
  reorderSSHHosts,
  deleteSSHHost,
  getSSHHostById,
  exportSSHHostWithCredentials,
  exportAllSSHHosts,
  enableAutoStart,
  disableAutoStart,
  getAutoStartStatus,
  testProxyConnection,
} from "@/api/ssh-host-management-api";

export {
  getTunnelStatuses,
  subscribeTunnelStatuses,
  getTunnelStatusByName,
  connectTunnel,
  disconnectTunnel,
  cancelTunnel,
  getC2STunnelPresets,
  createC2STunnelPreset,
  updateC2STunnelPreset,
  deleteC2STunnelPreset,
} from "@/api/tunnel-api";

export {
  getFileManagerRecent,
  addFileManagerRecent,
  removeFileManagerRecent,
  getFileManagerPinned,
  addFileManagerPinned,
  removeFileManagerPinned,
  getFileManagerShortcuts,
  addFileManagerShortcut,
  removeFileManagerShortcut,
} from "@/api/file-manager-metadata-api";

export {
  connectSSH,
  disconnectSSH,
  verifySSHTOTP,
  verifySSHWarpgate,
  quickConnect,
  getSSHStatus,
  keepSSHAlive,
  listSSHFiles,
  identifySSHSymlink,
  resolveSSHPath,
  readSSHFile,
  writeSSHFile,
  uploadSSHFile,
  downloadSSHFile,
  downloadSSHFileStream,
  createSSHFile,
  createSSHFolder,
  deleteSSHItem,
  setSudoPassword,
  copySSHItem,
  renameSSHItem,
  moveSSHItem,
  changeSSHPermissions,
  extractSSHArchive,
  compressSSHFiles,
  ensureSSHSessionForHost,
  browseSSHDirectory,
  type HostConnectionState,
  type EnsureSSHSessionResult,
  type BrowseSSHDirectoryResult,
} from "@/api/ssh-file-operations-api";

export {
  getRecentFiles,
  addRecentFile,
  removeRecentFile,
  getPinnedFiles,
  addPinnedFile,
  removePinnedFile,
  getFolderShortcuts,
  addFolderShortcut,
  removeFolderShortcut,
} from "@/api/file-manager-data-api";

export {
  getAllServerStatuses,
  getServerStatusById,
  getServerMetricsById,
  startMetricsPolling,
  stopMetricsPolling,
  sendMetricsHeartbeat,
  registerMetricsViewer,
  unregisterMetricsViewer,
  submitMetricsTOTP,
  refreshServerPolling,
  notifyHostCreatedOrUpdated,
} from "@/api/host-metrics-status-api";

export {
  getHostMetricsLayout,
  saveHostMetricsLayout,
  getHostPlatform,
  managerGet,
  managerGetSub,
  managerPost,
  type PlatformInfo,
} from "@/api/host-metrics-api";

export {
  getProxmoxStats,
  startProxmoxStatsPolling,
  stopProxmoxStatsPolling,
  sendProxmoxStatsHeartbeat,
  getProxmoxStatsHistory,
  type ProxmoxStatsHistoryRow,
  type ProxmoxStatsHistoryResponse,
} from "@/api/proxmox-stats-api";

export {
  getHostSidebarPreferences,
  saveHostSidebarPreferences,
} from "@/api/host-sidebar-preferences-api";

export {
  getCredentialSidebarPreferences,
  saveCredentialSidebarPreferences,
} from "@/api/credential-sidebar-preferences-api";

export { getUiPreferences, saveUiPreferences } from "@/api/ui-preferences-api";

export {
  getGlobalMonitoringSettings,
  updateGlobalMonitoringSettings,
  getLogLevel,
  updateLogLevel,
  getSessionTimeout,
  updateSessionTimeout,
  getGuacamoleSettings,
  updateGuacamoleSettings,
} from "@/api/settings-api";

// ============================================================================
// AUTHENTICATION
// ============================================================================

export async function registerUser(
  username: string,
  password: string,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/users/create", {
      username,
      password,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "register user");
  }
}

export async function adminCreateUser(
  username: string,
  password: string,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/users/admin-create", {
      username,
      password,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "admin create user");
  }
}

export async function loginUser(
  username: string,
  password: string,
  rememberMe: boolean = false,
): Promise<AuthResponse> {
  try {
    const response = await authApi.post("/users/login", {
      username,
      password,
      rememberMe,
    });

    if (response.data.token) {
      localStorage.setItem("jwt", response.data.token);
    }

    if (response.data.success && !response.data.requires_totp) {
      markUserAuthenticated();
    }

    return {
      success: response.data.success,
      is_admin: response.data.is_admin,
      username: response.data.username,
      requires_totp: response.data.requires_totp,
      temp_token: response.data.temp_token,
      rememberMe: response.data.rememberMe,
      is_oidc: response.data.is_oidc,
      totp_enabled: response.data.totp_enabled,
      token: response.data.token,
    };
  } catch (error) {
    throw handleApiError(error, "login user");
  }
}

export async function requestTrustedProxyLogin(): Promise<{
  enabled: boolean;
  success?: boolean;
  username?: string;
  userId?: string;
  is_admin?: boolean;
  token?: string;
}> {
  const response = await authApi.post("/users/proxy-login");
  if (response.data.token) localStorage.setItem("jwt", response.data.token);
  if (response.data.success) markUserAuthenticated();
  return response.data;
}

export async function logoutUser(): Promise<{
  success: boolean;
  message: string;
}> {
  try {
    const response = await authApi.post("/users/logout");

    clearTermixSessionStorage();

    if (isElectron()) {
      const electronAPI = (
        window as unknown as {
          electronAPI?: { clearSessionCookies?: () => Promise<void> };
        }
      ).electronAPI;
      electronAPI?.clearSessionCookies?.().catch(() => {});
    } else {
      const isSecure = window.location.protocol === "https:";
      const cookieString = isSecure
        ? "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; Secure; SameSite=Lax"
        : "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax";
      document.cookie = cookieString;
    }

    return response.data;
  } catch (error) {
    clearTermixSessionStorage();

    if (isElectron()) {
      const electronAPI = (
        window as unknown as {
          electronAPI?: { clearSessionCookies?: () => Promise<void> };
        }
      ).electronAPI;
      electronAPI?.clearSessionCookies?.().catch(() => {});
    } else {
      const isSecure = window.location.protocol === "https:";
      const cookieString = isSecure
        ? "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; Secure; SameSite=Lax"
        : "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax";
      document.cookie = cookieString;
    }
    handleApiError(error, "logout user");
  }
}

export async function getUserInfo(): Promise<UserInfo> {
  try {
    const response = await authApi.get("/users/me");
    markUserAuthenticated();
    return response.data;
  } catch (error) {
    handleApiError(error, "fetch user info");
  }
}

export async function getRemoteSyncUserInfo(): Promise<RemoteSyncUserInfo | null> {
  if (!isElectron()) return null;
  try {
    // ?? null so a missing preload bridge matches the declared return type
    // rather than resolving to undefined.
    return ((await window.electronAPI?.invoke?.("get-remote-sync-user-info")) ??
      null) as RemoteSyncUserInfo | null;
  } catch {
    return null;
  }
}

export async function dismissDonationModal(): Promise<void> {
  try {
    await authApi.post("/users/me/dismiss-donation-modal");
  } catch (error) {
    handleApiError(error, "dismiss donation modal");
  }
}

export async function getCurrentToken(): Promise<string | null> {
  try {
    const response = await authApi.get("/users/me/token");
    return response.data?.token ?? null;
  } catch {
    return null;
  }
}

export async function unlockUserData(
  password: string,
): Promise<{ success: boolean; message: string }> {
  try {
    const response = await authApi.post("/users/unlock-data", { password });
    return response.data;
  } catch (error) {
    handleApiError(error, "unlock user data");
  }
}

export async function getRegistrationAllowed(): Promise<{ allowed: boolean }> {
  try {
    const response = await authApi.get("/users/registration-allowed");
    return response.data;
  } catch (error) {
    handleApiError(error, "check registration status");
  }
}

export async function getPasswordLoginAllowed(): Promise<{ allowed: boolean }> {
  try {
    const response = await authApi.get("/users/password-login-allowed");
    return response.data;
  } catch (error) {
    handleApiError(error, "check password login status");
  }
}

export async function getOIDCConfig(): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.get("/users/oidc-config");
    return response.data;
  } catch (error: unknown) {
    const httpError = asHttpError(error);
    console.warn(
      "Failed to fetch OIDC config:",
      httpError.response?.data?.error || httpError.message,
    );
    return null;
  }
}

export async function getAdminOIDCConfig(): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.get("/users/oidc-config/admin");
    return response.data;
  } catch (error) {
    handleApiError(error, "fetch admin OIDC config");
  }
}

export async function getSetupRequired(): Promise<{ setup_required: boolean }> {
  try {
    const response = await authApi.get("/users/setup-required");
    return response.data;
  } catch (error) {
    handleApiError(error, "check setup status");
  }
}

// Module-level (not per-component) so concurrent/duplicate mounts -- e.g.
// React StrictMode's intentional double-invoke of effects in dev, or any
// other double-call -- share one in-flight request instead of each minting
// its own session. Minting more than one session here is not just wasted
// work: the backend sets a fresh `jwt` cookie on every call, and since the
// auth middleware prefers the cookie over the Authorization header, a
// second mint silently invalidates whichever token the app already started
// using, surfacing as a spurious "session expired/revoked" on the very
// next request.
let desktopAutoSessionRequest: Promise<DesktopAutoSessionOutcome> | null = null;

// A 403 from the auto-session endpoint means the backend positively
// evaluated the request and declined (not loopback, or not exactly one
// local user) -- that verdict won't change by retrying. Any other failure
// (connection refused, timeout, 5xx) most likely means the embedded
// backend process hasn't finished booting yet, which is routine on a cold
// first launch, so it's worth retrying rather than treated as final.
export type DesktopAutoSessionOutcome =
  | { kind: "success"; data: AuthResponse }
  | { kind: "declined" }
  | { kind: "retry" };

/**
 * Electron-only, non-iframed local login: exchanges the embedded backend's
 * single auto-provisioned local user for a session without ever showing a
 * login form. Only succeeds when exactly one user exists locally (a synced
 * or otherwise multi-user install never satisfies this, and falls through
 * to a normal login screen instead).
 */
export async function requestDesktopAutoSession(): Promise<DesktopAutoSessionOutcome> {
  if (desktopAutoSessionRequest) return desktopAutoSessionRequest;

  desktopAutoSessionRequest = (async () => {
    try {
      const response = await authApi.post("/users/internal/auto-session");
      if (response.data?.token) {
        localStorage.setItem("jwt", response.data.token);
      }
      if (response.data?.success) {
        markUserAuthenticated();
        return { kind: "success" as const, data: response.data };
      }
      return { kind: "declined" as const };
    } catch (err) {
      const status =
        (err as { status?: number; response?: { status?: number } })?.status ??
        (err as { response?: { status?: number } })?.response?.status;
      if (status === 403) {
        return { kind: "declined" as const };
      }
      return { kind: "retry" as const };
    }
  })();

  try {
    return await desktopAutoSessionRequest;
  } finally {
    desktopAutoSessionRequest = null;
  }
}

export async function getUserCount(): Promise<UserCount> {
  try {
    const response = await authApi.get("/users/count");
    return response.data;
  } catch (error) {
    handleApiError(error, "fetch user count");
  }
}

export async function initiatePasswordReset(
  username: string,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/users/initiate-reset", { username });
    return response.data;
  } catch (error) {
    handleApiError(error, "initiate password reset");
  }
}

export async function verifyPasswordResetCode(
  username: string,
  resetCode: string,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/users/verify-reset-code", {
      username,
      resetCode,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "verify reset code");
  }
}

export async function completePasswordReset(
  username: string,
  tempToken: string,
  newPassword: string,
  confirmDataWipe = false,
): Promise<Record<string, unknown>> {
  try {
    const response = await authApi.post("/users/complete-reset", {
      username,
      tempToken,
      newPassword,
      confirmDataWipe,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "complete password reset");
  }
}

export async function changePassword(oldPassword: string, newPassword: string) {
  try {
    const response = await authApi.post("/users/change-password", {
      oldPassword,
      newPassword,
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "change password");
  }
}

export async function getOIDCAuthorizeUrl(
  rememberMe = false,
  desktopCallbackPort?: number,
  providerId?: number,
  appCallbackUrl?: string,
): Promise<OIDCAuthorize> {
  try {
    const response = await authApi.get("/users/oidc/authorize", {
      params: { rememberMe, desktopCallbackPort, providerId, appCallbackUrl },
    });
    return response.data;
  } catch (error) {
    handleApiError(error, "get OIDC authorize URL");
  }
}

// ============================================================================
export {
  getUserList,
  getSessions,
  revokeSession,
  revokeAllUserSessions,
  createApiKey,
  getApiKeys,
  deleteApiKey,
  makeUserAdmin,
  removeAdminStatus,
  deleteUser,
  deleteAccount,
  updateRegistrationAllowed,
  getOidcAutoProvision,
  updateOidcAutoProvision,
  getOidcSilentLoginDefault,
  updateOidcSilentLoginDefault,
  updatePasswordLoginAllowed,
  getPasswordResetAllowed,
  updatePasswordResetAllowed,
  updateOIDCConfig,
  disableOIDCConfig,
  getCommandHistoryEnabled,
  updateCommandHistoryEnabled,
  adminResetUserPassword,
  adminDisableUserTotp,
  adminExportUserData,
  type ApiKey,
  type CreatedApiKey,
} from "@/api/user-management-api";

// ADMIN USER DATA MANAGEMENT
// ============================================================================

export {
  adminGetUserHosts,
  adminCreateUserHost,
  adminUpdateUserHost,
  adminDeleteUserHost,
  adminGetHostPassword,
  adminGetUserCredentials,
  adminGetUserCredentialDetails,
  adminCreateUserCredential,
  adminUpdateUserCredential,
  adminDeleteUserCredential,
  adminGetUserSnippets,
  adminCreateUserSnippet,
  adminUpdateUserSnippet,
  adminDeleteUserSnippet,
} from "@/api/admin-user-data-api";

export {
  setupTOTP,
  enableTOTP,
  disableTOTP,
  verifyTOTPLogin,
  generateBackupCodes,
  getUserAlerts,
  dismissAlert,
  getReleasesRSS,
  getVersionInfo,
  releaseUrlFrom,
  getDatabaseHealth,
} from "@/api/system-status-api";

// SSH CREDENTIALS MANAGEMENT
// ============================================================================

export {
  getCredentials,
  getCredentialDetails,
  createCredential,
  updateCredential,
  duplicateCredential,
  deleteCredential,
  getCredentialHosts,
  getCredentialFolders,
  getSSHHostWithCredentials,
  getHostPassword,
  applyCredentialToHost,
  removeCredentialFromHost,
  migrateHostToCredential,
  getFoldersWithStats,
  renameFolder,
  getSSHFolders,
  updateFolderMetadata,
  reorderFolders,
  deleteAllHostsInFolder,
  renameCredentialFolder,
  reorderCredentials,
  detectKeyType,
  detectPublicKeyType,
  validateKeyPair,
  generatePublicKeyFromPrivate,
  generateKeyPair,
  deployCredentialToHost,
} from "@/api/credentials-api";

export {
  getVaultProfiles,
  createVaultProfile,
  updateVaultProfile,
  deleteVaultProfile,
  type VaultProfilePayload,
} from "@/api/vault-profiles-api";

// ============================================================================
// SNIPPETS API
// ============================================================================

export type {
  NetworkTopologyNode,
  NetworkTopologyEdge,
  NetworkTopologyData,
} from "@/api/snippets-api";
export {
  getSnippets,
  createSnippet,
  updateSnippet,
  deleteSnippet,
  executeSnippet,
  getNetworkTopology,
  saveNetworkTopology,
  getSnippetFolders,
  createSnippetFolder,
  updateSnippetFolderMetadata,
  renameSnippetFolder,
  deleteSnippetFolder,
  reorderSnippets,
} from "@/api/snippets-api";

// ============================================================================
export type {
  UptimeInfo,
  RecentActivityItem,
  ServiceLink,
} from "@/api/dashboard-api";
export {
  getUptime,
  getRecentActivity,
  logActivity,
  resetRecentActivity,
  getServiceLinks,
  createServiceLink,
  deleteServiceLink,
  updateServiceLink,
} from "@/api/dashboard-api";

// ============================================================================
export {
  saveCommandToHistory,
  getCommandHistory,
  deleteCommandFromHistory,
  clearCommandHistory,
} from "@/api/command-history-api";

export {
  linkOIDCToPasswordAccount,
  unlinkOIDCFromPasswordAccount,
} from "@/api/oidc-account-api";

export type {
  GuacamoleTokenRequest,
  GuacamoleTokenResponse,
} from "@/api/guacamole-api";
export {
  getGuacamoleDpi,
  getGuacamoleToken,
  getGuacamoleTokenFromHost,
  getGuacdStatus,
} from "@/api/guacamole-api";

// ============================================================================
// RBAC MANAGEMENT
// ============================================================================

export {
  getRoles,
  createRole,
  updateRole,
  deleteRole,
  getUserRoles,
  assignRoleToUser,
  removeRoleFromUser,
  shareHost,
  shareFolder,
  updateHostAccess,
  getHostAccess,
  revokeHostAccess,
  getHostAuthOverride,
  setHostAuthOverride,
  getPermissionsCatalog,
  getSharedHosts,
  shareSnippet,
  getSnippetAccess,
  revokeSnippetAccess,
  getSharedSnippets,
} from "@/api/rbac-api";
export type {
  SharePermissionLevel,
  ShareTarget,
  PermissionCatalogEntry,
} from "@/api/rbac-api";

// ============================================================================
// DOCKER MANAGEMENT API
// ============================================================================

export {
  connectDockerSession,
  verifyDockerTOTP,
  verifyDockerWarpgate,
  disconnectDockerSession,
  keepaliveDockerSession,
  getDockerSessionStatus,
  validateDockerAvailability,
  listDockerContainers,
  getDockerContainerDetails,
  startDockerContainer,
  stopDockerContainer,
  restartDockerContainer,
  pauseDockerContainer,
  unpauseDockerContainer,
  removeDockerContainer,
  getContainerLogs,
  downloadContainerLogs,
  getContainerStats,
} from "@/api/docker-api";

export {
  getOpenTabs,
  syncOpenTabs,
  deleteOpenTab,
  patchOpenTab,
  addOpenTab,
  getActiveSessions,
  getUserPreferences,
  saveUserPreferences,
  type OpenTabRecord,
  type OpenTabSyncPayload,
  type OpenTabUpsertPayload,
  type ActiveSessionInfo,
  type UserPreferences,
} from "@/api/open-tabs-api";

// ============================================================================
// TERMIX ID API
// ============================================================================

export {
  getMyTermixId,
  checkTermixIdHandle,
  createTermixId,
  updateTermixId,
  deleteTermixId,
  addTermixIdKey,
  generateTermixIdKey,
  setTermixIdKeyEnabled,
  deleteTermixIdKey,
  getMyCa,
  createCa,
  rotateCa,
  deleteCa,
  issueCertificate,
  getLinkedCredentialIds,
  type TermixIdentity,
  type TermixIdentityKey,
  type TermixIdMe,
  type GeneratedKey,
  type TermixIdCa,
  type IssuedCertificate,
} from "@/api/termix-id-api";
