Source: ZEventsApiEvents/ZEventsApiEvent.js

import ifPromise from 'onyx-common/ifPromise'
import hasKey from 'onyx-common/hasKey'
import parseEmbedCode from 'onyx-common/parseEmbedCode'
import normalizeCurrency from 'onyx-common/normalizeCurrency'
import isPastDate from 'onyx-common/isPastDate'
import isFutureDate from 'onyx-common/isFutureDate'
import stripHtml from 'onyx-common/stripHtml'
import calculateSecondsBetween from 'onyx-common/calculateSecondsBetween'
import normalizeArray from 'onyx-common/normalizeArray'
import formatCurrency from 'onyx-common/formatCurrency'
import textCompare from 'onyx-common/textCompare'
import isBase64 from 'onyx-common/isBase64'
import cleanBase64 from 'onyx-common/cleanBase64'
import createEmbedCode from 'onyx-common/createEmbedCode'

const ZEventsApiEvent = ({ prototype }) => {
  prototype.normalizeInProgressAction = function (ret) {
    if (ret.isDigital && ret.isLive && ret.isHost) return 'start_streaming'
    if ((ret.isDigital && !ret.isFree && ret.hasTickets) || (ret.isDigital && ret.isFree)) return 'watch'
    if (ret.isSoldOut) return 'sold_out'
    if (ret.isCurrentlySellingTickets) return 'buy_tickets'
    return false
  }

  prototype.normalizeEventProvider = function (provider) {
    return this.normalizeEmbedCode(provider.embed_code, provider.provider_type)
  }

  prototype.normalizeEmbedCode = function (embedCode, providerType) {
    const videoInfo = parseEmbedCode(embedCode, providerType)
    return videoInfo
  }

  prototype.normalizeEventSpeaker = function (_data) {
    const data = this.normalizeData(_data)

    const ret = {
      eventSpeakerJoinId: parseInt(data.description.record_id),
      description: data.description.body,
      topic: data.topic,
      startTime: this.normalizeDate(data.start_time),
      endTime: this.normalizeDate(data.end_time),
      speaker: {
        isKwivrrUser: !!data.is_kwivrr_user,
        avatarUrl: data.avatar_url,
        id: parseInt(data.id),
        firstName: data.firstname,
        lastName: data.lastname,
        email: data.email
      }
    }

    return ret
  }

  /**
   * @typedef {object} Event
   * @property {number} id - event id
   * @property {string} title - event title
   * @property {string} description - event description
   * @property {string} descriptionText - event description (stripped)
   * @property {string} emailContent - event email content
   * @property {string} emailContentText - emailContent (stripped)
   * @property {Date} startDate - when does event start?
   * @property {Date} endDate - when does event end?
   * @property {Date} archiveDate - when should event fall off home page feed and should ticket sales be disabled?
   * @property {Date} publishDate - when should event be available and ticket sales commence?
   * @property {string} shopUrl - shopping link for live stream event
   * @property {string} learnMoreUrl - external splash page url
   * @property {string} eventPageUrl - url for splash page sharing
   * @property {string} livestreamPageUrl - url for livestream page sharing
   * @property {string} eventImageUrl - image for hero on splash, and thumbs elsewhere
   * @property {boolean} hasImage - was an image uploaded, eventImageUrl will always have a default image even if none was provided
   * @property {boolean} hasGeneralTicketsRemaining - are general tickets still available?
   * @property {number} generalTicketSales - if host, how many general ticket sales have occurred?
   * @property {number} generalTicketsSold - if host, how many general tickets have been sold?
   * @property {number} generalTicketPrice - general ticket price
   * @property {string} generalTicketDescription - general ticket description
   * @property {string} generalTicketDescriptionText - generalTicketDescription (stripped)
   * @property {number} generalCapacity - total general tickets
   * @property {boolean} hasVipTicketsRemaining - are vip tickets are still available?
   * @property {number} vipTicketSales - if host, how many vip ticket sales have occurred?
   * @property {number} vipTicketsSold - if host, how many vip tickets have been sold?
   * @property {number} vipTicketPrice - general ticket price
   * @property {string} vipTicketDescription - vip ticket description
   * @property {string} vipTicketDescriptionText - vipTicketDescription (stripped)
   * @property {number} vipCapacity - vip ticket description
   * @property {number} userTicketsCount - how many tickets does currentUser have?
   * @property {boolean} isOnWaitlist - is current user already on the wait list?
   * @property {boolean} withCountdownDisplay - should a countdown display on the splash page?
   * @property {boolean} isPrivate - is this a private event?
   * @property {boolean} isPublic - is this a public event?
   * @property {boolean} isPhysical - is this a physical event?
   * @property {boolean} isDigital - is this a digital event?
   * @property {boolean} isTicketed - is this a ticketed event?
   * @property {boolean} isFree - is this a free event?
   * @property {boolean} isGeneralTicketed - alias of isTicketed
   * @property {boolean} isVipTicketed - is this a vip ticketed event?
   * @property {number[]} eventGroupIds - event group ids event is part of
   * @property {string[]} availableLanguages - languages available for this event
   * @property {string} streamType - if digital event, enum('youtube', 'vimeo', 'kwivrr')
   * @property {string} streamUrl - if digital event and not streamType = 'kwivrr', this is the video url to play
   * @property {string} promotionalVideoType - service type for promotional video enum('youtube', 'vimeo', 'kwivrr')
   * @property {string} promotionalVideoUrl - url for promotional video if provided
   * @property {boolean} hasPromotionalVideo - do we have a promo video to attempt to load?
   * @property {boolean} hasTicketsRemaining - does this event have tickets of any type remaining?
   * @property {number} totalTicketSales - sum of vip and ga ticket sales
   * @property {number} totalTicketsSold - sum of vip and ga tickets sold
   * @property {boolean} isPublished - is this event published?
   * @property {boolean} isArchived - is this event archived?
   * @property {boolean} isCurrentlySellingGeneralTickets - are general tickets currently available for purchase?
   * @property {boolean} isCurrentlySellingVipTickets - are vip tickets currently available for sale?
   * @property {boolean} hasEnded - has the event ended?
   * @property {boolean} isPastDate - alias of hasEnded
   * @property {boolean} hasStarted - has the event started?
   * @property {boolean} isInProgress - is the event currently in progress?
   * @property {boolean} isLive - alias of isInProgress
   * @property {boolean} isKwivrrStream - is this a red5 stream event?
   * @property {boolean} isCurrentlyStreaming - if a digital event, is this currently active?
   * @property {boolean} hasEventGroups - does event have any event groups?
   * @property {boolean} hasAvailableLanguages - does event have available languages?
   * @property {string} hostStreamPageUrl - if isDigital, host stream url
   * @property {string} viewStreamPageUrl - if isDigital, viewer stream url
   * @property {string} shareUrl - url to share, if currently streaming and a free event, this will point to stream viewer page
   * @property {boolean} isSoldOutOfGeneralTickets - if tickets were being sold, are we sold out of general tickets?
   * @property {boolean} isSoldOutOfVipTickets - if isVipTicketed are we sold out of vip tickets?
   * @property {boolean} isSoldOut - if ticketed are we sold out of all ticket classes?
   * @property {boolean} hasTickets - if currentUser logged in, do they have tickets?
   * @property {boolean} hasImageOrPromotionalVideo - does event have a cover image or promo video?
   * @property {string} hostName - host full name
   * @property {string} hostId - host id
   * @property {string} hostAvatarUrl - host avatar url
   * @property {string} hostTagLine - host tagLine
   * @property {string} hostFirstName - host first name
   * @property {string} hostLastName - host last name
   * @property {boolean} isStreamEmbed - for streamType = 'resi', we use raw embed code
   * @property {string} streamEmbed - for resi this is the raw embed code
   * @property {string} inProgressAction - cta for splash page enum('watch', 'sold_out', 'buy_now', false)
   * @property {boolean} hasInProgressAction - does it have an inProgress action?
   * @property {boolean} hasInProgressActionWatch - is watch the inProgress cta?
   * @property {boolean} hasInProgressActionBuyTickets - is buy_tickets the inProgress cta?
   * @property {boolean} hasInProgressActionSoldOut - is sold_out the inProgress cta?
   * @property {boolean} hasInProgressActionRegister - is register the inProgress cta?
   * @property {boolean} hasInProgressActionUnregister - is unregister the inProgress cta?
   * @property {boolean} secondsUntilStart - how many seconds until event begins?  0 if completed or started
   * @property {boolean} hasStreamOrEmbed - does the record actually have a provider attached?
   * @property {boolean} isDeleteable - this event deletable?
   * @property {string} joinUrl - for Zoom, this is the viewer url
   * @property {string} startUrl - for Zoom, this is the host url
   * @property {number} providerId - provider id for event (must be passed with update call for a live stream)
   * @property {boolean} isStreamLive - for digital events, did the host start the stream?
   * @property {boolean} isStreamEnded - for digital events, did the host end the stream?
   * @property {boolean} isStreamLiveAndUnpaused - for digital events, is the host currently streaming and the stream is unpaused?
   * @property {boolean} isInterested - is user interested in event?
   */

  prototype.normalizeEvent = function (_data) {
    const data = this.normalizeData(_data)

    if (!hasKey(data, 'vip_ticket_sales')) data.vip_ticket_sales = 0
    if (!hasKey(data, 'general_ticket_sales')) data.general_ticket_sales = 0
    if (!hasKey(data, 'general_ticket_count')) data.general_ticket_count = 0
    if (!hasKey(data, 'vip_ticket_count')) data.vip_ticket_count = 0
    if (!hasKey(data, 'is_interested')) data.is_interested = false

    const mapProps = {
      id: ['id', val => parseInt(val)],
      count_down_display: ['withCountdownDisplay', val => !!val],
      description_plain_text: 'description',
      general_ticket_price: ['generalTicketPrice', val => val ? normalizeCurrency(val) : 0],
      has_general_tickets_remaining: ['hasGeneralTicketsRemaining', (val) => !!val],
      general_ticket_description_text: 'generalTicketDescription',
      general_capacity: ['generalCapacity', (val) => val ? parseInt(val) : 0],
      aggregate_interested_count: ['interestedCount', val => parseInt(val)],
      vip_ticket_price: ['vipTicketPrice', (val) => val ? normalizeCurrency(val) : 0],
      disclaimer_text: 'disclaimer',
      email_content: 'emailContent',
      has_vip_tickets_remaining: ['hasVipTicketsRemaining', (val) => !!val],
      vip_ticket_description_text: 'vipTicketDescription',
      vip_capacity: ['vipCapacity', val => parseInt(val)],
      user_tickets_count: ['userTicketsCount', (val) => val ? parseInt(val) : 0],
      name: 'title',
      start_date: ['startDate', val => this.normalizeDate(val)],
      end_date: ['endDate', val => this.normalizeDate(val)],
      shop_url: 'shopUrl',
      learnmore_url: ['learnMoreUrl', val => val ? this.normalizeUrl(val) : ''],
      event_type: ['isPhysical', val => val.trim().toLowerCase() === 'physical'],
      event_image_url: 'eventImageUrl',
      available_languages: ['availableLanguages', (val) => val.filter(lan => !!lan)],
      event_groups: ['eventGroupIds', (val) => val.map(id => parseInt(id))],
      has_image: ['hasImage', (val) => !!val],
      is_on_waitlist: ['isOnWaitlist', (val) => !!val],
      event_room_member_id: 'eventRoomMemberId',
      is_blocked: ['isBlocked', val => !!val],
      is_host: ['isHost', val => !!val],
      vip_ticket_sales: ['vipTicketSales', (val) => val ? normalizeCurrency(val) : 0],
      general_ticket_sales: ['generalTicketSales', (val) => val ? normalizeCurrency(val) : 0],
      general_ticket_count: ['generalTicketsSold', (val) => val ? parseInt(val) : 0],
      vip_ticket_count: ['vipTicketsSold', (val) => val ? parseInt(val) : 0],
      archive_date: ['archiveDate', (val) => this.normalizeDate(val)],
      publish_date: ['publishDate', (val) => this.normalizeDate(val)],
      visibility: ['isPrivate', (val) => val.trim().toLowerCase() === 'private'],
      embed_code: 'promotionalVideoEmbedCode',
      is_stream_ended: ['isStreamEnded', val => !!val],
      is_stream_live: ['isStreamLive', val => !!val],
      is_stream_live_and_unpaused: ['isStreamLiveAndUnpaused', val => !!val],
      is_stream_paused: ['isStreamPaused', val => !!val],
      is_stream_available: ['isStreamAvailable', val => !!val],
      viewer_count: 'viewerCount',
      is_interested: ['isInterested', val => !!val]
    }

    const ret = this.filterAndMapProps(data, mapProps)
    ret.eventId = ret.id // legacy helper
    ret.hasStreamOrEmbed = false
    if (hasKey(data, 'provider') && data.provider) {
      const tmp = this.normalizeEventProvider(data.provider)
      ret.streamType = tmp.service
      ret.streamUrl = tmp.embedUrl
      ret.streamEmbed = tmp._orig
      ret.hasStreamOrEmbed = true
      ret.startUrl = data.provider?.start_url
      ret.joinUrl = data.provider?.join_url
      ret.providerId = data.provider?.id
      ret.compareStreamType = textCompare(ret.streamType)
    }

    ret.isStreamEmbed = ret.streamType === 'resi'

    ret.hasPromotionalVideo = false
    if (hasKey(data, 'embed_code') && data.embed_code) {
      const tmp = this.normalizeEmbedCode(data.embed_code)
      ret.promotionalVideoType = tmp.service
      ret.promotionalVideoUrl = tmp.embedUrl
      ret.hasPromotionalVideo = true
    }

    ret.isPublic = !ret.isPrivate
    ret.isDigital = !ret.isPhysical
    ret.totalTicketSales = ret.generalTicketSales + ret.vipTicketSales
    ret.formattedTotalTicketSales = formatCurrency(ret.totalTicketSales)
    ret.totalTicketsSold = ret.generalTicketsSold + ret.vipTicketsSold
    ret.isGeneralTicketed = Boolean(ret.generalTicketPrice > 0)
    ret.isVipTicketed = Boolean(ret.vipTicketPrice > 0)
    ret.isTicketed = ret.isGeneralTicketed
    ret.isFree = !ret.isTicketed
    ret.hasTicketsRemaining = Boolean(ret.hasVipTicketsRemaining || ret.hasGeneralTicketsRemaining)
    ret.isPublished = Boolean(!ret.publishDate || isPastDate(ret.publishDate))
    ret.isArchived = true
    if (!ret.archiveDate || isFutureDate(ret.archiveDate)) ret.isArchived = false

    ret.isCurrentlySellingGeneralTickets = Boolean(ret.isGeneralTicketed && !ret.isArchived && ret.isPublished)
    ret.isCurrentlySellingVipTickets = Boolean(ret.isVipTicketed && !ret.isArchived && ret.isPublished)
    ret.isCurrentlySellingTickets = Boolean(ret.isCurrentlySellingGeneralTickets || ret.isCurrentlySellingVipTickets)
    ret.hasEnded = isPastDate(ret.endDate)
    ret.isPastDate = ret.hasEnded // legacy alias
    ret.hasStarted = isPastDate(ret.startDate)
    ret.isInProgress = Boolean(ret.hasStarted && !ret.hasEnded)
    ret.isKwivrrStream = Boolean(ret.isDigital && ret.compareStreamType === 'kwivrr')
    ret.eventPageUrl = this.siteUrl + '/#view=event&eventId=' + ret.id
    ret.isCurrentlyStreaming = Boolean(ret.isInProgress && ret.isDigital)
    ret.isLive = ret.isInProgress // legacy alias
    const kwivvrHostUrl = this.siteUrl + '/#view=livestream-pub&eventId=' + ret.id
    const kwivvrViewerUrl = this.siteUrl + '/#view=livestream-sub&eventId=' + ret.id
    const livestreamEmbedUrl = this.siteUrl + '/#view=livestream-embed&eventId=' + ret.id

    ret.hostStreamUrl = null
    ret.viewStreamUrl = null
    if (ret.isDigital) {
      ret.hostStreamUrl = ret.isKwivrrStream ? kwivvrHostUrl : livestreamEmbedUrl
      ret.viewStreamUrl = ret.isKwivrrStream ? kwivvrViewerUrl : livestreamEmbedUrl
    }
    ret.shareUrl = this.getShareUrl(`/api/v1/events/${ret.id}/share`)
    // if (ret.isCurrentlyStreaming && !ret.isTicketed) ret.shareUrl = ret.streamViewerUrl

    ret.hasEventGroups = Boolean(ret.eventGroupIds.length > 0)
    ret.hasAvailableLanguages = Boolean(ret.availableLanguages.length > 0)
    ret.descriptionText = stripHtml(ret.description)
    ret.emailContentText = stripHtml(ret.emailContent)
    ret.disclaimerText = stripHtml(ret.disclaimer)
    ret.generalTicketDescriptionText = stripHtml(ret.generalTicketDescription)
    ret.vipTicketDescriptionText = stripHtml(ret.vipTicketDescription)
    ret.isSoldOutOfGeneralTickets = Boolean(ret.isGeneralTicketed && !ret.hasGeneralTicketsRemaining)
    ret.isSoldOutOfVipTickets = Boolean(ret.isVipTicketed && !ret.hasVipTicketsRemaining)
    ret.isSoldOut = Boolean(ret.isTicketed && ret.isSoldOutOfGeneralTickets && (ret.isSoldOutOfVipTickets || !ret.isVipTicketed))
    ret.hasTickets = Boolean(ret.userTicketsCount > 0)
    ret.hasImageOrPromotionalVideo = Boolean(ret.hasImage || ret.hasPromotionalVideo)
    ret.hostName = `${data.host.firstname} ${data.host.lastname}`
    ret.hostId = parseInt(data.host.id)
    ret.hostAvatarUrl = data.host.profile.avatar_url
    ret.hostTagLine = data.host.profile.tagline
    ret.hostFirstName = data.host.firstname
    ret.hostLastName = data.host.lastname
    ret.inProgressAction = this.normalizeInProgressAction(ret)
    ret.hasInProgressAction = ret.inProgressAction !== false
    ret.hasInProgressActionStartStreaming = ret.inProgressAction === 'start_streaming'
    ret.hasInProgressActionWatch = ret.inProgressAction === 'watch'
    ret.hasInProgressActionBuyTickets = ret.inProgressAction === 'buy_tickets'
    ret.hasInProgressActionSoldOut = ret.inProgressAction === 'sold_out'
    ret.secondsUntilStart = 0

    if (!ret.isInProgress && !ret.hasEnded) ret.secondsUntilStart = calculateSecondsBetween(ret.startDate, new Date())

    ret.formattedGeneralTicketPrice = formatCurrency(ret.generalTicketPrice)
    ret.formattedVipTicketPrice = formatCurrency(ret.vipTicketPrice)
    ret.isDeleteable = hasKey(data, 'is_deleteable') ? !!data.is_deleteable : true

    const getIsCheckInTime = () => {
      var now = new Date()
      var oneHourFromNow = new Date(now)
      oneHourFromNow.setHours(oneHourFromNow.getHours() + 1)
      const startDate = new Date(ret.startDate)
      var c = new Date(oneHourFromNow.getTime())
      c.setDate(c.getDate() + 1)
      const isWithinAnHourOfStarting = startDate >= now && startDate < c
      return isWithinAnHourOfStarting || ret.isLive
    }

    const getNumWatching = (num) => {
      if (num >= 1000000000) {
        return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'
      }
      if (num >= 1000000) {
        return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'
      }
      if (num >= 1000) {
        return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'
      }
      return num
    }

    ret.numWatching = getNumWatching(2100)
    ret.isCheckInTime = getIsCheckInTime()

    return ret
  }

  prototype.normalizeEventOrder = function (_data) {
    const data = this.normalizeData(_data)
    return data
  }

  prototype.normalizeEventManagementPayload = function (payload, mode = 'create') {
    const _normalizeStreamType = (st) => {
      const testSt = textCompare(st)
      switch (testSt) {
        case 'jitsi': return 'Jitsi'
        case 'zoom': return 'Zoom'
      }

      return st
    }

    let {
      providerId,
      eventSpeakers,
      isTicketed,
      title,
      isPhysical,
      streamType,
      embedCode,
      isPrivate,
      eventImageUrl,
      promotionalVideoEmbedCode,
      description,
      disclaimer,
      emailContent,
      shopUrl,
      learnMoreUrl,
      availableLanguages,
      eventGroupIds,
      isStartingNow,
      startDate,
      endDate,
      publishDate,
      archiveDate,
      withCountdownDisplay,
      generalTicketPrice,
      generalCapacity,
      generalTicketDescription,
      vipTicketPrice,
      vipCapacity,
      vipTicketDescription
      // isAllowingLocalDeals
    } = payload

    const testStreamType = streamType && textCompare(streamType)
    const getDate = date => date === null ? date : new Date(date)

    const eventPayload = {
      event_type: isPhysical ? 1 : 0,
      visibility: isPrivate ? 1 : 0,
      name: title,
      description: description,
      disclaimer: disclaimer,
      email_content: emailContent,
      event_image_url: eventImageUrl,
      // "???": "", // Splash page url?
      learnmore_url: learnMoreUrl,
      available_languages: availableLanguages,
      // "event_group_events_attributes": [] // Array of ids: [1,2,3]
      // "???": ?, // Custom timezone?
      start_date: getDate(isStartingNow ? Date.now() : startDate),
      end_date: getDate(endDate),
      publish_date: getDate(publishDate),
      archive_date: getDate(archiveDate),
      count_down_display: withCountdownDisplay ? 1 : 0,
      event_speakers: eventSpeakers
      // addons_attributes: [
      //   {
      //     name: 'low-end souvenir',
      //     description: 'a cheap trinket',
      //     price: 10,
      //     capacity: 100,
      //     product_type: 'addon'
      //   },
      //   {
      //     name: 'high-end souvenir',
      //     description: 'a _gold_ cheap trinket',
      //     price: 50,
      //     capacity: 101,
      //     product_type: 'addon'
      //   }
      // ]
    }

    if (eventImageUrl && eventImageUrl.length > 0) {
      if (isBase64(eventImageUrl)) {
        eventPayload.event_image_base64 = cleanBase64(eventImageUrl)
      } else {
        eventPayload.event_image_url = eventImageUrl
      }
    }

    if (!isPhysical) {
      eventPayload.shop_url = shopUrl
      switch (testStreamType) {
        case 'kwivrr':
        case 'jitsi':
          embedCode = '<iframe></iframe>'
          break
        case 'resi':
          // embedCode = embedCode
          break
        case 'zoom':
          break
        default:
          embedCode = createEmbedCode(embedCode, streamType)
          break
      }

      eventPayload.provider_attributes = {
        provider_type: _normalizeStreamType(streamType),
        embed_code: embedCode
      }
      // eventPayload.stream_type = streamType === 'zoom' ? 'Zoom' : streamType
      // eventPayload.stream_type = streamType === 'jitsi' ? 'Jitsi' : streamType
    }

    if (isTicketed) {
      const createTickets = (name, capacity, price, description, arr) => {
        if (!capacity || !price) return
        capacity = parseFloat(capacity)
        price = parseFloat(price)

        if (!isNaN(vipCapacity) && !isNaN(vipTicketPrice)) {
          arr.push({
            name,
            description,
            price,
            capacity
          })
        }
      }
      const tickets = []
      createTickets('general', generalCapacity, generalTicketPrice, generalTicketDescription, tickets)
      createTickets('vip', vipCapacity, vipTicketPrice, vipTicketDescription, tickets)
      eventPayload.ticketing_types_attributes = tickets
    }

    if (promotionalVideoEmbedCode) {
      const tmp = parseEmbedCode(promotionalVideoEmbedCode)
      eventPayload.embed_code = tmp.embedCode
    }

    if (hasKey(payload, 'eventGroupIds')) {
      eventPayload.event_group_events_attributes = eventGroupIds.map(event_group_id => {
        return { event_group_id }
      })
    }

    if (!startDate && !endDate) {
      eventPayload.start_date = new Date()
      const tomorrow = new Date()
      tomorrow.setDate(tomorrow.getDate() + 1)
      eventPayload.end_date = tomorrow
    }

    if (mode === 'update') {
      eventPayload.provider_id = providerId
    }

    console.log('##eventPayload', eventPayload)
    return eventPayload
  }

  prototype._associateEventSpeaker = function ({ eventId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${eventId}/speakers`),
      method: 'POST',
      requestType: 'json',
      data: {
        ...rest
      }
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} EventSpeaker
   * @property {number} eventSpeakerJoinId - join entity id (used for delete/update actions)
   * @property {number} id - entity id (kwivvrId or speakerProfileId)
   * @property {string} name - speaker name
   * @property {string} topic - topic to discuss
   * @property {string} description - more details on topic
   * @property {string} startTime - when speaker goes on
   * @property {string} endTime - when speaker goes off
   * @property {string} avatarUrl - speaker's avatar
   * @property {boolean} isKwivvrUser - is "id" a kwivrr account id or a speaker profile id?
   */

  /**
   * @function associateEventSpeaker
   * @param {object} payload - The eventSpeaker payload
   * @param {number} payload.eventId - event id
   * @param {number} [payload.kwivrrId] - associate by user id if exists, otherwise, check for speaker profile id
   * @param {number} [payload.speakerProfileId] - associate by speaker_profile_id (fallthrough if kwivrrId not provided)
   * @param {string} payload.topic - topic speaker is covering
   * @param {string} payload.description - more detailed description of topic
   * @param {string} payload.startTime - when speaker goes on
   * @param {string} payload.endTime - when speaker's session is over
   * @returns {EventSpeaker} - EventSpeaker instance
   * @example
   *
   * associateEventSpeaker({
   *   eventId: 100,
   *   speakerProfileId: 6,
   *   topic:"9d3fjpsarasdgywm7lj8snq5pr39e3",
   *   description:"5zbn5idwlpvbg3miqidwwm2pvkxohm",
   *   startTime:"2021-07-07T07:39:05.797-05:00",
   *   endTime:"2021-07-09T07:39:05.797-05:00"
   * })
   */

  prototype.associateEventSpeaker = function (payload) {
    const {
      eventId,
      topic,
      description,
      startTime,
      endTime
    } = payload

    const finalPayload = {
      eventId,
      key_note: topic,
      description: description,
      start_time: startTime,
      end_time: endTime
    }

    if (hasKey(payload, 'kwivrrId')) finalPayload.kwivrr_id = payload.kwivrrId
    else if (hasKey(payload, 'speakerProfileId')) finalPayload.speaker_profile_id = payload.speakerProfileId

    const raw = ifPromise(payload, () => this._associateEventSpeaker(finalPayload))
    return raw
      .then(res => this.normalizeEventSpeaker(res.data))
      .catch(error => this.onError('associateEventSpeaker', error))
  }

  prototype._createEvent = function ({ userId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/events`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    const self = this
    const ret = this.authenticatedFetchWrap(payload)
      .then(res => {
        const event = self.normalizeEvent(res.data)
        if (!hasKey(rest.zevents_event, 'event_speakers')) return event

        rest.zevents_event.event_speakers.forEach(({ speaker, topic, description, startTime, endTime }) => {
          const finalPayload = {
            eventId: event.id,
            topic: topic,
            description: description,
            startTime: startTime,
            endTime: endTime
          }
          if (speaker.isKwivrrUser) finalPayload.kwivrrId = speaker.id
          else finalPayload.speakerProfileId = speaker.id

          self.associateEventSpeaker(finalPayload)
        })
        return event
      })
    return ret
  }

  /**
   * createEvent function
   *
   * @param {object} payload - The event payload
   * @param {string} [payload.userId='me'] - userId to event
   * @param {string} payload.title - title of event
   * @param {string} payload.startDate - startDate of event
   * @param {string} payload.endDate - endDate of event
   * @param {string} payload.publishDate - publishDate of event
   * @param {string} payload.promotionalVideoEmbedCode - publishDate of event
   * @param {string} payload.generalCapacity - generalCapacity of event
   * @param {string} payload.vipCapacity - vipCapacity of event
   * @param {string} payload.generalTicketPrice - generalTicketPrice of event
   * @param {string} payload.vipTicketPrice - vipTicketPrice of event
   * @param {string} payload.archiveDate - archiveDate of event
   * @param {string} payload.eventType - eventType of event
   * @param {boolean} payload.visibility - visibility of event (isPrivate)
   * @param {string} payload.streamType - streamType of event (vimeo, youtube, etc)
   * @param {string} payload.shopUrl - shopUrl of event
   * @param {string} payload.learnmoreUrl - learnmoreUrl of event
   * @param {Array} payload.availableLanguages - availableLanguages of event
   * @param {boolean} payload.countDownDisplay - event has countdown or not
   * @param {Array} payload.eventGroupEventsAttributes - eventGroupEventsAttributes of event
   * @example
   *
   *     createEvent({
   *       userId,
   *       title,
   *       startDate,
   *       endDate,
   *       publishDate,
   *       promotionalVideoEmbedCode,
   *       generalCapacity,
   *       vipCapacity,
   *       generalTicketPrice,
   *       vipTicketPrice,
   *       archiveDate,
   *       eventType,
   *       isPrivate,
   *       streamType,
   *       shopUrl,
   *       learnmoreUrl,
   *       availableLanguages,
   *       countDownDisplay,
   *       eventGroupEventsAttributes
   *     })
   * @returns {object}
   *    {
            id,
            type,
            name,
            location,
            startDate,
            endDate,
            promotionalVideoEmbedCode,
            state,
            userId,
            tenantId,
            liveStreamName,
            broadcasted,
            streamType,
            shopUrl,
            learnmoreUrl,
            eventType,
            shareUrl,
            eventImageUrl,
            host,
            eventGroupEventsAttributes
        }
   */

  prototype.createEvent = function (payload) {
    const { userId, ...rest } = payload
    console.log('##payload ', rest)
    const normalizedEventPayload = this.normalizeEventManagementPayload(rest)

    const finalPayload = {
      userId: this.normalizeUserId(userId),
      zevents_event: normalizedEventPayload
    }
    const raw = ifPromise(payload, () => this._createEvent(finalPayload))
    return raw
      .then(res => res)
      .catch(error => this.onError('createEvent', error))
  }

  prototype._updateEvent = function ({ userId, id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/events/${id}`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  // name: title,
  // start_date,
  // end_date,
  // publish_date,
  // embed_code,
  // general_capacity,
  // vip_capacity,
  // general_ticket_price,
  // vip_ticket_price,
  // archive_date,
  // event_type: type,
  // user_id,
  // visibility: isPrivate,
  // livestream_type: provider,
  // shop_url,
  // learnmore_url,
  // available_languages,
  // count_down_display,
  // event_group_events_attributes,

  // need: coverImageUrl, description, disclaimer, hasCustomTimeZone, generalTicketDescriptionText, vipTicketDescriptionText

  /**
   * @function updateEvent
   * @param {object} payload - The event payload
   * @param {string} [payload.userId='me'] - userId to event
   * @param {string} payload.title - title of event
   * @param {string} payload.startDate - startDate of event
   * @param {string} payload.endDate - endDate of event
   * @param {string} payload.publishDate - publishDate of event
   * @param {string} payload.embedCode - publishDate of event
   * @param {string} payload.generalCapacity - generalCapacity of event
   * @param {string} payload.vipCapacity - vipCapacity of event
   * @param {string} payload.generalTicketPrice - generalTicketPrice of event
   * @param {string} payload.vipTicketPrice - vipTicketPrice of event
   * @param {string} payload.archiveDate - archiveDate of event
   * @param {string} payload.eventType - eventType of event
   * @param {boolean} payload.visibility - visibility of event (isPrivate)
   * @param {string} payload.streamType - streamType of event (vimeo, youtube, etc)
   * @param {string} payload.shopUrl - shopUrl of event
   * @param {string} payload.learnmoreUrl - learnmoreUrl of event
   * @param {Array} payload.availableLanguages - availableLanguages of event
   * @param {boolean} payload.countDownDisplay - event has countdown or not
   * @param {Array} payload.eventGroupEventsAttributes - eventGroupEventsAttributes of event
   * @example
   *
   *     updatEvent({
   *       title,
   *       startDate,
   *       endDate,
   *       publishDate,
   *       embedCode,
   *       generalCapacity,
   *       vipCapacity,
   *       generalTicketPrice,
   *       vipTicketPrice,
   *       archiveDate,
   *       eventType,
   *       isPrivate,
   *       streamType,
   *       shopUrl,
   *       learnmoreUrl,
   *       availableLanguages,
   *       countDownDisplay,
   *       eventGroupEventsAttributes
   *     })
   * @returns {object}
   *    {
            id,
            type,
            name,
            location,
            startDate,getEve
            state,
            userId,
            tenantId,
            liveStreamName,
            broadcasted,
            streamType,
            shopUrl,
            learnmoreUrl,
            eventType,
            shareUrl,
            eventImageUrl,
            host,
            eventGroupEventsAttributes
        }
   */

  prototype.updateEvent = function (payload) {
    const { userId, id, ...rest } = payload
    console.log('##payload ', rest)
    const normalizedEventPayload = this.normalizeEventManagementPayload(rest, 'update')

    const finalPayload = {
      userId: this.normalizeUserId(userId),
      id: id,
      zevents_event: normalizedEventPayload
    }

    const raw = ifPromise(payload, () => this._updateEvent(finalPayload))
    return raw
      .then(res => this.normalizeEvent(res.data))
      .catch(error => this.onError('updateEvent', error))
  }

  prototype._deleteEvent = function ({ userId, id }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/events/${id}`),
      method: 'DELETE'
    }
    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function deleteEvent
   * @param {object} payload - The event payload
   * @param {string} [payload.userId='me'] - userId tied to event
   * @param {string} payload.id - id of event
   * @returns {void}
   * @example
   *
   * deleteEvent({
   *   id: 123
   * })
   */
  prototype.deleteEvent = function (payload) {
    const {
      userId,
      id
    } = payload

    const finalPayload = {
      userId: this.normalizeUserId(userId),
      id
    }

    const raw = ifPromise(payload, () => this._deleteEvent(finalPayload))
    return raw
      .catch(error => this.onError('deleteEvent', error))
  }

  prototype._disjoinEventSpeaker = function ({ eventId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${eventId}/speakers`),
      method: 'DELETE',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function disjoinEventSpeaker
   * @param {object} payload - The event payload
   * @param {number} payload.eventId - eventId speaker is assigned to
   * @param {string} payload.eventSpeakerJoinId - event speaker join table id
   * @returns {void}
   * @example
   *
   * deleteEvent({
   *   eventId: 100,
   *   eventSpeakerJoinId: 3838
   * })
   */

  prototype.disjoinEventSpeaker = function (payload) {
    const finalPayload = {
      eventId: payload.eventId,
      speaker_event_join_id: payload.eventSpeakerJoinId
    }

    const raw = ifPromise(payload, () => this._disjoinEventSpeaker(finalPayload))
    return raw
      .catch(error => this.onError('disjoinEventSpeaker', error))
  }

  prototype._getEvent = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}`),
      method: 'GET',
      data: rest
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @function getEvent
   * @param {object} payload - The event payload
   * @param {number} payload.id - eventId
   * @returns {Event} - Event object
   * @example
   *
   * getEvent({
   *   id: 100
   * })
   */
  prototype.getEvent = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    if (payload.withChat) finalPayload.with_chat = true

    const raw = ifPromise(payload, () => this._getEvent(finalPayload))
    return raw
      .then(res => this.normalizeEvent(res.data))
      .catch(error => this.onError('getEvent', error))
  }

  prototype.getEventWithChat = function (payload) {
    const finalPayload = {
      ...payload,
      withChat: true
    }

    return this.getEvent(finalPayload)
  }

  prototype._getEventReceipt = function ({ eventId }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${eventId}/receipt`),
      method: 'GET'
    }

    return this.authenticatedFetchWrap(payload)
  }

  prototype.getEventReceipt = function (payload) {
    const finalPayload = {
      eventId: payload.eventId
    }

    const raw = ifPromise(payload, () => this._getEventReceipt(finalPayload))
    return raw
      .then(res => {
        this.normalizeListData(res.data, this.normalizeEventOrder)
      })
      .catch(error => this.onError('getEventReceipt', error))
  }

  prototype._getEvents = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/events'),
      method: 'GET',
      data
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} getEventsListSummary
   * @property {number} page - page of results
   * @property {number} perPage - results per page
   * @property {boolean} hasMore - are more pages available?
   */

  /**
   * @typedef {object} getEventsCriteriaSummary
   * @property {string} scope - scope
   * @property {string} startDate - startDate
   * @property {string} endDate - endDate
   * @property {number} hostId - hostId
   * @property {number} userId - userId
   */

  /**
   * @typedef {object} getEventsReturn
   * @property {Event[]} entries - list of users returned
   * @property {getEventsListSummary} listSummary - list summary object
   * @property {getEventsCriteriaSummary} criteriaSummary - list summary object (empty)
   */

  /**
   * @function getEvents
   * @param {object} payload - The payload
   * @param {number} payload.scope - scope to limit returns to (completed, upcoming, attended, registeredFor)
   * @param {number} [payload.page=1] - page of results
   * @param {string} [payload.selectedDate] - for ranged queries, start of range
   * @returns {getEventsReturn}
   * @example
   *
   * getEvents({
   *   scope: 'completed'
   * })
   */

  prototype.getEvents = function (payload) {
    const {
      scope,
      ...rest
    } = payload

    const finalPayload = {
      scope: scope,
      ...rest
    }

    const raw = ifPromise(payload, () => this._getEvents(finalPayload))
    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeEvent))
      .catch(error => this.onError('getEvents', error))
  }

  prototype.getEventsHosting = function (payload) {
    const finalPayload = {
      scope: 'hosting',
      page: payload?.page || 1
    }
    if (hasKey(payload, 'selectedDate')) finalPayload.date = payload.selectedDate

    return this.getEvents(finalPayload)
  }

  prototype.getEventsHosted = function (payload) {
    const finalPayload = {
      scope: 'completed',
      page: payload?.page || 1
    }
    if (hasKey(payload, 'selectedDate')) finalPayload.date = payload.selectedDate

    return this.getEvents(finalPayload)
  }

  prototype.getEventsAttending = function (payload) {
    const finalPayload = {
      scope: 'registeredFor',
      page: payload?.page || 1
    }
    if (hasKey(payload, 'selectedDate')) finalPayload.date = payload.selectedDate

    return this.getEvents(finalPayload)
  }

  prototype.getEventsAttended = function (payload) {
    const finalPayload = {
      scope: 'attended',
      page: payload?.page || 1
    }
    if (hasKey(payload, 'selectedDate')) finalPayload.date = payload.selectedDate

    return this.getEvents(finalPayload)
  }

  prototype._getEventsByUserId = function ({ userId, scope, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/feed?scope=${scope}`),
      method: 'GET',
      data: rest
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} getEventsByUserIdListSummary
   * @property {number} page - page of results
   * @property {number} perPage - results per page
   * @property {boolean} hasMore - are more pages available?
   */

  /**
   * @typedef {object} getEventsByUserIdCriteriaSummary
   * @property {string} scope - scope
   * @property {number} userId - userId
   */

  /**
   * @typedef {object} getEventsByUserIdReturn
   * @property {Event[]} entries - list of users returned
   * @property {getEventsByUserIdListSummary} listSummary - list summary object
   * @property {getEventsByUserIdCriteriaSummary} criteriaSummary - list summary object (empty)
   */

  /**
   * @function getEventsByUserId
   * @param {object} payload - The payload
   * @param {number} payload.scope - scope to limit returns to (attended, attending, hosted, hosting)
   * @param {number} [payload.page=1] - page of results
   * @param {number} [payload.userId] - user's id
   * @returns {getEventsByUserIdReturn}
   * @example
   *
   * getEventsByUserId({
   *   scope: 'completed'
   * })
   */

  prototype.getEventsByUserId = function (payload) {
    const {
      scope,
      userId,
      page
    } = payload

    const finalPayload = {
      scope,
      userId,
      page
    }

    const raw = ifPromise(payload, () => this._getEventsByUserId(finalPayload))
    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeEvent))
      .catch(error => this.onError('getEventsByUserId', error))
  }

  prototype.getEventsHostingByUserId = function ({ userId, page }) {
    const finalPayload = {
      scope: 'hosting',
      userId,
      page
    }

    return this.getEventsByUserId(finalPayload)
  }

  prototype.getEventsHostedByUserId = function ({ userId, page }) {
    const finalPayload = {
      scope: 'hosted',
      userId,
      page
    }

    return this.getEventsByUserId(finalPayload)
  }

  prototype.getEventsAttendingByUserId = function ({ userId, page }) {
    const finalPayload = {
      scope: 'attending',
      userId,
      page
    }

    return this.getEventsByUserId(finalPayload)
  }

  prototype.getEventsAttendedByUserId = function ({ userId, page }) {
    const finalPayload = {
      scope: 'attended',
      userId,
      page
    }

    return this.getEventsByUserId(finalPayload)
  }

  prototype._getEventServiceFees = function ({ accessToken, ...rest }) {
    const payload = {
      url: this.getUrl('/api/v1/events/service_fees'),
      method: 'GET',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} EventServiceFeeTableRow
   * @property {number} gte - greater-than-or-equal
   * @property {number} lt - less than, if null, this is the max bucket
   * @property {number} fee - per ticket fee for this row
   */

  /**
   * @function getEventServiceFees
   * @returns {EventServiceFeeTableRow[]} - row of service fee table
   * @example
   *
   * getEventServiceFees()
   */

  prototype.getEventServiceFees = function (payload) {
    const raw = ifPromise(payload, () => this._getEventServiceFees())
    return raw
      .then(res => res.data)
      .catch(error => this.onError('getEventServiceFees', error))
  }

  prototype._getEventHostTickets = function ({ event_id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${event_id}/host_tickets`),
      method: 'GET',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  prototype.isInState = function (currentState, stateToTest) {
    const finalTest = normalizeArray(stateToTest)

    return finalTest.includes(currentState)
  }

  prototype.isActiveTicket = function (ticket) {
    return this.isActiveTicketState(ticket.ticketState)
  }

  prototype.isActiveTicketState = function (state) {
    return !this.isInState(state, [
      'REVOKED',
      'REFUNDED',
      'PARTIALLY_REFUNDED'
    ])
  }

  prototype.isCheckInableTicket = function (ticket) {
    if (ticket.isCheckedIn) return false
    return ticket.isActive
  }

  prototype.isUncheckInableTicket = function (ticket) {
    if (!ticket.isCheckedIn) return false
    return ticket.isActive
  }

  prototype.isUpgradeableTicket = function (ticket) {
    if (ticket.isVip) return false
    if (!ticket.hasVip) return false
    if (ticket.isTransferred) return false
    return ticket.isActive
  }

  prototype.isTransferrableTicket = function (ticket) {
    if (ticket.isCheckedIn) return false
    if (this.isInState(ticket.ticketState, 'PENDING_TRANSFER')) return false
    if (this.isInState(ticket.ticketState, 'FORCE_TRANSFERRED')) return false

    // ok it's not checked in and it's not in a pending transfer nor force transferred state (you'd need to reclaim first)
    // let's verify it's still active
    return ticket.isActive
  }

  prototype.isPendingTransferTicket = function (ticket) {
    if (!ticket.isActive) return false

    return this.isInState(ticket.ticketState, 'PENDING_TRANSFER')
  }

  prototype.isForceTransferrableTicket = function (ticket) {
    return this.isTransferrableTicket(ticket)
  }

  prototype.isReclaimableTicket = function (ticket) {
    if (ticket.isGiftedToMe) return false
    if (ticket.isCheckedIn) return false
    if (!ticket.isActive) return false
    if (this.isInState(ticket.ticketState, 'PENDING_TRANSFER')) return true
    return ticket.isTransferred
  }

  prototype.isRevokableTicket = function (ticket) {
    if (ticket.isCheckedIn) return false
    return ticket.isActive
  }

  prototype.isSetCredentialsableTicket = function (ticket) {
    // this can always be done on an active ticket
    return ticket.isActive
  }

  prototype.isTransferredTicket = function (ticket) {
    if (!ticket.giftedUserEmail) return false
    return this.isInState(ticket.ticketState, ['CLAIMED', 'FORCE_TRANSFERRED'])
  }

  prototype.isPartiallyRefundedTicket = function (ticket) {
    return this.isInState(ticket.ticketState, 'PARTIALLY_REFUNDED')
  }

  prototype.isRefundedTicket = function (ticket) {
    return this.isInState(ticket.ticketState, 'REFUNDED')
  }

  prototype.isRevokedTicket = function (ticket) {
    return this.isInState(ticket.ticketState, 'REVOKED')
  }

  prototype.normalizeEventHostTicketEntry = function (_data) {
    const data = this.normalizeData(_data)
    const ticketState = this.mapApiStatus(data.api_status)

    const ret = {
      qrCode: data.qr_code,
      currentVipPrice: normalizeCurrency(data.event.vip_ticket_price),
      currentGeneralPrice: normalizeCurrency(data.event.general_ticket_price),
      id: parseInt(data.id),
      isCheckedIn: !!data.is_checked_in,
      orderId: parseInt(data.order_id),
      currentTicketHolder: data.ticket_holder.email,
      purchasedBy: data.buyer.email,
      purchasedDatetime: this.normalizeDate(data.purchased_datetime),
      ticketType: data.type,
      price: normalizeCurrency(data.price),
      credentialsId: data.credentials_id ? parseInt(data.credentials_id) : undefined,
      ticketState,
      giftedUserEmail: data?.gifted_user_email
    }

    ret.isVip = ret.ticketType === 'vip'
    ret.isGeneral = !ret.isVip
    ret.hasVip = ret.currentVipPrice > 0
    ret.isActive = this.isActiveTicket(ret)
    ret.isTransferred = this.isTransferredTicket(ret)
    ret.isCheckInable = this.isCheckInableTicket(ret)
    ret.isUncheckInable = this.isUncheckInableTicket(ret)
    ret.isTransferrable = this.isTransferrableTicket(ret)
    ret.isReclaimable = this.isReclaimableTicket(ret)
    ret.isSetCredentialsable = this.isSetCredentialsableTicket(ret)
    ret.isRevokable = this.isRevokableTicket(ret)
    ret.isPendingTransfer = this.isPendingTransferTicket(ret)
    ret.isPartiallyRefunded = this.isPartiallyRefundedTicket(ret)
    ret.isRefunded = this.isRefundedTicket(ret)
    ret.isRevoked = this.isRevokedTicket(ret)
    ret.isUpgradeable = this.isUpgradeableTicket(ret)

    ret.formattedPrice = formatCurrency(ret.price)

    return ret
  }

  prototype.getEventHostTickets = function (payload) {
    const finalPayload = {
      event_id: payload.eventId,
      page: payload.page || 1
    }
    if (payload.term) finalPayload.term = payload.term

    const raw = ifPromise(payload, () => this._getEventHostTickets(finalPayload))
    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeEventHostTicketEntry))
      .catch(error => this.onError('getEventHostTickets', error))
  }

  prototype._searchEvents = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/events/search'),
      method: 'GET',
      requestType: 'json',
      data
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} searchEventsListSummary
   * @property {number} page - page of results
   * @property {number} perPage - results per page
   * @property {boolean} hasMore - are more pages available?
   */

  /**
   * get url from video embed code
   *
   * @typedef {object} searchEventsReturn
   * @property {Event[]} entries - list of speaker profile ids returned
   * @property {searchEventsListSummary} listSummary - list summary object
   * @property {object} criteriaSummary - criteria summary object (empty)
   */

  /**
   * @function getEventSpeakers
   * @param {object} payload - The payload
   * @param {number} payload.id - event id
   * @param {number} [payload.page=1] - result set page
   * @returns {getEventSpeakersReturn} - getEventSpeakersReturn
   * @example
   * getEventSpeakers({
   *   page: 2
   * })
   */

  /**
   * @function searchEvents
   * @param {object} payload - The payload
   * @param {number} [payload.page=1] - result set page
   * @param {string} payload.term - term to search against
   * @returns {searchEventsReturn} - searchEvents instance
   * @example
   *
   * searchEvents({
   *   term: 'foo',
   *   page: 2
   * })
   */

  prototype.searchEvents = function (payload) {
    const finalPayload = {
      term: payload.term,
      page: payload?.page || 1
    }

    const raw = ifPromise(payload, () => this._searchEvents(finalPayload))

    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeEvent))
      .catch(error => this.onError('searchEvents', error))
  }

  prototype._showEventSpeaker = function ({ id, eventSpeakerJoinId }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/speakers/${eventSpeakerJoinId}`),
      method: 'GET'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function showEventSpeaker
   * @param {object} payload - The eventSpeaker payload
   * @param {number} payload.id - event id
   * @param {number} payload.speakerEventJoinId - event speaker join id
   * @returns {EventSpeaker} - EventSpeaker instance
   * @example
   *
   * showEventSpeaker({
   *   id: 10,
   *   speakerEventJoinId: 100
   * })
   */

  prototype.showEventSpeaker = function (payload) {
    const finalPayload = {
      id: payload.id,
      eventSpeakerJoin: payload.eventSpeakerJoin
    }
    const raw = ifPromise(payload, () => this._showEventSpeaker(finalPayload))
    return raw
      .then(res => this.normalizeEventSpeaker(res.data))
      .catch(error => this.onError('showEventSpeaker', error))
  }

  prototype._getEventSpeakers = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/speakers`),
      method: 'GET',
      requestType: 'json',
      data: rest
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} getEventSpeakerSummary
   * @property {number} page - page of results
   * @property {number} perPage - results per page
   * @property {boolean} hasMore - are more pages available?
   */

  /**
   * @typedef {object} getEventSpeakersReturn
   * @property {EventSpeaker[]} entries - list of speaker profile ids returned
   * @property {getEventSpeakerSummary} listSummary - list summary object
   * @property {object} criteriaSummary - criteria summary object (empty)
   */

  /**
   * @function getEventSpeakers
   * @param {object} payload - The payload
   * @param {number} payload.id - event id
   * @param {number} [payload.page=1] - result set page
   * @returns {getEventSpeakersReturn} - getEventSpeakersReturn
   * @example
   *
   * getEventSpeakers({
   *   id: 10,
   *   page: 2
   * })
   */

  prototype.getEventSpeakers = function (payload) {
    const finalPayload = {
      id: payload.id,
      page: payload?.page || 1
    }

    const raw = ifPromise(payload, () => this._getEventSpeakers(finalPayload))
    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeEventSpeaker))
      .catch(error => this.onError('getEventSpeakers', error))
  }

  prototype._updateEventSpeaker = function ({ eventId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${eventId}/speakers`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function updateEventSpeaker
   * @param {object} payload - The eventSpeaker payload
   * @param {number} payload.eventId - event id
   * @param {number} payload.speakerEventJoinId - event speaker join id
   * @param {number} [payload.kwivrrId] - associate by user id if exists, otherwise, check for speaker profile id
   * @param {number} [payload.speakerProfileId] - associate by speaker_profile_id (fallthrough if kwivrrId not provided)
   * @param {string} payload.topic - topic speaker is covering
   * @param {string} payload.description - more detailed description of topic
   * @param {string} payload.startTime - when speaker goes on
   * @param {string} payload.endTime - when speaker's session is over
   * @returns {EventSpeaker} - EventSpeaker instance
   * @example
   *
   * updateEventSpeaker({
   *   eventId: 10,
   *   speakerEventJoinId: 100,
   *   speakerProfileId: 6,
   *   topic:"9d3fjpsarasdgywm7lj8snq5pr39e3",
   *   description:"5zbn5idwlpvbg3miqidwwm2pvkxohm",
   *   startTime:"2021-07-07T07:39:05.797-05:00",
   *   endTime:"2021-07-09T07:39:05.797-05:00"
   * })
   */

  prototype.updateEventSpeaker = function (payload) {
    const finalPayload = {
      eventId: payload.eventId,
      speaker_event_join_id: payload.eventSpeakerJoinId,
      key_note: payload.topic,
      description: payload.description,
      start_time: payload.startTime,
      end_time: payload.endTime
    }

    if (hasKey(payload, 'kwivrrId')) finalPayload.kwivrr_id = payload.kwivrrId
    else if (hasKey(payload, 'speakerProfileId')) finalPayload.speaker_profile_id = payload.speakerProfileId

    const raw = ifPromise(payload, () => this._updateEventSpeaker(finalPayload))
    return raw
      .then(res => this.normalizeEventSpeaker(res.data))
      .catch(error => this.onError('updateEventSpeaker', error))
  }

  prototype._getEventCalendar = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/events/calendar'),
      method: 'GET',
      requestType: 'json',
      data
    }

    return this.authenticatedFetchWrap(payload)
  }

  prototype.normalizeEventCalendar = function (data) {
    const dates = {}
    const keysWeCareAbout = ['completed', 'attended', 'registered_for', 'hosting']

    keysWeCareAbout.forEach(k => {
      if (hasKey(data, k)) {
        const obj = data[k]
        for (const [dateString, count] of Object.entries(obj)) {
          if (!hasKey(dates, dateString)) dates[dateString] = 0
          dates[dateString] = dates[dateString] + parseInt(count)
        }
      }
    })

    return dates
  }

  /**
   * @function getEventCalendar
   * @param {object} payload - The event payload
   * @param {string} [payload.userId] - userId to masquerade if needed
   * @param {string} payload.startDate - start date of lookup range
   * @param {string} payload.endDate - end date of lookup range
   * @returns {object} - object with date strings as keys and counts for that date
   * @example
   * getEventCalendar({
   *   startDate: '2021-02-01'
   *   endDate: '2021-03-01'
   * })
   */
  prototype.getEventCalendar = function (payload) {
    const finalPayload = {
      start_date: payload.startDate,
      end_date: payload.endDate
    }

    if (hasKey(payload, 'userId')) finalPayload.user_id = this.normalizeUserId(payload.userId)

    const raw = ifPromise(payload, () => this._getEventCalendar(finalPayload))
    return raw
      .then(res => this.normalizeEventCalendar(res.data))
      .catch(error => this.onError('getEventCalendar', error))
  }

  /**
   * @typedef {object} EventOverview
   * @property {number} gaTicketTotalSales - ga ticket total sales
   * @property {number} vipTicketTotalSales - vip ticket total sales
   * @property {number} gaTicketsSold - ga tickets sold count
   * @property {number} vipTicketsSold - vip tickets sold count
   * @property {number} gaTicketsCheckedInCount - ga tickets checked in count
   * @property {number} vipTicketsCheckedInCount - vip tickets checked in count
   * @property {number} gaTicketsAllocated - ga tickets capacity
   * @property {number} vipTicketsAllocated - vip tickets capacity
   */

  prototype.normalizeEventOverview = function (_data) {
    const data = this.normalizeData(_data)

    const mapToProps = {
      ga_tickets_total_sales: 'gaTicketsTotalSales',
      vip_tickets_total_sales: 'vipTicketsTotalSales',
      ga_tickets_sold: 'gaTicketsSold',
      vip_tickets_sold: 'vipTicketsSold',
      ga_tickets_checked_in_count: 'gaTicketsCheckedIn',
      vip_tickets_checked_in_count: 'vipTicketsCheckedIn',
      ga_tickets_allocated: 'gaTicketsAllocated',
      vip_tickets_allocated: 'vipTicketsAllocated'
    }

    const ret = this.filterAndMapProps(data, mapToProps)

    ret.formattedGaTicketsTotalSales = formatCurrency(ret.gaTicketsTotalSales)
    ret.formattedVipTicketsTotalSales = formatCurrency(ret.vipTicketsTotalSales)

    return ret
  }

  prototype._getEventOverview = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/overview`),
      method: 'GET',
      requestType: 'json'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function getEventOverview
   * @param {object} payload - The event payload
   * @param {number} payload.id - event id
   * @returns {EventOverview} - Event overview detail
   */
  prototype.getEventOverview = function (payload) {
    const finalPayload = {
      id: payload.eventId
    }

    const raw = ifPromise(payload, () => this._getEventOverview(finalPayload))
    return raw
      .then(res => this.normalizeEventOverview(res.data))
      .catch(error => this.onError('getEventOverview', error))
  }

  prototype._cloneEvent = function ({ userId, id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/events/${id}/clone`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function cloneEvent
   * @param {object} payload - The event payload
   * @param {string} [payload.userId='me'] - user to assign cloned event to
   * @param {number} payload.id - event id
   * @param {string} payload.title - title for cloned event
   * @returns {Event} - new Event object
   */
  prototype.cloneEvent = function (payload) {
    const finalPayload = {
      userId: this.normalizeUserId(payload.userId),
      id: payload.id,
      zevents_event: {
        name: payload.title
      }
    }

    const raw = ifPromise(payload, () => this._cloneEvent(finalPayload))

    return raw
      .then(res => this.normalizeEvent(res.data))
      .catch(error => this.onError('cloneEvent', error))
  }

  prototype.normalizeEcheckPayment = function (payment, isSaveable = true) {
    return {
      echeck: {
        account_holder_name: payment.echeck.accountHolderName,
        routing_number: payment.echeck.routingNumber,
        masked_bank_account_number: this.maskBankAccountNumber(payment.echeck.accountNumber),
        encrypted_bank_account_number: this.encryptECheck(payment.echeck.accountNumber),
        ...(isSaveable ? { save_echeck: !!payment.echeck.saveEcheck } : {})
      }
    }
  }

  prototype.normalizeEcheckIdPayment = function (payment) {
    return {
      echeck_id: parseInt(payment.echeckId)
    }
  }

  prototype.normalizeCreditCardIdPayment = function (payment) {
    return {
      card_id: parseInt(payment.cardId)
    }
  }

  prototype.normalizeCreditCardPayment = function (payment, isSaveable = true) {
    return {
      credit_card: {
        name: hasKey(payment.creditCard, 'nameOnCard') ? payment.creditCard.nameOnCard : payment.creditCard.name,
        card_type: payment.creditCard.cardType,
        address: payment.creditCard.address.line1,
        city: payment.creditCard.address.city,
        state: payment.creditCard.address.state,
        zipcode: payment.creditCard.address.zipCode,
        country: payment.creditCard.address.country || 'US',
        encrypted_card_number: this.encryptCreditCardNumber(payment.creditCard.cardNumber),
        gateway_cvv: payment.creditCard.ccv,
        last_four: this.maskCreditCardNumber(payment.creditCard.cardNumber),
        expiration_month: hasKey(payment.creditCard, 'expirationMonth') ? payment.creditCard.expirationMonth : payment.creditCard.expiration.split('/')[0],
        expiration_year: hasKey(payment.creditCard, 'expirationYear') ? payment.creditCard.expirationYear : payment.creditCard.expiration.split('/')[1],
        expiration_day: '01',
        ...(isSaveable ? { save_card: !!payment.creditCard.saveCard } : {})
      }
    }
  }

  prototype.normalizePayment = function (payment, isSaveable = true) {
    if (hasKey(payment, 'cardId')) return this.normalizeCreditCardIdPayment(payment)
    if (hasKey(payment, 'echeckId')) return this.normalizeEcheckIdPayment(payment)
    if (hasKey(payment, 'echeck')) return this.normalizeEcheckPayment(payment, isSaveable)
    if (hasKey(payment, 'creditCard')) return this.normalizeCreditCardPayment(payment, isSaveable)

    return undefined
  }

  prototype._purchaseEventTickets = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/purchase`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  prototype.normalizeEventTicketPurchase = function (_data) {
    const data = this.normalizeData(_data)
    return data
    // return this.normalizeEventGuestTicketEntry(_data)
  }

  /**
   * @typedef {object} PaymentAddress
   * @property {string} line1 - line1 of address
   * @property {string} city - city
   * @property {string} state - state
   * @property {string} country - country
   * @property {string} zipCode - zip code
   */

  /**
   * @typedef {object} PaymentEcheck
   * @property {string} accountHolderName - name on account
   * @property {string} routingNumber - routing number
   * @property {boolean} saveEcheck - save eCheck on file?
   */

  /**
   * @typedef {object} PaymentCreditCard
   * @property {string} name - name on card
   * @property {string} cardNumber - cc number
   * @property {string} ccv - security code
   * @property {string} cardType - cc card type
   * @property {number} expirationMonth - card exp month
   * @property {number} expirationYear - card exp year
   * @property {boolean} saveCard - save card on file?
   * @property {PaymentAddress} address - address on card
   */

  /**
   * @typedef {object} Payment
   * @property {number} [cardId] - credit card id on file to use
   * @property {number} [echeckId] - echeck id on file to use
   * @property {PaymentEcheck} [echeck] - new echeck details
   * @property {PaymentCreditCard} [creditCard] - new echeck details
   */

  /**
   * @function purchaseEventTickets
   * @param {object} payload - The payload
   * @param {number} payload.id - event id
   * @param {number} payload.generalTicketCount - how many general tickets
   * @param {number} payload.vipTicketCount - how many vip tickets
   * @param {number} payload.applyCredits - should kwivrr credit be applied
   * @param {string} [payload.preferredLanguage] - if event is multi-lingual, which language would you prefer?
   * @param {Payment} payload.payment - payment object
   * @returns {void}
   */

  prototype.purchaseEventTickets = function (payload) {
    const {
      id,
      payment,
      applyCredits,
      vipTicketCount,
      generalTicketCount
    } = payload

    const finalPayload = {
      id: id,
      apply_credits: !!applyCredits,
      zevents_event_order: {
        general_ticket_count: parseInt(generalTicketCount),
        vip_ticket_count: parseInt(vipTicketCount)
      },
      payment: this.normalizePayment(payment)
    }

    if (hasKey(payload, 'preferredLanguage')) finalPayload.zevents_event_order.preferred_language = payload.preferredLanguage

    const raw = ifPromise(payload, () => this._purchaseEventTickets(finalPayload))
    return raw
      .then(res => this.normalizeEventTicketPurchase(res.data))

      .catch(error => {
        console.log('##', error)
        const mapErrors = {
          'exceeds capacity': 'EVENT_TICKET_EXCEEDS_CAPACITY'
        }
        return this.onError('purchaseEventTickets', error, mapErrors)
      })
  }

  prototype.normalizeRegisterForEvent = function (_data) {
    const data = this.normalizeData(_data)
    return data
  }

  prototype._registerForEvent = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/register`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)

    /**
     * @function registerForEvent
     * @param {object} payload - The event payload
     * @param {string} payload.id - event id to register for
     * @returns {void}
     * @example
     *
     * registerForEvent({
     *   id: 11818
     * })
     */
  }

  prototype.registerForEvent = function (payload) {
    const {
      id
    } = payload

    const finalPayload = {
      id
    }

    const raw = ifPromise(payload, () => this._registerForEvent(finalPayload))
    return raw
      .then(res => this.normalizeEventTicketPurchase(res.data))
      .catch(error => this.onError('registerForEvent', error))
  }

  prototype.normalizeUnregisterForEvent = function (_data) {
    const data = this.normalizeData(_data)
    return data
  }

  prototype._unregisterForEvent = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/unregister`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function unegisterForEvent
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to unregister for
   * @returns {void}
   * @example
   *
   * unregisterForEvent({
   *   id: 11818
   * })
   */

  prototype.unregisterForEvent = function (payload) {
    const {
      eventId
    } = payload

    const finalPayload = {
      id: eventId
    }

    const raw = ifPromise(payload, () => this._unregisterForEvent(finalPayload))
    return raw
      .catch(error => this.onError('unregisterForEvent', error))
  }

  prototype._sellCustomEventTickets = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/sell_custom`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function sellCustomEventTickets
   * @param {object} payload - The payload
   * @param {number} payload.id - event id
   * @param {number} payload.userId - user id to assign tickets
   * @param {string} [payload.preferredLanguage] - if event is multi-lingual, which language would you prefer?
   * @param {number} payload.generalTicketCount - how many general tickets
   * @param {number} payload.generalTicketPrice - how much for general ticket?
   * @param {number} [payload.vipTicketCount] - if vip tickets are sold, how many vip tickets
   * @param {number} [payload.vipTicketPrice] - if vip tickets are sold, how much for vip ticket?
   * @param {Payment} payload.payment - payment object
   * @returns {void}
   */

  prototype.sellCustomEventTickets = function (payload) {
    const {
      id,
      userId,
      payment,
      generalTicketCount,
      generalTicketPrice,
      vipTicketCount,
      vipTicketPrice
    } = payload

    const finalPayload = {
      id: id,
      apply_credits: false,
      zevents_event_order: {
        user_id: parseInt(userId),
        general_ticket_count: parseInt(generalTicketCount),
        general_price: generalTicketPrice || 0,
        vip_ticket_count: parseInt(vipTicketCount),
        vip_price: vipTicketPrice || 0
      },
      payment: this.normalizePayment(payment, false)
    }

    if (hasKey(payload, 'preferredLanguage')) finalPayload.zevents_event_order.preferred_language = payload.preferredLanguage

    const raw = ifPromise(payload, () => this._sellCustomEventTickets(finalPayload))
    return raw
      .then(res => this.normalizeEventTicketPurchase(res.data))
      .catch(error => this.onError('sellCustomEventTickets', error))
  }

  prototype._endEventStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/end_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function endEventStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to end
   * @returns {void}
   * @example
   *
   * endEventStream({
   *   id: 11818
   * })
   */
  prototype.endEventStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._endEventStream(finalPayload))
    return raw
      .catch(error => this.onError('endEventStream', error))
  }

  prototype._startEventStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/start_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function startEventStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to start
   * @returns {void}
   * @example
   *
   * startEventStream({
   *   id: 11818
   * })
   */
  prototype.startEventStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._startEventStream(finalPayload))
    return raw
      .catch(error => this.onError('startEventStream', error))
  }

  prototype._pauseEventStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/pause_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function pauseEventStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to pause
   * @returns {void}
   * @example
   *
   * pauseEventStream({
   *   id: 11818
   * })
   */
  prototype.pauseEventStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._pauseEventStream(finalPayload))
    return raw
      .catch(error => this.onError('pauseEventStream', error))
  }

  prototype._resumeEventStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/resume_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function resumeEventStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to pause
   * @returns {void}
   * @example
   *
   * resumeEventStream({
   *   id: 11818
   * })
   */
  prototype.resumeEventStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._resumeEventStream(finalPayload))
    return raw
      .catch(error => this.onError('resumeEventStream', error))
  }
  prototype._provisionStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/provision_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function provisionStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to start
   * @returns {void}
   * @example
   *
   * provisionStream({
   *   id: 11818
   * })
   */
  prototype.provisionStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._provisionStream(finalPayload))
    return raw
      .catch(error => this.onError('provisionStream', error))
  }
  prototype._deprovisionStream = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/deprovision_stream`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }
  /**
   * @function deprovisionStream
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to start
   * @returns {void}
   * @example
   *
   * deprovisionStream({
   *   id: 11818
   * })
   */
  prototype.deprovisionStream = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._deprovisionStream(finalPayload))
    return raw
      .catch(error => this.onError('deprovisionStream', error))
  }
  /**
   * @function registerForEvent
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to register for
   * @returns {void}
   * @example
   *
   * registerForEvent({
   *   id: 11818
   * })
   */
  prototype.registerForEvent = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._registerForEvent(finalPayload))
    return raw
      .catch(error => this.onError('registerForEvent', error))
  }
  prototype._unregisterForEvent = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${id}/unregister`),
      method: 'POST'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function unregisterForEvent
   * @param {object} payload - The event payload
   * @param {string} payload.id - event id to unregister for
   * @returns {void}
   * @example
   *
   * unregisterForEvent({
   *   id: 11818
   * })
   */
  prototype.unregisterForEvent = function (payload) {
    const finalPayload = {
      id: payload.id
    }

    const raw = ifPromise(payload, () => this._unregisterForEvent(finalPayload))
    return raw
      .catch(error => this.onError('unregisterForEvent', error))
  }

  prototype._getEventGuestTickets = function ({ event_id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/events/${event_id}/guest_tickets`),
      method: 'GET',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  prototype.normalizeEventGuestTicketEntry = function (_data) {
    const data = this.normalizeData(_data)

    const ticketState = this.mapApiStatus(data.api_status)
    const ret = {
      giftedUserEmail: data?.gifted_user_email,
      isCheckedIn: !!data.is_checked_in,
      ticketState,
      currentVipPrice: normalizeCurrency(data.event.vip_ticket_price),
      currentGeneralPrice: normalizeCurrency(data.event.general_ticket_price),
      id: parseInt(data.id),
      orderId: parseInt(data.order_id),
      currentTicketHolder: data.ticket_holder.email,
      userName: `${data.buyer.firstname} ${data.buyer.lastname}`,
      userEmail: data.buyer.email,
      refund: 0,
      credit: 0,
      ticketType: data.type,
      qrCode: data.qr_code
    }

    const hasSourceable = hasKey(data.event_order, 'payment_source') && data.event_order.payment_source?.sourceable?.id

    if (data?.event_order) {
      ret.chargeInfo = {
        individualCharges: [
          {
            id: hasSourceable ? data.event_order.payment_source.sourceable.id : -1,
            ticketType: data.type,
            numTickets: parseInt(data.event_order.event_tickets.length),
            ticketTotal: normalizeCurrency(data.event_order.sub_total),
            formattedTicketTotal: formatCurrency(normalizeCurrency(data.event_order.sub_total)),
            subtotal: normalizeCurrency(data.event_order.sub_total),
            formattedSubtotal: formatCurrency(normalizeCurrency(data.event_order.sub_total)),
            serviceFee: normalizeCurrency(data.event_order.service_fee),
            formattedServiceFee: formatCurrency(normalizeCurrency(data.event_order.service_fee)),
            tax: normalizeCurrency(data.event_order.tax_total),
            formattedTax: formatCurrency(normalizeCurrency(data.event_order.tax_total)),
            total: normalizeCurrency(data.event_order.total_price),
            formattedTotal: formatCurrency(normalizeCurrency(data.event_order.total_price)),
            paymentDetails: {
              nameOnCard: hasSourceable ? data.event_order.payment_source.sourceable.name_on_card : `${data.buyer.firstname} ${data.buyer.lastname}`,
              lastFourDigits: hasSourceable ? data.event_order.payment_source.sourceable.last_four : '(credit applied)'
            }
          }
        ]
      }
    }

    ret.isGiftedToMe = !data?.event_order
    ret.isVip = ret.ticketType === 'vip'
    ret.isGeneral = !ret.isVip
    ret.hasVip = ret.currentVipPrice > 0
    ret.isActive = this.isActiveTicket(ret)
    ret.isTransferred = this.isTransferredTicket(ret)
    ret.isCheckInable = this.isCheckInableTicket(ret)
    ret.isUncheckInable = this.isUncheckInableTicket(ret)
    ret.isTransferrable = this.isTransferrableTicket(ret)
    ret.isReclaimable = this.isReclaimableTicket(ret)
    ret.isSetCredentialsable = this.isSetCredentialsableTicket(ret)
    ret.isRevokable = this.isRevokableTicket(ret)
    ret.isPendingTransfer = this.isPendingTransferTicket(ret)
    ret.isRefunded = this.isRefundedTicket(ret)
    ret.isParticallyRefunded = this.isPartiallyRefundedTicket(ret)
    ret.isRevoked = this.isRevokedTicket(ret)
    ret.isUpgradeable = this.isUpgradeableTicket(ret)
    console.log('##', data.event)
    ret.event = this.normalizeEvent(data.event)
    ret.isCheckInTime = ret.event.isCheckInTime
    return ret
  }

  prototype.getEventGuestTickets = function (payload) {
    const finalPayload = {
      event_id: payload.eventId,
      page: payload.page || 1
    }

    if (payload.term) finalPayload.term = payload.term

    const raw = ifPromise(payload, () => this._getEventGuestTickets(finalPayload))
    return raw
      .then(res => {
        return this.normalizeListData(res.data, this.normalizeEventGuestTicketEntry)
      })
      // .catch(error => {
      //   console.log('##error', error)
      //   return this.onError('getEventGuestTickets', error)
      // })
  }
}

export default ZEventsApiEvent