// A host-registered platform view (map, QR scanner, camera preview, …) — the plugin surface of
// the Presentable model (docs/navigation-presentable-plan.md). The host registers a *capability*
// (`engine.registerView("map") { params, channel -> MapView(...) }`); when and with what to show
// it is app code. Dual-natured like every Presentable: a fullscreen destination
// (`Router.push(map)` / `map.open()`) AND an embeddable layout node (wire type `"native"` — place
// it among a screen's children, sized by its styles). One instance lives in one place at a time;
// pushing a mounted instance fullscreen is promotion (the native view reparents, state intact).
//
// Plugins ship a typed TS wrapper over the raw `call`/`on` channel — see QRScanner for the
// wrapper convention.

import { _channelCall, _channelEmit, _channelOff, _channelOn, type ChannelListeners } from "../runtime/channel"
import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type UIElementBase } from "./UINode"

export type UINativeViewStyle = ElementStyle & DrawableStyle

/** An instance of a host-registered platform view (map, QR scanner, camera preview, …) —
 *  embeddable among a screen's children AND fullscreen-presentable (`open()`); one instance lives
 *  in one place at a time. Talk to it via the `call`/`on` channel. */
export interface NativeView extends UIElementBase<UINativeViewStyle, DrawableStyle & BaseStyle> {
  readonly type: "native",
  /** The registered view kind this instance resolves to (the `registerView` name). */
  readonly viewName: string,
  /** Creation params, passed to the host factory (JSON-serializable). */
  readonly params: any,

  /** Show fullscreen as the current destination (see Presentable). */
  open(options?: PresentOptions): void,
  /** Dismiss if this is the visible destination. */
  close(): void,

  /** Became the visible destination. */
  onOpen(callback: () => void): this
  /** Stopped being the visible destination. */
  onClose(callback: () => void): this
  /** Hardware/system back while current. */
  onBackPressed(callback: () => void): this

  /** Invoke a method on the native view (`map.call("setCenter", [lng, lat])`). Args and the
   *  result are JSON-serialized. Rejects if the host has no such view/method. */
  call(method: string, ...args: any[]): Promise<any>
  /** Subscribe to an event the native view emits (`map.on("markerTap", cb)`). */
  on(event: string, callback: (data?: any) => void): this
  off(event: string, callback: (data?: any) => void): this
}

let nextViewId = 1

/** @internal Base for typed plugin wrappers (QRScanner, CameraView — src/plugins/): subclass it,
 *  pin the viewName/params, and layer typed methods over the raw `call`/`on` channel. Not a
 *  public global — apps use the `NativeView(name)` factory below. */
export class NativeViewElement extends Element<"native"> implements Presentable {
  readonly viewName: string
  readonly params: any
  /** @internal stable per-instance handle for the viewCall channel + promotion identity. */
  readonly _viewId: number
  /** @internal event listeners by event name — the host invokes these (vlist pcl/psl convention)
   *  or calls _emitViewEvent. */
  readonly nvl: ChannelListeners = {}
  readonly ol: (() => void)[] = []
  readonly cl: (() => void)[] = []
  _backButtonCallback?: () => void
  private _vd?: object

  constructor(viewName: string, params: any) {
    super("native", {})
    this.viewName = viewName
    this.params = params
    this._viewId = nextViewId++
  }

  /** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
  _viewDesc(): object {
    return this._vd ??= { type: "native", viewName: this.viewName, params: this.params, viewId: this._viewId, _p: this }
  }

  open(options?: PresentOptions): void {
    if (!_navSupported()) {
      throw new Error(`NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.openView)`)
    }
    _bumpNavEpoch()
    _creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
    _setCurrent(this)
  }
  close(): void {
    _bumpNavEpoch()
    if (_navSupported()) _creatorUI.closeView!()
    if (Presentable.current === this) _setCurrent(null)
  }

  onOpen(callback: () => void): this {
    this.ol.push(callback)
    return this
  }
  onClose(callback: () => void): this {
    this.cl.push(callback)
    return this
  }
  onBackPressed(callback: () => void): this {
    this._backButtonCallback = callback
    return this
  }

  call(method: string, ...args: any[]): Promise<any> {
    return _channelCall(
      typeof _creatorUI !== "undefined" ? _creatorUI : undefined, "viewCall",
      `NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.viewCall)`,
      this._viewId, method, args)
  }

  on(event: string, callback: (data?: any) => void): this {
    _channelOn(this.nvl, event, callback)
    return this
  }
  off(event: string, callback: (data?: any) => void): this {
    _channelOff(this.nvl, event, callback)
    return this
  }
  /** @internal host entry: deliver a native event (payload as a JSON string, or omitted). */
  _emitViewEvent(event: string, dataJson?: string): void {
    _channelEmit(this.nvl, event, dataJson)
  }
}

/**
 * Create an instance of a host-registered platform view. `NativeView.isSupported(name)` reports
 * whether this host registered a factory for it. Style via the chained `.style()`.
 */
// PURE IIFE (not a plain function + expando assignment) so an app that never uses NativeView
// tree-shakes it away — top-level property assignments read as side effects to the bundler.
export const NativeView: {
  (name: string, params?: object): NativeView
  /** Whether this host registered a `registerView` factory under `name`. */
  isSupported(name: string): boolean
} = /*#__PURE__*/ (() => {
  const factory = (name: string, params: object = {}): NativeView =>
    new NativeViewElement(name, params) as unknown as NativeView
  factory.isSupported = (name: string): boolean =>
    typeof _creatorUI !== "undefined" && typeof _creatorUI.isViewSupported === "function" && _creatorUI.isViewSupported(name)
  return factory
})()
