import { useEffect, useRef, useState } from 'react'
import gsap from 'gsap'
/* taxonomy-ok: organism — nests Button (atom) + VectorLoader (atom) + Tooltip (utility). Moved
 * from kol-shell 2026-10-22 (the one nav rail, user: "lets go for your recommendation"): the
 * app rail and the site rail are one component, and a site must not install the app shell
 * for its sidebar — the PageHeader precedent. kol-shell re-exports it. */
import Button from '../atoms/Button.jsx'
import useScrollFade from '../hooks/useScrollFade.js'
import { Tooltip } from '../utilities/Popover.jsx'
import { GRAB } from '../utilities/motion.js'
/* the grab pill's proximity wake, travel and dwell — ONE implementation, shared
 * with kol-framework's useDragResize (OneGrabGestureBothRails, kol-fxr
 * 2026-08-30: two rails on one screen felt different because each package had
 * built the gesture itself). It lived here until then. */
import useGrabEdge from '../hooks/useGrabEdge.js'
import { Icon } from '@kolkrabbi/kol-icons'
import VectorLoader from '../atoms/VectorLoader.jsx'
import { useRailSlot } from '../hooks/railSlot.jsx'
import { RAIL_ROW } from '../hooks/railRow.js'
import NavRailSite from './NavRailSite.jsx'

