import React from 'react';
import './Editor.scss';
import Sidebar from './Components/Sidebar/Sidebar';
import { ElementsSidebar } from './Components/ElementsSidebar/ElementsSidebar';
import { AvailableContainer } from './Interfaces/AvailableContainer';
import { Configuration } from './Interfaces/Configuration';
import { SVG } from './Components/SVG/SVG';
import { History } from './Components/History/History';
import { ContainerModel, IContainerModel, MakeIterator } from './Components/SVG/Elements/ContainerModel';
import Properties from './Interfaces/Properties';
import { IHistoryState } from './App';

interface IEditorProps {
  configuration: Configuration,
  history: Array<IHistoryState>,
  historyCurrentStep: number
}

interface IEditorState {
  isSidebarOpen: boolean,
  isSVGSidebarOpen: boolean,
  isHistoryOpen: boolean,
  history: Array<IHistoryState>,
  historyCurrentStep: number,
}

class Editor extends React.Component<IEditorProps> {
  public state: IEditorState;

  constructor(props: IEditorProps) {
    super(props);
    this.state = {
      isSidebarOpen: true,
      isSVGSidebarOpen: false,
      isHistoryOpen: false,
      configuration: props.configuration,
      history: props.history,
      historyCurrentStep: props.historyCurrentStep
    } as IEditorState;
  }

  public getCurrentHistory = (): IHistoryState[] => this.state.history.slice(0, this.state.historyCurrentStep + 1);
  public getCurrentHistoryState = (): IHistoryState => this.state.history[this.state.historyCurrentStep];

  /**
   * Toggle the components sidebar
   */
  public ToggleSidebar() {
    this.setState({
      isSidebarOpen: !this.state.isSidebarOpen
    } as IEditorState);
  }

  /**
   * Toggle the elements
   */
  public ToggleElementsSidebar() {
    this.setState({
      isSVGSidebarOpen: !this.state.isSVGSidebarOpen
    } as IEditorState);
  }

  /**
   * Toggle the elements
   */
  public ToggleHistory() {
    this.setState({
      isHistoryOpen: !this.state.isHistoryOpen
    } as IEditorState);
  }

  /**
   * Select a container
   * @param container Selected container
   */
  public SelectContainer(container: ContainerModel) {
    const history = this.getCurrentHistory();
    const current = history[history.length - 1];
    this.setState({
      history: history.concat([{
        MainContainer: current.MainContainer,
        TypeCounters: current.TypeCounters,
        SelectedContainer: container
      }]),
      historyCurrentStep: history.length
    } as IEditorState);
  }

  /**
   * Handled the property change event in the properties form
   * @param key Property name
   * @param value New value of the property
   * @returns void
   */
  public OnPropertyChange(key: string, value: string | number): void {
    const history = this.getCurrentHistory();
    const current = history[history.length - 1];

    if (current.SelectedContainer === null ||
      current.SelectedContainer === undefined) {
      throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
    }

    if (current.MainContainer === null ||
      current.MainContainer === undefined) {
      throw new Error('[OnPropertyChange] Property was changed before the main container was added');
    }

    if (parent === null) {
      const clone: IContainerModel = structuredClone(current.SelectedContainer);
      (clone.properties as any)[key] = value;
      this.setState({
        history: history.concat([{
          SelectedContainer: clone,
          MainContainer: clone,
          TypeCounters: current.TypeCounters
        }]),
        historyCurrentStep: history.length
      } as IEditorState);
      return;
    }

    const clone: IContainerModel = structuredClone(current.MainContainer);
    const it = MakeIterator(clone);
    let container: ContainerModel | null = null;
    for (const child of it) {
      if (child.properties.id === current.SelectedContainer.properties.id) {
        container = child as ContainerModel;
        break;
      }
    }

    if (container === null) {
      throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
    }

    (container.properties as any)[key] = value;

    this.setState(
      {
        history: history.concat([{
          SelectedContainer: container,
          MainContainer: clone,
          TypeCounters: current.TypeCounters
        }]),
        historyCurrentStep: history.length
      } as IEditorState);
  }

