import React, { useCallback, useMemo } from 'react'
import classNames from 'classnames'
import format from 'date-fns/format'
import { BsArrowRightShort, BsMap, BsArrowCounterclockwise } from 'react-icons/bs'

import { Heading } from './Typography'
import { Button, PrimaryPillButton, SecondaryPillButton } from './Button'
import Card from './Card'
import Select from './Select'
import Spinner from './Spinner'
import FadeTransition from './FadeTransition'
import LoadingOverlay from './LoadingOverlay'

import { InjectionContext, useInjection } from '../components'
import { useMobile } from '../hooks/useMediaQuery'
import useChartData from '../hooks/useChartData'
import useMapData from '../hooks/useMapData'
import useTileData from '../hooks/useTileData'
import useDates from '../hooks/useDates'
import useMobileView from '../hooks/useMobileView'
import useLineageFilter from '../hooks/useLineageFilter'
import useChartZoom from '../hooks/useChartZoom'
import useLocationSearch from '../hooks/useLocationSearch'

import { ConfigContext } from '../config'

export const UI = ({
  api,
  data = { lineages: [] }, // legacy API, prefer to pass lineages directly
  config,
  darkMode,
  lastModified,
  lineages = data.lineages,
  lineColor = 'blueGray',
  tiles
}) => {
  const [{
    Chloropleth,
    DateFilter,
    FilterSection,
    LineageFilter,
    LocalIncidence,
    LocationFilter,
    MapView,
    StickyMobileSection
  }, injectProps] = useInjection()

  const [chartDataState, chartDataActions] = useChartData(api, lineages)
  const [mapDataState, mapDataActions, results] = useMapData(api, config.map.settings, lineages)
  const [
    { date, playing },
    { setDate, setPlaying, persistDate }
  ] = useDates(results ? results.dates : [], config.timeline)

  const handleOnClick = useCallback((area_id) => {
    chartDataActions.load(area_id)
  }, [chartDataActions.load])

  const parameter_options = config.parameters.map((x) => <option key={x.id} value={x.id}>{x.display}</option>)

  const isMobile = useMobile()
  const [mobileView, setMobileView] = useMobileView(isMobile)

  const { normalisedTiles, tileIndex } = useTileData(tiles, results, config.ontology)
  const locationSearch = useLocationSearch(tileIndex, config.area_search_terms)

  const isInitialLoad = useMemo(() => (
    mapDataState.lineage === null || chartDataState.area === null
  ), [mapDataState.lineage, chartDataState.area])

  const areaName = useMemo(() => (
    chartDataState.area === 'overview'
      ? config.ontology.overview.heading
      : chartDataState.area in tileIndex
        ? tileIndex[chartDataState.area].area_name
        : undefined
  ), [chartDataState.area, tileIndex])

  const locationFilter = useMemo(() => {
    const { ontology } = config

    const props = {
      loading: isInitialLoad ||
        (chartDataState.status === 'LOADING' &&
          (isMobile || chartDataState.loading.area !== chartDataState.area)) ||
        locationSearch.isLoading,
      onChange: chartDataActions.load,
      value: chartDataState.area,
      overview: ontology.overview
    }

    if (chartDataState.area === 'overview') {
      return {
        ...props,
        category: ontology.overview.category,
        heading: areaName,
        subheading: (
          <span className='flex items-center text-subheading'>
            Explore {ontology.area.noun_plural} {
            isMobile
              ? <button onClick={() => setMobileView('map')} className='px-1 underline text-primary font-medium'>on the map</button>
              : 'on the map'
            }
          </span>
        )
      }
    }
    const {
      area_description = chartDataState.area
    } = tileIndex[chartDataState.area] || {}
    return {
      ...props,
      category: ontology.area.category,
      heading: areaName,
      subheading: area_description,
      showOverviewButton: chartDataState.loadingArea !== 'overview',
      loadOverview: () => chartDataActions.load('overview'),
      ...injectProps.LocationFilter
    }
  }, [chartDataState, isMobile, tileIndex.overview, isInitialLoad, locationSearch.isLoading, injectProps.LocationFilter])

  const { timeline } = config
  const formattedDate = useMemo(
    () => date ? format(new Date(date), timeline.date_format.heading) : '',
    [date]
  )

  const dateFilter = {
    loading: isInitialLoad,
    label: config.timeline.label,
    dates: results ? results.dates : null,
    heading: formattedDate,
    value: date,
    onChange: (e) => {
      const { value } = e.target
      const set_to = results.dates[value]
      setDate(set_to)
    },
    playing: playing,
    setPlaying: setPlaying,
    persistDate: (e) => {
      const { value } = e.target
      const set_to = results.dates[value]
      persistDate(set_to)
    },
    ...injectProps.DateFilter
  }

  const lineageFilter = {
    ...useLineageFilter(lineages, chartDataState.lineages, config, darkMode),
    isMobile,
    ...injectProps.LineageFilter
  }

  const formattedLastModified = useMemo(
    () => lastModified ? format(new Date(lastModified), config.datetime_format) : '',
    [lastModified]
  )

  const mapValues = useMemo(() => {
    const values = {}
    if (results === null) return values
    for (const { area, lookup } of results.values) {
      values[area] = lookup[date]
    }
    return values
  }, [results, date])

  const chartZoom = useChartZoom()
  const { dateRange, clearChartZoom, zoomEnabled, setZoomEnabled } = chartZoom

  const { activeLineages, sortedLineages } = lineageFilter
  const selectedLineage = mapDataState.loading.lineage || mapDataState.lineage

  const mapParameterConfig = useMemo(() => {
    const param = config.parameters.find(_ => _.id === mapDataState.colorBy)
    if (param) {
      return {
        format: param.format || mapDataState.colorBy === 'p' ? 'percentage' : undefined,
        precision: param.precision
      }
    }
    return undefined
  }, [mapDataState.colorBy])

  const fadeUncertaintyEnabled = useMemo(() => {
    const { fade_uncertainty = {} } = config.map
    return mapDataState.colorBy in fade_uncertainty ? fade_uncertainty[mapDataState.colorBy] : undefined
  }, [mapDataState.colorBy])

  return (
    <>
      { isMobile && lastModified &&
        <p className='text-xs tracking-wide leading-6 text-center text-heading'>
          Data updated <span className='font-medium'>{formattedLastModified}</span>
        </p> }
      { mobileView === 'chart' &&
        <div className='bg-white dark:bg-gray-700 px-4 pt-3 relative z-10'>
          <LocationFilter
            className='h-20'
            {...locationFilter}
            {...locationSearch}
          />
        </div> }
      { !isMobile &&
        <FilterSection className='-mt-header-overlap max-w-full mx-auto' loading={isInitialLoad} {...injectProps.FilterSection}>
          <Card className='w-80 box-content flex-shrink-0'>
            <DateFilter {...dateFilter} />
          </Card>
          <Card className='w-80 box-content flex-shrink-0 relative'>
            <LocationFilter {...locationFilter} {...locationSearch} />
          </Card>
          <Card className='box-content flex-shrink-0 xl:flex-shrink md:pb-0'>
            <LineageFilter className='h-full flex flex-col md:-mb-3' {...lineageFilter} />
          </Card>
        </FilterSection> }
      <Card className='relative flex-grow flex flex-col md:grid md:grid-cols-2 md:grid-rows-1-full md:gap-6 pt-3 pb-0' extraPadding>
        <MapView
          isHidden={mobileView === 'chart'}
          heading={
            <div className='h-8 md:h-auto flex justify-between items-center'>
              <Heading>Map</Heading>
              { isMobile && !isInitialLoad &&
                <div className='flex items-center max-w-none min-w-0'>
                  <div className='w-12 flex justify-center'>
                    <FadeTransition in={chartDataState.status === 'LOADING'}>
                      <Spinner className='block h-4 w-4 text-gray-500 dark:text-gray-200' />
                    </FadeTransition>
                  </div>
                  <PrimaryPillButton
                    className='flex items-center space-x-1 min-w-0 h-8 pr-2'
                    onClick={() => setMobileView('chart')}
                  >
                    <span className='truncate'>{locationFilter.heading}</span>
                    <BsArrowRightShort className='w-6 h-6 flex-shrink-0' />
                  </PrimaryPillButton>
                </div> }
            </div>
          }
          {...injectProps.MapView}
        >
          <form className={classNames(
            'grid grid-cols-3 gap-3 lg:flex lg:gap-0 lg:space-x-3 text-sm pb-3 mt-2 md:mt-3 transition-opacity',
            { 'opacity-50 pointer-events-none': mapDataState.status === 'LOADING' && !isInitialLoad }
          )}>
            <div className='lg:max-w-max lg:w-48'>
              <label className='block font-medium mb-1'>
                Lineage
              </label>
              <Select
                value={selectedLineage || ''}
                name='lineages'
                onChange={e => mapDataActions.setLineage(e.target.value)}
              >
                {sortedLineages.map(({ lineage, label }) =>
                  <option key={lineage} value={lineage}>
                    {label}
                  </option>
                )}
              </Select>
            </div>
            <div>
              <label className='block font-medium mb-1'>
                Colour by
              </label>
              <Select
                value={mapDataState.loading.colorBy || mapDataState.colorBy}
                name='parameters'
                onChange={e => mapDataActions.colorBy(e.target.value)}
              >
                {parameter_options}
              </Select>
            </div>
            { mapDataState.scale !== undefined &&
              <div>
                <label className='block font-medium mb-1'>
                  Colour Scale
                </label>
                <Select
                  value={mapDataState.scale || ''}
                  name='color_scale_type'
                  onChange={e => mapDataActions.setScale(e.target.value)}
                >
                  <option value='linear'>Linear</option>
                  <option value='quadratic'>Quadratic</option>
                </Select>
              </div> }
          </form>
          <LoadingOverlay
            className='flex-grow -mx-3 md:m-0 flex flex-col md:rounded-md overflow-hidden'
            loading={mapDataState.status === 'LOADING' && (isMobile || !isInitialLoad)}
          >
            <Chloropleth
              className='flex-grow'
              color_scale_type={mapDataState.colorBy === 'R' ? 'R_scale' : mapDataState.scale}
              config={config.map.viewport}
              darkMode={darkMode}
              enable_fade_uncertainty={fadeUncertaintyEnabled}
              geojson={normalisedTiles}
              handleOnClick={handleOnClick}
              isMobile={isMobile}
              lineColor={lineColor}
              max_val={results ? results.max : 0}
              min_val={results ? results.min : 0}
              parameterConfig={mapParameterConfig}
              selected_area={chartDataState.loadingArea || chartDataState.area}
              values={mapValues}
              {...injectProps.Chloropleth}
            />
            <div className='absolute inset-0 z-10 shadow-inner pointer-events-none' style={{ borderRadius: 'inherit' }} />
          </LoadingOverlay>
        </MapView>
        <div className={classNames('flex-grow flex flex-col relative', { hidden: mobileView === 'map' || (isMobile && locationSearch.isSearching) })}>
          { !isMobile &&
            <FadeTransition in={!!dateRange}>
              <div className='absolute left-0 right-0 -top-6 h-0 flex'>
                <Button
                  onClick={clearChartZoom}
                  className='ml-auto mr-4 -mt-1.5 h-6 pl-1.5 pr-1.5 flex items-center text-primary hover:bg-gray-50 border-gray-300 dark:border-gray-500'
                >
                  <BsArrowCounterclockwise className='h-4 w-4 mr-1' />
                  <span className='text-xs tracking-wide font-medium'>Reset date range</span>
                </Button>
              </div>
            </FadeTransition> }
          <LocalIncidence
            className={classNames(
              'transition-opacity flex-grow', {
                'delay-1000 opacity-50 pointer-events-none': chartDataState.status === 'LOADING' && !isInitialLoad
              }
            )}
            activeLineages={activeLineages}
            areaName={areaName}
            chartDefinitions={config.chart.definitions}
            chartZoom={chartZoom}
            date={date}
            darkMode={darkMode}
            isMobile={isMobile}
            lineColor={lineColor}
            selected_area={chartDataState.area}
            setDate={persistDate}
            values={chartDataState.data}
            zoomEnabled={isMobile ? zoomEnabled : true}
            {...injectProps.LocalIncidence}
          />
          { !isMobile && lastModified &&
            <div className='self-end mt-1 -mb-6 -mr-6 px-2 border-t border-l border-gray-200 dark:border-gray-500 rounded-tl-md h-6'>
              <p className='text-xs tracking-wide leading-6 text-heading'>
                Data updated <span className='font-medium'>{formattedLastModified}</span>
              </p>
            </div> }
        </div>
        { mobileView === 'chart' && !locationSearch.isSearching &&
          <StickyMobileSection className='overflow-x-hidden -mx-3 px-4 py-3' title='Lineages' {...injectProps.StickyMobileSection}>
            <LineageFilter {...lineageFilter} />
            <div className='grid items-center gap-3 grid-flow-col box-content mt-1 auto-cols-fr'>
              <PrimaryPillButton onClick={() => setMobileView('map')} className='flex items-center justify-center'>
                <BsMap className='h-5 w-5 mr-2 flex-shrink-0' />
                View map
              </PrimaryPillButton>
              { dateRange
                ? <SecondaryPillButton
                  onClick={clearChartZoom}
                  className='text-center'
                >
                  <span className='whitespace-nowrap truncate font-medium'>Reset date range</span>
                </SecondaryPillButton>
                : <SecondaryPillButton
                    onClick={() => setZoomEnabled(!zoomEnabled)}
                    className={classNames(
                      'flex justify-center',
                      { 'text-primary ring ring-primary dark:ring-gray-500 ring-opacity-40 border-primary dark:border-dark-primary': zoomEnabled }
                    )}
                  >
                    <span className='whitespace-nowrap font-medium'>
                      {zoomEnabled ? 'Select range on chart' : 'Set date range'}
                    </span>
                  </SecondaryPillButton> }
            </div>
          </StickyMobileSection> }
        <FadeTransition in={isInitialLoad}>
          <div className='bg-white dark:bg-gray-700 bg-opacity-50 dark:bg-opacity-50 absolute inset-0 md:rounded-md' />
        </FadeTransition>
      </Card>
      { mobileView === 'map' &&
        <DateFilter
          className='p-3 bg-white dark:bg-gray-700 shadow border-t border-gray-100 dark:border-gray-600 relative z-10'
          {...dateFilter}
        /> }
    </>
  )
}

const emptyInjection = {}
const InitializeUI = ({ injection = emptyInjection, ...props }) => {
  return (
    <ConfigContext.Provider value={props.config}>
      <InjectionContext.Provider value={injection}>
        <UI {...props} />
      </InjectionContext.Provider>
    </ConfigContext.Provider>
  )
}

export default InitializeUI