// =============================================================================
// Table of Contents - Floating Outline
// Right-rail outline rendered by `@domternal/extension-toc`. Lives OUTSIDE
// `.dm-editor` (which has `overflow: hidden`), so tokens pull from `:root`.
// =============================================================================

.dm-toc-outline {
  // Positioning wrapper. Inner `.dm-toc-outline-ticks` stacks + clips
  // ticks; card is an absolute sibling, untouched by the clip.
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  z-index: var(--dm-toc-z-index, 10);

  // No background / no border in collapsed state - just a column of ticks.
  // The expanded card adds its own chrome.
  background: transparent;
  border: 0;
  margin: 0;
  pointer-events: auto;

  // Positioning by `data-anchor`:
  //
  // - editor: nav is `position: sticky`. Plugin toggles
  //   `data-bottom-visible` via an IntersectionObserver on a sentinel
  //   at the shell's bottom edge.
  //   - bottom-visible='false' (editor extends below the viewport):
  //     nav sticks at `--dm-toc-mid-top` (= `calc(50vh - height/2)`,
  //     set from the measured nav height). The BOX is centered around
  //     50vh - no transform - so the visual is fully constrained by
  //     the containing block and never escapes the editor's bounds.
  //   - bottom-visible='true' (editor's bottom is on screen): sticky
  //     offset becomes `1rem`. The nav's natural position is set by
  //     the shell (flex-center) or by inline margin-top (frozen mode)
  //     so it stays put until the viewport top scrolls past it.
  // - viewport: `position: fixed` to the right edge of the viewport,
  //   vertically centered.
  &[data-anchor='editor'] {
    position: sticky;
    top: var(--dm-toc-mid-top, 50vh);
  }
  &[data-anchor='editor'][data-bottom-visible='true'] {
    top: var(--dm-toc-editor-top, 1rem);
  }
  // `scroll-mode='container'`: sticky pins the nav at the host's
  // visible middle, zero JS during scroll. Plugin publishes
  // `--dm-toc-mid-half-height` (= nav.offsetHeight / 2).
  &[data-anchor='editor'][data-scroll-mode='container'] {
    position: sticky;
    top: calc(50% - var(--dm-toc-mid-half-height, 0px));
    right: 0;
  }
  &[data-anchor='viewport'] {
    position: fixed;
    top: 50%;
    right: var(--dm-toc-right-offset, 24px);
    transform: translateY(-50%);
  }

  // Visibility guards. The plugin sets data-state and data-viewport
  // based on heading count and matchMedia. Hiding via CSS (vs JS
  // `display: none` directly) lets consumers override without
  // monkey-patching the plugin.
  //
  // State machine: hidden | collapsed | expanded
  //   hidden    - 0 headings / mobile / < minHeadings
  //   collapsed - default; ticks visible, card hidden
  //   expanded  - hover or focus-within; card visible, ticks hidden
  &[data-state='hidden'],
  &[data-viewport='mobile'] {
    display: none;
  }
}

// Editor-mode shell: out-of-flow column running the host's full
// vertical extent at its right inside edge. Width shrinks to the nav's
// intrinsic width; the shell itself is invisible. The nav inside
// uses the shell's height as its sticky range.
.dm-toc-outline-shell[data-anchor='editor'] {
  position: absolute;
  top: 25px;
  bottom: 25px;
  right: var(--dm-toc-page-offset, 8px);
  z-index: var(--dm-toc-z-index, 10);
  pointer-events: none;

  &:has(.dm-toc-outline[data-state='hidden']),
  &:has(.dm-toc-outline[data-viewport='mobile']) {
    display: none;
  }
}

// mode='center': flex-center sets the nav's natural position to host
// middle (used on mount when the editor fits the viewport). mode='frozen'
// uses inline margin-top instead (set by the plugin at the B→A
// transition). mode='middle' uses default block layout.
.dm-toc-outline-shell[data-anchor='editor'][data-mode='center'] {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

// `scroll-mode='container'`: shell spans the host (no top/bottom
// inset) and is JS-stretched to host.scrollHeight so the sticky's
// containing block covers the full scroll range.
.dm-toc-outline-shell[data-anchor='editor'][data-scroll-mode='container'] {
  top: 0;
  bottom: 0;
}

// Ticks column - capped at ~50% (Notion behavior): overflow simply
// clips, gap stays constant, column doesn't scroll. Defaults to
// `50vh` (page mode); container mode plugin overrides the var.
.dm-toc-outline-ticks {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--dm-toc-tick-gap, 10px);
  padding: var(--dm-toc-padding-block, 12px) var(--dm-toc-padding-inline, 8px);
  max-height: var(--dm-toc-ticks-max-h, 50vh);
  overflow: hidden;
}

