/* global location, history */
import API from './api.js'
import ICON_NAVIGATION_SEARCH from './icon-search.svg'
import updateHead from '#amidala/main/shared/update-head.js'
import idFromString from '#amidala/main/shared/id-from-string.js'

const { h, preactHooks, matter, register } = globalThis.ami
const { useLayoutEffect, useState } = preactHooks
const {
  AccordionItem, Asset, Box, Breadcrumb, Button,
  Container,
  Form, Heading, Input, Link,
  RichText,
  Tabs, Tags
} = matter

const CATEGORY_ARTICLE_COUNT = 3
const SUFFIX_PREFIX = '/help-centre/'
const DEFAULT_PROPS = {
  locale: 'en-gb',
  'api-url': 'https://support.acinguiux.com/api/v2/help_center'
}

// -----------------------------------------------------------------------------
// Main web component.
// -----------------------------------------------------------------------------

function HelpCentre (props) {
  const {
    'image-url': imageUrl,
    locale,
    'api-url': apiUrl,
    'vote-url': voteUrl,
    'vote-creds': voteCreds,
    'dictionary-url': dictionaryUrl,
    variant
  } = { ...DEFAULT_PROPS, ...props }

  // Initialise state.
  const [state, setState] = useState(() => ({
    view: null,
    id: null,
    api: new API({ apiUrl, voteUrl, voteCreds, dictionaryUrl, imageUrl, locale })
  }))

  // Main routing function.
  const route = () => {
    const [view, id] = getViewAndId(state.api)
    setState({ ...state, view, id })
    updateHead('link', { rel: 'canonical' }, { href: location.href.split('#')[0] })

    return [view, id]
  }

  useLayoutEffect(() => {
    // Display preloader as soon as the dictionary is ready.
    state.api.onDictionaryReady(() => setState(currState => currState.view ? currState : { ...currState, view: 'loading' }))

    ;(async () => {
      // Initialise API.
      await state.api.init()

      // Initialise routing.
      globalThis.addEventListener('popstate', route)

      // Set initial route.
      const [view] = route()

      // Force default route if the current route is invalid.
      if (!view) {
        const categories = state.api.getCategories({})
        const href = getSuffix('category', categories[0].id, categories[0].name)
        navigate(href)
      }
    })()
  }, [])

  const dispatcher = {
    loading: () => h(Box, { className: 'text-center' }, state.api.labels.loading),
    category: () => h(CategoryView, { api: state.api, categoryId: state.id, isCompact: variant === 'compact' }),
    section: () => h(SectionView, { api: state.api, sectionId: state.id }),
    article: () => h(SectionView, { api: state.api, articleId: state.id }),
    search: () => h(SearchView, { api: state.api, query: state.id })
  }

  return dispatcher[state.view]?.()
}

register(HelpCentre, 'help-centre')

// -----------------------------------------------------------------------------
// View components.
// -----------------------------------------------------------------------------

function CategoryView ({ api, categoryId, isCompact }) {
  const tabLinks = api.getCategories({}).map(category => ({
    name: category.name,
    value: getSuffix('category', category.id, category.name),
    selected: category.id === categoryId
  }))

  return h('div', { className: 'flex flex-col gap-5' },
    h(SearchHeader, { api }),
    h(Box, { className: 'text-center' },
      h(Heading, { _level: 2 }, api.labels.categoryListHeading)
    ),
    h(Tabs, { _model: { links: tabLinks }, _variant: isCompact ? 'compact' : undefined }),
    h(Grid, { className: 'grid-cols-3 md:grid-cols-2 sm:grid-cols-1 animate-fade' }, api.getSections({ categoryId }).map(section =>
      h(Box, { className: 'bg-bga text-txa rounded-xl flex flex-col' },
        h(Heading, { _level: 3 }, section.name),
        h('ul', { className: 'grow' }, api.getArticles({ sectionId: section.id }).slice(0, CATEGORY_ARTICLE_COUNT).map(article =>
          h('li',
            h(Link, {
              _variant: 'simple',
              _model: {
                name: article.name,
                value: getSuffix('section', section.id, section.name, article.name)
              }
            })
          )
        )),
        h(Link, {
          _variant: 'full',
          _model: {
            name: api.labels.viewAllQuestions.replace('[section]', section.name),
            value: getSuffix('section', section.id, section.name)
          }
        })
      ))
    )
  )
}

