import {
  ExpandOutlined,
  ZoomInOutlined,
  ZoomOutOutlined,
} from '@ant-design/icons';
import { t } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Button, Empty, Grid, Space, Spin, Tooltip } from 'antd';
import Konva from 'konva';
import React from 'react';
import { Image, Layer, Rect, Stage } from 'react-konva';
import { useIsFirstRender } from 'usehooks-ts';
import { v4 as uuidv4 } from 'uuid';
import { Controls } from './controls';
import { Rectangle } from './custom-shapes';
import {
  useBackground,
  useDrawStage,
  useLoadImage,
  useScaleStage,
  useSelectedShapeId,
  useShapes,
} from './hooks';
import { useStoreInstance } from './store-context';
import { ShapeConfig, ShapeTypes, StoreValue } from './types';

interface ImageAnnotatorProps {
  background?: string;
  shapes?: ShapeConfig[];
  initialBackground?: string;
  initialShapes?: ShapeConfig[];
  containerWidth?: React.CSSProperties['width'];
  containerHeight?: React.CSSProperties['height'];
  readOnly?: boolean;
  fitToView?: boolean;
}

export function ImageAnnotator({
  background,
  shapes,
  initialBackground,
  initialShapes,
  containerWidth = '100%',
  containerHeight = '100%',
  fitToView = true,
  readOnly = false,
}: ImageAnnotatorProps) {
  const { i18n } = useLingui();
  const screens = Grid.useBreakpoint();
  const containerRef = React.useRef<HTMLDivElement>();
  const stageRef = React.useRef<Konva.Stage>();
  const isFirstRender = useIsFirstRender();

  const storeInstance = useStoreInstance();
  const internalShapes = useShapes();
  const internalBackground = useBackground();
  const selectedShapeId = useSelectedShapeId();
  const { scale, scaleIn, scaleOut, scaleTo, canZoomIn, canZoomOut } =
    useScaleStage();
  const { start, handle, finish, rect } = useDrawStage({
    canDraw: !readOnly,
    onFinish: (finalRect) => {
      storeInstance.addShapes([
        {
          id: uuidv4(),
          type: ShapeTypes.RECTANGLE,
          data: {
            label: 'default label',
            value: '',
          },
          ...finalRect,
        },
      ]);
    },
  });

  const fitImageWidth = React.useCallback(
    (_image: HTMLImageElement, isContainerReady = true) => {
      const SCROLL_BAR_WIDTH_PX = 15;

      const viewWidth =
        containerRef.current.clientWidth -
        (isContainerReady ? 0 : SCROLL_BAR_WIDTH_PX);
      const imageWidth = _image.naturalWidth;
      const newScale = viewWidth / imageWidth;

      scaleTo({
        x: newScale,
        y: newScale,
      });
    },
    [scaleTo],
  );

  const { image, isLoading } = useLoadImage(internalBackground, {
    onSuccess(_image) {
      if (fitToView) {
        fitImageWidth(_image, false);
      }
    },
  });

  React.useEffect(() => {
    const newStore = getStoreChangeFromDeriveProps({
      shapes,
      background,
    });
    const firstStore: Partial<StoreValue> = {};
    if (initialBackground && isFirstRender) {
      firstStore.background = initialBackground;
    }
    if (initialShapes && isFirstRender) {
      firstStore.shapes = initialShapes;
    }

    storeInstance.setStore((prev) => ({
      ...prev,
      ...firstStore,
      ...newStore,
    }));
  }, [shapes, background, isFirstRender]);

  return (
    <div
      ref={containerRef}
      style={{
        width: containerWidth,
        height: containerHeight,
        position: 'relative',
        overflow: 'auto',
        border: '1px dashed #d9d9d9',
        borderRadius: '8px',
      }}
    >
      {internalBackground ? (
        <Stage
          ref={stageRef}
          scale={scale}
          width={image.naturalWidth * scale.x}
          height={image.naturalHeight * scale.y}
          onMouseDown={(event) => {
            const isClickedOnEmpty = event.target.id() === 'background-image';

            if (isClickedOnEmpty) {
              storeInstance.setSelectedShape(null);
              start(event);
            }
          }}
          onMouseMove={handle}
          onMouseUp={finish}
        >
          <Layer>
            <Image
              image={image}
              width={image.naturalWidth}
              height={image.naturalHeight}
              id="background-image"
            />
            {internalShapes.map((shape) => (
              <Rectangle
                {...shape}
                isSelected={shape.id === selectedShapeId}
                isTransformable={!readOnly}
                onSelect={() => storeInstance.setSelectedShape(shape.id)}
                onChange={(shapeConfig) => {
                  const newShapes = internalShapes.map((item) =>
                    item.id === shape.id
                      ? {
                          ...shape,
                          ...shapeConfig,
                        }
                      : item,
                  );
                  storeInstance.setShapes(newShapes);
                }}
                draggable={!readOnly}
                key={shape.id}
              />
            ))}
            <Rect {...rect} />
            <Controls containerRef={containerRef} hidden={!screens.lg}>
              <Space>
                <Tooltip title={t(i18n)`Zoom out`}>
                  <Button
                    shape="circle"
                    icon={<ZoomOutOutlined />}
                    onClick={scaleOut}
                    disabled={!canZoomOut}
                  />
                </Tooltip>
                <Tooltip title={t(i18n)`Zoom in`}>
                  <Button
                    shape="circle"
                    icon={<ZoomInOutlined />}
                    onClick={scaleIn}
                    disabled={!canZoomIn}
                  />
                </Tooltip>
                <Tooltip title={t(i18n)`Fit image`}>
                  <Button
                    shape="circle"
                    icon={<ExpandOutlined />}
                    onClick={() => fitImageWidth(image)}
                  />
                </Tooltip>
              </Space>
            </Controls>
          </Layer>
        </Stage>
      ) : (
        <Empty
          image={Empty.PRESENTED_IMAGE_SIMPLE}
          description={t(i18n)`No data, please upload an image.`}
          style={{
            position: 'absolute',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
          }}
        />
      )}
      {isLoading && (
        <Spin
          style={{
            position: 'absolute',
            left: '50%',
            top: '50%',
            transform: 'translate(-50%, -50%)',
          }}
        />
      )}
    </div>
  );
}

function getStoreChangeFromDeriveProps({
  background,
  shapes,
}: Pick<ImageAnnotatorProps, 'background' | 'shapes'>) {
  const newStore: Partial<StoreValue> = {};
  if (background !== undefined) {
    newStore.background = background;
  }
  if (shapes !== undefined) {
    newStore.shapes = shapes;
  }

  return newStore;
}
