text: import Service, {inject as service} from '@ember/service';
  import {
      Color,
      darkenToContrastThreshold,
      lightenToContrastThreshold
  } from '@tryghost/color-utils';
  import {action, get} from '@ember/object';
  import {isEmpty} from '@ember/utils';
  import {tracked} from '@glimmer/tracking';
  
  function collectMetadataClasses(transition, prop) {
      let oldClasses = [];
      let newClasses = [];
      let {from, to} = transition;
  
      while (from) {
          oldClasses = oldClasses.concat(get(from, `metadata.${prop}`) || []);
          from = from.parent;
      }
  
      while (to) {
          newClasses = newClasses.concat(get(to, `metadata.${prop}`) || []);
          to = to.parent;
      }
  
      return {oldClasses, newClasses};
  }
  
  function updateBodyClasses(transition) {
      let {body} = document;
      let {oldClasses, newClasses} = collectMetadataClasses(transition, 'bodyClasses');
  
      oldClasses.forEach((oldClass) => {
          body.classList.remove(oldClass);
      });
      newClasses.forEach((newClass) => {
          body.classList.add(newClass);
      });
  }
  
  export default class UiService extends Service {
      @service config;
      @service dropdown;
      @service feature;
      @service mediaQueries;
      @service router;
      @service settings;
  
      @tracked contextualNavMenu = null;
      @tracked isFullScreen = false;
      @tracked mainClass = '';
      @tracked showMobileMenu = false;
  
      get isMobile() {
          return this.mediaQueries.isMobile;
      }
  
      get isSideNavHidden() {
          return this.isFullScreen || this.isMobile;
      }
  
      get hasSideNav() {
          return !this.isSideNavHidden;
      }
  
      get backgroundColor() {
          // hardcoded background colors because
          // grabbing color from .gh-main with getComputedStyle always returns #ffffff
          return this.feature.nightShift ? '#151719' : '#ffffff';
      }
  
      get adjustedAccentColor() {
          const accentColor = Color(this.settings.get('accentColor'));
          const backgroundColor = Color(this.backgroundColor);
  
          // WCAG contrast. 1 = lowest contrast, 21 = highest contrast
          const accentContrast = accentColor.contrast(backgroundColor);
  
          if (accentContrast > 2) {
              return accentColor.hex();
          }
  
          let adjustedAccentColor = accentColor;
  
          if (this.feature.nightShift) {
              adjustedAccentColor = lightenToContrastThreshold(accentColor, backgroundColor, 2);
          } else {
              adjustedAccentColor = darkenToContrastThreshold(accentColor, backgroundColor, 2);
          }
  
          return adjustedAccentColor.hex();
      }
  
      constructor() {
          super(...arguments);
  
          this.router.on('routeDidChange', (transition) => {
              updateBodyClasses(transition);
  
              this.updateDocumentTitle();
  
              let {newClasses: mainClasses} = collectMetadataClasses(transition, 'mainClasses');
              this.mainClass = mainClasses.join(' ');
          });
      }
  
      @action
      closeMenus() {
          this.dropdown.closeDropdowns();
          this.showMobileMenu = false;
      }
  
      @action
      closeMobileMenu() {
          this.showMobileMenu = false;
      }
  
      @action
      openMobileMenu() {
          this.showMobileMenu = true;
      }
  
      @action
      setMainClass(mainClass) {
          this.mainClass = mainClass;
      }
  
      @action
      updateDocumentTitle() {
          let {currentRoute} = this.router;
          let tokens = [];
  
          while (currentRoute) {
              let titleToken = get(currentRoute, 'metadata.titleToken');
  
              if (typeof titleToken === 'function') {
                  titleToken = titleToken();
              }
  
              if (titleToken) {
                  tokens.unshift(titleToken);
              }
  
              currentRoute = currentRoute.parent;
          }
  
          let blogTitle = this.config.get('blogTitle');
  
          if (!isEmpty(tokens)) {
              window.document.title = `${tokens.join(' - ')} - ${blogTitle}`;
          } else {
              window.document.title = blogTitle;
          }
      }
  
      @action
      initBodyDragHandlers() {
          // when any drag event is occurring we add `data-user-is-dragging` to the
          // body element so that we can have dropzones start listening to pointer
          // events allowing us to have interactive elements "underneath" drop zones
          this.bodyDragEnterHandler = (event) => {
              if (!event.dataTransfer) {
                  return;
              }
  
              document.body.dataset.userIsDragging = true;
              window.clearTimeout(this.dragTimer);
          };
  
          this.bodyDragLeaveHandler = (event) => {
              // only remove document-level "user is dragging" indicator when leaving the document
              if (event.screenX !== 0 || event.screenY !== 0) {
                  return;
              }
  
              window.clearTimeout(this.dragTimer);
              this.dragTimer = window.setTimeout(() => {
                  delete document.body.dataset.userIsDragging;
              }, 50);
          };
  
          this.cancelDrag = () => {
              delete document.body.dataset.userIsDragging;
          };
  
          document.body.addEventListener('dragenter', this.bodyDragEnterHandler, {capture: true});
          document.body.addEventListener('dragleave', this.bodyDragLeaveHandler, {capture: true});
          document.body.addEventListener('dragend', this.cancelDrag, {capture: true});
          document.body.addEventListener('drop', this.cancelDrag, {capture: true});
      }
  
      @action
      cleanupBodyDragHandlers() {
          document.body.removeEventListener('dragenter', this.bodyDragEnterHandler, {capture: true});
          document.body.removeEventListener('dragleave', this.bodyDragLeaveHandler, {capture: true});
          document.body.removeEventListener('dragend', this.cancelDrag, {capture: true});
          document.body.removeEventListener('drop', this.cancelDrag, {capture: true});
      }
  }
  