// QRScanner — the host's camera QR scanner as a Presentable. A typed wrapper over the
// `NativeView("qrScanner")` capability (docs/navigation-presentable-plan.md): open it fullscreen
// (`await scanner.open()`), push it (`Router.push(scanner)`), or embed it among a screen's
// children as a live preview node — everything a NativeView can do, plus a typed `onScan`.
//
// Host-OPTIONAL, like every plugin: it exists where the host registers a "qrScanner" view
// (Android; web can via `_creatorUI._registerView`) — gate on `QRScanner.isSupported`. Opening
// prepares first (camera permission) while the current destination stays visible — the same
// prepare-then-present contract as ARScene.
//
// The pre-Presentable `_creatorUtils.openScanner/closeScanner` bridges are gone from the ABI
// (2026-07-15): hosts keep their implementations only to serve previously compiled bundles.

import { _navEpoch, type PresentOptions } from "../ui/presentable"
import { NativeView, NativeViewElement } from "../ui/NativeView"
import { _requestCameraPermission } from "./permission"

export interface QRScanner extends NativeView {
  /** Fires per decoded camera frame: the decoded string, or `null` for a frame with no readable
   *  code (expect those repeatedly while the user aims). The same code can be reported more than
   *  once — `close()` or debounce once you have what you need. */
  onScan(callback: (data: string | null) => void): this
  /** Request camera permission, then present the scanner. The current destination (and its
   *  loading state) stays visible until the scanner is ready; rejects if the camera is denied,
   *  the host can't scan, or another navigation superseded this one. */
  open(options?: PresentOptions): Promise<void>
}

class QRScannerElement extends NativeViewElement {
  constructor() {
    super("qrScanner", {})
  }

  onScan(callback: (data: string | null) => void): this {
    return this.on("scan", (payload?: { data?: string | null }) => callback(payload?.data ?? null))
  }

  /** @internal prepare-then-present: permission before the swap (see Presentable._prepare). */
  async _prepare(): Promise<void> {
    const epoch = _navEpoch()
    await _requestCameraPermission()
    if (_navEpoch() !== epoch) {
      throw new Error("QRScanner.open() superseded: another destination was opened while preparing")
    }
  }

  override async open(options?: PresentOptions): Promise<void> {
    // Fail before prompting for the camera — a permission dialog for a scanner that can't exist
    // would be worse than the error.
    if (!NativeView.isSupported("qrScanner")) {
      throw new Error("QRScanner isn't available on this host — it needs a registered \"qrScanner\" view.")
    }
    await this._prepare()
    super.open(options)
  }
}

/**
 * Create a QR scanner view. `QRScanner.isSupported` reports whether this host can scan at all —
 * check it before offering the feature.
 */
// PURE IIFE so an app that never scans tree-shakes the whole plugin away (see NativeView).
export const QRScanner: {
  (): QRScanner
  /** Whether this host registered a "qrScanner" view. */
  readonly isSupported: boolean
} = /*#__PURE__*/ (() => {
  const factory = (): QRScanner => new QRScannerElement() as unknown as QRScanner
  Object.defineProperty(factory, "isSupported", {
    get: (): boolean => NativeView.isSupported("qrScanner"),
  })
  return factory as any
})()
