// TypeScript types for the FrankenApp Web SDK (FrankAPI).
// Drop this alongside frankapi.js or import via a triple-slash reference:
//   /// <reference types="./frankapi" />

export interface FrankAPIResult {
  /** Normalised success flag (handles both { ok } and { success } shells). */
  ok: boolean;
   /** Machine-readable reason when ok=false. Common values:
   *  "not_native" | "missing_userId" | "invalid_token" |
   *  "missing_refresh_token" | "invalid_refresh_token" |
   *  "biometric_session_expired" | "set_session_failed" |
       *  "unlock_in_progress" | "offer_in_progress" |
       *  "silent_keychain_update_unavailable" |
   *  "session_not_restored" | "enrollment_not_verified" |
   *  "user_cancelled" | "not_enrolled" | "failed" | "locked_out" |
   *  "reserved" | string
   *  Note: token length alone is NEVER used as a rejection reason -
   *  Supabase refresh_token length can vary (commonly ~12 chars). */
  reason?: string;
  /** Raw bridge response (for debugging only). */
  raw?: unknown;
  /** Diagnostic: length of the refresh_token observed (never used to reject). */
  tokenLength?: number;
  /** Diagnostic: true when tokenLength is below the warning threshold (20). */
  shortToken?: boolean;
  /** True when the SDK invoked supabase.auth.refreshSession() during validation. */
  refreshSessionAttempted?: boolean;
  /** Result of the refreshSession() round-trip when attempted. */
  refreshSessionOk?: boolean;
}

export interface BiometricEnableResult extends FrankAPIResult {
  /** True only when the Keychain write was verified via isEnrolled(). */
  verified?: boolean;
}

export interface BiometricUnlockResult extends FrankAPIResult {
  userId?: string;
  /** Restorable auth token (e.g. Supabase refresh_token). */
  token?: string;
  /** Present (and false) when the native Keychain reports no enrollment. */
  enrolled?: boolean;
}

export interface FrankenAppSDK {
  readonly version: string;

  native: {
    /** True only inside the FrankenApp iOS / Android / tvOS shell. */
    isAvailable(): boolean;
    platform(): "ios" | "android" | "tvos" | "web";
    bridgeVersion(): string | null;
    /** Returns the merged capability flag map injected by the shell, or null on web / older shells. */
    capabilities(): Record<string, boolean> | null;
  };

  nativeViewport: {
    /** Applies the no-code safe-area fix inside native shells. No-op on web. */
    apply(): FrankAPIResult;
    /** Removes FrankAPI injected safe-area elements and listeners. */
    disable(): void;
    /** True after the native viewport helper has applied. */
    isApplied(): boolean;
    /** Re-reads theme/status bar color and repositions fixed backdrop bars. */
    refreshBackdrop(): void;
  };

  biometric: {
    isAvailable(): Promise<{ ok: true; available: boolean }>;
    isEnrolled(): Promise<{ ok: true; enrolled: boolean }>;
    enable(opts: { userId: string; token: string; reason?: string }): Promise<BiometricEnableResult>;
    unlock(opts?: { reason?: string }): Promise<BiometricUnlockResult>;
    disable(): Promise<FrankAPIResult>;
  };

