// Who may see a row of the admin sidebar, and where that lets them go.
//
// Plain JS, in its own file, for two reasons. It is shared — AdminLayout renders
// by it and Admin -> Navigation builds its palette by it (THEMING_AND_NAV.md
// §8.1), and a second copy of this answer is exactly the thing this file exists
// to abolish. And it is the closest thing in the client to an authorization
// decision, so it belongs somewhere the test runner can reach, which a .jsx file
// is not.
//
// **A row's own `roles` is the whole answer.** Until Phase 2 PR 8 this was
// `roles` AND a hardcoded `MOD_PATHS` list of five paths that confined
// moderators, AND a third prefix list in the redirect effect that disagreed with
// both (docs/website/MODULE_SYSTEM.md §1.4). A module's rows could never be
// added to a list core hardcodes, which is what forced the derivation — but the
// lists had already drifted from each other without a module in sight.

/**
 * Can a viewer with this role see this row?
 *
 * Applied AFTER the override merge in both callers: an override is presentation
 * and this is the boundary, so an override saying `hidden: false` on a row this
 * role cannot see still shows nothing (THEMING_AND_NAV.md §7).
 *
 * A row with no `roles` is visible to everyone who reached the admin area at
 * all — that is the self-service case (Account, My Characters), and staff are a
 * superset of players.
 */
export function navItemVisibleTo(item, role) {
  return !item.roles || item.roles.includes(role)
}

/**
 * The paths a viewer with this role may reach, derived from the rows they see.
 *
 * Takes the BASE nav, never the override-merged one: an override must not be
 * able to move this boundary in either direction. Hiding a row from a
 * moderator's sidebar must not also bar them from the page behind it, and
 * un-hiding one must not admit them to a page their role does not carry.
 *
 * @param {Array<{items: Array}>} baseNav  the grouped admin nav
 * @param {string} role
 * @returns {Array<{to: string, exact: boolean}>}
 */
export function allowedPathsFor(baseNav, role) {
  return (Array.isArray(baseNav) ? baseNav : [])
    .flatMap((g) => g.items || [])
    .filter((item) => navItemVisibleTo(item, role))
    .map((item) => ({ to: item.to, exact: item.end === true }))
}

/**
 * Is this pathname one of them?
 *
 * A row carrying `end` matches exactly — `/admin` is the dashboard, not a prefix
 * of the whole admin area, and treating it as one would let every path through.
 * Every other row also covers its sub-routes, which is what keeps
 * `/admin/moderation/appeals/12` and a module's detail pages reachable without
 * anyone listing them.
 */
export function isAllowedPath(pathname, allowed) {
  return (allowed || []).some(({ to, exact }) =>
    exact ? pathname === to : pathname === to || pathname.startsWith(`${to}/`),
  )
}

/**
 * The first place in this nav a viewer with this role can actually go.
 *
 * Added in Phase 3 slice 3, for the player portal, whose index route was
 * `PlayerCharacters` — a UO page. When it left, `/player` had nothing behind it,
 * and the three ways out were: redirect somewhere fixed, invent a core landing
 * page, or resolve the index from the nav the viewer already has. This is the
 * third, and it is the only one that keeps today's behaviour — with the module
 * installed the first row is still Characters, so a player still lands on their
 * characters after signing in, and with nothing installed they land on Account.
 *
 * **From the BASE nav, never the override-merged one**, the same rule
 * `allowedPathsFor` follows and for a sharper version of the same reason: an
 * override is presentation, and a landing page is behaviour. An admin reordering
 * the sidebar must not silently change where everybody arrives, and — more to
 * the point — must not be able to move it somewhere a role cannot follow.
 *
 * Deliberately generic, and deliberately in this file rather than in the portal
 * layout. The admin area has the same shape of question (its index is a
 * hardcoded Dashboard), and the direction of travel is one logged-in area that
 * shows the right things for the viewer's permissions rather than two that
 * duplicate each other. When that happens this is the function it needs, and it
 * already answers for both nav shapes.
 *
 * @param {Array} baseNav  flat or grouped, before overrides
 * @param {string} role
 * @param {string} fallback  where to go when the viewer can see nothing at all
 */
export function firstDestinationFor(baseNav, role, fallback) {
  const items = (Array.isArray(baseNav) ? baseNav : []).flatMap((entry) =>
    entry && Array.isArray(entry.items) ? entry.items : [entry],
  )
  const first = items.find((item) => item && item.to && navItemVisibleTo(item, role))
  return first ? first.to : fallback
}