/**
 * NavRail — the flat rail. ONE fixed column, every child a direct child of it
 * (RailFlatGrabOpen, kol-mirror 2026-08-28 — user, on the monitor rail he
 * measured it against: "the div structure is super simple ok … it's 1 div
 * parent and everything is just in that container", and on the SideNav-backed
 * rail that had replaced it: it was "impossible" to get this shape out of it).
 *
 * This REVERSES RailSideNavPixelParity (0.13.0), which made the rail a collapsed
 * kol-framework `SideNav`. That ticket's pixel argument is settled the other way:
 * the rail does not share pixels with the brand sidebar because it never becomes
 * one. `RailLogomarkAtTop` stands — the mark is at the top in both states.
 *
 * THE GRAB OPENS IT (user: "when you grab it should not move the icons one
 * pixel, only reveal the title, and a chevron to see sub categories" · "when it
 * opens it should push the main content inside"). Closed, every row is the rail
 * minus its gutter — the 32px rung alone shows and the label and chevron exist
 * but are clipped by the row's own width. Open, the clip reveals them. Nothing
 * in the icon column moves between the two states, and the content column
 * follows because AppShell offsets by the same live variable.
 *
 * ONE WIDTH VARIABLE: `--kol-shell-rail-width` on `:root` — this rail is that
 * wide and AppShell's content is offset by it, written per pointermove during a
 * drag and tweened on release, so the page is pushed live through both. A drag
 * writes the DOM, not React state: a pointermove per frame is not a render.
 *
 * The pill is kol-r2b2's, verbatim (user: "make it like it is in kol-r2b2, it
 * has animation and gsap") — `.kol-rail-grab` in kol-theme's kol-animation.css,
 * its pointer numbers in kol-component's `utilities/motion` (`GRAB`).
 *
 * TWO LEVELS (RailTwoLevelSections, kol-fxr 2026-08-28 — user: "the NAV has 20px
 * icons in 32px containers, maybe level below has 12px icons in 20px container
 * aligned to right? … you expand you see that they have main 56 items each, which
 * you won't be aware of until you actually open labs, which should not happen
 * automatically"). An item with `sub` is a SECTION: the same L1 row, plus a caret.
 * Its rows are L2 — a 12px glyph in a 20px box, indented so its glyph column sits
 * to the RIGHT of L1's (glyph x 14 → 30), resting a rung quieter at `oq-64` and
 * going `oq-96` when it is the route. fxr's labs chrome is ~44 rows across four
 * method sections; on one level they flatten into an icon column built for five
 * destinations and the section names have nowhere to render at all.
 *
 * L2 is NOT a `Button`: the icon-button ladder is 22/26/32/40 (2026-09-03) and this
 * rung is 20 — there is nothing below sm, and adding one is a change to that
 * ladder's law, which is the user's call and not this ticket's. The row is
 * written directly and keys `aria-current`, so kol-theme's
 * `.kol-shell-rail .kol-btn-nav` active rules still reach it. A sub row with no
 * `icon` renders label-only, as it did before this level existed.
 *
 * NOTHING AUTO-EXPANDS. An L2 row is behind BOTH the rail's clipped width and its
 * section's own disclosure, and `open` starts `false` with no prop to get it
 * wrong — arriving on a route reveals nothing, by construction rather than by a
 * flag a consumer has to remember.
 *
 * THE PRESETS (user 2026-10-24: *"the rail, do you make a preset for this? like catalog has preset?"*) — three rails, one
 * name each, in `RAIL_PRESETS` below: `app` is the studio rail (this file's AppRail: the 48px rail that grabs open,
 * sections ENTER and EXPAND, the pill on the phone, Media · Sign in · Settings at the foot); `site` is the brand rail
 * (NavRailSite — brand.kolkrabbi.io); `workshop` is the workshop rail (kol-workshop's ShellSidebar, registered through
 * `registerRailPreset` since kol-component cannot import kol-workshop). AppShell passes the preset and reads its phone
 * shape; an app passes nothing else about the rail.
 *
 * THE WORDS (user 2026-10-24): a section ENTERS (its rows replace the list under a Back row) and EXITS (Back); a section
 * EXPANDS (its rows open under it in place) and COLLAPSES. No other words, in code or docs.
 *
 * @param {'app'|'site'|'workshop'} preset  which rail (default `app`); `variant="site"` is the old spelling of `preset="site"`
 * @param {Array}  items        `{ icon, path, label, sub?: [{ icon?, path, label }] }`
 *   a section (`sub`) with `to` is a FOLDER NOTE: its icon and label open `to`, its chevron expands or enters
 * @param {Array}  bottomItems  pinned below the rule (Settings)
 * @param {Object} logomark     `{ svgUrl, title }` — the mark, and the app name beside it when open
 * @param {string} currentPath  active match: '/' exact, else prefix → aria-current="page"
 * @param {Function} onNavigate `(path) => void` — every click, the mark included
 * @param {boolean} drawer  off-canvas mode: no grab, no drag, no token writes, rows always
 *   labelled, and the rail sizes itself from `--kol-shell-drawer-width` (default 240px).
 *   AppShell sets this from `touch="drawer"` — see ShellRailNoDrawerOnMobile, 2026-08-31.
 * Under a `RailSlotProvider` the opened width also carries a CONSUMER SLOT below the rows
 * (NavRailConsumerSlot, kol-monitor 2026-10-15) — content that is not a row list (monitor's module
 * catalog: rows that insert, not paths). Always mounted so portalled content keeps its state across
 * open/close; zero tall and clipped while closed. No provider → no slot, the render unchanged.
 *
 * @param {boolean} [fade=true]  the list fades out at its foot — the workshop rail's fade
 *   (2026-10-01), here too on the user's word (2026-10-02). The rows sit in their own scroll
 *   region between the mark and the pinned bottom rows, so a list longer than the window scrolls
 *   and runs out under the fade instead of being cut by the edge. `false` draws the list plain.
 */
const RAIL_W = '--kol-shell-rail-width'
const CLOSED = 48

/* the OPEN width is the rail's own ladder (`--kol-shell-rail-open` in kol-theme: 264, 320
 * from 1536), read at drag time so the rail lands on whichever rung the window is on */
const openWidth = () => {
  const v = getComputedStyle(document.documentElement).getPropertyValue('--kol-shell-rail-open').trim()
  const px = parseFloat(v)
  return Number.isFinite(px) ? (v.endsWith('rem') ? px * 16 : px) : 264
}

