@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
@use 'typography-utils';
@use '../theming/inspection';

// Definition and versioning functions live in their own files to avoid circular dependencies, but
// we re-export them here so that historical imports from this file continue to work without needing
// to be updated.
@forward './versioning';

@mixin typography-hierarchy($theme, $selector: '.mat-typography', $back-compat: false) {
  @if inspection.get-theme-version($theme) == 1 {
    @include _m3-typography-hierarchy($theme, $selector, $back-compat);
  }
  @else {
    @include _m2-typography-hierarchy($theme, $selector);
  }
}

@function _get-selector($selectors, $prefix) {
  $result: ();
  @each $selector in $selectors {
    // Don't add "naked" tag selectors, and don't nest prefix selector.
    @if string.index($selector, '.') == 1 {
      $result: list.append($result, $selector, $separator: comma);
    }
    // Don't nest the prefix selector in itself.
    @if $selector != $prefix {
      $result: list.append($result, '#{$prefix} #{$selector}', $separator: comma);
    }
  }
  @return $result;
}

@mixin _m3-typography-level($theme, $selector-prefix, $level, $selectors, $margin: null) {
  #{_get-selector($selectors, $selector-prefix)} {
    // TODO(mmalerba): When we expose system tokens as CSS vars, we should change this to emit token
    //  slots.
    font: inspection.get-theme-typography($theme, $level, font);
    letter-spacing: inspection.get-theme-typography($theme, $level, letter-spacing);
    @if $margin != null {
      margin: 0 0 $margin;
    }
  }
}

@mixin _m3-typography-hierarchy($theme, $selector-prefix, $add-m2-selectors) {
 $levels: (
   display-large: (
     selectors: ('.mat-display-large', 'h1'),
     m2-selectors: ('.mat-h1', '.mat-headline-1'),
     margin: 0.5em
   ),
   display-medium: (
     selectors: ('.mat-display-medium', 'h2'),
     m2-selectors: ('.mat-h2', '.mat-headline-2'),
     margin: 0.5em
   ),
   display-small: (
     selectors: ('.mat-display-small', 'h3'),
     m2-selectors: ('.mat-h3', '.mat-headline-3'),
     margin: 0.5em
   ),
   headline-large: (
     selectors: ('.mat-headline-large', 'h4'),
     m2-selectors: ('.mat-h4', '.mat-headline-4'),
     margin: 0.5em
   ),
   headline-medium: (
     selectors: ('.mat-headline-medium', 'h5'),
     m2-selectors: ('.mat-h5', '.mat-headline-5'),
     margin: 0.5em
   ),
   headline-small: (
     selectors: ('.mat-headline-small', 'h6'),
     m2-selectors: ('.mat-h6', '.mat-headline-6'),
     margin: 0.5em
   ),
   title-large: (
     selectors: ('.mat-title-large'),
     m2-selectors: ('.mat-subtitle-1'),
   ),
   title-medium: (
     selectors: ('.mat-title-medium'),
     m2-selectors: ('.mat-subtitle-2'),
   ),
   title-small: (
     selectors: ('.mat-title-small')
   ),
   body-large: (
     selectors: ('.mat-body-large', $selector-prefix),
     m2-selectors: ('.mat-body', '.mat-body-strong', '.mat-body-2'),
   ),
   body-medium: (
     selectors: ('.mat-body-medium')
   ),
   body-small: (
     selectors: ('.mat-body-small')
   ),
   label-large: (
     selectors: ('.mat-label-large')
   ),
   label-medium: (
     selectors: ('.mat-label-medium')
   ),
   label-small: (
     selectors: ('.mat-label-small'),
     m2-selectors: ('.mat-small', '.mat-caption')
   ),
 );

  @each $level, $options in $levels {
    @if $add-m2-selectors {
      $options: map.set($options, selectors,
          list.join(map.get($options, selectors), map.get($options, m2-selectors) or ()));
    }
    $options: map.remove($options, m2-selectors);

    // Apply styles for the level.
    @include _m3-typography-level($theme, $selector-prefix, $level, $options...);

    // Also style <p> inside body-large.
    @if $level == body-large {
      #{_get-selector(map.get($options, selectors), $selector-prefix)} {
        p {
          margin: 0 0 0.75em;
        }
      }
    }
  }
}

