// Wraps a Phoenix LiveView hook so its implementation module is fetched
// over the network only when the hook actually mounts. esbuild's
// `--splitting --format=esm` emits the dynamic import as its own chunk,
// keeping it out of the core app.js bundle on pages that don't use it.
//
// Two flavors:
//   * lazyHook(loader)       – plain dynamic-import wrapper.
//   * lazyMapHook(loader)    – also ensures Leaflet is loaded and assigned
//                              to window.L once before the impl mounts,
//                              shared across every map page.

type HookImpl = Record<string, any>
type ModuleLoader = () => Promise<Record<string, HookImpl>>

interface LazyState {
  _impl?: HookImpl
  _pendingUpdate?: boolean
  _destroyed?: boolean
}

let leafletLoader: Promise<void> | null = null

function loadLeaflet(): Promise<void> {
  if (!leafletLoader) {
    leafletLoader = import("../vendor/leaflet/leaflet.js").then((mod) => {
      // UMD: when imported as ESM, the namespace itself is the L value.
      // Some bundlers wrap it under .default — handle both.
      window.L = (mod as any).default ?? mod
    })
  }
  return leafletLoader
}

// Pick the first non-lifecycle export from the module as the hook impl,
// or use the named export matching `hookName` when given.
function pickImpl(mod: Record<string, HookImpl>, hookName?: string): HookImpl {
  if (hookName && mod[hookName]) return mod[hookName]
  for (const key of Object.keys(mod)) {
    if (key === "default") continue
    const v = mod[key]
    if (v && typeof v === "object" && ("mounted" in v || "updated" in v || "destroyed" in v)) {
      return v
    }
  }
  if (mod.default) return mod.default
  throw new Error("lazyHook: no hook export found in module")
}

function attach(hook: ViewHook & LazyState, impl: HookImpl) {
  hook._impl = impl
  for (const k of Object.keys(impl)) {
    if (k === "mounted" || k === "updated" || k === "destroyed") continue
    ;(hook as any)[k] = impl[k]
  }
}

export function lazyHook(loader: ModuleLoader, hookName?: string) {
  return {
    mounted(this: ViewHook & LazyState) {
      const hook = this
      loader().then((mod) => {
        if (hook._destroyed) return
        const impl = pickImpl(mod, hookName)
        attach(hook, impl)
        impl.mounted?.call(hook)
        if (hook._pendingUpdate) {
          impl.updated?.call(hook)
          hook._pendingUpdate = false
        }
      })
    },
    updated(this: ViewHook & LazyState) {
      if (this._impl?.updated) this._impl.updated.call(this)
      else this._pendingUpdate = true
    },
    destroyed(this: ViewHook & LazyState) {
      this._destroyed = true
      this._impl?.destroyed?.call(this)
    },
  }
}

export function lazyMapHook(loader: ModuleLoader, hookName?: string) {
  return {
    mounted(this: ViewHook & LazyState) {
      const hook = this
      Promise.all([loadLeaflet(), loader()]).then(([, mod]) => {
        if (hook._destroyed) return
        const impl = pickImpl(mod, hookName)
        attach(hook, impl)
        impl.mounted?.call(hook)
        if (hook._pendingUpdate) {
          impl.updated?.call(hook)
          hook._pendingUpdate = false
        }
      })
    },
    updated(this: ViewHook & LazyState) {
      if (this._impl?.updated) this._impl.updated.call(this)
      else this._pendingUpdate = true
    },
    destroyed(this: ViewHook & LazyState) {
      this._destroyed = true
      this._impl?.destroyed?.call(this)
    },
  }
}