function SectionView ({ api, sectionId, articleId }) {
  if (articleId) {
    sectionId = api.getArticles({ articleId }).section_id
  }

  const section = api.getSections({ sectionId })[0]
  const category = api.getCategories({ categoryId: section.category_id })[0]
  const articles = api.getArticles({ sectionId })
  const firstCategory = api.getCategories({})[0]

  return h(Grid,
    h(Breadcrumb, {
      _model: {
        links: [{
          name: api.labels.home,
          value: getSuffix('category', firstCategory.id, firstCategory.name)
        }, {
          name: category.name,
          value: getSuffix('category', category.id, category.name)
        }, {
          name: section.name,
          value: getSuffix('section', section.id, section.name)
        }]
      }
    }),
    h(Box, { className: 'rounded-xl bg-bga text-txa' },
      h(Heading, { _level: 2 }, section.name),
      h(SearchForm, { api })
    ),
    h(Container, { _variant: 'list' }, articles.map(article =>
      h(AccordionItem, {
        _variant: 'faq',
        _model: {
          title: article.title
        },
        children: h(Box,
          h(RichText, article.body),
          h(Voting, { api, articleId: article.id })
        )
      })
    )),
    h(Box, { className: 'text-txa bg-bga rounded-xl' },
      h(Heading, { _level: 3 }, api.labels.otherSectionsHeading.replace('[category]', category.name)),
      h(Tags, {
        _model: {
          tags: api
            .getSections({ categoryId: section.category_id })
            .filter(s => s.id !== sectionId)
            .map(s => ({
              name: s.name,
              value: getSuffix('section', s.id, s.name)
            }))
        }
      })
    )
  )
}

function SearchView ({ api, query }) {
  const [state, setState] = useState({
    results: [],
    nextPage: 0
  })

  useLayoutEffect(() => {
    (async () => {
      const [results, nextPage] = await api.search(query, 1)
      setState({ results, nextPage })
    })()
  }, [query])

  const firstCategory = api.getCategories({})[0]
  return h(Grid,
    h(Breadcrumb, {
      _model: {
        links: [{
          name: api.labels.home,
          value: getSuffix('category', firstCategory.id, firstCategory.name)
        }, {
          name: api.labels.searchBreadcrumb,
          value: getSuffix('search', query)
        }]
      }
    }),
    h(SearchHeader, { api, query }),
    // Search results.
    state.results.length === 0 && h(Box, api.labels.searchNoResults),
    h(Grid, { className: 'grid gap-5 lg:grid-cols-2' }, state.results.map(result =>
      h(Box, { className: 'bg-bga text-txa rounded-xl animate-fade' },
        h(Heading, { _level: 3 },
          h(Link, {
            _variant: 'full',
            _model: {
              name: result.name,
              value: getSuffix('section', result.section_id, null, result.name)
            }
          })
        ),
        h(RichText, result.body)
      ))
    ),
    // Load more.
    state.nextPage > 0 && h('div', { className: 'flex items-center justify-center' },
      h(Button, {
        async onClick (event) {
          event.preventDefault()
          const [results, nextPage] = await api.search(query, state.nextPage)
          state.results.push(...results)
          setState({ results: state.results, nextPage })
        },
        _model: { name: api.labels.loadMore }
      })
    )
  )
}

// -----------------------------------------------------------------------------
// Helper components.
// -----------------------------------------------------------------------------

function Grid ({ children, className }) {
  return h('div', { className: `grid gap-5 ${className ?? ''}`, children })
}