.dm-toc-outline-tick {
  // Reset button defaults - we draw a solid filled rectangle.
  appearance: none;
  border: 0;
  margin: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  background: var(--dm-toc-tick-color, rgba(55, 53, 47, 0.4));
  // Lock tick height so the gap stays constant; overflow is clipped
  // by the wrapper instead of squeezed.
  flex-shrink: 0;
  height: var(--dm-toc-tick-height, 2px);
  border-radius: var(--dm-toc-tick-radius, 1px);
  // Width per heading level - h1 widest, deeper levels narrower.
  // Default for unmatched levels falls through to h3-width.
  width: var(--dm-toc-tick-h3-width, 8px);
  transition:
    background-color 120ms ease-out,
    width 120ms ease-out;

  &:hover {
    background: var(--dm-toc-tick-hover-color, rgba(55, 53, 47, 0.7));
  }

  // Keyboard focus needs a real ring. The hover-only alpha bump
  // (0.4 -> 0.7) is too subtle to act as a focus indicator for users
  // who can't see hover state. Outline-offset 3px keeps the ring
  // clear of the tick body without colliding with the next tick at
  // the default `--dm-toc-tick-gap: 10px`.
  &:focus-visible {
    background: var(--dm-toc-tick-hover-color, rgba(55, 53, 47, 0.7));
    outline: 2px solid var(--dm-accent, #2563eb);
    outline-offset: 3px;
  }

  // Active state: the tick for the heading the user is reading. Two
  // selectors so consumers can target either the JS class (`--active`)
  // or the a11y attribute (`aria-current="location"`).
  &.dm-toc--active,
  &[aria-current='location'] {
    background: var(--dm-toc-tick-active-color, rgba(55, 53, 47, 0.95));
  }

  // Per-level width selectors. h1 / h2 / h3 are the canonical Notion
  // levels; we also handle h4-h6 for consumers configuring custom
  // levels via `levels: [1,2,3,4,5,6]`.
  &[data-level='1'] { width: var(--dm-toc-tick-h1-width, 18px); }
  &[data-level='2'] { width: var(--dm-toc-tick-h2-width, 12px); }
  &[data-level='3'] { width: var(--dm-toc-tick-h3-width, 8px); }
  &[data-level='4'] { width: var(--dm-toc-tick-h4-width, 6px); }
  &[data-level='5'] { width: var(--dm-toc-tick-h5-width, 5px); }
  &[data-level='6'] { width: var(--dm-toc-tick-h6-width, 4px); }
}

// Ticks fade out when the card takes over.
.dm-toc-outline[data-state='expanded'] .dm-toc-outline-tick {
  opacity: 0;
  pointer-events: none;
}

// =============================================================================
// Expanded card
// =============================================================================
// The card is absolutely positioned within the nav (which is fixed at
// the right gutter). Anchored to the nav's right edge and vertically
// centered against the same midline as the tick column - it visually
// REPLACES the ticks rather than appearing alongside them. CSS opacity
// + transform handle the slide-in transition; the plugin only flips
// the parent's data-state attribute.

.dm-toc-outline-card {
  position: absolute;
  right: 0;
  top: 50%;
  // `--dm-toc-card-shift-y` is set by the plugin to nudge the card back
  // into the viewport when the nav sits near the top or bottom edge.
  transform:
    translateY(calc(-50% + var(--dm-toc-card-shift-y, 0px)))
    translateX(var(--dm-toc-card-offset, 8px));
  min-width: var(--dm-toc-card-min-width, 180px);
  max-width: var(--dm-toc-card-max-width, 260px);
  // Card matches the ticks wrapper height; the rest of the rows
  // are reachable by scrolling the card itself.
  max-height: var(--dm-toc-ticks-max-h, 50vh);
  overflow-y: auto;
  padding: var(--dm-toc-card-padding-block, 6px) 0;
  background: var(--dm-toc-card-bg, rgba(255, 255, 255, 0.98));
  border: var(--dm-toc-card-border, 1px solid rgba(0, 0, 0, 0.06));
  border-radius: var(--dm-toc-card-radius, 6px);
  box-shadow: var(--dm-toc-card-shadow, 0 8px 24px rgba(0, 0, 0, 0.08));

  // Tokenised slim scrollbar so dark theme overrides keep contrast.
  scrollbar-width: thin;
  scrollbar-color: var(--dm-scrollbar-thumb, rgba(0, 0, 0, 0.18)) transparent;

  &::-webkit-scrollbar {
    width: 6px;
  }
  &::-webkit-scrollbar-track {
    background: transparent;
    margin: 0.375rem 0;
  }
  &::-webkit-scrollbar-thumb {
    background: var(--dm-scrollbar-thumb, rgba(0, 0, 0, 0.18));
    border-radius: 3px;
    transition: background-color 0.15s;
  }
  &::-webkit-scrollbar-thumb:hover {
    background: var(--dm-scrollbar-thumb-hover, rgba(0, 0, 0, 0.28));
  }

  display: flex;
  flex-direction: column;
  align-items: stretch;

  // Hidden by default in collapsed state. opacity + transform together
  // let the card softly slide in from the right when expanding. The
  // pointer-events flip prevents interactions with the invisible card.
  opacity: 0;
  pointer-events: none;
  transition:
    opacity var(--dm-toc-card-transition, 180ms) ease-out,
    transform var(--dm-toc-card-transition, 180ms) ease-out;
}

.dm-toc-outline[data-state='expanded'] .dm-toc-outline-card {
  opacity: 1;
  pointer-events: auto;
  transform:
    translateY(calc(-50% + var(--dm-toc-card-shift-y, 0px)))
    translateX(0);
}

.dm-toc-outline-row {
  appearance: none;
  border: 0;
  margin: 0;
  background: transparent;
  cursor: pointer;
  text-align: start;
  font: inherit;
  font-size: var(--dm-toc-row-font-size, 13px);
  line-height: var(--dm-toc-row-line-height, 1.4);
  color: var(--dm-toc-row-color, rgba(55, 53, 47, 0.7));
  // Constant row height; long lists scroll the card, no squeeze.
  flex-shrink: 0;
  padding:
    var(--dm-toc-row-padding-block, 4px)
    var(--dm-toc-row-padding-inline-end, 16px)
    var(--dm-toc-row-padding-block, 4px)
    var(--dm-toc-row-padding-inline-start, 12px);
  // Truncate long heading text - it's a navigation aid, not the
  // headings themselves.
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: 3px;
  transition: background-color 120ms ease-out, color 120ms ease-out;

  &:hover {
    background: var(--dm-toc-row-hover-bg, rgba(0, 0, 0, 0.04));
  }

  &:focus-visible {
    outline: 2px solid var(--dm-accent, #2563eb);
    outline-offset: -2px;
  }

  // Active row mirrors the active tick. Selector matches both the
  // JS-applied class (`--active`, shared with ticks) and the a11y
  // attribute the plugin sets simultaneously - same convention as ticks.
  &.dm-toc--active,
  &[aria-current='location'] {
    color: var(--dm-toc-row-active-color, rgba(55, 53, 47, 0.95));
    font-weight: var(--dm-toc-row-active-weight, 600);
  }

  // Per-level indent - h1 sits flush at the start padding, h2 nudges
  // in by one row-indent, h3 by two, etc. Keeps the visual hierarchy
  // legible at a glance even when text is truncated.
  &[data-level='1'] {
    padding-inline-start: var(--dm-toc-row-padding-inline-start, 12px);
  }
  &[data-level='2'] {
    padding-inline-start:
      calc(var(--dm-toc-row-padding-inline-start, 12px) + var(--dm-toc-row-indent, 14px));
  }
  &[data-level='3'] {
    padding-inline-start:
      calc(var(--dm-toc-row-padding-inline-start, 12px) + var(--dm-toc-row-indent, 14px) * 2);
  }
  &[data-level='4'] {
    padding-inline-start:
      calc(var(--dm-toc-row-padding-inline-start, 12px) + var(--dm-toc-row-indent, 14px) * 3);
  }
  &[data-level='5'] {
    padding-inline-start:
      calc(var(--dm-toc-row-padding-inline-start, 12px) + var(--dm-toc-row-indent, 14px) * 4);
  }
  &[data-level='6'] {
    padding-inline-start:
      calc(var(--dm-toc-row-padding-inline-start, 12px) + var(--dm-toc-row-indent, 14px) * 5);
  }
}

// =============================================================================
// Inline /toc block
// Lives INSIDE the editor as a regular content block. NodeView renders
// the heading list; click on a row routes to scrollToHeading.
// =============================================================================

.dm-toc-block {
  display: block;
  margin-block: var(--dm-toc-block-margin-block, 1rem);
  padding:
    var(--dm-toc-block-padding-block, 12px)
    var(--dm-toc-block-padding-inline, 16px);
  background: var(--dm-toc-block-bg, rgba(0, 0, 0, 0.02));
  border: var(--dm-toc-block-border, 1px solid rgba(0, 0, 0, 0.08));
  border-radius: var(--dm-toc-block-radius, 6px);

  // Block is contenteditable=false (atom node, NodeView-rendered).
  // Surface a subtle outline ring on PM selection so the user knows
  // the block is selected (e.g. for delete with Backspace on the
  // gap cursor immediately after).
  &.ProseMirror-selectednode {
    outline: 2px solid var(--dm-accent, #2563eb);
    outline-offset: 2px;
  }
}

.dm-toc-block-empty {
  margin: 0;
  color: var(--dm-toc-block-empty-color, rgba(55, 53, 47, 0.5));
  font-style: var(--dm-toc-block-empty-font-style, italic);
}

.dm-toc-block-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.dm-toc-block-item {
  margin: 0;
}

.dm-toc-block-link {
  // Reset button defaults for an in-flow link-style affordance.
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: inherit;
  font-size: var(--dm-toc-block-link-font-size, 0.9375rem);
  line-height: var(--dm-toc-block-link-line-height, 1.5);
  color: var(--dm-toc-block-link-color, rgba(55, 53, 47, 0.8));
  text-align: start;
  width: 100%;
  display: block;
  padding:
    var(--dm-toc-block-link-padding-block, 4px)
    var(--dm-toc-block-link-padding-inline, 8px);
  border-radius: var(--dm-toc-block-link-radius, 3px);
  transition: background-color 120ms ease-out, color 120ms ease-out;

  &:hover {
    color: var(--dm-toc-block-link-hover-color, rgba(55, 53, 47, 1));
    background: var(--dm-toc-block-link-hover-bg, rgba(0, 0, 0, 0.04));
  }

  &:focus-visible {
    outline: 2px solid var(--dm-accent, #2563eb);
    outline-offset: -2px;
  }

  // Active row mirrors the outline's active tick/row contract.
  &.dm-toc-block-link--active,
  &[aria-current='location'] {
    color: var(--dm-toc-block-link-active-color, rgba(55, 53, 47, 0.95));
    font-weight: var(--dm-toc-block-link-active-weight, 600);
  }

  // Per-level indent. Same shape as outline rows; padding on the
  // link itself so the hover background covers the full visual row
  // including the indent.
  &[data-level='1'] {
    padding-inline-start: var(--dm-toc-block-link-padding-inline, 8px);
  }
  &[data-level='2'] {
    padding-inline-start:
      calc(var(--dm-toc-block-link-padding-inline, 8px) + var(--dm-toc-block-link-indent, 16px));
  }
  &[data-level='3'] {
    padding-inline-start:
      calc(var(--dm-toc-block-link-padding-inline, 8px) + var(--dm-toc-block-link-indent, 16px) * 2);
  }
  &[data-level='4'] {
    padding-inline-start:
      calc(var(--dm-toc-block-link-padding-inline, 8px) + var(--dm-toc-block-link-indent, 16px) * 3);
  }
  &[data-level='5'] {
    padding-inline-start:
      calc(var(--dm-toc-block-link-padding-inline, 8px) + var(--dm-toc-block-link-indent, 16px) * 4);
  }
  &[data-level='6'] {
    padding-inline-start:
      calc(var(--dm-toc-block-link-padding-inline, 8px) + var(--dm-toc-block-link-indent, 16px) * 5);
  }
}

// Reduced-motion guard. Repeated here defensively even though `index.scss`
// has a global block, in case that block is ever scoped to `.dm-editor`-only.
@media (prefers-reduced-motion: reduce) {
  .dm-toc-outline-tick,
  .dm-toc-outline-card,
  .dm-toc-outline-row,
  .dm-toc-block-link {
    transition: none;
  }

  // Card start-state offset becomes 0 too: with no transition, the
  // X translate would jump from `var(--dm-toc-card-offset)` to 0
  // instantly anyway, but resetting the start position to 0 means
  // the card never enters its mid-animation visual state on a fast
  // expand/collapse cycle (which can briefly leak a 1-frame
  // off-position card).
  .dm-toc-outline-card {
    transform:
      translateY(calc(-50% + var(--dm-toc-card-shift-y, 0px)))
      translateX(0);
  }
}

// Print rules for the outline and the inline `/toc` block live in
// `_print.scss` with the rest of the paper layer.

// Forced-colors / Windows High Contrast: backgrounds are stripped, so
// ticks need explicit borders to stay visible. We use `Highlight` /
// `ButtonText` (OS palette) rather than `forced-color-adjust: none`.
@media (forced-colors: active) {
  .dm-toc-outline-tick {
    background: ButtonText;
    border: 1px solid ButtonText;

    &.dm-toc--active,
    &[aria-current='location'] {
      background: Highlight;
      border-color: Highlight;
    }
  }

  .dm-toc-outline-card {
    border: 1px solid ButtonText;
  }

  .dm-toc-outline-row,
  .dm-toc-block-link {
    &.dm-toc--active,
    &.dm-toc-block-link--active,
    &[aria-current='location'] {
      color: Highlight;
    }
  }

  .dm-toc-block {
    border: 1px solid ButtonText;
  }
}
