"use client"
import Downshift from "downshift"
import {
  type ChangeEvent,
  type FocusEvent,
  type FormEvent,
  useCallback,
  useEffect,
  useReducer,
} from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"

import SkeletonShimmer from "@/components/SkeletonShimmer"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import isValidJson from "@/utils/isValidJson"

import Input from "../Input"
import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer"
import SearchList from "./SearchList"

import styles from "./search.module.css"

import type { BookingWidgetSchema } from "@/types/components/bookingWidget"
import {
  ActionType,
  type SetStorageData,
} from "@/types/components/form/bookingwidget"
import type { SearchHistoryItem, SearchProps } from "@/types/components/search"
import type { Location, Locations } from "@/types/trpc/routers/hotel/locations"

const name = "search"

export default function Search({ locations, handlePressEnter }: SearchProps) {
  const { register, setValue, unregister, getValues } =
    useFormContext<BookingWidgetSchema>()
  const intl = useIntl()
  const value = useWatch({ name })
  const locationString = getValues("location")
  const location =
    locationString && isValidJson(decodeURIComponent(locationString))
      ? JSON.parse(decodeURIComponent(locationString))
      : null
  const [state, dispatch] = useReducer(
    reducer,
    { defaultLocations: locations, initialValue: value },
    init
  )
  const handleMatchLocations = useCallback(
    function (searchValue: string) {
      return locations.filter((location) => {
        return location.name.toLowerCase().includes(searchValue.toLowerCase())
      })
    },
    [locations]
  )

  function handleClearSearchHistory() {
    localStorage.removeItem(localStorageKey)
    dispatch({ type: ActionType.CLEAR_HISTORY_LOCATIONS })
  }

  function dispatchInputValue(inputValue: string) {
    if (inputValue) {
      dispatch({
        payload: { search: inputValue },
        type: ActionType.SEARCH_LOCATIONS,
      })
    } else {
      dispatch({ type: ActionType.CLEAR_SEARCH_LOCATIONS })
    }
  }

  function handleOnChange(
    evt: FormEvent<HTMLInputElement> | ChangeEvent<HTMLInputElement>
  ) {
    const newValue = evt.currentTarget.value
    setValue(name, newValue)
    dispatchInputValue(value)
  }

  function handleOnFocus(evt: FocusEvent<HTMLInputElement>) {
    const searchValue = evt.currentTarget.value
    if (searchValue) {
      const matchingLocations = handleMatchLocations(searchValue)
      if (matchingLocations.length) {
        dispatch({
          payload: { search: searchValue },
          type: ActionType.SEARCH_LOCATIONS,
        })
      }
    }
  }

  function handleOnSelect(selectedItem: Location | null) {
    if (selectedItem) {
      const stringified = JSON.stringify(selectedItem)
      setValue("location", encodeURIComponent(stringified))
      sessionStorage.setItem(sessionStorageKey, stringified)
      setValue(name, selectedItem.name)
      const newHistoryItem: SearchHistoryItem = {
        type: selectedItem.type,
        id: selectedItem.id,
      }
      const oldSearchHistoryWithoutTheNew =
        state.searchHistory?.filter(
          (h) => h.type !== newHistoryItem.type || h.id !== newHistoryItem.id
        ) ?? []
      const oldHistoryItems = oldSearchHistoryWithoutTheNew.map((h) => ({
        id: h.id,
        type: h.type,
      }))

      const searchHistory = [newHistoryItem, ...oldHistoryItems]
      localStorage.setItem(localStorageKey, JSON.stringify(searchHistory))

      const enhancedSearchHistory: Locations = [
        ...getEnhancedSearchHistory([newHistoryItem], locations),
        ...oldSearchHistoryWithoutTheNew,
      ]
      dispatch({
        payload: {
          location: selectedItem,
          searchHistory: enhancedSearchHistory,
        },
        type: ActionType.SELECT_ITEM,
      })
    } else {
      sessionStorage.removeItem(sessionStorageKey)
    }
  }

  useEffect(() => {
    const searchData = sessionStorage.getItem(sessionStorageKey)
    const searchHistory = localStorage.getItem(localStorageKey)
    const payload: SetStorageData["payload"] = {}
    if (searchData) {
      payload.searchData = JSON.parse(searchData)
    }
    if (searchHistory) {
      payload.searchHistory = getEnhancedSearchHistory(
        JSON.parse(searchHistory),
        locations
      )
    }
    dispatch({
      payload,
      type: ActionType.SET_STORAGE_DATA,
    })
  }, [dispatch, locations])

  const stayType = state.searchData?.type === "cities" ? "city" : "hotel"
  const stayValue =
    (value === state.searchData?.name &&
      ((state.searchData?.type === "cities" && state.searchData?.name) ||
        state.searchData?.id)) ||
    ""

  useEffect(() => {
    if (stayType === "city") {
      unregister("hotel")
      setValue(stayType, stayValue, {
        shouldValidate: true,
      })
    } else {
      unregister("city")
      setValue(stayType, Number(stayValue), {
        shouldValidate: true,
      })
    }
  }, [stayType, stayValue, unregister, setValue])

  useEffect(() => {
    if (location) {
      sessionStorage.setItem(sessionStorageKey, JSON.stringify(location))
    }
  }, [location])

  function getLocationLabel(): string {
    const fallbackLabel = intl.formatMessage({ id: "Where to?" })
    if (location?.type === "hotels") {
      return location?.relationships?.city?.name || fallbackLabel
    }
    if (state.searchData?.type === "hotels") {
      return state.searchData?.relationships?.city?.name || fallbackLabel
    }
    return fallbackLabel
  }

  return (
    <Downshift
      initialSelectedItem={state.searchData}
      inputValue={value}
      itemToString={(value) => (value ? value.name : "")}
      onSelect={handleOnSelect}
      onInputValueChange={(inputValue) => dispatchInputValue(inputValue)}
      defaultHighlightedIndex={0}
    >
      {({
        getInputProps,
        getItemProps,
        getLabelProps,
        getMenuProps,
        getRootProps,
        highlightedIndex,
        isOpen,
        openMenu,
      }) => (
        <div className={styles.container}>
          {value ? (
            // Adding hidden input to define hotel or city based on destination selection for basic form submit.
            <input type="hidden" {...register(stayType)} />
          ) : null}
          <label {...getLabelProps({ htmlFor: name })} className={styles.label}>
            <Caption
              type="bold"
              color={isOpen ? "uiTextActive" : "red"}
              asChild
            >
              <span>{getLocationLabel()}</span>
            </Caption>
          </label>
          <div {...getRootProps({}, { suppressRefError: true })}>
            <label className={styles.searchInput}>
              <Input
                {...getInputProps({
                  id: name,
                  onFocus(evt) {
                    handleOnFocus(evt)
                    openMenu()
                  },
                  placeholder: intl.formatMessage({
                    id: "Hotels & Destinations",
                  }),
                  ...register(name, {
                    onChange: handleOnChange,
                  }),
                  onKeyDown: (e) => {
                    if (e.key === "Enter" && !isOpen) {
                      handlePressEnter()
                    }
                  },
                  type: "search",
                })}
              />
            </label>
          </div>
          <SearchList
            getItemProps={getItemProps}
            getMenuProps={getMenuProps}
            handleClearSearchHistory={handleClearSearchHistory}
            highlightedIndex={highlightedIndex}
            isOpen={isOpen}
            locations={state.locations}
            search={state.search}
            searchHistory={state.searchHistory}
          />
        </div>
      )}
    </Downshift>
  )
}

export function SearchSkeleton() {
  const intl = useIntl()
  return (
    <div className={styles.container}>
      <div className={styles.label}>
        <Caption type="bold" color="red" asChild>
          <span>{intl.formatMessage({ id: "Where to?" })}</span>
        </Caption>
      </div>
      <div>
        <SkeletonShimmer width={"100%"} />
      </div>
    </div>
  )
}

/**
 * Takes a stored search history and returns the same history, but with the same
 * data and the same format as the complete location objects
 */
function getEnhancedSearchHistory(
  searchHistory: SearchHistoryItem[],
  locations: Locations
): Locations {
  return searchHistory
    .map((historyItem) =>
      locations.find(
        (location) =>
          location.type === historyItem.type && location.id === historyItem.id
      )
    )
    .filter((r) => !!r) as Locations
}
