@use '../../variables' as var;

@use '../atoms';
@use '../layout';
@use '../themes';

$-border-radius: var.$border-radius;
$-color-background: var.$color-background;
$-header-height: var.$touch-target-size;

/**
 * Patterns that are composites of multiple atomic utilities, but
 * are not standalone components.
 */

/**
 * Give an element a border, background color and internal vertical spacing
 */

// A content block, with default padding and vertical spacing between elements
@mixin block {
  @include layout.padding(5);
  @include layout.vertical-spacing;
}

@mixin frame {
  @include block;
  @include atoms.border;
  border-radius: $-border-radius;
  background-color: $-color-background;

  @include themes.theme('clean') {
    // A frame should not have any borders in the clean theme.
    border: none;
  }
}

/**
 * A frame with a shadow and (optional) shadow hover effect. Fills available
 * horizontal space.
 *
 * @param {boolean} [$with-hover] - Should this frame have a hover effect?
 */
@mixin card($with-hover: true) {
  @include frame;
  @include atoms.shadow;
  @if $with-hover {
    &:hover,
    &.is-focused {
      @include atoms.shadow($active: true);
    }
  }

  width: 100%;

  @include themes.theme('clean') {
    // A card should have no shadows at all in the clean theme.
    box-shadow: none;
  }
}

/**
 * A container that lays out a collection of actions—typically buttons. Default
 * to a row layout, but also available as `column`.
 *
 * @param {'row'|'column'} [$direction]
 */
@mixin actions($direction: row) {
  @if $direction == row {
    @include layout.row(right);
    @include layout.horizontal-spacing($size: 4);
  } @else {
    @include layout.column;
    @include layout.vertical-spacing($size: 3);
  }
}

/**
 * A modal container, with responsive positioning and sizing.
 *
 * A modal should contain one immediate-child element; in most cases, an
 * element that applies the dialog[1] pattern. Content within the dialog
 * (grandchild elements of modal) can be managed with `overflow` rules so as not
 * to exceed the size constraints of the modal within the viewport.
 *
 * Example structure follows:
 *
 * <modal>
 *   <dialog>
 *     <div /> <!-- dialog can contain any arbitrary content -->
 *     <div style="overflow: auto">
 *       This content will scroll vertically if it is too tall for the
 *       available space in the modal.
 *     </div>
 *     <div />
 *     ...
 *   </dialog>
 * </modal>
 *
 * [1]: The modal's immediate child need not specifically apply the dialog
 *      pattern, but any element that needs to manage overflow within the modal
 *      must be a grandchild of the modal container element.
 */
@mixin modal {
  // The modal container is positioned horizontally- and vertically-centered
  @include layout.fixed-centered;

  // This sizing applies to smaller viewports
  width: 90vw;
  max-width: 90vw;
  max-height: 90vh;

  // For viewports with more horizontal room, adjust width.
  // Set a reasonable min-width on the modal so it doesn't look too dinky,
  // and a max-width so it doesn't stretch too wide.
  @media screen and (min-width: 48rem) {
    width: auto;
    min-width: 28rem;
    max-width: 44rem; // default for older browsers
    max-width: min(44rem, 90vw);
  }

  // For viewports with more vertical room, adjust vertical positioning to
  // near the top of the viewport.
  @media screen and (min-height: 32rem) {
    top: 10vh;
    // Provide at least 10vh of space between bottom of modal and bottom of
    // viewport, or else the modal will look vertically mis-aligned
    // 100vh - 10vh top - 10vh bottom = 80vh
    max-height: 80vh;
    transform: translate(-50%, 0);
  }

  // Apply rules to the immediate child (likely a dialog) to allow for overflow
  // management in modal's grandchild elements.
  & > * {
    // A flex layout allows item children to overflow without defining an
    // absolute container height.
    // See https://www.codementor.io/@stephenbunch/how-to-make-a-scrollable-container-with-dynamic-height-using-flexbox-bkaunxewg
    @include layout.column;

    // These height rules are duplicative of the height rules on the modal,
    // but need to be applied to this element to be able to constrain
    // overflow in children. Height rules must be on an overflow-constrained
    // element's immediate parent.
    max-height: 90vh;

    @media screen and (min-height: 32rem) {
      max-height: 80vh;
    }
  }
}