/// Emits baseline typographic styles based on a given config.
/// @param {Map} $config-or-theme A typography config for an entire theme.
/// @param {String} $selector Ancestor selector under which native elements, such as h1, will
///     be styled.
@mixin _m2-typography-hierarchy($theme, $selector) {
  // Note that it seems redundant to prefix the class rules with the `$selector`, however it's
  // necessary if we want to allow people to overwrite the tag selectors. This is due to
  // selectors like `#{$selector} h1` being more specific than ones like `.mat-title`.
  .mat-h1,
  .mat-headline-5,
  #{$selector} .mat-h1,
  #{$selector} .mat-headline-5,
  #{$selector} h1 {
    font: inspection.get-theme-typography($theme, headline-5, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-5, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h2,
  .mat-headline-6,
  #{$selector} .mat-h2,
  #{$selector} .mat-headline-6,
  #{$selector} h2 {
    font: inspection.get-theme-typography($theme, headline-6, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-6, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h3,
  .mat-subtitle-1,
  #{$selector} .mat-h3,
  #{$selector} .mat-subtitle-1,
  #{$selector} h3 {
    font: inspection.get-theme-typography($theme, subtitle-1, font);
    letter-spacing: inspection.get-theme-typography($theme, subtitle-1, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h4,
  .mat-body-1,
  #{$selector} .mat-h4,
  #{$selector} .mat-body-1,
  #{$selector} h4 {
    font: inspection.get-theme-typography($theme, body-1, font);
    letter-spacing: inspection.get-theme-typography($theme, body-1, letter-spacing);
    margin: 0 0 16px;
  }

  // Note: the spec doesn't have anything that would correspond to h5 and h6, but we add these for
  // consistency. The font sizes come from the Chrome user agent styles which have h5 at 0.83em
  // and h6 at 0.67em.
  .mat-h5,
  #{$selector} .mat-h5,
  #{$selector} h5 {
    @include typography-utils.font-shorthand(
       // calc is used here to support css variables
      calc(#{inspection.get-theme-typography($theme, body-2, font-size)} * 0.83),
      inspection.get-theme-typography($theme, body-2, font-weight),
      inspection.get-theme-typography($theme, body-2, line-height),
      inspection.get-theme-typography($theme, body-2, font-family)
    );

    margin: 0 0 12px;
  }

  .mat-h6,
  #{$selector} .mat-h6,
  #{$selector} h6 {
    @include typography-utils.font-shorthand(
       // calc is used here to support css variables
      calc(#{inspection.get-theme-typography($theme, body-2, font-size)} * 0.67),
      inspection.get-theme-typography($theme, body-2, font-weight),
      inspection.get-theme-typography($theme, body-2, line-height),
      inspection.get-theme-typography($theme, body-2, font-family)
    );

    margin: 0 0 12px;
  }

  .mat-body-strong,
  .mat-subtitle-2,
  #{$selector} .mat-body-strong,
  #{$selector} .mat-subtitle-2 {
    font: inspection.get-theme-typography($theme, subtitle-2, font);
    letter-spacing: inspection.get-theme-typography($theme, subtitle-2, letter-spacing);
  }

  .mat-body,
  .mat-body-2,
  #{$selector} .mat-body,
  #{$selector} .mat-body-2,
  #{$selector} {
    font: inspection.get-theme-typography($theme, body-2, font);
    letter-spacing: inspection.get-theme-typography($theme, body-2, letter-spacing);

    p {
      margin: 0 0 12px;
    }
  }

  .mat-small,
  .mat-caption,
  #{$selector} .mat-small,
  #{$selector} .mat-caption {
    font: inspection.get-theme-typography($theme, caption, font);
    letter-spacing: inspection.get-theme-typography($theme, caption, letter-spacing);
  }

  .mat-headline-1,
  #{$selector} .mat-headline-1 {
    font: inspection.get-theme-typography($theme, headline-1, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-1, letter-spacing);
    margin: 0 0 56px;
  }

  .mat-headline-2,
  #{$selector} .mat-headline-2 {
    font: inspection.get-theme-typography($theme, headline-2, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-2, letter-spacing);
    margin: 0 0 64px;
  }

  .mat-headline-3,
  #{$selector} .mat-headline-3 {
    font: inspection.get-theme-typography($theme, headline-3, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-3, letter-spacing);
    margin: 0 0 64px;
  }

  .mat-headline-4,
  #{$selector} .mat-headline-4 {
    font: inspection.get-theme-typography($theme, headline-4, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-4, letter-spacing);
    margin: 0 0 64px;
  }
}
