import { route, getParam, setParams, getSuffix, updateSuffix } from './route-tools.js'
import processProducts from './process-products.js'
import { getFilters, updateFiltersWithCounts } from './filter-tools.js'
import ICON_CART from './icon-cart.svg'

const { h, register, matter, preactHooks, t } = globalThis.ami
const { useState, useLayoutEffect, useMemo, useRef } = preactHooks
const { Card, Box, Heading, Input, List, Button, Link } = matter

const LOAD_MORE_LIMIT = 12
const BASE_SEARCH_FIELDS = ['name', 'description']
const SEARCH_DEBOUNCE = 300
const SEARCH_PARAM = 'search' // Do not use `q` as it is one of the forwarded params in router.js.

function SalsifyProducts ({ src, filter1, filter2, 'disable-filters': disableFilters, columns = 4 }) {
  const areFiltersDisabled = disableFilters === 'true'
  const [state, setState] = useState({})

  // Update state based on the current URL.
  const updateState = (allProducts, allFilters) => {
    // Resolve selected product based on the URL suffix.
    const productId = getSuffix()
    const selectedProduct = productId ? allProducts.find(p => p.id === productId) : null

    // Apply search.
    let searchedProducts = allProducts
    const searchTerm = getParam(SEARCH_PARAM)
    if (searchTerm) {
      const searchableFields = [...BASE_SEARCH_FIELDS, ...allFilters.map(f => f.field)]
      searchedProducts = allProducts.filter(product => searchableFields.some(
        field => product[field]?.toLowerCase().includes(searchTerm.toLowerCase())
      ))
    }

    // Mark selected filters.
    const appliedFilters = []
    for (const filter of allFilters) {
      filter.selected = getParam(filter.field) || null
      if (filter.selected) {
        appliedFilters.push({ field: filter.field, value: filter.selected })
      }
    }

    updateFiltersWithCounts(searchedProducts, allFilters, appliedFilters)

    // Filter products.
    const filteredProducts = searchedProducts.filter(product =>
      appliedFilters.every(filter => !filter.value || product[filter.field] === filter.value)
    )

    // Limit products.
    const page = Number.parseInt(getParam('page'), 10) || 1
    const limit = page * LOAD_MORE_LIMIT
    const limitedProducts = filteredProducts.slice(0, limit)

    setState({
      products: limitedProducts,
      selectedProduct,
      filters: allFilters,
      searchTerm,
      hasMore: filteredProducts.length > limit
    })
  }

  useLayoutEffect(() => {
    let cleanup
    (async () => {
      const res = await fetch(src)
      const data = await res.json()
      const allProducts = processProducts(data)
      const allFilters = getFilters(allProducts, filter1, filter2)

      updateState(allProducts, allFilters)
      const handler = () => updateState(allProducts, allFilters)
      globalThis.addEventListener('popstate', handler)
      cleanup = () => globalThis.removeEventListener('popstate', handler)
    })().catch(console.error)
    return () => cleanup?.()
  }, [src, filter1, filter2])

  if (!state.products) {
    return null
  }

  const config = {
    areFiltersDisabled,
    filters: state.filters,
    hasMore: state.hasMore,
    searchTerm: state.searchTerm,
    columns: Number.parseInt(columns, 10)
  }

  return state.selectedProduct
    ? h(ProductDetails, { product: state.selectedProduct, config })
    : h(ProductList, { products: state.products, config })
}

