@use '../utils/constants.scss';
@use '../utils/mixins.scss' as mixins;
@use '../utils/tokens.scss' as tokens;
@use '../utils/stateLayerVars.scss' as state-layer-vars;

$calendarBaseClass: '#{constants.$prefix}calendar';

// No motion tokens exist in the token set, so the duration is local — the same
// approach dialog.module.scss takes with $dialog-motion-duration. The distance
// is a spacing token (0.5rem), so the slide scales with the root font size the
// way every other dimension in this lib does.
$monthMotionDuration: 0.25s;
$monthMotionOffset: tokens.$bm-sem-space-50;

// Tokens resolve to `var(--…)`, and `-var(…)` isn't valid CSS, so the reverse
// direction needs `calc` — the lib's precedent for negating one (button, card,
// combobox).
$monthMotionOffsetReverse: calc(0px - #{tokens.$bm-sem-space-50});

// Signed horizontal offset the entering month slides in from. Set per
// direction, and flipped again in RTL.
$monthEnterOffset: '--#{constants.$prefix}calendar-month-enter-offset';

.#{$calendarBaseClass} {
  // The grid is a fixed 7 × 40px wide per Figma; without this the root block
  // would stretch to its container and leave the table floating inside it.
  inline-size: fit-content;

  // Announced but not drawn: the column's full weekday name, and each cell's
  // full date. Both are cases where the visible text is an abbreviation and the
  // spoken text needs to be the whole thing. This needs a real rule — an
  // undeclared class resolves to undefined in the CSS-module build, and the
  // text would render on screen instead of being hidden.
  &__visually-hidden {
    @include mixins.visually-hidden;
  }

  &__header {
    display: flex;
    align-items: center;
    // The 40px arrow buttons on both sides are equal width, so space-between
    // centres the label without any further rules.
    justify-content: space-between;
    block-size: tokens.$bm-sem-size-height-md;
  }

  // Flex reverses the arrows' positions under `dir="rtl"`, but the chevron
  // artwork is direction-agnostic, so without this both arrows end up pointing
  // inward at the label. Rotating the glyph is the breadcrumbs precedent.
  [dir='rtl'] &__header .#{constants.$prefix}icon {
    transform: rotate(180deg);
  }

  &__header-label {
    font: tokens.$bm-sem-typo-label-md;
    color: tokens.$bm-sem-color-text-primary;
  }

  &__grid {
    // Suppresses the UA's default cell borders and 2px spacing. Row rhythm is
    // handled by cell padding below instead of `border-spacing`, which would
    // also add space above the first row and below the last.
    border-collapse: collapse;
  }

  &__day {
    box-sizing: border-box;
    // Figma binds the cell's width to the same `height/md` token it uses for
    // the 40px date cells, so the columns line up between header and grid.
    inline-size: tokens.$bm-sem-size-height-md;
    block-size: tokens.$bm-sem-size-height-sm;

    font: tokens.$bm-sem-typo-label-xs;
    color: tokens.$bm-sem-color-text-secondary;
    text-align: center;
  }

  // Month transition. Only the date rows animate — the weekday header doesn't
  // change between months, so animating it would be noise. `data-nav-direction`
  // is absent until the user navigates, so the first paint never animates.
  //
  // The direction is carried as a custom property rather than a second keyframe
  // so there is exactly one `animation` declaration. That matters for RTL:
  // `translateX` has no logical equivalent, so the sign has to be flipped by
  // selector, and a `[dir='rtl'] &` override (0,3,0) would outrank the
  // reduced-motion rule below and defeat it. Flipping a value instead of an
  // animation-name keeps the two concerns from colliding.
  &__dates[data-nav-direction] {
    animation: #{$calendarBaseClass}-month-enter $monthMotionDuration ease-out;

    // WCAG 2.3.3 — the month swaps instantly when the user asks for less
    // motion. Nested here so it shares the rule's specificity and wins on order.
    @media (prefers-reduced-motion: reduce) {
      animation: none;
    }
  }

  &__dates[data-nav-direction='next'] {
    #{$monthEnterOffset}: #{$monthMotionOffset};
  }

  &__dates[data-nav-direction='previous'] {
    #{$monthEnterOffset}: #{$monthMotionOffsetReverse};
  }

  // `[dir='rtl'] &` is this lib's dominant RTL precedent (breadcrumbs, slider,
  // header).
  [dir='rtl'] &__dates[data-nav-direction='next'] {
    #{$monthEnterOffset}: #{$monthMotionOffsetReverse};
  }

  [dir='rtl'] &__dates[data-nav-direction='previous'] {
    #{$monthEnterOffset}: #{$monthMotionOffset};
  }

  &__date {
    padding: 0;
  }

  // 4px between week rows, with no leading or trailing gap.
  //
  // Structure-dependent: this relies on `<td>` being a direct child of `<tr>`
  // and on the rows being direct siblings. Audit it before inserting any
  // wrapper element into the grid (see AGENTS.md).
  &__week + &__week > &__date {
    padding-block-start: tokens.$bm-sem-space-25;
  }

  // The 40px round cell body. A `<span>` while the calendar is display-only,
  // and a `<button>` sharing this same class once a calendar becomes
  // interactive (C-3) — which is why the name and the `position: relative` the
  // state layer needs both stay put across the tag change.
  &__date-button {
    position: relative;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;

    inline-size: tokens.$bm-sem-size-height-md;
    block-size: tokens.$bm-sem-size-height-md;
    padding: 0;

    border: none;
    border-radius: tokens.$bm-sem-radius-round;
    background-color: transparent;

    font: tokens.$bm-sem-typo-body-sm;
    color: tokens.$bm-sem-color-text-primary;

    // The static `<span>` isn't clickable, so it keeps the default cursor; the
    // tag-qualified rule below raises it to a pointer only once the cell is a
    // `<button>`. No `:hover` / `:focus-visible` rules here either — those
    // belong to the state layer, per .claude/rules/accessibility.md.
    cursor: default;
  }

  // Interactive cells (C-3) render a `<button>` instead of a `<span>` but keep
  // the `&__date-button` class, so the tag itself is what selects the pointer.
  // The native outline goes too: the state layer draws the focus-visible ring,
  // and leaving the browser's in place draws a second one inside it — the same
  // suppression closeButton, actionList and card use.
  //
  // The state layer sits between the button's background and its text, as in
  // actionList and navigation: `isolation` gives the button its own stacking
  // context, so the layer's negative `z-index` drops it under the day number
  // but not under the selected fill. Painted over the text, the hover overlay
  // would dim it. Structure-dependent: the layer must stay a direct child.
  button.#{$calendarBaseClass}__date-button {
    cursor: pointer;
    outline: none;
    isolation: isolate;

    > .#{state-layer-vars.$stateLayerBaseClass} {
      z-index: -1;
    }
  }

  // Adjacent-month days.
  //
  // Both renderings are reachable since C-5: `showOutsideDays` defaults to
  // `true` and dims them, per the C-1 acceptance criteria and the convention in
  // MUI, React Aria and react-day-picker; `false` leaves the cells blank, which
  // is what Figma draws. Which of the two should be the zero-config default is
  // still design's to answer, and it no longer blocks anything. `text/secondary`
  // is used rather than the disabled treatment because these days are
  // selectable (C-3) and become focusable (C-10), so the disabled look would
  // signal the wrong affordance.
  //
  // A cell hidden by `showOutsideDays={false}` needs no rule of its own: it
  // renders an empty `&__date-button` span, which already carries the 40px box
  // the row's height comes from.
  &__date[data-outside-month='true'] &__date-button {
    color: tokens.$bm-sem-color-text-secondary;
  }

  // Today (C-4), per Figma `Calendar.Date` today=true: the number in the
  // selected text colour and SemiBold, with a 6px dot centred 4px above the
  // circle's bottom edge. Figma binds no variable to either value: 4px is the
  // `space-25` step, and 6px has no semantic token at all, so the dot is sized
  // from the primitive scale step until design adds a calendar component token.
  // Placed after the outside-month rule so today wins over the dimming when it
  // shows as padding, and before the selected rule so a selected today keeps
  // the fill (its dot takes the selected number's colour there). `highlightToday={false}` drops
  // `data-today`, and with it all of this; `aria-current` stays on the cell.
  &__date[data-today='true'] &__date-button {
    color: tokens.$bm-sem-color-text-selected;
    font: tokens.$bm-sem-typo-label-sm;

    &::after {
      content: '';
      position: absolute;
      inset-block-end: tokens.$bm-sem-space-25;
      inset-inline: 0;
      margin-inline: auto;
      inline-size: tokens.$bm-primitive-dimension-37;
      block-size: tokens.$bm-primitive-dimension-37;
      border-radius: tokens.$bm-sem-radius-round;
      background-color: tokens.$bm-sem-color-icon-selected;
    }
  }

  // Selected date (C-3), on the `<button>` and on the static `<span>` alike — a
  // `value` with no `onChange` shows its selection on a display grid. Placed
  // after the outside-month rule so that, at equal specificity, selected wins
  // when a selected date is showing as padding — a controlled `value`, then the
  // user paged away. The state layer's own vars
  // are overridden rather than adding raw
  // `:hover` / `:active` rules, matching Button's filled/accent treatment
  // (button.module.scss).
  &__date[data-selected='true'] &__date-button {
    background-color: tokens.$bm-expressive-color-inverse-bg;
    color: tokens.$bm-expressive-color-inverse-fg-stronger;
    font: tokens.$bm-sem-typo-label-sm;

    // Today's dot, when today is selected. Figma has no today+selected
    // variant; the number's colour on the fill keeps it visible, as AC3
    // requires.
    &::after {
      background-color: tokens.$bm-expressive-color-inverse-fg-stronger;
    }

    #{state-layer-vars.getStateLayerSelector('&', 'hover')} {
      #{state-layer-vars.$background}: tokens.$bm-sem-color-state-layer-hover-strong;
    }

    #{state-layer-vars.getStateLayerSelector('&', 'active')} {
      #{state-layer-vars.$background}: tokens.$bm-sem-color-state-layer-active-strong;
    }
  }
}

// The new month slides in from the side it came from: paging forward brings it
// in from the trailing edge, paging back from the leading edge. The offset
// falls back to 0 so a missing custom property degrades to a plain fade.
@keyframes #{$calendarBaseClass}-month-enter {
  from {
    opacity: 0;
    transform: translateX(var(#{$monthEnterOffset}, 0));
  }

  to {
    opacity: 1;
    transform: none;
  }
}