/* THE GRAB EDGE — proximity, not contact: the pill wakes within `GRAB.near` of
 * the line and sleeps only past `GRAB.sleep` (hovering the line itself flapped
 * the class every frame and restarted the fade). Along the line it DWELLS (user
 * ruling 2026-08-28: "make it come to the cursor but have some sticky time where
 * it lands, so it's not constantly jerking"): it targets the pointer's own
 * position and re-targets only once the pointer is `GRAB.stick` px away, so it
 * lands on you, holds while you move inside the radius, then travels to where you
 * are now — inside `GRAB.range`, the middle band of the edge, so it never rides
 * up beside the logomark. One window listener, rAF-throttled. */

/* THE DRAG — hold the pill and the width follows the pointer between closed and
 * open; release snaps to the nearer state, a click (no travel past GRAB.slop)
 * toggles. `onSnap` reports the resting state once — the sub rows render only
 * while open, because closed they would still take their height and push the
 * rungs below them. */
function useRailDrag(railRef, grabRef, onSnap, snapRef, enabled = true) {
  useEffect(() => {
    const strip = grabRef.current, rail = railRef.current, root = document.documentElement
    /* A DRAWER IS NOT A DRAGGABLE RAIL (ShellRailNoDrawerOnMobile, kol-chess
     * 2026-08-31). In drawer mode the rail is off-canvas at a fixed width and
     * the content owns the whole viewport, so the drag must not run at all —
     * it writes `--kol-shell-rail-width` on `:root` per pointermove, which is
     * exactly the inline token a consumer could not reach without `!important`. */
    if (!enabled) return undefined
    if (!strip || !rail) return undefined
    gsap.set(root, { [RAIL_W]: `${CLOSED}px` })
    let drag = null
    const snapTo = (w) => {
      gsap.to(root, { [RAIL_W]: `${w}px`, ...GRAB.snap, overwrite: 'auto' })
      onSnap(w !== CLOSED)
    }
    /* the ONLY way out of this effect (RailSectionPressOpensRail, kol-fxr
     * 2026-08-30). `snapTo` closes over the animation and the CLOSED/open
     * ladder, so a section press has to reach it rather than reimplement it —
     * a consumer setting `--kol-shell-rail-width` from outside widens the rail
     * but leaves `railOpen` false, and the L2 rows render behind it: a wide,
     * EMPTY rail, worse than the dead press this fixes. */
    /* `(false)` closes, anything else opens — the same seam the L2 rows use
     * to open, now also the tap opener's toggle */
    if (snapRef) snapRef.current = (open = true) => snapTo(open === false ? CLOSED : openWidth())
    const onDown = (e) => {
      strip.setPointerCapture(e.pointerId)
      gsap.killTweensOf(root)
      drag = { x: e.clientX, w: rail.offsetWidth, open: openWidth(), moved: false }
      strip.classList.add('is-dragging')
    }
    const onMove = (e) => {
      if (!drag) return
      const dx = e.clientX - drag.x
      if (Math.abs(dx) > GRAB.slop) drag.moved = true
      root.style.setProperty(RAIL_W, `${Math.min(Math.max(drag.w + dx, CLOSED), drag.open)}px`)
    }
    const onUp = () => {
      if (!drag) return
      const w = rail.offsetWidth
      const open = drag.open
      snapTo(drag.moved ? (w > (CLOSED + open) / 2 ? open : CLOSED) : (drag.w > CLOSED ? CLOSED : open))
      drag = null
      strip.classList.remove('is-dragging')
    }
    strip.addEventListener('pointerdown', onDown)
    strip.addEventListener('pointermove', onMove)
    strip.addEventListener('pointerup', onUp)
    strip.addEventListener('pointercancel', onUp)
    return () => {
      strip.removeEventListener('pointerdown', onDown)
      strip.removeEventListener('pointermove', onMove)
      strip.removeEventListener('pointerup', onUp)
      strip.removeEventListener('pointercancel', onUp)
    }
  }, [railRef, grabRef, onSnap, snapRef])
}