  /**
   * Add a new container to a selected container
   * @param type The type of container
   * @returns void
   */
  public AddContainer(type: string): void {
    const history = this.getCurrentHistory();
    const current = history[history.length - 1];

    if (current.SelectedContainer === null ||
      current.SelectedContainer === undefined) {
      return;
    }

    if (current.MainContainer === null ||
      current.MainContainer === undefined) {
      return;
    }

    // Get the preset properties from the API
    const properties = this.props.configuration.AvailableContainers.find(option => option.Type === type);

    if (properties === undefined) {
      throw new Error(`[AddContainer] Object type not found. Found: ${type}`);
    }

    // Set the counter of the object type in order to assign an unique id
    const newCounters = Object.assign({}, current.TypeCounters);
    if (newCounters[type] === null ||
      newCounters[type] === undefined) {
      newCounters[type] = 0;
    } else {
      newCounters[type]++;
    }
    const count = newCounters[type];

    // Create maincontainer model
    const structure: IContainerModel = structuredClone(current.MainContainer);
    const clone = Object.assign(new ContainerModel(null, {} as Properties), structure);

    // Find the parent
    const it = MakeIterator(clone);
    let parent: ContainerModel | null = null;
    for (const child of it) {
      if (child.properties.id === current.SelectedContainer.properties.id) {
        parent = child as ContainerModel;
        break;
      }
    }

    if (parent === null) {
      throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
    }

    // Create the container
    const newContainer = new ContainerModel(
      parent,
      {
        id: `${type}-${count}`,
        x: 0,
        y: 0,
        width: properties?.Width,
        height: parent.properties.height,
        ...properties.Style
      } as Properties,
      [],
      {
        type
      }
    );

    // And push it the the parent children
    parent.children.push(newContainer);

    // Update the state
    this.setState({
      history: history.concat([{
        MainContainer: clone,
        TypeCounters: newCounters,
        SelectedContainer: parent
      }]),
      historyCurrentStep: history.length
    } as IEditorState);
  }

  public jumpTo(move: number): void {
    this.setState({
      historyCurrentStep: move
    } as IEditorState);
  }

  /**
   * Render the application
   * @returns {JSX.Element} Rendered JSX element
   */
  render() {
    const current = this.getCurrentHistoryState();
    return (
      <div className="App font-sans h-full">
        <Sidebar
          componentOptions={this.props.configuration.AvailableContainers}
          isOpen={this.state.isSidebarOpen}
          onClick={() => this.ToggleSidebar()}
          buttonOnClick={(type: string) => this.AddContainer(type)}
        />
        <button
          className='fixed z-10 top-4 left-4 text-lg bg-blue-200 hover:bg-blue-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
          onClick={() => this.ToggleSidebar()}
        >
          &#9776; Components
        </button>

        <ElementsSidebar
          MainContainer={current.MainContainer}
          SelectedContainer={current.SelectedContainer}
          isOpen={this.state.isSVGSidebarOpen}
          isHistoryOpen={this.state.isHistoryOpen}
          onClick={() => this.ToggleElementsSidebar()}
          onPropertyChange={(key: string, value: string) => this.OnPropertyChange(key, value)}
          selectContainer={(container: ContainerModel) => this.SelectContainer(container)}
        />
        <button
          className='fixed z-10 top-4 right-12 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
          onClick={() => this.ToggleElementsSidebar()}
        >
          &#9776; Elements
        </button>

        <History
          history={this.state.history}
          historyCurrentStep={this.state.historyCurrentStep}
          isOpen={this.state.isHistoryOpen}
          onClick={() => this.ToggleHistory()}
          jumpTo={(move) => { this.jumpTo(move); }}
        />
        <button
          className='fixed z-10 top-4 right-72 text-lg bg-slate-200 hover:bg-slate-300 transition-all drop-shadow-md hover:drop-shadow-lg py-2 px-3 rounded-lg'
          onClick={() => this.ToggleHistory()}>
            &#9776; History
        </button>

        <SVG selected={current.SelectedContainer}>
          { current.MainContainer }
        </SVG>
      </div>
    );
  }
}

export default Editor;
