// Which nav rows a viewer may see, when the answer belongs to a module.
//
// Phase 2, PR 8 of docs/website/MODULE_SYSTEM.md §2.7 (§1.5 states the problem);
// the contract is docs/website/MODULE_API.md §3.3.
//
// Nine of the sixteen rows in the public header used to carry a `feature`, and
// every one of them was a shard surface an admin can disable or gate to a higher
// audience. The provider that answered those questions moved out with the module
// in Phase 3 slice 3, and core cannot call it directly and still be a core. It
// keeps this generic seam instead, and the module fills it.
//
// **The namespace comes from the registration, not from the string.** A row's
// `feature` is resolved by the provider its OWN module registered, so a module
// author writes `feature: 'status'` exactly as it reads today: nothing parses a
// prefix, and a typo'd namespace is not a thing that can exist. Core's own rows
// carry no `moduleId` and resolve against the owner id `core` — which nothing
// registers now that the shard rows are gone, and that is the correct resting
// state rather than a gap: no core nav row carries a `feature`.
//
// Everything here fails OPEN, and that is deliberate: this is presentation, the
// gate is server-side
// (a disabled feature 404s and an out-of-rung one 403s whether or not a link was
// rendered), so an unknown answer shows the link rather than blanking the nav.
// The one thing a UI mistake must never do here is hide a page from someone
// entitled to it.

/**
 * The predicate the layouts filter their nav with.
 *
 * @param {Map<string, {has: (name: string) => boolean} | null | undefined>} flagsByOwner
 *   one entry per registered provider, keyed by the id of the module that
 *   registered it. The value is whatever that provider's hook returned this
 *   render: a Set-like of the flags this viewer may see, or `null` while the
 *   answer is still in flight.
 * @returns {(item: object) => boolean}
 */
export function buildFeatureGate(flagsByOwner) {
  return function isVisible(item) {
    if (!item || !item.feature) return true
    const owner = item.moduleId ?? 'core'
    // No provider for this owner: the row names a flag nothing answers for. That
    // is the no-module-installed case — no core row carries a `feature` once the
    // module is out — and it is a correct no-op rather than a hidden row.
    if (!flagsByOwner || !flagsByOwner.has(owner)) return true
    const flags = flagsByOwner.get(owner)
    // Still loading, or a provider that returned something unusable. Both are
    // "we do not know yet", and both show the link.
    if (!flags || typeof flags.has !== 'function') return true
    return flags.has(item.feature)
  }
}

/** The gate an area with no providers gets: everything is visible. */
export const OPEN_GATE = () => true

export default buildFeatureGate
