Source: thematic.js

/**
 * @module Thematic
 * @description Handles logic for...
 */
/* global browser */

'use strict'

if (typeof process === 'undefined') {
  const module = {}
}

module.exports = {
  isDefaultTheme,
  chooseNext,
  getCurrentId,
  getDefaultTheme,
  buildToolsMenuItem,
  buildThemes,
  stopRotation,
  startRotation,
  rotate,
  handleMessage,
  commands
}

/**
 * Takes in an array of all themes installed.
 * Gets a default Mozilla. Logs to console if no default theme found.
 * @param {Array<Object>} allThemes - the list of all installed themes
 * @returns {Object} The default theme of the user
 */
function getDefaultTheme (allThemes) {
  let themes = allThemes.filter(info => info.name === 'Default')
  if (themes.length > 0) {
    console.log(themes[0])
    return themes[0]
  }

  themes = allThemes.filter(info => info.id === 'default-theme@mozilla.org')
  if (themes.length > 0) {
    console.log(themes[0])
    return themes[0]
  }

  for (const theme of allThemes) {
    if (isDefaultTheme(theme)) {
      console.log(theme)
      return theme
    }
  }
  console.log('No default theme found!')
}

/**
 * Determines if a given theme is a built-in Mozilla or Firefox theme.
 * @param {Object} theme - The theme object to check.
 * @returns {boolean} True if it is a default theme, false otherwise.
 */
function isDefaultTheme (theme) {
  return [
    'firefox-compact-dark@mozilla.org',
    'firefox-compact-light@mozilla.org',
    'default-theme@mozilla.org',
    'firefox-alpenglow@mozilla.org',
    'thunderbird-compact-dark@mozilla.org',
    'thunderbird-compact-light@mozilla.org',
    '{972ce4c6-7e08-4474-a285-3208198ce6fd}'
  ].includes(theme.id)
}

/**
 * Returns the ID of current theme
 * @param {Object} localThemeId User's current theme id, assuming it exists
 * @param {Array<Object>} userThemes Array of non-default, user-added themes
 * @param {Object} defaultTheme Default Mozilla/Firefox theme
 * @returns {string} currentId
 */
function getCurrentId (localThemeId, userThemes, defaultTheme) {
  if (Object.keys(localThemeId).length !== 0) {
    return localThemeId.currentId
  }

  if (userThemes.length > 0) {
    console.log('Setting currentId to first user theme')
    return userThemes[0].id
  }

  console.log('Setting currentId to default theme')
  return defaultTheme.id
}

/**
 * Accesses all installed themes and organizes them categorically.
 * @returns {Promise<void>}
 */
async function buildThemes () {
  const allExtensions = await browser.management.getAll()
  const allThemes = allExtensions.filter(info => info.type === 'theme')
  // console.log(allThemes)

  const localThemeId = await browser.storage.local.get('currentId')
  const defaultTheme = getDefaultTheme(allThemes)
  const defaultThemes = allThemes.filter(theme => isMozillaTheme(theme))
  const userThemes = allThemes.filter(theme => !isMozillaTheme(theme))
  const currentId = getCurrentId(localThemeId, userThemes, defaultTheme)

  const themes = {
    currentId: currentId,
    defaultTheme: defaultTheme,
    defaultThemes: defaultThemes,
    userThemes: userThemes
  }
  await browser.storage.local.set(themes)
  buildToolsMenu(themes)
}

/**
 * Determines if a theme is a theme from Mozilla.
 * @param {Object} theme Theme to be tested.
 * @returns {Boolean} True if Mozilla theme, false if not a Mozilla theme.
 */
function isMozillaTheme (theme) {
  return theme.id.endsWith('mozilla.org')
}

// https://stackoverflow.com/questions/49432579/await-is-only-valid-in-async-function
// bleah
async function buildThemesHelper () {
  await buildThemes()
}

buildThemesHelper()

/*
async function asyncHelper(fn) {
  await fn()
}

asyncHelper(buildThemes)
*/

/**
 * Calculates the next theme index based on user preferences (random or sequential).
 * @param {number} currentIndex - The index of the currently active theme.
 * @param {Object} items - Object containing userThemes array.
 * @returns {Promise<number>} The index of the next theme to enable.
 */
async function chooseNext (currentIndex, items) {
  const pref = await browser.storage.sync.get('random')
  if (pref.random) {
    let newIndex = currentIndex
    while (newIndex === currentIndex) {
      const a = Math.floor(Math.random() * items.userThemes.length)
      newIndex = a
    }
    return newIndex
  }
  return (currentIndex + 1) % items.userThemes.length
}

/**
 * Rotates to the next theme in user's collection.
 * @returns {Promise<void>}
 */
