import { memo, useMemo } from 'react';
import { scaleLinear } from '@visx/scale';
import { CustomProjection, Graticule } from '@visx/geo';
import { DatumObject } from '@visx/shape/lib/types';
import { GeoPermissibleObjects, Projection } from '@visx/geo/lib/types';

import { call, countMinMax, interpolate } from '../../utils';
import { valueSelector, ChartData } from '../../utils/data';
import { MAP_COLORS } from '../../config';
import { TextLabelSettings } from '../../types';
import { TooltipBinder } from 'hooks/useTooltip';

function createCollection<T extends { id: string }>(arr: T[]) {
  const obj: Record<string, T> = {};
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    obj[item.id] = item;
  }
  return obj;
}

const defaultFixExtent = [50, 50, 50, 50];

export const defaultProps = {
  fitExtent: defaultFixExtent,
  graticule: true,
  graticuleColor: '#eee',
  colors: MAP_COLORS,
  fillColor: '#dadada',
  strokeColor: '#fff',
  strokeWidth: 0.5,
  showLegend: true,
  textLabel: 'label',
  textLabelSize: 12,
};

export type ChoroplethSVGProps = TextLabelSettings & {
  width: number;
  height: number;
  data: ChartData[];
  features: { features: GeoPermissibleObjects[] };
  projection: Projection;
  fitExtent?: [number, number, number, number];
  fitSize?: boolean;
  translate: [number, number];
  scale: number;
  colors: string[];
  graticule: boolean;
  graticuleColor: string;
  fillColor: string;
  strokeColor: string;
  strokeWidth: number;
  onFeatureClick?: (
    data: DatumObject,
    ev: React.MouseEvent<SVGGElement, MouseEvent>
  ) => void;
  bindTooltip?: TooltipBinder<ChartData>;
  background?: string;
};

function ChoroplethSVG({
  width,
  height,
  data,
  // valueSelector,
  // labelSelector,
  features,
  projection,
  fitExtent,
  fitSize,
  translate,
  scale,
  colors,
  graticule,
  graticuleColor,
  fillColor,
  strokeColor,
  strokeWidth,
  onFeatureClick,
  bindTooltip,
  textLabel,
  textLabelSize,
  textLabelColor,
  background,
}: ChoroplethSVGProps) {
  // const labelSelector = (d: DefaultDatum) => d.label;
  // const _textLabel = selector<ChartData>((textLabel as keyof ChartData)!);

  const collection = useMemo(() => createCollection(data), [data]);
  const values = useMemo(() => data.map(valueSelector), [data]);
  const [min, max] = countMinMax(values);

  const colorScale = scaleLinear({ domain: [min, max], range: colors });

  const projectionProps: Partial<
    React.ComponentProps<typeof CustomProjection>
  > = {
    projection,
    translate: [width / 2, height / 2],
  };
  if (fitExtent) {
    projectionProps.fitExtent = [
      [
        [fitExtent[3], fitExtent[0]],
        [width - fitExtent[1], height - fitExtent[2]],
      ],
      features,
    ];
  }
  if (fitSize) {
    projectionProps.fitSize = [[width, height], features];
  }
  if (translate) {
    projectionProps.translate = translate;
  }
  if (scale) {
    projectionProps.scale = interpolate(scale, 0, 1, 0, 1000);
  }

  return (
    <svg width={width} height={height}>
      {background && (
        <rect x={0} y={0} width="100%" height="100%" fill={background} />
      )}
      <CustomProjection data={features.features} {...projectionProps}>
        {(projection) => {
          return (
            <g>
              {graticule && (
                <Graticule
                  graticule={(g) => projection.path(g) || ''}
                  stroke={graticuleColor}
                />
              )}
              {projection.features.map((f, i) => {
                const { path, centroid, feature } = f;
                const [centroidX, centroidY] = centroid;

                if (!path) return;

                // TODO refactor
                // @ts-ignore
                const datum = collection[feature.id];
                const value = datum && valueSelector(datum);
                const label = datum && textLabel && datum.label;

                const color = value ? colorScale(value) : fillColor;

                const handleClick: React.MouseEventHandler<SVGGElement> = (
                  ev
                ) => {
                  onFeatureClick!({ feature: feature, data: datum }, ev);
                };
                const tooltipProps = call(bindTooltip, datum);

                return (
                  <g
                    onClick={onFeatureClick && handleClick}
                    {...tooltipProps}
                    key={i}
                  >
                    <path
                      d={path}
                      fill={color}
                      stroke={strokeColor}
                      strokeWidth={strokeWidth}
                    />
                    {label && (
                      <text
                        dy=".33em"
                        x={centroidX}
                        y={centroidY}
                        fill={textLabelColor}
                        fontSize={textLabelSize}
                        textAnchor="middle"
                      >
                        {label}
                      </text>
                    )}
                  </g>
                );
              })}
            </g>
          );
        }}
      </CustomProjection>
    </svg>
  );
}

ChoroplethSVG.defaultProps = defaultProps;

export default memo(ChoroplethSVG);
