text: import Controller from '@ember/controller';
  import config from 'ghost-admin/config/environment';
  import fetch from 'fetch';
  import ghostPaths from 'ghost-admin/utils/ghost-paths';
  import moment from 'moment';
  import {A} from '@ember/array';
  import {action} from '@ember/object';
  import {capitalize} from '@ember/string';
  import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
  import {resetQueryParams} from 'ghost-admin/helpers/reset-query-params';
  import {inject as service} from '@ember/service';
  import {task, timeout} from 'ember-concurrency';
  import {tracked} from '@glimmer/tracking';
  
  const PAID_PARAMS = [{
      name: 'All members',
      value: null
  }, {
      name: 'Free members',
      value: 'false'
  }, {
      name: 'Paid members',
      value: 'true'
  }];
  
  export default class MembersController extends Controller {
      @service ajax;
      @service config;
      @service ellaSparse;
      @service feature;
      @service ghostPaths;
      @service membersStats;
      @service router;
      @service store;
      @service utils;
      @service settings;
  
      queryParams = [
          'label',
          {paidParam: 'paid'},
          {searchParam: 'search'},
          {orderParam: 'order'},
          {filterParam: 'filter'}
      ];
  
      @tracked members = A([]);
      @tracked searchParam = '';
      @tracked searchIsFocused = false;
      @tracked filterParam = null;
      @tracked softFilterParam = null;
      @tracked paidParam = null;
      @tracked label = null;
      @tracked orderParam = null;
      @tracked modalLabel = null;
      @tracked showLabelModal = false;
      @tracked showDeleteMembersModal = false;
      @tracked showUnsubscribeMembersModal = false;
      @tracked showAddMembersLabelModal = false;
      @tracked showRemoveMembersLabelModal = false;
      @tracked filters = A([]);
      @tracked softFilters = A([]);
  
      @tracked _availableLabels = A([]);
  
      paidParams = PAID_PARAMS;
  
      constructor() {
          super(...arguments);
          this._availableLabels = this.store.peekAll('label');
  
          if (this.isTesting === undefined) {
              this.isTesting = config.environment === 'test';
          }
      }
  
      // Computed properties -----------------------------------------------------
  
      get listHeader() {
          let {searchParam, selectedLabel, members} = this;
  
          if (members.loading) {
              return 'Loading...';
          }
  
          if (searchParam) {
              return 'Search result';
          }
  
          let count = ghPluralize(members.length, 'member');
  
          if (selectedLabel && selectedLabel.slug) {
              if (members.length > 1) {
                  return `${count} match current filter`;
              } else {
                  return `${count} matches current filter`;
              }
          }
  
          return count;
      }
  
      get hideSearchBar() {
          return !this.members.length
              && !this.searchParam
              && !this.searchIsFocused;
      }
  
      get showingAll() {
          return !this.searchParam && !this.paidParam && !this.label && !this.filterParam && !this.softFilterParam;
      }
  
      get availableOrders() {
          // don't return anything if email analytics is disabled because
          // we don't want to show an order dropdown with only a single option
  
          if (this.feature.get('emailAnalytics')) {
              return [{
                  name: 'Newest',
                  value: null
              }, {
                  name: 'Open rate',
                  value: 'email_open_rate'
              }];
          }
  
          return [];
      }
  
      get selectedOrder() {
          return this.availableOrders.find(order => order.value === this.orderParam);
      }
  
      get availableLabels() {
          let labels = this._availableLabels
              .filter(label => !label.isNew)
              .filter(label => label.id !== null)
              .sort((labelA, labelB) => labelA.name.localeCompare(labelB.name, undefined, {ignorePunctuation: true}));
          let options = labels.toArray();
  
          options.unshiftObject({name: 'All labels', slug: null});
  
          return options;
      }
  
      get selectedLabel() {
          let {label, availableLabels} = this;
          return availableLabels.findBy('slug', label);
      }
  
      get labelModalData() {
          let label = this.modalLabel;
          let labels = this.availableLabels;
  
          return {
              label,
              labels
          };
      }
  
      get selectedPaidParam() {
          return this.paidParams.findBy('value', this.paidParam) || {value: '!unknown'};
      }
  
      get isFiltered() {
          return !!(this.label || this.paidParam || this.searchParam || this.filterParam);
      }
  
      get filterColumns() {
          const defaultColumns = ['name', 'email'];
          const availableFilters = this.filters.length ? this.filters : this.softFilters;
          return availableFilters.map((filter) => {
              return filter.type;
          }).filter((f, idx, arr) => {
              return arr.indexOf(f) === idx;
          }).filter(d => !defaultColumns.includes(d));
      }
  
      get filterColumnLabels() {
          const filterColumnLabelMap = {
              'subscriptions.plan_interval': 'Billing period',
              subscribed: 'Subscribed to email',
              'subscriptions.status': 'Subscription Status'
          };
          return this.filterColumns.map((d) => {
              return filterColumnLabelMap[d] ? filterColumnLabelMap[d] : capitalize(d.replace(/_/g, ' '));
          });
      }
  
      getApiQueryObject({params, extraFilters = []} = {}) {
          let {label, paidParam, searchParam, filterParam} = params ? params : this;
  
          let filters = [];
  
          filters = filters.concat(extraFilters);
  
          if (label) {
              filters.push(`label:'${label}'`);
          }
  
          if (paidParam !== null) {
              if (paidParam === 'true') {
                  filters.push('status:-free');
              } else {
                  filters.push('status:free');
              }
          }
          if (filterParam) {
              filters.push(filterParam);
          }
  
          let searchQuery = searchParam ? {search: searchParam} : {};
  
          return Object.assign({}, {filter: filters.join('+')}, searchQuery);
      }
  
      // Actions -----------------------------------------------------------------
  
      @action
      refreshData() {
          this.fetchMembersTask.perform();
          this.fetchLabelsTask.perform();
          this.membersStats.invalidate();
          this.membersStats.fetchCounts();
      }
  
      @action
      changeOrder(order) {
          this.orderParam = order.value;
      }
  
      @action
      applyFilter(filterStr, filters) {
          this.softFilters = A([]);
          this.filterParam = filterStr || null;
          this.filters = filters;
      }
  
      @action
      applySoftFilter(filterStr, filters) {
          this.softFilters = filters;
          this.softFilterParam = filterStr || null;
          let {label, paidParam, searchParam, orderParam} = this;
          this.fetchMembersTask.perform({label, paidParam, searchParam, orderParam, filterParam: filterStr});
      }
  
      @action
      resetSoftFilter() {
          if (this.softFilters.length > 0 || !!this.softFilterParam) {
              this.softFilters = A([]);
              this.softFilterParam = null;
              this.fetchMembersTask.perform();
          }
      }
  
      @action
      resetFilter() {
          this.softFilters = A([]);
          this.softFilterParam = null;
          this.filters = A([]);
          this.filterParam = null;
          this.fetchMembersTask.perform();
      }
  
      @action
      search(e) {
          this.searchTask.perform(e.target.value);
      }
  
      @action
      exportData() {
          let exportUrl = ghostPaths().url.api('members/upload');
          let downloadParams = new URLSearchParams(this.getApiQueryObject());
          downloadParams.set('limit', 'all');
  
          this.utils.downloadFile(`${exportUrl}?${downloadParams.toString()}`);
      }
  
      @action
      changeLabel(label, e) {
          if (e) {
              e.preventDefault();
              e.stopPropagation();
          }
          this.label = label.slug;
      }
  
      @action
      addLabel(e) {
          if (e) {
              e.preventDefault();
              e.stopPropagation();
          }
          const newLabel = this.store.createRecord('label');
          this.modalLabel = newLabel;
          this.showLabelModal = !this.showLabelModal;
      }
  
      @action
      editLabel(label, e) {
          if (e) {
              e.preventDefault();
              e.stopPropagation();
          }
          let modalLabel = this.availableLabels.findBy('slug', label);
          this.modalLabel = modalLabel;
          this.showLabelModal = !this.showLabelModal;
      }
  
      @action
      toggleLabelModal() {
          this.showLabelModal = !this.showLabelModal;
      }
  
      @action
      changePaidParam(paid) {
          this.paidParam = paid.value;
      }
  
      @action
      toggleDeleteMembersModal() {
          this.showDeleteMembersModal = !this.showDeleteMembersModal;
      }
  
      @action
      toggleUnsubscribeMembersModal() {
          this.showUnsubscribeMembersModal = !this.showUnsubscribeMembersModal;
      }
  
      @action
      toggleAddMembersLabelModal() {
          this.showAddMembersLabelModal = !this.showAddMembersLabelModal;
      }
  
      @action
      toggleRemoveMembersLabelModal() {
          this.showRemoveMembersLabelModal = !this.showRemoveMembersLabelModal;
      }
  
      @action
      deleteMembers() {
          return this.deleteMembersTask.perform();
      }
  
      @action
      unsubscribeMembers() {
          return this.unsubscribeMembersTask.perform();
      }
  
      @action
      addLabelToMembers(selectedLabel) {
          return this.addLabelToMembersTask.perform(selectedLabel);
      }
  
      @action
      removeLabelFromMembers(selectedLabel) {
          return this.removeLabelFromMembersTask.perform(selectedLabel);
      }
  
      // Tasks -------------------------------------------------------------------
  
      @task({restartable: true})
      *searchTask(query) {
          yield timeout(250); // debounce
          this.searchParam = query;
      }
  
      @task
      *fetchLabelsTask() {
          yield this.store.query('label', {limit: 'all'});
      }
  
      @task({restartable: true})
      *fetchMembersTask(params) {
          // params is undefined when called as a "refresh" of the model
          let {label, paidParam, searchParam, orderParam, filterParam} = typeof params === 'undefined' ? this : params;
  
          // use a fixed created_at date so that subsequent pages have a consistent index
          let startDate = new Date();
  
          // bypass the stale data shortcut if params change
          let forceReload = !params
              || label !== this._lastLabel
              || paidParam !== this._lastPaidParam
              || searchParam !== this._lastSearchParam
              || orderParam !== this._lastOrderParam
              || filterParam !== this._lastFilterParam;
          this._lastLabel = label;
          this._lastPaidParam = paidParam;
          this._lastSearchParam = searchParam;
          this._lastOrderParam = orderParam;
          this._lastFilterParam = filterParam;
  
          // unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
          // keeps navigation between list->details->list snappy
          if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
              return this.members;
          }
  
          this._startDate = startDate;
  
          this.members = yield this.ellaSparse.array((range = {}, query = {}) => {
              const searchQuery = this.getApiQueryObject({
                  params,
                  extraFilters: [`created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`]
              });
              const order = orderParam ? `${orderParam} desc` : `created_at desc`;
  
              query = Object.assign({
                  order,
                  limit: range.length,
                  page: range.page
              }, searchQuery, query);
  
              return this.store.query('member', query).then((result) => {
                  return {
                      data: result,
                      total: result.meta.pagination.total
                  };
              });
          }, {
              limit: 50
          });
      }
  
      @task({drop: true})
      *deleteMembersTask() {
          const query = new URLSearchParams(this.getApiQueryObject());
  
          // Trigger download before deleting. Uses the CSV export endpoint but
          // needs to fetch the file and trigger a download directly rather than
          // via an iframe. The iframe approach can't tell us when a download has
          // started/finished meaning we could end up deleting the data before exporting it
          const exportParams = new URLSearchParams(this.getApiQueryObject());
          exportParams.set('limit', 'all');
          const exportUrl = `${ghostPaths().url.api('members/upload')}?${exportParams.toString()}`;
  
          yield fetch(exportUrl, {method: 'GET'})
              .then(res => res.blob())
              .then((blob) => {
                  const blobUrl = window.URL.createObjectURL(blob);
                  const a = document.createElement('a');
                  a.href = blobUrl;
                  a.download = `members.${moment().format('YYYY-MM-DD')}.csv`;
                  document.body.appendChild(a);
                  if (!this.isTesting) {
                      a.click();
                  }
                  a.remove();
                  URL.revokeObjectURL(blobUrl);
              });
  
          // backup downloaded, continue with deletion
  
          const deleteUrl = `${this.ghostPaths.url.api('members')}?${query}`;
  
          // response contains details of which members failed to be deleted
          const response = yield this.ajax.del(deleteUrl);
  
          // reset and reload
          this.store.unloadAll('member');
          this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
          this.membersStats.invalidate();
          this.membersStats.fetchCounts();
  
          return response.meta;
      }
  
      @task({drop: true})
      *unsubscribeMembersTask() {
          const query = new URLSearchParams(this.getApiQueryObject());
          const unsubscribeUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
          // response contains details of which members failed to be unsubscribe
          const response = yield this.ajax.put(unsubscribeUrl, {
              data: {
                  bulk: {
                      action: 'unsubscribe',
                      meta: {}
                  }
              }
          });
  
          // reset and reload
          this.store.unloadAll('member');
          this.reload();
  
          this.membersStats.invalidate();
          this.membersStats.fetchCounts();
  
          return response?.bulk?.meta;
      }
  
      @task({drop: true})
      *addLabelToMembersTask(selectedLabel) {
          const query = new URLSearchParams(this.getApiQueryObject());
          const addLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
          const response = yield this.ajax.put(addLabelUrl, {
              data: {
                  bulk: {
                      action: 'addLabel',
                      meta: {
                          label: {
                              id: selectedLabel
                          }
                      }
                  }
              }
          });
  
          // reset and reload
          this.store.unloadAll('member');
          this.reload();
  
          return response?.bulk?.meta;
      }
  
      @task({drop: true})
      *removeLabelFromMembersTask(selectedLabel) {
          const query = new URLSearchParams(this.getApiQueryObject());
          const removeLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
          const response = yield this.ajax.put(removeLabelUrl, {
              data: {
                  bulk: {
                      action: 'removeLabel',
                      meta: {
                          label: {
                              id: selectedLabel
                          }
                      }
                  }
              }
          });
  
          // reset and reload
          this.store.unloadAll('member');
          this.reload();
  
          return response?.bulk?.meta;
      }
      // Internal ----------------------------------------------------------------
  
      resetFilters(params) {
          if (!params?.filterParam) {
              this.filters = A([]);
              this.softFilterParam = null;
              this.softFilters = A([]);
          }
      }
  
      reload(params) {
          this.membersStats.invalidate();
          this.membersStats.fetchCounts();
          this.fetchMembersTask.perform(params);
      }
  }
  