/* global process, DOMParser, alert, FileReader */
import { Address } from './fields/address.js'
import { Checkbox } from './fields/checkbox.js'
import { DateTime } from './fields/date.js'
import { Description } from './fields/description.js'
import { Input } from './fields/input.js'
import { Name } from './fields/name.js'
import { Radio } from './fields/radio.js'
import { Rating } from './fields/rating.js'
import { Section } from './fields/section.js'
import { Select } from './fields/select.js'
import { Textarea } from './fields/textarea.js'

const { h, register, preactHooks, matter } = globalThis.ami
const { useState, useEffect } = preactHooks
const { Box, Button, Icon, RichText, Form } = matter

const ICON_SUCCESS = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM6.35355 13.3535L9.64645 16.6464C9.84171 16.8417 10.1583 16.8417 10.3536 16.6464L17.6477 9.35229C17.8425 9.15752 17.843 8.8419 17.6489 8.64644L16.9435 7.93604C16.7485 7.7396 16.4309 7.73904 16.2352 7.93479L10 14.17L7.76355 11.9422C7.56818 11.7476 7.25212 11.7479 7.05713 11.9429L6.35355 12.6464C6.15829 12.8417 6.15829 13.1583 6.35355 13.3535Z" fill="#343434"/>
</svg>
`

const CAPTCHA_CONTAINER_ID = 'captcha-container'
const SUCCESS_CODE = 200
const DEFAULTS = {
  FUNCTION_URL: process.env.FORMSTACK_FUNCTION_URL,
  CAPTCHA_SITE_KEY: process.env.CAPTCHA_SITE_KEY,
  SUCCESS_TEXT: 'Thank you. The form was submitted successfully.',
  ERROR_TEXT: 'There was an error submitting the form. Please try again.',
  CAPTCHA_POLICY_TEXT: `
    This site is protected by reCAPTCHA and the Google
    <a href="https://policies.google.com/privacy">Privacy Policy</a> and
    <a href="https://policies.google.com/terms">Terms of Service</a> apply.
  `
}

// -----------------------------------------------------------------------------
// Helper functions
// -----------------------------------------------------------------------------

function getStep (decimals) {
  if (decimals === 0) {
    return 'any'
  }

  if (!decimals) {
    return undefined
  }

  return `.${Array.from({ length: decimals - 1 }, () => 0).join('')}1`
}

function getFieldType (field) {
  return { phone: 'tel', name: 'text', address: 'text' }[field.type] ?? field.type
}

async function blobToBase64 (blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(reader.result.split(',')[1])
    reader.onerror = reject
    reader.readAsDataURL(blob)
  })
}

async function getQueryString (formElement, captchaToken) {
  const formData = new FormData(formElement)

  // Convert blobs to base64.
  for (const [fieldName, fieldValue] of formData) {
    if (fieldValue instanceof Blob) {
      const contents = await blobToBase64(fieldValue)
      formData.set(fieldName, `${fieldValue.name};${contents}`)
    }
  }

  // Add recaptcha token to the form data.
  formData.append('g-recaptcha-response', captchaToken)

  // Avoid encoding of square brackets in field names to match the Formstack API.
  return `${new URLSearchParams(formData)}`
    .split('&')
    .map(par => {
      const [name, value] = par.split('=')
      return `${name.replaceAll('%5B', '[').replaceAll('%5D', ']')}=${value}`
    })
    .join('&')
}

function updateFormData (form) {
  const dp = new DOMParser()
  const doc = dp.parseFromString(form.html, 'text/html')
  const formElement = doc.querySelector('form')

  for (const field of form.fields) {
    const fieldName = `field_${field.id}`
    field.attrs = {
      id: fieldName,
      name: fieldName,
      type: getFieldType(field),
      size: field.size,
      minlength: field.minlength || null,
      maxlength: field.maxlength || null,
      required: (field.required === '1' && field.hidden !== '1') || null,
      disabled: field.readonly === '1' || null,
      value: formElement.value ?? field.default ?? null,
      placeholder: field.placeholder,
      min: field.min_value,
      max: field.max_value,
      step: getStep(Number(field.decimals))
    }

    // Get default submit value.
    form.submit_button_title ||= formElement.querySelector('input[type="submit"]').value

    field.subfields = field?.visible_subfields?.reduce((obj, subfieldName) => {
      // Extract subfield label.
      obj[subfieldName] = {
        label: formElement.querySelector(`label.fsSupporting[for="field${field.id}-${subfieldName}"]`).textContent
      }

      // Extract subfield options.
      const options = formElement.querySelectorAll(`#field${field.id}-${subfieldName} option`)
      if (options.length > 0) {
        obj[subfieldName].options = [...options].map(option => ({
          name: option.textContent,
          value: option.value
        }))
      }

      return obj
    }, {})
  }
}

