// Utils
const { get, set, unset } = require('@vue/cli-shared-utils')
const { log } = require('../util/logger')

let answers = {}
let prompts = []

function generatePromptError (value) {
  let message
  if (typeof value === 'string') {
    message = value
  } else {
    message = 'Invalid input'
  }
  return {
    message
  }
}

async function getEnabled (value) {
  const type = typeof value
  if (type === 'function') {
    const result = await value(answers)
    return !!result
  } else if (type === 'boolean') {
    return value
  } else {
    return true
  }
}

function validateInput (prompt, value) {
  const validate = prompt.raw.validate
  if (typeof validate === 'function') {
    return validate(value, answers)
  }
  return true
}

function getFilteredValue (prompt, value) {
  const filter = prompt.raw.filter
  if (typeof filter === 'function') {
    return filter(value)
  }
  return value
}

function getTransformedValue (prompt, value) {
  const transformer = prompt.raw.transformer
  if (typeof transformer === 'function') {
    return transformer(value, answers)
  }
  return value
}

function generatePromptChoice (prompt, data, defaultValue) {
  return {
    value: JSON.stringify(getTransformedValue(prompt, data.value)),
    name: data.name,
    checked: data.checked,
    disabled: data.disabled,
    isDefault: data.value === defaultValue
  }
}

async function getChoices (prompt) {
  const data = prompt.raw.choices
  if (!data) {
    return null
  }

  let result
  if (typeof data === 'function') {
    result = await data(answers)
  } else {
    result = data
  }
  let defaultValue
  if (prompt.type === 'list' || prompt.type === 'rawlist') {
    defaultValue = await getDefaultValue(prompt)
  }
  return result.map(
    item => generatePromptChoice(prompt, item, defaultValue)
  )
}

function setAnswer (id, value) {
  set(answers, id, value)
}

function removeAnswer (id) {
  unset(answers, id)
}

function generatePrompt (data) {
  return {
    id: data.name,
    type: data.type,
    visible: true,
    enabled: true,
    name: data.short || null,
    message: data.message,
    group: data.group || null,
    description: data.description || null,
    link: data.link || null,
    choices: null,
    value: null,
    valueChanged: false,
    error: null,
    tabId: data.tabId || null,
    raw: data
  }
}

async function updatePrompts () {
  for (const prompt of prompts) {
    const oldVisible = prompt.visible
    prompt.visible = await getEnabled(prompt.raw.when)

    prompt.choices = await getChoices(prompt)

    if (oldVisible !== prompt.visible && !prompt.visible) {
      removeAnswer(prompt.id)
      prompt.valueChanged = false
    } else if (prompt.visible && !prompt.valueChanged) {
      let value
      const answer = getAnswer(prompt.id)
      if (typeof answer !== 'undefined') {
        value = await getTransformedValue(prompt, answer)
      } else if (typeof prompt.raw.value !== 'undefined') {
        value = prompt.raw.value
      } else {
        value = await getDefaultValue(prompt)
      }
      prompt.rawValue = value
      prompt.value = JSON.stringify(value)
      const finalValue = await getFilteredValue(prompt, value)
      setAnswer(prompt.id, finalValue)
    }
  }

  log('Prompt answers', answers)
}

// Public API

async function setAnswers (newAnswers) {
  answers = newAnswers
  await updatePrompts()
}

async function changeAnswers (cb) {
  cb(answers)
  await updatePrompts()
}

function getAnswers () {
  return answers
}

function getAnswer (id) {
  return get(answers, id)
}

async function reset (answers = {}) {
  prompts = []
  await setAnswers(answers)
}

function list () {
  return prompts
}

function add (data) {
  const prompt = generatePrompt(data)
  prompts.push(prompt)
  return prompt
}

async function start () {
  await updatePrompts()
}

function remove (id) {
  const index = prompts.findIndex(p => p.id === id)
  index !== -1 && prompts.splice(index, 1)
}

async function setValue ({ id, value }) {
  const prompt = findOne(id)
  if (!prompt) {
    console.warn(`Prompt '${prompt}' not found`)
    return null
  }

  const validation = await validateInput(prompt, value)
  if (validation !== true) {
    prompt.error = generatePromptError(validation)
  } else {
    prompt.error = null
  }
  prompt.rawValue = value
  const finalValue = await getFilteredValue(prompt, value)
  prompt.value = JSON.stringify(value)
  prompt.valueChanged = true
  setAnswer(prompt.id, finalValue)
  await updatePrompts()
  return prompt
}

function findOne (id) {
  return prompts.find(
    p => p.id === id
  )
}

async function getDefaultValue (prompt) {
  let defaultValue = prompt.raw.default
  if (typeof defaultValue === 'function') {
    defaultValue = await defaultValue(answers)
  }

  if (prompt.type === 'checkbox') {
    const choices = prompt.raw.choices
    if (choices) {
      return choices.filter(
        c => c.checked
      ).map(
        c => c.value
      )
    }
  } else if (prompt.type === 'confirm') {
    if (prompt.raw.checked) return true
    return defaultValue || false
  }
  return defaultValue
}

async function answerPrompt ({ id, value }, context) {
  await setValue({ id, value: JSON.parse(value) })
  return list()
}

module.exports = {
  setAnswers,
  changeAnswers,
  getAnswers,
  getAnswer,
  reset,
  list,
  add,
  remove,
  start,
  setValue,
  findOne,
  getDefaultValue,
  answerPrompt
}
