import { createContext, useContext, useMemo, useState } from 'react'
import { featureProviders } from './registry.js'
import { buildFeatureGate, OPEN_GATE } from './featureGate.js'

// The React half of the feature seam. The decision logic is featureGate.js,
// which is plain JS and therefore testable in a runner with no DOM; this file is
// wiring, the same split registry.js and shared.js already use.
//
// **Calling a hook per provider inside a loop is the point, and it is legal
// here.** The rules of hooks require the same hooks in the same order on every
// render of a component — not a statically known list. The provider list is
// fixed before the first render (registration happens while module chunks
// evaluate, and main.jsx does not mount until DOMContentLoaded), there is no
// unregistering, and the snapshot below freezes it per component instance
// anyway. So the loop's length cannot change between renders of this provider,
// which is the actual requirement.
//
// A provider hook returns a Set-like of the flags this viewer may see, or `null`
// while it is still fetching. Core knows nothing else about it: what a flag
// means, how it is fetched, and what it is gated on are all the module's.

const FeatureGateContext = createContext(OPEN_GATE)

export function ModuleFeaturesProvider({ children }) {
  // Snapshotted once. useState's initialiser runs on the first render only, so
  // even a provider that somehow registered late cannot change this instance's
  // hook count mid-life — it would be ignored until the next mount, which is a
  // far better failure than a crashed render.
  const [providers] = useState(featureProviders)

  // eslint-disable-next-line react-hooks/rules-of-hooks -- fixed-length list, see above
  const values = providers.map((provider) => provider.hook())

  const gate = useMemo(
    () => {
      const byOwner = new Map()
      // First registration wins for a given owner: a module that registers two
      // namespaces answers its own nav rows from the first, rather than from
      // whichever happened to be stored last.
      providers.forEach((provider, i) => {
        if (!byOwner.has(provider.id)) byOwner.set(provider.id, values[i])
      })
      return buildFeatureGate(byOwner)
    },
    // One dependency per provider — a fixed-length list, for the same reason the
    // hook loop above is fixed-length.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [providers, ...values],
  )

  return <FeatureGateContext.Provider value={gate}>{children}</FeatureGateContext.Provider>
}

/**
 * The predicate to filter nav rows with: `(item) => boolean`, true when the row
 * carries no `feature` or when its module says this viewer may see it.
 *
 * Outside a provider it is the open gate, so a component rendered in isolation
 * (a test, a preview) shows its whole nav rather than none of it.
 */
export function useFeatureGate() {
  return useContext(FeatureGateContext)
}

export default ModuleFeaturesProvider