function initCaptcha (sitekey) {
  // Do not run if captcha has already been initialised or is disabled.
  if (!sitekey || document.getElementById(CAPTCHA_CONTAINER_ID)) {
    return
  }

  // Create a hidden global container for the captcha script and widget.
  // Recaptcha does not work in Shadow DOM.
  const captchaContainer = el('div', { id: CAPTCHA_CONTAINER_ID, style: 'display: none' }, [
    el('script', { src: 'https://www.google.com/recaptcha/api.js?render=explicit&onload=captchaCallback' }),
    el('div')
  ])

  // Callback to initialise the captcha widget once the API script is loaded.
  globalThis.captchaCallback = () =>
    globalThis.grecaptcha.render(captchaContainer.lastChild, { sitekey, badge: 'inline', size: 'invisible' })

  document.body.appendChild(captchaContainer)
}

function el (tag, attrs, children) {
  const element = document.createElement(tag)
  Object.assign(element, attrs)
  children && element.append(...children)
  return element
}

// -----------------------------------------------------------------------------
// Main components
// -----------------------------------------------------------------------------

export function FormstackForm (props) {
  const {
    'form-id': formId,
    'success-text': successText = DEFAULTS.SUCCESS_TEXT,
    'error-text': errorText = DEFAULTS.ERROR_TEXT,
    'function-url': functionUrl = DEFAULTS.FUNCTION_URL,
    'captcha-site-key': captchaSiteKey = DEFAULTS.CAPTCHA_SITE_KEY,
    'captcha-policy-text': captchaPolicyText = DEFAULTS.CAPTCHA_POLICY_TEXT,
  } = props

  if (!formId) {
    console.error('Missing form ID.')
    return null
  }

  const getFormUrl = `${functionUrl}/api/get-form/${formId}`
  const submitFormUrl = `${functionUrl}/api/submit-form/${formId}`

  const [form, setForm] = useState(null)
  const [success, setSuccess] = useState(false)

  useEffect(() => {
    initCaptcha(captchaSiteKey)
    fetch(getFormUrl).then(res => res.json()).then(formData => {
      updateFormData(formData)
      setForm(formData)
    })
  }, [formId])

  if (!form) {
    return null
  }

  if (success) {
    return h('div', { className: 'bg-bga text-txa rounded-2xl overflow-hidden', ariaLive: 'polite' },
      h(Box,
        h('div', { className: 'flex items-center justify-center flex-col space-y-2' },
          h(Icon, ICON_SUCCESS),
          h('span', { className: 'text-lg' }, successText)
        )
      )
    )
  }

  async function submitForm (formElement, captchaToken) {
    const res = await fetch(submitFormUrl, {
      method: 'POST',
      body: await getQueryString(formElement, captchaToken)
    })

    if (res.status === SUCCESS_CODE) {
      setSuccess(true)
    } else {
      // Show alert in case of failure. This prevents the form from being wiped out.
      alert(errorText)
    }
  }

  return h(Form, {
    name: form.name,
    async onSubmit (event) {
      const formElement = event.target
      event.preventDefault()
      // Prevent submission if recaptcha is not loaded.
      if (!globalThis.grecaptcha) {
        alert(errorText)
        return
      }

      // Get the recaptcha token and submit the form.
      globalThis.grecaptcha.ready(async () => {
        const token = await globalThis.grecaptcha.execute()
        await submitForm(formElement, token)
      })
    },
    children: h(Box, { className: 'text-txa bg-bga rounded-2xl animate-fade' },
      form.fields.map(field => h(Field, { field })),
      h(Button, {
        type: 'submit',
        _model: { name: form.submit_button_title }
      }),
      h('div', { className: 'text-sm' },
        h(RichText, captchaPolicyText)
      )
    )
  })
}

function Field ({ field }) {
  const fieldMap = [
    { types: ['section'], field: Section },
    { types: ['text', 'email', 'phone', 'number', 'file'], field: Input },
    { types: ['textarea'], field: Textarea },
    { types: ['datetime'], field: DateTime },
    { types: ['select'], field: Select },
    { types: ['radio'], field: Radio },
    { types: ['checkbox'], field: Checkbox },
    { types: ['name'], field: Name },
    { types: ['rating'], field: Rating },
    { types: ['address'], field: Address },
    { types: ['richtext'], field: Description }
  ]

  const ResolvedField = fieldMap.find(entry => entry.types.includes(field.type))?.field
  if (!ResolvedField) {
    return null
  }

  const resolvedFieldComponent = h(ResolvedField, { field })
  return field.hidden === '1' ? h('div', { className: 'hidden' }, resolvedFieldComponent) : resolvedFieldComponent
}

register(FormstackForm, 'formstack-form')