/* one row: the rung exactly where the closed rail had it, then the label and
 * (with `sub`) the chevron — the row clips to the rail's width */
/* the L2 glyph goes through the same injection seam Button gives L1 */

function RailItem({ icon, path, to, label, sub, currentPath, onNavigate, iconComponent, railOpen, onOpenRail, onEnter, depth = 0 }) {
  const [open, setOpen] = useState(false)
  /* A SECTION ROW IS NOT A DESTINATION (RailSectionPressOpensRail, kol-fxr
   * 2026-08-30 — user: *"if I press effects from collapsed nav it should maybe
   * open? currently pressing it does nothing"*).
   *
   * Closed, the row clips to 48px and the disclosure caret sits OUTSIDE that
   * clip, so the only reachable control was the icon — which called
   * `onNavigate` with a path no consumer dispatches, because a section has no
   * action. A guaranteed dead press, in the rail's default state.
   *
   * Pressing it now opens the rail and expands that section. This is the
   * behaviour `SideNav` had before the flat-rail reversal. It does not fight
   * "nothing auto-expands" — that rule is about ARRIVING on a route; this is an
   * explicit press. */
  const isSection = sub?.length > 0
  /* …AND IN AN OPEN RAIL THE SAME PRESS FOLDS AND UNFOLDS IT (2026-10-05, labs on a phone). Open,
   * it still fell through to `onNavigate` with that path no consumer dispatches: dead at a desk,
   * and worse on a touch drawer, where AppShell closes the drawer on every navigate — pressing a
   * section's name shut the nav and loaded nothing. */
  /* FOLDER NOTE (2026-10-22, user: "like foldernote in obsidian where a parent/group/folder can
   * also be a note"). Opt-in per section: give it `to` and its icon and label open that page,
   * while the chevron alone expands or enters — `RailSection`'s `to` split in kol-workshop. A
   * section without `to` is unchanged. */
  const press = () => {
    if (isSection && to) { onNavigate?.(to); return }
    if (isSection && onEnter) { if (!railOpen) onOpenRail?.(); onEnter(); return }
    if (isSection) { if (!railOpen) onOpenRail?.(); setOpen((v) => !railOpen || !v); return }
    onNavigate?.(path)
  }
  const target = to ?? path
  const active = target === '/' ? currentPath === '/' : currentPath.startsWith(target)
  return (
    <>
      {/* an expanded row is the SAME row as its parent — same glyph, same type, same ink — shifted so its icon starts on
        * the parent's LABEL column (user 2026-10-23: *"same image size same text size but aligned to where the word LABS
        * starts"*): the label sits 44px in (the 32px rung + the 12px gap) and the glyph 6px inside its rung, so one step is 38px — the child's glyph starts on the parent's label column */}
      <div className="flex items-center gap-3 w-full overflow-hidden shrink-0" style={depth ? { paddingLeft: depth * 38 } : undefined}>
        {/* the DS tooltip, not the browser's (the native-title sweep, 2026-09-26) — to the RIGHT,
          * out over the page, since the collapsed rail's glyph is all there is to read */}
        <Tooltip label={label} placement="right" triggerClassName="inline-flex shrink-0">
        <Button
          iconOnly={icon}
          iconSize={RAIL_ROW.glyph}
          iconComponent={iconComponent}
          variant="nav"
          /* an expanded row sits on the sm rung (26), the glyph and the type unchanged */
          size={depth ? 'sm' : 'md'}
          className="shrink-0"
          /* nav ACTIVE keys off aria-current="page" (kol-components-atoms.css) —
           * a brighter glyph, never the pressed fill */
          aria-current={active ? 'page' : undefined}
          /* oq-96 on every rung: the nav variant's oq-64 rest is site-nav quiet
           * and too dim on an icon rail (user: "make the color .96 on every icon
           * in the rail") */
          style={{ color: 'var(--kol-oq-96)' }}
          onClick={press}
          aria-label={label}
        />
        </Tooltip>
        <span
          className={`${RAIL_ROW.label} text-oq-96 flex-1 min-w-0 truncate cursor-pointer`}
          onClick={press}
        >
          {label}
        </span>
        {sub?.length > 0 && (
          <Tooltip label={label} placement="right" triggerClassName="inline-flex shrink-0">
          <Button
            iconOnly={onEnter ? 'chevron-right' : open ? 'chevron-down' : 'chevron-right'}
            iconSize={16}
            iconComponent={iconComponent}
            variant="nav"
            /* md, the row's rung — at sm it stood 26 beside the 32 glyph button (render gate R1,
             * one row one size, found on apps/editor's labs rail 2026-09-29) */
            size="md"
            className="shrink-0"
            style={{ color: 'var(--kol-oq-96)' }}
            aria-expanded={onEnter ? undefined : open}
            onClick={onEnter ? () => { if (!railOpen) onOpenRail?.(); onEnter() } : () => setOpen((v) => !v)}
            aria-label={`${label} sub categories`}
          />
          </Tooltip>
        )}
      </div>
      {/* EXPAND: the rows open under their section as FULL rows, one label column in (user 2026-10-23 — the 12px glyph,
        * oq-64, 18px-gutter L2 row is retired: *"why are the sub items so small? low opacity?"*). An expanded row may expand
        * again; it recurses. THE EXPANDED ROWS ARE A GROUP (user 2026-10-24: *"should the expanded list not be a little
        * tighter? … I dont see that its a contained group"*): their own container, `.kol-rail-group`, the rows at the sm
        * rung (26) with the same 20 glyph and type, on a 4px gap — against the top level's 32 rows on 8 */}
      {railOpen && open && !onEnter && (
        <div className="kol-rail-group flex w-full flex-col items-start gap-1">
          {sub.map((s) => (
            <RailItem key={s.path} {...s} depth={depth + 1} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={railOpen} onOpenRail={onOpenRail} />
          ))}
        </div>
      )}
    </>
  )
}