  /**
   * High-level Supabase auth helpers. PREFER these in customer apps - they
   * hide refresh_token plumbing, length checks, enrollment verification, and
   * session restore. Pass your Supabase client; never touch refresh tokens.
   */
  auth: {
    /** Reads the current Supabase session, validates refresh_token, calls
     *  biometric.enable, verifies Keychain enrollment, then auto-attaches the
     *  non-prompting token-sync listener so future refresh rotations are saved. */
    enableBiometricLogin(opts: {
      supabase: any;
      reason?: string;
    }): Promise<FrankAPIResult & {
      enrolled?: boolean;
      autoAttachOk?: boolean;
      debug?: {
        enableOriginalTokenLength: number;
        enableRefreshSessionAttempted: boolean;
        enableRefreshSessionOk?: boolean;
        enableRefreshTokenRotated: boolean;
        enableSavedTokenSource: "original" | "refreshed" | "verification_rotated";
        enableSavedTokenLength: number;
        silentRewriteMethodAvailable: boolean;
        silentRewriteAttempted: boolean;
        silentRewriteOk: boolean;
        silentRewriteReason: string | null;
        enableVerificationOk: boolean;
        enableFinalSavedTokenSource: string;
        biometricSupabaseLoginFullySupported: boolean;
      };
      biometricSupabaseLoginFullySupported?: boolean;
      requiresBridgeMethod?: string;
    }>;
    /** App-level first sign-in helper. Checks native availability, current
     *  enrollment, and prior decline state, then asks the user whether to turn
     *  on Face ID. If accepted, it calls enableBiometricLogin(). Use this right
     *  after a successful password login so every wrapped app gets the normal
     *  "Turn on Face ID?" flow without creating a Settings screen first. */
    offerBiometricLogin(opts: {
      supabase: any;
      reason?: string;
      promptTitle?: string;
      promptMessage?: string;
      force?: boolean;
      prompt?: (input: { message: string; reason: string; userId: string }) => boolean | Promise<boolean>;
    }): Promise<FrankAPIResult & { enrolled?: boolean; skipped?: boolean }>;
    /** Prompts Face ID / Touch ID, restores the Supabase session from the
     *  stored refresh_token, and verifies the user is signed in. It performs
     *  exactly one native biometric prompt per explicit call. If another
     *  unlock is already running, returns reason "unlock_in_progress". On a
     *  permanently invalid stored token, returns reason
     *  "biometric_session_expired" once AND clears the Keychain so the UI
     *  stops offering Face ID until the user password-logs-in and re-enables
     *  it. Token rotation persistence only uses a native silent Keychain update
     *  method when available - never a second biometric prompt. */
    unlockWithBiometrics(opts: {
      supabase: any;
      reason?: string;
    }): Promise<FrankAPIResult & {
      userId?: string;
      autoAttachOk?: boolean;
      debug?: {
        unlockCallId: number;
        nativePromptAttemptCount: number;
        refreshSessionAttemptCount: number;
        keychainRewriteAttempted: boolean;
        finalReason: string;
        unlockTokenExisted: boolean;
        restoreSessionOk: boolean;
        refreshTokenRotated: boolean;
        keychainUpdated: boolean;
        finalResult: string;
        unlockRestoreMethodUsed: string | null;
        unlockRestoreErrorMessage: string | null;
        silentRewriteMethodAvailable: boolean;
        silentRewriteAttempted: boolean;
        silentRewriteOk: boolean;
        silentRewriteReason: string | null;
      };
    }>;
    /** Deletes the Keychain credential. Idempotent. */
    disableBiometricLogin(): Promise<FrankAPIResult>;
    /** Non-prompting: is there a Keychain credential for this app? */
    isBiometricLoginEnabled(): Promise<{ ok: true; enabled: boolean; enrolled: boolean }>;
    /**
     * Hooks supabase.auth.onAuthStateChange and silently rewrites the Keychain
     * with the FRESH refresh_token on every SIGNED_IN / TOKEN_REFRESHED event
     * while the user is enrolled. Prevents "Face ID expired" after a normal
     * sign-out/sign-in cycle. Idempotent. FrankAPI v0.4.13 also calls this
     * automatically after successful enable/unlock; calling it once on app boot
     * remains recommended for immediate coverage.
     */
    attach(opts: { supabase: any }): { ok: boolean; reason?: string };
    /** Stops the onAuthStateChange listener installed by attach(). */
    detach(): { ok: boolean };
    /**
     * Sign-out that PRESERVES the Keychain credential. When Face ID is enrolled,
     * this clears only the WebView's local auth storage and does not call the
     * auth server revoke endpoint, because even scope: "local" revokes the
     * current refresh_token that Face ID needs for the next unlock. If the caller
     * passes scope: "global" or "others" while Face ID is enrolled, the SDK
     * auto-downgrades to the local-storage-only path. To do a real global
     * sign-out, call disableBiometricLogin() first.
     */
    signOut(opts?: { supabase?: any; scope?: "local" | "global" | "others" }): Promise<FrankAPIResult & {
      scope?: string;
      debug?: {
        enrolledBeforeSignOut: boolean;
        localFlagBeforeSignOut: boolean;
        requestedScope: string;
        signOutScopeUsed: string;
        signOutMode: string;
        localOnlySignOutUsed: boolean;
        nativeEnrolledAfterSignOut: boolean;
        localFlagAfterSignOut: boolean;
        finalEnabledAfterSignOut: boolean;
      };
    }>;
    /**
     * v0.4.19. High-level OAuth sign-in. On native iOS / Android shells, opens
     * the provider consent screen in SFSafariViewController via the oauth.signIn
     * bridge (so Google's WebView block does not bite). On Lovable Cloud apps,
     * falls back to the managed OAuth broker before raw Supabase OAuth, avoiding
     * missing-provider-secret failures. Same call site works in both. When the
     * native shell returns tokens, the SDK calls supabase.auth.setSession for you.
     */
    signInWithOAuth(opts: {
      provider: "google" | "apple" | "microsoft" | string;
      supabase?: any;
      redirectTo?: string;
      scopes?: string | string[];
      extraParams?: Record<string, string>;
    }): Promise<FrankAPIResult & {
      path?: "native" | "native_passthrough" | "lovable_broker" | "web";
      provider?: string;
      session?: unknown;
      callbackUrl?: string;
      redirected?: boolean;
      message?: string;
    }>;
    /**
     * v0.4.19. Re-prompts Face ID after the app has been backgrounded past a
     * configurable threshold. SDK never renders UI - host renders the lock
     * overlay in response to onLocked / onChange callbacks. When autoUnlock is
     * true (default) the native Face ID prompt fires automatically as soon as
     * the lock triggers; the user only sees the overlay if they cancel.
     *
     * Typical wiring on app boot, AFTER auth.attach():
     *   FrankAPI.auth.sessionLock.configure({
     *     supabase, timeoutMs: 5 * 60 * 1000,
     *     onLocked: () => setShowOverlay(true),
     *     onUnlocked: () => setShowOverlay(false),
     *   });
     *   FrankAPI.auth.sessionLock.start();
     */
    sessionLock: {
      configure(opts: {
        supabase?: any;
        /** Default 5 minutes. */
        timeoutMs?: number;
        reason?: string;
        /** Auto-call unlockWithBiometrics() the moment the lock fires. Default true. */
        autoUnlock?: boolean;
        onLocked?: (e: { reason: string }) => void;
        onUnlocked?: (r: FrankAPIResult) => void;
        onUnlockFailed?: (r: FrankAPIResult) => void;
      }): { ok: boolean };
      start(): { ok: boolean; reason?: string; alreadyStarted?: boolean };
      stop(): { ok: boolean };
      lock(opts?: { reason?: string; autoUnlock?: boolean }): Promise<FrankAPIResult & { locked?: boolean }>;
      unlock(): Promise<FrankAPIResult>;
      isLocked(): boolean;
      onChange(fn: (state: { locked: boolean; reason?: string }) => void): () => void;
    };
  };



