import * as React from 'react';
import { motion } from 'framer-motion';
import { Properties } from '../Properties/Properties';
import { IContainerModel } from '../../Interfaces/ContainerModel';
import { findContainerById, getDepth, MakeIterator } from '../../utils/itertools';
import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/MenuItem';

interface IElementsSidebarProps {
  MainContainer: IContainerModel | null
  isOpen: boolean
  isHistoryOpen: boolean
  SelectedContainer: IContainerModel | null
  onPropertyChange: (key: string, value: string) => void
  SelectContainer: (container: IContainerModel) => void
  DeleteContainer: (containerid: string) => void
  AddContainer: (index: number, type: string, parent: string) => void
}

interface Point {
  x: number
  y: number
}

interface IElementsSidebarState {
  isContextMenuOpen: boolean
  contextMenuPosition: Point
  onClickContainerId: string
}

export class ElementsSidebar extends React.PureComponent<IElementsSidebarProps> {
  public state: IElementsSidebarState;
  public elementRef: React.RefObject<HTMLDivElement>;

  constructor(props: IElementsSidebarProps) {
    super(props);
    this.state = {
      isContextMenuOpen: false,
      contextMenuPosition: {
        x: 0,
        y: 0
      },
      onClickContainerId: ''
    };
    this.elementRef = React.createRef();
  }

  componentDidMount(): void {
    this.elementRef.current?.addEventListener('contextmenu', (event) => this.handleRightClick(event));
    window.addEventListener('click', (event) => this.handleLeftClick(event));
  }

  componentWillUnmount(): void {
    this.elementRef.current?.removeEventListener('contextmenu', (event) => this.handleRightClick(event));
    window.removeEventListener('click', (event) => this.handleLeftClick(event));
  }

  public handleRightClick(event: MouseEvent): void {
    event.preventDefault();

    if (!(event.target instanceof HTMLButtonElement)) {
      this.setState({
        isContextMenuOpen: false,
        onClickContainerId: ''
      });
      return;
    }

    const contextMenuPosition: Point = { x: event.pageX, y: event.pageY };
    this.setState({
      isContextMenuOpen: true,
      contextMenuPosition,
      onClickContainerId: event.target.id
    });
  }

  public handleLeftClick(event: MouseEvent): void {
    if (!this.state.isContextMenuOpen) {
      return;
    }

    this.setState({
      isContextMenuOpen: false,
      onClickContainerId: ''
    });
  }

  public handleDragOver(event: React.DragEvent): void {
    event.preventDefault();
  }

  public handleOnDrop(event: React.DragEvent): void {
    event.preventDefault();
    const type = event.dataTransfer.getData('type');
    const target: HTMLButtonElement = event.target as HTMLButtonElement;

    if (this.props.MainContainer === null) {
      throw new Error('[handleOnDrop] Tried to drop into the tree without a required MainContainer!');
    }

    const targetContainer: IContainerModel | undefined = findContainerById(
      this.props.MainContainer,
      target.id
    );

    if (targetContainer === undefined) {
      throw new Error('[handleOnDrop] Tried to drop onto a unknown container!');
    }

    if (targetContainer === this.props.MainContainer) {
      // if the container is the root, only add type as child
      this.props.AddContainer(
        targetContainer.children.length,
        type,
        targetContainer.properties.id);
      return;
    }

    if (targetContainer.parent === null ||
      targetContainer.parent === undefined) {
      throw new Error('[handleDrop] Tried to drop into a child container without a parent!');
    }

    const rect = target.getBoundingClientRect();
    const y = event.clientY - rect.top; // y position within the element.

    // locate the hitboxes
    if (y < 12) {
      const index = targetContainer.parent.children.indexOf(targetContainer);
      this.props.AddContainer(
        index,
        type,
        targetContainer.parent.properties.id
      );
    } else if (y < 24) {
      this.props.AddContainer(
        targetContainer.children.length,
        type,
        targetContainer.properties.id);
    } else {
      const index = targetContainer.parent.children.indexOf(targetContainer);
      this.props.AddContainer(
        index + 1,
        type,
        targetContainer.parent.properties.id
      );
    }
  }

  public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode {
    if (this.props.MainContainer == null) {
      return null;
    }

    const it = MakeIterator(this.props.MainContainer);
    for (const container of it) {
      handleContainer(container);
    }
  }

  public render(): JSX.Element {
    let isOpenClasses = '-right-64';
    if (this.props.isOpen) {
      isOpenClasses = this.props.isHistoryOpen
        ? 'right-64'
        : 'right-0';
    }

    const containerRows: React.ReactNode[] = [];
    this.iterateChilds((container: IContainerModel) => {
      const depth: number = getDepth(container);
      const key = container.properties.id.toString();
      const text = '|\t'.repeat(depth) + key;
      const selectedClass: string = this.props.SelectedContainer !== undefined &&
        this.props.SelectedContainer !== null &&
        this.props.SelectedContainer.properties.id === container.properties.id
        ? 'border-l-4 border-blue-500 bg-slate-400/60 hover:bg-slate-400'
        : 'bg-slate-300/60 hover:bg-slate-300';
      containerRows.push(
        <motion.button
          whileHover={{ scale: 1.05 }}
          whileTap={{ scale: 1.2 }}
          initial={{ opacity: 0, scale: 0 }}
          animate={{ opacity: 1, scale: 1 }}
          transition={{
            duration: 0.150
          }}
          className={
            `w-full elements-sidebar-row whitespace-pre
          text-left text-sm font-medium transition-all ${selectedClass}`
          }
          id={key}
          key={key}
          onDrop={(event) => this.handleOnDrop(event)}
          onDragOver={(event) => this.handleDragOver(event)}
          onClick={() => this.props.SelectContainer(container)}>
          { text }
        </motion.button>
      );
    });

    return (
      <div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-screen w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
        <div className='bg-slate-100 font-bold sidebar-title'>
          Elements
        </div>
        <div ref={this.elementRef} className='overflow-y-auto overflow-x-hidden text-gray-800 flex-grow'>
          { containerRows }
        </div>
        <Menu
          className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
          x={this.state.contextMenuPosition.x}
          y={this.state.contextMenuPosition.y}
          isOpen={this.state.isContextMenuOpen}
        >
          <MenuItem className='contextmenu-item' text='Delete' onClick={() => this.props.DeleteContainer(this.state.onClickContainerId)} />
        </Menu>
        <Properties properties={this.props.SelectedContainer?.properties} onChange={this.props.onPropertyChange}></Properties>
      </div>
    );
  }
}