async function rotate () {
  const items = await browser.storage.local.get()

  if (items.userThemes.length < 1) {
    console.log('No user themes found!')
    return
  }

  if (typeof items.currentId === 'undefined') {
    console.log('No current theme Id found!')
    return
  }

  const previousId = items.currentId
  console.log('From: ' + previousId)
  const previousIndex = items.userThemes.findIndex((t) => t.id === previousId)

  if (previousIndex === -1) {
    // this will get resolved below as 1 will be added to this :/
    console.log('User theme index not found')
  } else {
    await browser.management.setEnabled(previousId, false)
  }

  const currentIndex = await chooseNext(previousIndex, items)
  const currentId = items.userThemes[currentIndex].id
  console.log('To: ' + currentId)

  await browser.storage.local.set({ currentId: currentId })
  await browser.management.setEnabled(currentId, true)
}

browser.alarms.onAlarm.addListener(rotate)

/**
 * Begins the process for rotating through themes on a timer.
 */
async function startRotation () {
  const info = await browser.runtime.getBrowserInfo()
  if (info.name !== 'Thunderbird') {
    await browser.storage.sync.set({ auto: true })
    const a = await browser.storage.sync.get('minutes')
    await browser.alarms.create('rotate', { periodInMinutes: a.minutes })
  }
}

/**
 * Stops the process of rotating through themes.
 */
async function stopRotation () {
  const info = await browser.runtime.getBrowserInfo()
  if (info.name !== 'Thunderbird') {
    await browser.storage.sync.set({ auto: false })
    await browser.alarms.clear('rotate')
  }
}

browser.storage.sync.get('auto').then((pref) => {
  if (pref.auto) {
    startRotation().catch((err) => { console.log(err) })
  }
})

function handleMessage (request, sender, sendResponse) {
  console.log('Message from the popup or options script: ' + request.message)
  switch (request.message) {
    case 'Start rotation':
      startRotation().catch((err) => { console.log(err) })
      sendResponse({ response: 'OK' })
      break
    case 'Stop rotation':
      stopRotation().catch((err) => { console.log(err) })
      sendResponse({ response: 'OK' })
      break
    default:
      console.log('Unknown message received')
      sendResponse({ response: 'Not OK' })
      break
  }
}

browser.runtime.onMessage.addListener(handleMessage)

// allow Jest's mocking to occur
// https://stackoverflow.com/questions/25649097/nodejs-override-a-function-in-a-module
function jestTest (fn, testfn) {
  if (typeof process === 'undefined') {
    fn()
  } else {
    testfn()
  }
}

async function jestTestAwait (fn, testfn) {
  if (typeof process === 'undefined') {
    await fn()
  } else {
    await testfn()
  }
}

async function commands (command) {
  // "command" value is defined a la manifest.json
  // console.log(command)
  switch (command) {
    case 'Switch to default theme':
      try {
        console.log(`${command} was selected!`)
        // rm the log
        const c = await browser.storage.local.get('defaultTheme')
        const defaultTheme = c.defaultTheme
        await browser.storage.local.set({ currentId: defaultTheme.id })
        browser.management.setEnabled(defaultTheme.id, true)
        jestTestAwait(stopRotation, module.exports.stopRotation)
      } catch (error) {
        console.log(error.message)
      }
      break
    case 'Rotate to next theme':
      jestTest(rotate, module.exports.rotate)
      break
    case 'Toggle autoswitching':
      try {
        const c = await browser.storage.sync.get('auto')
        const auto = c.auto
        if (auto) {
          jestTestAwait(stopRotation, module.exports.stopRotation)
        } else {
          jestTestAwait(startRotation, module.exports.startRotation)
        }
        await browser.storage.sync.set({ auto: !auto })
      } catch (error) {
        console.log(error.message)
      }
      break
    case 'Change theme category':
      console.log(`${command} was selected!`)
      break
    default:
      console.log(`${command} not recognized`)
      break
  }
}

async function commandsHelper (command) {
  await commands(command)
}

browser.commands.onCommand.addListener(commandsHelper)

function buildToolsMenuItem (theme) {
  browser.menus.create({
    id: theme.id,
    type: 'normal',
    title: theme.name,
    contexts: ['tools_menu']
  })
}

async function buildToolsMenu (themes) {
  const info = await browser.runtime.getBrowserInfo()

  if (info.name === 'Thunderbird') {
    return
  }

  await browser.menus.removeAll()

  if (themes.userThemes.length !== 0) {
    for (const theme of themes.userThemes) {
      buildToolsMenuItem(theme)
    }

    browser.menus.create({
      type: 'separator',
      contexts: ['tools_menu']
    })
  }

  for (const theme of themes.defaultThemes) {
    buildToolsMenuItem(theme)
  }
}

function extensionInstalled (info) {
  if (info.type === 'theme') {
    buildThemesHelper()
  }
}
browser.management.onInstalled.addListener(extensionInstalled)
browser.management.onUninstalled.addListener(extensionInstalled)

browser.menus.onClicked.addListener((info) => {
  console.log(info)
  const currentId = info.menuItemId
  browser.storage.local.set({ currentId: currentId }).then(() => {
    browser.management.setEnabled(currentId, true)
  }).catch((err) => { console.log(err) })
})