import * as React from 'react';
import { Interweave, Node } from 'interweave';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
import { GetDepth } from '../../../utils/itertools';
import { Dimension } from './Dimension';
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
import { TransformX } from '../../../utils/svg';
import { Camelize } from '../../../utils/stringtools';

interface IContainerProps {
  model: IContainerModel
}

/**
 * Render the container
 * @returns Render the container
 */
export function Container(props: IContainerProps): JSX.Element {
  const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);

  const width: number = props.model.properties.width;
  const height: number = props.model.properties.height;

  const x = props.model.properties.x;
  const y = props.model.properties.y;

  const xText = width / 2;
  const yText = height / 2;

  const transform = `translate(${x}, ${y})`;

  // g style
  const defaultStyle: React.CSSProperties = {
    transitionProperty: 'all',
    transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
    transitionDuration: '150ms'
  };

  // Rect style
  const style = Object.assign(
    JSON.parse(JSON.stringify(defaultStyle)),
    props.model.properties.style
  );

  const svg = (props.model.properties.customSVG != null)
    ? CreateReactCustomSVG(props.model.properties.customSVG, props.model.properties)
    : (<rect
      width={width}
      height={height}
      style={style}
    >
    </rect>);
  // Dimension props
  const depth = GetDepth(props.model);
  const dimensionMargin = DIMENSION_MARGIN * depth;
  const id = `dim-${props.model.properties.id}`;
  const xStart: number = 0;
  const xEnd = width;
  const yDim = -dimensionMargin;
  const strokeWidth = 1;
  const text = (width ?? 0).toString();

  let dimensionChildren: JSX.Element | null = null;
  if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
    const {
      childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
    } = GetChildrenDimensionProps(props, dimensionMargin);
    dimensionChildren = <Dimension
      id={childrenId}
      xStart={xChildrenStart}
      xEnd={xChildrenEnd}
      yStart={yChildren}
      yEnd={yChildren}
      strokeWidth={strokeWidth}
      text={textChildren} />;
  }

  return (
    <g
      style={defaultStyle}
      transform={transform}
      key={`container-${props.model.properties.id}`}
    >
      {SHOW_PARENT_DIMENSION
        ? <Dimension
          id={id}
          xStart={xStart}
          xEnd={xEnd}
          yStart={yDim}
          yEnd={yDim}
          strokeWidth={strokeWidth}
          text={text} />
        : null}
      {dimensionChildren}
      {svg}
      {SHOW_TEXT
        ? <text
          x={xText}
          y={yText}
        >
          {props.model.properties.displayedText}
        </text>
        : null}
      {containersElements}
    </g>
  );
}

function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
  const childrenId = `dim-children-${props.model.properties.id}`;

  const lastChild = props.model.children[props.model.children.length - 1];
  let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
  let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);

  // Find the min and max
  for (let i = props.model.children.length - 2; i >= 0; i--) {
    const child = props.model.children[i];
    const left = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
    if (left < xChildrenStart) {
      xChildrenStart = left;
    }
    const right = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
    if (right > xChildrenEnd) {
      xChildrenEnd = right;
    }
  }

  const yChildren = props.model.properties.height + dimensionMargin;
  const textChildren = (xChildrenEnd - xChildrenStart).toString();
  return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
}

function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): React.ReactNode {
  return <Interweave
    tagName='g'
    disableLineBreaks={true}
    content={customSVG}
    allowElements={true}
    transform={(node, children) => Transform(node, children, props)}
  />;
}

function Transform(node: HTMLElement, children: Node[], props: IContainerProperties): React.ReactNode {
  const supportedTags = ['line', 'path', 'rect'];
  if (supportedTags.includes(node.tagName.toLowerCase())) {
    const attributes: { [att: string]: string | object | null } = {};
    node.getAttributeNames().forEach(attName => {
      const attributeValue = node.getAttribute(attName);
      if (attributeValue === null) {
        attributes[attName] = attributeValue;
        return;
      }

      if (attributeValue.startsWith('{userData.') && attributeValue.endsWith('}')) {
        // support for userData
        if (props.userData === undefined) {
          return undefined;
        }

        const userDataKey = attributeValue.replace(/userData\./, '');

        const prop = Object.entries(props.userData).find(([key]) => `{${key}}` === userDataKey);
        if (prop !== undefined) {
          attributes[Camelize(attName)] = prop[1];
          return;
        }
      }

      if (attributeValue.startsWith('{{') && attributeValue.endsWith('}}')) {
        // support for object
        const stringObject = attributeValue.slice(1, -1);
        const object: JSON = JSON.parse(stringObject);
        attributes[Camelize(attName)] = object;
        return;
      }

      const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue);
      if (prop !== undefined) {
        attributes[Camelize(attName)] = prop[1];
        return;
      }
      attributes[Camelize(attName)] = attributeValue;
    });
    return React.createElement(node.tagName.toLowerCase(), attributes, children);
  }
  return undefined;
}