function SearchHeader ({ api, query }) {
  return h('div', { className: 'bg-bga text-txa rounded-xl grid lg:grid-cols-4 overflow-hidden' },
    h(Box, { className: 'lg:col-span-3' },
      h(Heading, { _level: 2 }, api.labels.searchHeading),
      h('p', api.labels.searchDescription),
      h(SearchForm, { api, query })
    ),
    api.imageUrl && h('div', { className: 'sm:hidden md:hidden' },
      h(Asset, {
        _tall: true,
        _model: { image: { src: api.imageUrl, alt: api.labels.searchHeading, width: 1600, height: 900 } }
      })
    )
  )
}

function onSubmit (event) {
  event.preventDefault()
  navigate(getSuffix('search', event.target[0].value))
}

function SearchForm ({ api, query }) {
  return h(Form, { onSubmit },
    h('div', { className: 'flex gap-5' },
      h('div', { className: 'grow' },
        h(Input, {
          type: 'search',
          value: query ? decodeURIComponent(query) : null,
          placeholder: api.labels.searchPlaceholder
        })
      ),
      h('div', { className: 'grow-0' },
        h(Button, {
          _textless: true,
          _model: { name: 'submit', icon: ICON_NAVIGATION_SEARCH }
        })
      )
    )
  )
}

function Voting ({ api, articleId }) {
  if (!api.voteUrl) {
    return null
  }

  const article = api.getArticles({ articleId })[0]
  const yesVotes = (article.vote_count + article.vote_sum) / 2
  const [state, setState] = useState({
    yesVotes,
    voteCount: article.vote_count,
    voted: api.hasVoted(articleId),
    message: null
  })

  return h('div', { className: 'flex gap-5 flex-col' },
    h('p', api.labels.voteHelpful.replace('[a]', state.yesVotes).replace('[b]', state.voteCount)),
    state.voted || h('div', { className: 'flex gap-5 items-center' },
      h('strong', api.labels.voteIntro),
      h(Button, {
        _size: 'xs',
        _model: { name: api.labels.voteYes },
        onClick: () => {
          api.vote(articleId, true)
          setState({
            voted: true,
            yesVotes: state.yesVotes + 1,
            voteCount: state.voteCount + 1,
            message: api.labels.votePositive
          })
        }
      }),
      h(Button, {
        _size: 'xs',
        _model: { name: api.labels.voteNo },
        onClick: () => {
          api.vote(articleId)
          setState({
            voted: true,
            yesVotes,
            voteCount: state.voteCount + 1,
            message: api.labels.voteNegative
          })
        }
      })
    ),
    state.message && h('p', state.message)
  )
}

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

function getUrlFriendlyName (str) {
  return encodeURIComponent(str.replaceAll(/[^a-zA-Z0-9\-_.!~*\s]*/g, '').trim().replaceAll(/\s+/g, '-'))
}

function getViewAndId (api) {
  const suffix = location.pathname.split('.html')[1]

  if (suffix?.startsWith?.(SUFFIX_PREFIX)) {
    let [view, id] = suffix.split('/').slice(2)

    if (view === 'search') {
      return [view, id]
    }

    id = Number(id)

    if (view === 'category' && api.getCategories({ categoryId: id }).length > 0) {
      return [view, id]
    }

    if (view === 'section' && api.getSections({ sectionId: id }).length > 0) {
      return [view, id]
    }
  }

  return []
}

function getSuffix (view, id, name, question) {
  if (name) {
    name = getUrlFriendlyName(name)
  }

  const urlObj = new URL(location.href)
  const fullPathname = urlObj.pathname.endsWith('/') ? `${urlObj.pathname}index.html` : urlObj.pathname
  const currentSuffix = urlObj.suffix = fullPathname.split('.html')[1]
  const pathnameWithoutSuffix = fullPathname.replace(currentSuffix, '')

  const suffix = [view, id, name].filter(Boolean).join('/')
  urlObj.pathname = pathnameWithoutSuffix + SUFFIX_PREFIX + suffix
  urlObj.hash = question ? idFromString(question) : ''

  return urlObj.href
}

function navigate (newUrl) {
  history.replaceState({}, '', newUrl)
  globalThis.dispatchEvent(new Event('popstate'))
}