/**
 * A scrolling "frame" that shows scroll-hint shadows at the top and bottom
 * of the frame if:
 *  - The content height exceeds the frame height: i.e. can be scrolled, and
 *  - The content is scrollable in the shadow's direction (up or down)
 *
 * Shadows are not visible once the frame has been scrolled all the way in the
 * shadow's direction. Shadows are not visible if the height of the content
 * does not overflow the frame (is not scrollable).
 *
 * The shadow hinting is handled by four positioned background gradients:
 *   - One gradient each at top and bottom of frame that obscure the shadow hints
 *     (shadow covers). These use `background-attachment: local`, which makes
 *     their position fixed to the _content_ within the scrollbox.
 *   - One gradient each at the top and the bottom of the frame that are the
 *     shadow hints (shadows). These use `background-attachment: scroll` such
 *     that they are always positioned at the top and the bottom of the
 *     _scrollbox_ frame. When these positions align with the positions of the
 *     shadow covers--at the top and the bottom of the overflowing content--
 *     they will be obscured by those shadow covers.
 *
 * See https://lea.verou.me/2012/04/background-attachment-local/
 *
 * Safari's behavior is different because of a bug with
 * `background-attachment: local`.
 * See https://bugs.webkit.org/show_bug.cgi?id=219324
 * In Safari:
 *   - Scroll-hint shadows do not appear if content does not overflow (this is
 *     consistent with other browsers)
 *   - Only the bottom scroll-hint shadow appears if content overflows
 *   - The bottom scroll-hint shadow is always present, even if content is
 *     fully scrolled
 *
 * @param {CSSLength} [$shadow-top-position=0] - Top scroll-indicating shadow
 *   (y) position. Default 0: at top edge of scrollbox. Use case: move
 *   shadow down to accommodate a sticky header of a known height, such that the
 *   shadow appears below the header(s).
 * @param {CSSLength} [$shadow-bottom-position=100%] - Bottom scroll-indicating
 *   shadow position relative to scrollbox. Default 100%: flush to bottom.
 */
@mixin scrollbox($shadow-top-position: 0, $shadow-bottom-position: 100%) {
  overflow: auto;
  @include atoms.border;
  position: relative;
  height: 100%;
  width: 100%;

  background:
    // Top Shadow cover
    linear-gradient($-color-background 30%, rgba(255, 255, 255, 0)) 0
      $shadow-top-position,
    // Bottom shadow cover
    linear-gradient(rgba(255, 255, 255, 0), $-color-background 70%) 0
      $shadow-bottom-position,
    // Top shadow
    linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0.1),
        rgba(0, 0, 0, 0.05) 5px,
        rgba(255, 255, 255, 0) 70%
      )
      0 $shadow-top-position,
    // Bottom shadow
    linear-gradient(
        to top,
        rgba(0, 0, 0, 0.1),
        rgba(0, 0, 0, 0.05) 5px,
        rgba(255, 255, 255, 0) 70%
      )
      0 $shadow-bottom-position;
  background-repeat: no-repeat;
  background-color: $-color-background;
  background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;

  background-attachment: local, local, scroll, scroll;
}

/**
 * A scrollbox with the top shadow repositioned down by touch-target-size
 * to accommodate, e.g., a sticky header at the top of the scrollable content.
 */
@mixin scrollbox--with-header {
  @include scrollbox($shadow-top-position: $-header-height);
}

/**
 * A sticky container that is sized one-touch-unit high. This is currently
 * expanded upon by table styling, but not used in other patterns (yet).
 */
@mixin sticky-header {
  position: sticky;
  top: 0;
  height: $-header-height;

  @include layout.padding;
  font-weight: bold;

  // This element needs a background of some sort so that text scrolling
  // beneath it doesn't show through
  background-color: var.$color-background;

  width: 100%;

  &__heading {
    @include layout.row($align: center, $justify: center);
    height: 100%;
  }
}
