import { useState, useMemo, useRef } from 'react';
import { useQuery } from '@apollo/client';

import { Box } from 'components/common';
import { Input } from 'components/inputs';
import { useDebounce } from './useDebounce';
import {
  LOCATION_SEARCH,
  LocationSearchData,
  LocationSearchVariables,
} from './schema';

export interface LocationFeature {
  bbox: [number, number, number, number];
  geometry: {
    coordinates: [number, number];
    type: string;
  };
  properties: {
    id: string;
    gid: string;
    layer: string;
    source: string;
    source_id: string;
    name: string;
    accuracy: string;
    country: string;
    country_gid: string;
    country_a: string;
    region: string;
    region_gid: string;
    region_a: string;
    continent: string;
    continent_gid: string;
    label: string;
  };
}

interface GeoLocation {
  bbox: [number, number, number, number];
  features: LocationFeature[];
}

function parseJson(json?: string) {
  if (typeof json === 'string') {
    try {
      const data = JSON.parse(json);
      return data;
    } catch (error) {
      console.error(error);
    }
  }
}

interface LocationSuggestionProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode;
}

const LocationSuggestion = ({
  children,
  ...props
}: LocationSuggestionProps) => {
  return (
    <div
      className="paddingX2 paddingY1 hover1 cup truncate"
      tabIndex={-1}
      data-suggestion={true}
      {...props}
    >
      {children}
    </div>
  );
};

interface LocationSearchProps {
  className?: string;
  style?: React.CSSProperties;
  name: string;
  placeholder?: string;
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
  onSelect?: (feature: LocationFeature) => void;
  initialState?: string;
  searchInputDelay?: number;
  searchLayers?: string;
  searchSize?: number;
}

export const LocationSearch = ({
  className,
  style,
  name,
  placeholder,
  onChange,
  onSelect,
  initialState = '',
  searchInputDelay = 500,
  searchLayers,
  searchSize,
}: LocationSearchProps) => {
  const [search, setSearch] = useState(initialState);
  const [suggestions, showSuggestions] = useState(false);
  const selectedRef = useRef<string | null>(null);

  const debouncedState = useDebounce(search, searchInputDelay);

  const { data, loading } = useQuery<
    LocationSearchData,
    LocationSearchVariables
  >(LOCATION_SEARCH, {
    skip: !debouncedState,
    variables: {
      search: debouncedState,
      layers: searchLayers,
      size: searchSize,
    },
  });

  const parsedData: GeoLocation | undefined = useMemo(
    () => parseJson(data?.json),
    [data]
  );

  const handleChange: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
    setSearch(ev.target.value);
    if (typeof onChange === 'function') {
      onChange(ev);
    }
  };

  const handleSelect = (feature: LocationFeature) => {
    const { properties } = feature;
    setSearch(properties.label);
    selectedRef.current = properties.label;
    showSuggestions(false);
    if (typeof onSelect === 'function') {
      onSelect(feature);
    }
  };

  const handleFocus: React.FocusEventHandler<HTMLInputElement> = (ev) => {
    showSuggestions(true);
  };

  const handleBlur: React.FocusEventHandler<HTMLInputElement> = (ev) => {
    const { relatedTarget } = ev;
    if (relatedTarget) {
      const { dataset } = relatedTarget as any;
      if (!dataset.suggestion) {
        showSuggestions(false);
      }
    } else {
      showSuggestions(false);
      if (search !== '' && selectedRef.current) {
        setSearch(selectedRef.current);
      }
    }
  };

  function renderSuggestions(data: GeoLocation) {
    const { features } = data;
    if (features?.length > 0) {
      return data.features.map((feature) => {
        const { properties } = feature;
        return (
          <LocationSuggestion
            onClick={() => handleSelect(feature)}
            key={properties.id}
          >
            {properties.label}
          </LocationSuggestion>
        );
      });
    } else if (features?.length === 0) {
      return <LocationSuggestion>Nothing found</LocationSuggestion>;
    } else {
      return null;
    }
  }

  return (
    <Box position="relative">
      <Input
        className={className}
        style={style}
        type="text"
        name={name}
        value={search}
        placeholder={placeholder}
        title={placeholder}
        autoComplete="off"
        onChange={handleChange}
        onFocus={handleFocus}
        onBlur={handleBlur}
      />
      {suggestions && (
        <Box
          className="w100 shadow fz14 bg-white"
          position="absolute"
          zIndex={1000}
        >
          {loading ? (
            <LocationSuggestion>Loading...</LocationSuggestion>
          ) : parsedData ? (
            renderSuggestions(parsedData)
          ) : null}
        </Box>
      )}
    </Box>
  );
};