function ProductList ({ products, config }) {
  const listId = useMemo(() => `_${crypto.randomUUID()}`, [])
  const searchTimeoutRef = useRef(null)
  const hasFilters = config.filters.length > 0 && !config.areFiltersDisabled

  return h('div', { className: 'grid grid-cols-1 gap-5 animate-fade' },
    hasFilters && h('div',
      h(Box, { className: 'p-6 col-span-3 bg-bga text-txa rounded-2xl' },
        h('div', { className: 'flex gap-5' },
          h('label', { className: 'flex-1' },
            h('span', { className: 'font-bold' }, t('Search')),
            h(Input, {
              'aria-controls': listId,
              type: 'search',
              name: t('Search'),
              value: config.searchTerm || '',
              onInput: event => {
                // Capture primitive values synchronously. Do NOT rely on the
                // event object inside async callbacks — it may be retargeted
                // (shadow DOM) or otherwise unavailable later.
                const query = event.target?.value ?? ''

                // Avoid flooding updates when user is typing.
                clearTimeout(searchTimeoutRef.current)
                searchTimeoutRef.current = setTimeout(() => {
                  // Use captured `query` instead of `event.target` here.
                  const params = query ? { [SEARCH_PARAM]: query, page: null } : { [SEARCH_PARAM]: null, page: null }
                  setParams(params)
                }, SEARCH_DEBOUNCE)
              }
            })
          ),
          config.filters.map(filter => h(Filter, { ariaControls: listId, filter }))
        ),
        h('p',
          h(Link, {
            'aria-controls': listId,
            _variant: 'underline',
            _model: { name: t('Reset filters'), value: '#' },
            onClick: e => {
              e.preventDefault()
              history.pushState({}, '', location.pathname)
              globalThis.dispatchEvent(new Event('popstate'))
            }
          })
        )
      )
    ),
    h('div', { id: listId, 'aria-live': 'polite' },
      products.length === 0 && h('div', { className: 'text-center' }, t('No results found.')),
      products.length > 0 && h('div', {
        className: `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-${config.columns} gap-4`,
        children: products.map(product =>
          h('div', { className: 'animate-fade' },
            h(Card, {
              id: `_${product.id}`,
              _model: { value: updateSuffix(product.id) },
              children: [
                h(Box,
                  h('div', { className: 'rounded-lg overflow-hidden p-6 border-1 border-txa/10', style: 'background: white' },
                    h(ProductImage, { product })
                  ),
                  h(Heading, { _level: 3 }, product.name),
                  h(Labels, { product, config })
                )
              ]
            })
          )
        )
      })
    ),
    config.hasMore && h('div', { className: 'flex justify-center' },
      h(Button, {
        'aria-controls': listId,
        className: 'w-auto',
        onClick: () => {
          const page = Number.parseInt(getParam('page'), 10) || 1
          setParams({ page: page + 1 })
        },
        _model: {
          name: t('Load more'),
        }
      })
    )
  )
}

function ProductDetails ({ product, config }) {
  useLayoutEffect(() => {
    window.scrollTo(0, 0)
  }, [])

  return h('div', {
    className: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 animate-fade',
    itemtype: 'https://schema.org/Product',
    itemscope: '',
    children: [
      h(Box, { className: 'lg:col-span-3 md:col-span-2 p-6 rounded-2xl bg-bga text-txa' },
        h(Link, {
          onClick: event => {
            event.preventDefault()
            route(event.currentTarget.href)
            dispatchEvent(new Event('hashchange')) // Force hash jump.
          },
          _variant: 'simple',
          _model: { name: t('View product list'), value: updateSuffix(null) },
        }),
        h(Heading, { _level: 2, itemprop: 'name' }, product.name)
      ),
      h('div', { className: 'flex flex-col gap-5' },
        h('div', { className: 'rounded-2xl overflow-hidden' },
          h(ProductImage, { product })
        ),
        h(StoreLinks, { product })
      ),
      h(Box, { className: 'lg:col-span-2 bg-bga text-txa rounded-2xl', itemprop: 'description' },
        product.description && [
          h(Heading, { _level: 3 }, t('Description')),
          h('p', product.description),
        ],
        h(Labels, { product, config }),
        product.features.length > 0 && [
          h(Heading, { _level: 3 }, t('Features')),
          h(List, product.features.map(f =>
            h('li', f)
          ))
        ]
      )
    ]
  })
}

function ProductImage ({ product }) {
  return h('div', { className: 'aspect-square' },
    h('img', {
      itemprop: 'image',
      className: 'block object-contain h-full',
      alt: product.name,
      src: product.image
    })
  )
}

function Filter ({ ariaControls, filter }) {
  return h('label', { className: 'flex-1' },
    h('span', { className: 'font-bold' }, filter.name),
    h(Input, {
      defaultValue: filter.selected || '',
      selectedValue: filter.selected,
      'aria-controls': ariaControls,
      type: 'select',
      onChange: event => {
        setParams({ [filter.field]: event.target.value, page: null })
      },
      children: [
        h('option', { value: '' }, ''),
        filter.options.map(o => h('option', { value: o.value, selected: o.value === filter.selected }, o.name))
      ]
    })
  )
}

function Labels ({ product, config }) {
  return [
    h('hr', { className: 'mb-2 w-6 h-1 bg-bgb' }),
    h('div', { className: 'space-y-1' },
      config.filters.map(filter => h('dl', { className: 'text-sm flex gap-2' },
        h('dt', { className: 'font-bold basis-24 grow-0 shrink-0' }, filter.name),
        h('dd', product[filter.field])
      ))
    )
  ]
}

function StoreLinks ({ product }) {
  return product.links.length > 0 && h(Box, { className: 'p-6 text-txa bg-bga rounded-xl' },
    h(Heading, { _level: 3 }, t('Buy now')),
    product.links.map(link =>
      h(Button, { _wide: true, _model: { ...link, icon: ICON_CART } })
    )
  )
}

register(SalsifyProducts, 'salsify-products')
