import * as React from 'react';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { type IPoint } from '../../Interfaces/IPoint';
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { BAR_WIDTH } from '../Bar/Bar';
import { Canvas } from '../Canvas/Canvas';
import { AddDimensions } from '../Canvas/DimensionLayer';
import { RenderSelector } from '../Canvas/Selector';
import { SelectorMode, SVG } from '../SVG/SVG';
import { RenderSymbol } from '../Canvas/Symbol';
import { useState } from 'react';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';

interface IViewerProps {
  className: string
  current: IHistoryState
  selectedContainer: IContainerModel | undefined
  selectContainer: (containerId: string) => void
  selectedSymbol: ISymbolModel | undefined
  margin: number
  isComponentsOpen: boolean
  isSymbolsOpen: boolean
}

export function Viewer({
  className,
  current,
  selectedContainer,
  selectContainer,
  selectedSymbol,
  margin,
  isComponentsOpen,
  isSymbolsOpen
}: IViewerProps): JSX.Element {
  function computeWidth(margin: number): number {
    return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
  }

  const [windowSize, setWindowSize] = useState([
    computeWidth(margin),
    window.innerHeight
  ]);

  React.useEffect(() => {
    function SVGAutoResizer(): void {
      setWindowSize([
        computeWidth(margin),
        window.innerHeight
      ]);
    }

    window.addEventListener('resize', SVGAutoResizer);

    return () => {
      window.removeEventListener('resize', SVGAutoResizer);
    };
  });

  React.useEffect(() => {
    setWindowSize([
      computeWidth(margin),
      window.innerHeight
    ]);
  }, [margin]);

  const mainContainer = FindContainerById(current.containers, current.mainContainer);

  if (mainContainer === undefined) {
    return <></>;
  }

  let selectorMode = SelectorMode.Nothing;
  if (isComponentsOpen) {
    selectorMode = SelectorMode.Containers;
  } else if (isSymbolsOpen) {
    selectorMode = SelectorMode.Symbols;
  }

  if (USE_EXPERIMENTAL_CANVAS_API) {
    function Draw(ctx: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint): void {
      if (mainContainer === undefined) {
        return;
      }

      const topDim = mainContainer.properties.y;
      const leftDim = mainContainer.properties.x;
      const rightDim = mainContainer.properties.x + mainContainer.properties.width;
      const bottomDim = mainContainer.properties.y + mainContainer.properties.height;

      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.save();
      ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
      ctx.fillStyle = '#000000';

      const it = MakeRecursionDFSIterator(mainContainer, current.containers, 0, [0, 0]);
      for (const { container, depth, currentTransform } of it) {
        const [x, y] = [
          container.properties.x + currentTransform[0],
          container.properties.y + currentTransform[1]
        ];

        // Draw container
        RenderContainers(ctx, container, x, y);

        // Draw dimensions
        RenderDimensions(
          ctx,
          leftDim,
          bottomDim,
          topDim,
          rightDim,
          depth,
          scale,
          current.containers,
          container,
          currentTransform
        );

        // Draw selector
        RenderSelector(ctx, frameCount, {
          containers: current.containers,
          scale,
          selected: selectedContainer
        });
      }

      // Draw symbols
      RenderSymbols(current, ctx, scale);
      ctx.restore();
    }
    return (
      <Canvas
        draw={Draw}
        className={`ml-16 ${className}`}
        width={window.innerWidth - BAR_WIDTH}
        height={window.innerHeight}
      />
    );
  }

  return (
    <SVG
      className={className}
      viewerWidth={windowSize[0]}
      viewerHeight={windowSize[1]}
      width={mainContainer.properties.width}
      height={mainContainer.properties.height}
      containers={current.containers}
      selectedContainer={selectedContainer}
      symbols={current.symbols}
      selectedSymbol={selectedSymbol}
      selectorMode={selectorMode}
      selectContainer={selectContainer}
    >
      {mainContainer}
    </SVG>
  );
}
function RenderSymbols(
  current: IHistoryState,
  ctx: CanvasRenderingContext2D,
  scale: number
): void {
  current.symbols.forEach((symbol) => {
    RenderSymbol(symbol, ctx, scale);
  });
}

function RenderDimensions(
  ctx: CanvasRenderingContext2D,
  leftDim: number,
  bottomDim: number,
  topDim: number,
  rightDim: number,
  depth: number,
  scale: number,
  containers: Map<string, IContainerModel>,
  container: IContainerModel,
  currentTransform: [number, number]
): void {
  ctx.save();
  const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
  const containerLeftDim = leftDim - depthOffset;
  const containerTopDim = topDim - depthOffset;
  const containerBottomDim = bottomDim + depthOffset;
  const containerRightDim = rightDim + depthOffset;
  const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
  AddDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
  ctx.restore();
}

function RenderContainers(ctx: CanvasRenderingContext2D, container: IContainerModel, x: number, y: number): void {
  ctx.save();
  ctx.strokeStyle = container.properties.style?.stroke ?? '#000000';
  ctx.fillStyle = container.properties.style?.fill ?? '#000000';
  ctx.lineWidth = Number(container.properties.style?.strokeWidth ?? 1);
  ctx.globalAlpha = Number(container.properties.style?.fillOpacity ?? 1);
  ctx.fillRect(x, y, container.properties.width, container.properties.height);
  ctx.globalAlpha = Number(container.properties.style?.strokeOpacity ?? 1);
  ctx.strokeRect(x, y, container.properties.width, container.properties.height);
  ctx.restore();
}
