/* global localStorage */
const STORAGE_KEY = 'help-centre'
const DEFAULT_LABELS = {
  loading: 'Loading...',
  error: 'Error loading data.',
  home: 'Home',
  searchHeading: 'Need assistance?',
  searchDescription: 'Search for topics, questions or keywords.',
  searchPlaceholder: 'Enter your search term',
  searchButton: 'Submit',
  searchBreadcrumb: 'Search results',
  searchNoResults: 'No results.',
  categoryListHeading: 'Find information by topic',
  viewAllQuestions: 'View all [section] questions',
  otherSectionsHeading: 'Other [category] sections',
  voteIntro: 'Was this article helpful?',
  voteYes: 'Yes',
  voteNo: 'No',
  voteHelpful: '[a] out of [b] found this helpful.',
  votePositive: 'Thank you for your feedback.',
  voteNegative: 'We apologise for the inconvenience. For further assistance, please don\'t hesitate to contact our customer service team.',
  loadMore: 'Load more results'
}

export default class API {
  constructor ({ apiUrl, voteUrl, voteCreds, dictionaryUrl, imageUrl, locale }) {
    this.voteCreds = voteCreds
    this.voteUrl = voteUrl
    this.dictionaryUrl = dictionaryUrl
    // Allow dictionary to be loaded before the main data to display the Loading label.
    this.dictionaryPromise = new Promise(resolve => {
      (this.dictionaryUrl ? this._fetch(this.dictionaryUrl) : Promise.resolve({})).then(labels => {
        this.labels = { ...DEFAULT_LABELS, ...labels }
        resolve(labels)
      })
    })
    this.apiUrl = apiUrl
    this.imageUrl = imageUrl
    this.locale = locale
    this.voteId = this._uuidv4()

    if (!localStorage.getItem(STORAGE_KEY)) {
      localStorage.setItem(STORAGE_KEY, '{}')
    }
  }

  onDictionaryReady (callback) {
    this.dictionaryPromise.then(callback)
  }

  async init () {
    // Fetch all entities one by one.
    await Promise.all(['categories', 'sections', 'articles'].map(this.fetchAllPages.bind(this)))
    await this.dictionaryPromise
  }

  async fetchAllPages (key) {
    const baseUrl = `${this.apiUrl}/${this.locale}`
    this[key] = []
    let page = 1
    let pagesToFetch = 1

    while (pagesToFetch > 0) {
      const items = await this._fetch(`${baseUrl}/${key}?per_page=100&page=${page}`)
      if (page === 1) {
        pagesToFetch = items.page_count
      }
      this[key].push(...items[key] || [])
      page++
      pagesToFetch--
    }
  }

  getCategories ({ categoryId }) {
    return categoryId ? this.categories.filter(category => category.id === categoryId) : this.categories
  }

  getSections ({ categoryId, sectionId }) {
    if (sectionId) {
      return this.sections.filter(section => section.id === sectionId)
    } else if (categoryId) {
      return this.sections.filter(section => section.category_id === categoryId)
    } else {
      return this.sections
    }
  }

  getArticles ({ sectionId, articleId }) {
    if (articleId) {
      return this.articles.filter(article => article.id === articleId)
    } else if (sectionId) {
      return this.articles.filter(article => article.section_id === sectionId)
    } else {
      return this.articles
    }
  }

  async search (query, page = 1) {
    const result = await this._fetch(`${this.apiUrl}/articles/search?query=${query}&per_page=20&page=${page}`)
    const nextPage = page < result.page_count ? page + 1 : 0
    return [result.results, nextPage]
  }

  hasVoted (articleId) {
    return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}')[articleId]
  }

  async vote (articleId, isUp) {
    const tokens = await this._fetch(`${this.voteUrl}/oauth/v1/mobility/token`, {
      method: 'post',
      headers: {
        'content-type': 'application/x-www-form-urlencoded',
        accept: 'application/json',
        authorization: `Basic ${btoa(this.voteCreds)}`
      },
      body: new URLSearchParams({ grant_type: 'client_credentials' })
    })
    const voteToken = tokens.access_token

    this._saveVote(articleId)

    return this._fetch(`${this.voteUrl}/zendesk/v1/vote/${this.locale}/articles/${articleId}/${isUp ? 'up' : 'down'}`, {
      method: 'post',
      headers: {
        authorization: `Bearer ${voteToken}`,
        requestId: this.voteId
      }
    })
  }

  _saveVote (articleId) {
    const save = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}')
    save[articleId] = true
    localStorage.setItem(STORAGE_KEY, JSON.stringify(save))
  }

  _uuidv4 () {
    return '10000000-1000-4000-8000-100000000000'.replaceAll(/[018]/g, c =>
      (+c ^ globalThis.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
    )
  }

  async _fetch (url, options) {
    const res = await fetch(url, options)
    if (!res.ok) {
      throw new Error(`Failed to load resource: ${url}`)
    }
    return res.json()
  }
}