/* ONE RAIL, TWO VARIANTS (2026-10-22, the one nav rail — user: "should it not be a variant of
 * NavRail? since they are so similar"). `app` (default) is the flat 48px rail below; `site` is the
 * grouped site sidebar that was kol-framework's `SideNav`, carried class-for-class so a site
 * renders exactly as before — it takes SideNav's props (`navTree`, `isActive`, `background`,
 * `currentPath`, `onNavigate(event, to)` …). kol-framework's `SideNav` is that variant under the
 * old name, with react-router and the theme toggle wired in. */
/* THE PRESET TABLE — what each named rail is; AppShell reads `phone` for the bar's shape. `workshop` is registered. */
export const RAIL_PRESETS = {
  app: { sections: 'enter', phone: 'pill', foot: ['media', 'signIn', 'settings'] },
  site: { component: NavRailSite },
  workshop: { component: null },
}
/* a package that owns a rail registers it under its name (kol-workshop → `workshop`); the name then renders here */
export function registerRailPreset(name, Component) { RAIL_PRESETS[name] = { ...(RAIL_PRESETS[name] ?? {}), component: Component } }

export default function NavRail({ variant, preset, ...props }) {
  const name = preset ?? (variant === 'site' ? 'site' : 'app')
  if (name === 'app') return <AppRail {...props} sections={props.sections ?? RAIL_PRESETS.app.sections} />
  const Named = RAIL_PRESETS[name]?.component
  if (!Named) { if (import.meta.env?.DEV) console.warn(`NavRail: no rail registered as preset "${name}"`); return null }
  return <Named {...props} />
}

