text: import {
    Action,
    StateProperty,
    getStoreService,
  } from './amp-story-store-service';
  import {Layout} from '#core/dom/layout';
  import {closest} from '#core/dom/query';
  import {copyChildren, removeChildren} from '#core/dom';
  import {dev, user} from '#utils/log';
  import {getStoryAttributeSrc} from './utils';
  import {htmlFor} from '#core/dom/static-template';
  import {isArray, isObject} from '#core/types';
  import {parseJson} from '#core/types/object/json';
  import {setImportantStyles} from '#core/dom/style';
  
  /** @const {string} */
  const TAG = 'amp-story-access';
  
  /**
   * @enum {string}
   */
  export const Type = {
    BLOCKING: 'blocking',
    NOTIFICATION: 'notification',
  };
  
  /**
   * Story access blocking type template.
   * @param {!Element} element
   * @return {!Element}
   */
  const getBlockingTemplate = (element) => {
    return htmlFor(element)`
        <div class="i-amphtml-story-access-overflow">
          <div class="i-amphtml-story-access-container">
            <div class="i-amphtml-story-access-header">
              <div class="i-amphtml-story-access-logo"></div>
            </div>
            <div class="i-amphtml-story-access-content"></div>
          </div>
        </div>`;
  };
  
  /**
   * Story access notification type template.
   * @param {!Element} element
   * @return {!Element}
   */
  const getNotificationTemplate = (element) => {
    return htmlFor(element)`
        <div class="i-amphtml-story-access-overflow">
          <div class="i-amphtml-story-access-container">
            <div class="i-amphtml-story-access-content">
              <span class="i-amphtml-story-access-close-button" role="button">
                &times;
              </span>
            </div>
          </div>
        </div>`;
  };
  
  /**
   * The <amp-story-access> custom element.
   */
  export class AmpStoryAccess extends AMP.BaseElement {
    /** @param {!AmpElement} element */
    constructor(element) {
      super(element);
  
      /** @private {?Element} */
      this.containerEl_ = null;
  
      /** @private @const {!./amp-story-store-service.AmpStoryStoreService} */
      this.storeService_ = getStoreService(this.win);
    }
  
    /** @override */
    buildCallback() {
      // Defaults to blocking paywall.
      if (!this.element.hasAttribute('type')) {
        this.element.setAttribute('type', Type.BLOCKING);
      }
  
      const drawerEl = this.renderDrawerEl_();
  
      this.containerEl_ = dev().assertElement(
        drawerEl.querySelector('.i-amphtml-story-access-container')
      );
      const contentEl = dev().assertElement(
        drawerEl.querySelector('.i-amphtml-story-access-content')
      );
  
      copyChildren(this.element, contentEl);
      removeChildren(this.element);
  
      this.element.appendChild(drawerEl);
  
      this.allowlistActions_();
  
      this.initializeListeners_();
    }
  
    /** @override */
    isLayoutSupported(layout) {
      return layout == Layout.CONTAINER;
    }
  
    /**
     * @private
     */
    initializeListeners_() {
      this.storeService_.subscribe(StateProperty.ACCESS_STATE, (isAccess) => {
        this.onAccessStateChange_(isAccess);
      });
  
      this.storeService_.subscribe(
        StateProperty.CURRENT_PAGE_INDEX,
        (currentPageIndex) => {
          this.onCurrentPageIndexChange_(currentPageIndex);
        },
        true /** callToInitialize */
      );
  
      this.element.addEventListener('click', (event) => this.onClick_(event));
    }
  
    /**
     * Reacts to access state updates, and shows/hides the UI accordingly.
     * @param {boolean} isAccess
     * @private
     */
    onAccessStateChange_(isAccess) {
      if (this.getType_() === Type.BLOCKING) {
        this.toggle_(isAccess);
      }
    }
  
    /**
     * Reacts to story active page index update, and maybe display the
     * "notification" story-access.
     * @param {number} currentPageIndex
     */
    onCurrentPageIndexChange_(currentPageIndex) {
      if (this.getType_() === Type.NOTIFICATION) {
        // Only show the notification if on the first page of the story.
        // Note: this can be overriden by an amp-access attribute that might
        // show/hide the notification based on the user's authorizations.
        this.toggle_(currentPageIndex === 0);
      }
    }
  
    /**
     * Handles click events and maybe closes the paywall.
     * @param {!Event} event
     * @return {*} TODO(#23582): Specify return type
     * @private
     */
    onClick_(event) {
      const el = dev().assertElement(event.target);
  
      if (el.classList.contains('i-amphtml-story-access-close-button')) {
        return this.toggle_(false);
      }
  
      // Closes the menu if click happened outside of the main container.
      if (!closest(el, (el) => el === this.containerEl_, this.element)) {
        this.storeService_.dispatch(Action.TOGGLE_ACCESS, false);
      }
    }
  
    /**
     * @param {boolean} show
     * @private
     */
    toggle_(show) {
      this.mutateElement(() => {
        this.element.classList.toggle('i-amphtml-story-access-visible', show);
      });
    }
  
    /**
     * Returns the element's type.
     * @return {string}
     * @private
     */
    getType_() {
      return this.element.getAttribute('type').toLowerCase();
    }
  
    /**
     * Renders and returns an empty drawer element element that will contain the
     * publisher provided DOM, depending on the type of <amp-story-access>.
     * Blocking template gets a header containing the publisher's logo, and
     * notification template gets a "dismiss" button.
     * @return {!Element|undefined}
     * @private
     */
    renderDrawerEl_() {
      switch (this.getType_()) {
        case Type.BLOCKING:
          const drawerEl = getBlockingTemplate(this.element);
  
          const logoSrc = getStoryAttributeSrc(
            this.element,
            'publisher-logo-src',
            /* warn */ true
          );
  
          if (logoSrc) {
            const logoEl = dev().assertElement(
              drawerEl.querySelector('.i-amphtml-story-access-logo')
            );
            setImportantStyles(logoEl, {'background-image': `url(${logoSrc})`});
          }
  
          return drawerEl;
          break;
        case Type.NOTIFICATION:
          return getNotificationTemplate(this.element);
          break;
        default:
          user().error(
            TAG,
            'Unknown "type" attribute, expected one of: ' +
              'blocking, notification.'
          );
      }
    }
  
    /**
     * Allowlists the <amp-access> actions.
     * Depending on the publisher configuration, actions can be:
     *   - login
     *   - login-<namespace>
     *   - login-<namespace>-<type>
     *
     * Publishers can provide one (object) or multiple (array) configurations,
     * identified by their "namespace" property.
     * Each configuration can have one or multiple login URLs, called "type".
     * All the namespace/type pairs have to be allowlisted.
     * @private
     */
    allowlistActions_() {
      const accessEl = dev().assertElement(
        this.win.document.getElementById('amp-access'),
        'Cannot find the amp-access configuration'
      );
  
      // Configuration validation is handled by the amp-access extension.
      let accessConfig = /** @type {!Array|!Object} */ (
        parseJson(accessEl.textContent)
      );
  
      if (!isArray(accessConfig)) {
        accessConfig = [accessConfig];
  
        // If there is only one configuration and the publisher provided a
        // namespace, we want to allow actions with or without namespace.
        if (accessConfig[0].namespace) {
          accessConfig.push({...accessConfig[0], namespace: undefined});
        }
      }
  
      const actions = [];
  
      /** @type {!Array} */ (accessConfig).forEach((config) => {
        const {login, namespace} = /** @type {{login, namespace}} */ (config);
  
        if (isObject(login)) {
          const types = Object.keys(login);
          types.forEach((type) =>
            actions.push(this.getActionObject_(namespace, type))
          );
        } else {
          actions.push(this.getActionObject_(namespace));
        }
      });
  
      this.storeService_.dispatch(Action.ADD_TO_ACTIONS_ALLOWLIST, actions);
    }
  
    /**
     * Allowlists an action for the given namespace / type pair.
     * @param {string=} namespace
     * @param {string=} type
     * @return {*} TODO(#23582): Specify return type
     * @private
     */
    getActionObject_(namespace = undefined, type = undefined) {
      const method = ['login', namespace, type].filter(Boolean).join('-');
      return {tagOrTarget: 'SCRIPT', method};
    }
  }
  