  haptics: {
    impact(style?: "light" | "medium" | "heavy"): Promise<FrankAPIResult>;
    success(): Promise<FrankAPIResult>;
    error(): Promise<FrankAPIResult>;
  };

  share: {
    open(opts: { title?: string; text?: string; url?: string }): Promise<FrankAPIResult>;
  };

  clipboard: {
    write(text: string): Promise<FrankAPIResult>;
    read(): Promise<FrankAPIResult & { text?: string }>;
  };

  oauth: {
    signIn(opts: {
      provider: "google" | "apple" | "microsoft" | string;
      authorizeUrl?: string;
      redirectScheme?: string;
    }): Promise<FrankAPIResult & { callbackUrl?: string; provider?: string }>;
  };
  camera: {
    takePhoto(opts?: { source?: "camera" | "library"; maxWidth?: number; quality?: number }):
      Promise<FrankAPIResult & { dataUrl?: string; width?: number; height?: number }>;
  };
  files: {
    pick(opts?: { accept?: string[]; multiple?: boolean }):
      Promise<FrankAPIResult & { files?: Array<{ name: string; mimeType: string; size: number; dataUrl: string }> }>;
  };
  location: {
    getCurrent(opts?: { timeoutMs?: number }):
      Promise<FrankAPIResult & { latitude?: number; longitude?: number; accuracy?: number }>;
  };
  badge: {
    set(count: number): Promise<FrankAPIResult>;
    clear(): Promise<FrankAPIResult>;
  };

  debug: {
    enable(): void;
    disable(): void;
    isOn(): boolean;
    snapshot(): {
      sdkVersion: string;
      isNative: boolean;
      platform: string;
      bridgeVersion: string | null;
      bridgePresent: boolean;
      aliasMatches: boolean;
      capabilities: Record<string, boolean> | null;
      biometricSupabaseLoginFullySupported: boolean;
      requiredBridgeMethods: { updateBiometricCredential: boolean };
      bridgeMethods: Record<string, boolean> | null;
      methods: Record<string, boolean> | null;
    };
  };

  /** Opt into FrankAPI Management telemetry. Also auto-init via
   *  <meta name="frankapi-project-id" content="<uuid>">.
   *  `capabilities` lets host apps override the shell-injected flag map (e.g. for local testing). */
  configure(opts: {
    projectId?: string | null;
    telemetry?: boolean;
    endpoint?: string;
    capabilities?: Record<string, boolean>;
  }): void;

  telemetry: {
    flush(): void;
    queueSize(): number;
    isEnabled(): boolean;
    projectId(): string | null;
  };

  // ---------- v0.4.20 Autopilot ----------
  /** Project ID resolved from the publishable key on script load. Null until identify() resolves. */
  projectId: string | null;
  /** Per-project entitlements returned by /api/public/sdk/identify. */
  entitlements: Record<string, unknown>;
  /** Per-project runtime config (telemetry, sessionLock, biometric, features) returned by identify. */
  runtimeConfig: Record<string, unknown>;
  /** Display name from the FrankenApp project record. */
  appName: string | null;
  /** Manually trigger identify (normally auto-runs when <script data-key="fa_pub_..."> is set). */
  identify(key: string): Promise<{ ok: boolean; projectId?: string; reason?: string }>;
  /** Resolves when identify() has finished (success or failure). */
  ready(): Promise<{ ok: boolean; projectId?: string; reason?: string }>;
}

declare global {
  interface Window {
    FrankenAppSDK?: FrankenAppSDK;
    /** Short alias. */
    FrankAPI?: FrankenAppSDK;
  }
}

export {};