function AppRail({
  items = [],
  bottomItems = [],
  logomark,
  currentPath = '',
  onNavigate,
  iconComponent,
  hidden = false,
  drawer = false,
  fade = true,
  sections = 'expand',
}) {
  const railRef = useRef(null)
  const grabRef = useRef(null)
  const [railOpen, setRailOpen] = useState(false)
  /* ENTER MODE (`sections="enter"`, 2026-10-06 — the user, on labs: "having a 'enter' mode in the
   * left sidebar instead of collapse folder?"): pressing a section replaces the list with that
   * section's rows under a Back row, instead of expanding it in place. `expand` is the accordion
   * every rail had and keeps (`fold` was its old spelling and still reads as `expand`).
   * ONE RULE (user 2026-10-24: *"EFFECTS in LABS is supposed to COLLAPSE, thats the last step"*): a section whose rows
   * hold sections of their own ENTERS; a section whose rows are leaves EXPANDS and COLLAPSES in place, its rows the
   * expanded rows above. Chrome enters → Labs enters → Effects expands. `entered` is a PATH STACK; Back EXITS one level,
   * the mark still goes home. */
  const [entered, setEntered] = useState([])
  const snapOpenRef = useRef(null)
  const slotRef = useRef(null)
  const ctx = useRailSlot()
  /* the list fades where it clips — the head once scrolled, the foot while more lies below (kol-theme `.kol-scroll-fade`) */
  const listRef = useRef(null)
  useScrollFade(listRef, fade)
  const setSlot = ctx?.setSlot
  const setCtxOpen = ctx?.setRailOpen
  const slotOpen = drawer || railOpen
  useEffect(() => { setSlot?.(slotRef.current); return () => setSlot?.(null) }, [setSlot, hidden])
  useEffect(() => { setCtxOpen?.(slotOpen) }, [setCtxOpen, slotOpen])
  /* the grab strip is not rendered in drawer mode, so the hook's ref stays null
   * and it no-ops on its own — no second argument needed */
  useGrabEdge(grabRef)
  useRailDrag(railRef, grabRef, setRailOpen, snapOpenRef, !drawer)
  if (hidden) return null
  const enterMode = (sections === 'fold' ? 'expand' : sections) === 'enter'
  const enters = (item) => enterMode && !!item.sub?.some((s) => s.sub?.length > 0)
  /* the entered sections, root first — a stale path (the items changed under it) ends the walk */
  const trail = []
  if (enterMode) for (const p of entered) { const next = (trail.at(-1)?.sub ?? items).find((i) => i.path === p); if (!next) break; trail.push(next) }
  const section = trail.at(-1) ?? null
  const row = (item) => (
    <RailItem key={item.path} {...item} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={drawer || railOpen} onOpenRail={() => snapOpenRef.current?.()}
      onEnter={enters(item) ? () => setEntered([item.path]) : undefined} />
  )
  return (
    <div
      ref={railRef}
      /* ONE SIDE, ALWAYS LEFT (ShellDrawerSideCorrection, kol-chess 2026-09-01).
        * 0.34.0 mirrored the panel to fix a button: the complaint was the
        * trigger travelling to mid-screen, never the navigation's side. The
        * trigger is now pinned top-right in both states, so a left panel has
        * nothing to collide with and the drawer opens where the rail lives. */
      className="kol-shell-rail bg-surface-primary fixed inset-y-0 left-0 flex flex-col items-start pt-4 pb-4 px-2 gap-2 border-r border-fg-08"
      /* A drawer takes its own width, NOT the live rail token — that token is
        * zeroed in drawer mode so the content gets the whole viewport back. */
      style={{ width: drawer ? 'var(--kol-shell-drawer-width, 240px)' : `var(${RAIL_W})` }}
    >
      {!drawer && (
        /* A BUTTON, and on touch A THICKER LINE (ShellRailCollapsedWithTapOpen,
         * kol-mirror 2026-09-01; corrected by RailGrabTapIsALine, kol-monitor
         * 2026-09-02 — user: "it was a thicker line" · "who decided it should be
         * a chevron?"): `touch="shell"` keeps the 48px rail visible and its
         * only opener was this 8px strip, whose pill wakes on pointer
         * PROXIMITY — a thumb never hovers. The 0.38.0 answer was a disc with a
         * chevron; that was the DS agent's shape, not the user's ruling. The
         * opener is the strip ITSELF: under `(pointer: coarse)` the theme
         * widens it to a thumb-sized hit and shows the pill thick at rest — the
         * same affordance a fine pointer drags, no glyph. A press with no travel
         * already toggled; it just had no hit area on touch. The drag hook binds
         * pointer events, so a button works exactly as the div did, and it says
         * what it is. Enter/Space toggle for the keyboard. */
        <button
          type="button"
          ref={grabRef}
          className="kol-rail-grab"
          aria-label={railOpen ? 'Collapse navigation' : 'Expand navigation'}
          aria-expanded={railOpen}
          /* a pointer press never leaves focus on the strip (2026-10-25): a rail closing on its key returned focus here
           * and the strip lit its full-height focus ring — *"make sure this doesnt happen when I press E 2 times"*.
           * Pointer events still reach the drag hook; Tab + Enter/Space keep the keyboard path */
          onMouseDown={(e) => e.preventDefault()}
          onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); snapOpenRef.current?.(!railOpen) } }}
        />
      )}
      {logomark && (
        /* the mark, and the app name beside it when open — uppercase like the
         * rows (user 2026-08-28: "uppercase CONSISTENCY"). The `w-8` centring box
         * puts the 20px mark on the same x as the 20px glyphs below it. */
        <Tooltip label={logomark.title} placement="right" asChild>
        <div
          onClick={() => onNavigate?.('/')}
          className="text-oq-96 cursor-pointer flex items-center gap-3 w-full overflow-hidden shrink-0 pt-1 mb-4"
        >
          <span className="w-8 flex justify-center shrink-0"><VectorLoader src={logomark.svgUrl} size={20} /></span>
          <span className="kol-helper-12 uppercase flex-1 min-w-0 truncate">{logomark.title}</span>
        </div>
        </Tooltip>
      )}
      {/* THE LIST IS ITS OWN REGION (2026-10-02): it takes the room between the mark and the pinned
        * bottom rows — what the bare spacer used to hold — scrolls when the rows outrun the window,
        * and fades at its foot unless `fade={false}`. Same column, same gap, as the rail's own. */}
      <div ref={listRef} style={ctx && slotOpen ? { flex: '0 1 auto' } : undefined} className={`kol-shell-rail-list flex min-h-0 w-full flex-1 flex-col items-start gap-2 overflow-y-auto overflow-x-hidden ${fade ? 'kol-shell-rail-list--fade kol-scroll-fade' : ''}`.trim()}>
        {section ? (
          <>
            <RailItem icon="arrow-left" path={section.path} label={section.label} currentPath="" onNavigate={() => setEntered(trail.slice(0, -1).map((t) => t.path))} iconComponent={iconComponent} railOpen={drawer || railOpen} />
            {section.sub.map((s) => <RailItem key={s.path} {...s} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={drawer || railOpen}
              onEnter={enters(s) ? () => setEntered([...trail.map((t) => t.path), s.path]) : undefined} />)}
          </>
        ) : items.map(row)}
      </div>
      {ctx && (
        <div
          ref={slotRef}
          className="kol-shell-rail-slot w-full min-h-0 overflow-x-hidden"
          style={{ flex: slotOpen ? '1 1 auto' : '0 0 0px', overflowY: slotOpen ? 'auto' : 'hidden' }}
        />
      )}
      {/* the rule runs the full rail width, out past the px-2 */}
      {bottomItems.length > 0 && <div className="self-stretch -mx-2 border-t border-fg-08" />}
      {bottomItems.map(row)}
    </div>
  )
}
