import * as React from 'react';
import * as _ from 'lodash';
import { DiagramEngine } from '@projectstorm/react-diagrams-core';
import { DefaultNodeModel } from '@projectstorm/react-diagrams';
import styled from '@emotion/styled';
import Toggle from 'react-toggle'
import { ILabShell, JupyterFrontEnd } from '@jupyterlab/application';
import { CustomPortLabel } from '../port/CustomPortLabel';
import { Dialog } from '@jupyterlab/apputils';
import { formDialogWidget } from '../../dialog/formDialogwidget';
import { showFormDialog } from '../../dialog/FormDialog';
import { CommentDialog } from '../../dialog/CommentDialog';
import ReactTooltip from "react-tooltip"
import { marked } from 'marked';
import Color from 'colorjs.io';
import { commandIDs } from "../../commands/CommandIDs";

var S;
(function (S) {
    S.Node = styled.div<{ borderColor:string,background: string; selected: boolean;  }>`
		background-color: ${(p) => {
        const color = new Color(p.background);
        color.alpha = 0.75;
        color.oklch.c *= 1.2;
        return color.to('oklch').toString();
    }};
    box-shadow: 1px 1px 10px ${(p) => p.selected ? '3px rgb(0 192 255 / 0.5)' : '0px rgb(0 0 0 / 0.5)'};    
		border-radius: 5px;
		font-family: sans-serif;
		color: white;
		overflow: visible;
		font-size: 11px;
		border: solid 1px ${(p) => (p.selected ? (p.borderColor==undefined? 'rgb(0,192,255)': p.borderColor ):'black')};
	`;

    S.Title = styled.div`
		background: rgba(0, 0, 0, 0.3);
		display: flex;
		white-space: nowrap;
		justify-items: center;
    box-shadow: inset 0 -2px 4px 0 rgb(0 0 0 / 0.05);  
	`;

    S.TitleName = styled.div`
		flex-grow: 1;
		padding: 5px 5px;
	`;

    S.CommentContainer = styled.div<{ selected: boolean;  }>`
        background: rgba(0, 0, 0, 0.3);
        border-radius: 5px;
		font-family: sans-serif;
		color: rgb(255, 255, 255);
		border: solid 2px black;
		font-size: 12px;
        border: solid 2px ${(p) => p.selected ? 'rgb(0,192,255)':'black'};
        padding: 5px;
    `;

    S.DescriptionName = styled.div<{ color:string }>`
        color: ${(p) => p.color ?? 'rgb(0, 0, 0)'};
        text-align: justify;
        font-family: 'Roboto', sans-serif;
        font-weight: 700;
        font-size: 13px;
	`;

    S.Ports = styled.div`
		display: flex;
		background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2));
	`;

    S.PortsContainer = styled.div`
        max-width: 640px;
        white-space: pre;
		flex-grow: 1;
		display: flex;
		flex-direction: column;

		&:first-of-type {
			margin-right: 10px;
		}

		&:only-child {
			margin-right: 0px;
		}
	`;

})(S || (S = {}));

export interface DefaultNodeProps {
    node: DefaultNodeModel;
    engine: DiagramEngine;
    app: JupyterFrontEnd;
    shell : ILabShell;
}

/**
 * Default node that models the DefaultNodeModel. It creates two columns
 * for both all the input ports on the left, and the output ports on the right.
 */
export class CustomNodeWidget extends React.Component<DefaultNodeProps> {

    generatePort = (port) => {
        return <CustomPortLabel engine={this.props.engine} port={port} key={port.getID()} node={this.props.node} />;
    };
    element:Object;
    state = {
        nodeDeletable: false,
        commentInput: this.props.node['extras']['commentInput'],
        showDescription: false,
        descriptionStr: "",

    };

    handleDeletableNode(key, event) {
        this.setState({
            [key]: event.target.checked
                ? this.props.node.setLocked(true)
                : this.props.node.setLocked(false),
        })
    }

    handleOnChangeCanvas(){
        this.props.engine.fireEvent({}, 'onChange');
    }

    /**
     * Allow to edit Literal Component
     */
    handleEditLiteral() {
        if (!this.props.node.getOptions()["name"].startsWith("Literal")) {
            return;
        }
        this.props.app.commands.execute(commandIDs.editNode)
    }

    /**
     * Allow to edit Comment Component
     */
    async handleEditComment(){
        let dialogResult = await showFormDialog({
            body: formDialogWidget(
                    <CommentDialog commentInput={this.state.commentInput}/>
            ),
            buttons: [Dialog.cancelButton(), Dialog.okButton({ label: ('Submit') })],
            focusNodeSelector: 'textarea'
        })

        if (dialogResult["button"]["label"] == 'Cancel') {
          // When Cancel is clicked on the dialog, just return
          return false;
		    }

        const newVal = dialogResult["value"]['']
        //  update value both in internal component state
        this.setState({ commentInput: newVal });
        // and in model object
        this.props.node['extras']['commentInput'] = newVal;
        this.handleOnChangeCanvas();
    }

    /**
     * Show/Hide Component's Description Tooltip
     */
    async handleDescription() {
        await this.setState({ showDescription: !this.state.showDescription });
        this.getDescriptionStr();
        ReactTooltip.show(this.element as Element);
    }

    renderText = text => {
        var renderer = new marked.Renderer();
        renderer.link = function(href, title, text) {
            var link = marked.Renderer.prototype.link.apply(this, arguments);
            return link.replace("<a","<a target='_blank'");
        };
        marked.setOptions({
            renderer: renderer
        });
        const __html = marked(text ?? '')
        return { __html }
    }

    getDescriptionStr() {
        let dscrptStr = this.props.node['extras']['description'] ?? '***No description provided***';
        this.setState({ descriptionStr: dscrptStr });
    }

    // Hide Error Tooltip
    hideErrorTooltip(){
        delete this.props.node.getOptions().extras["tip"];
        this.props.node.getOptions().extras["borderColor"]="rgb(0,192,255)";
    }
    
    render() {

        const isCommentNode = this.props.node['extras']['type'] == 'comment';
        const isLiteralNode = this.props.node.getOptions()["name"].startsWith('Literal');
        const isArgumentNode = this.props.node.getOptions()["name"].startsWith('Argument');

        if (isCommentNode) {
            return (
                <S.CommentContainer
                    onDoubleClick={this.handleEditComment.bind(this)}
                    selected={this.props.node.isSelected()}>
                    <S.TitleName><b>{this.props.node.getOptions().name}</b></S.TitleName>
                    <div className='comment-component-content'>
                        {this.state.commentInput}
                    </div>
                </S.CommentContainer>
            );
        } 
        else if (isLiteralNode) {
            return (
                <S.Node
                    borderColor={this.props.node.getOptions().extras["borderColor"]}
                    data-default-node-name={this.props.node.getOptions().name}
                    selected={this.props.node.isSelected()}
                    background={this.props.node.getOptions().color}
                    onDoubleClick={this.handleEditLiteral.bind(this)}>
                    <S.Title>
                        <S.TitleName>{this.props.node.getOptions().name}</S.TitleName>
                    </S.Title>
                    <S.Ports>
                        <S.PortsContainer>{_.map(this.props.node.getInPorts(), this.generatePort)}</S.PortsContainer>
                        <S.PortsContainer>{_.map(this.props.node.getOutPorts(), this.generatePort)}</S.PortsContainer>
                    </S.Ports>
                </S.Node>
            );
        }
        else if (this.props.node.getOptions()["name"] !== 'Start' && this.props.node.getOptions()["name"] !== 'Finish') {
            return (
                <div style={{position: "relative"}}>
                    {/** Description Tooltip */}
                    {this.state.showDescription && <div className="description-tooltip">
                        <div data-no-drag style={{ cursor: "default" }}>
                            <button
                              type="button"
                              className="close"
                              data-dismiss="modal"
                              aria-label="Close"
                              onClick={() => {
                                  this.setState({ showDescription: false });
                              }}>
                                <span aria-hidden="true">&times;</span>
                            </button>
                            <S.DescriptionName color={this.props.node.getOptions().color}>{this.props.node.getOptions()["name"]}</S.DescriptionName>
                            <div className="scrollable"
                                 onWheel={(e) => {
                                  e.stopPropagation();
                                  e.currentTarget.scrollBy(e.deltaX, e.deltaY);
                                }}
                            >
                                <p className="description-title">Description:</p>
                                <div
                                  className="description-container">
                                    <div className="markdown-body"
                                         dangerouslySetInnerHTML={this.renderText(this.state.descriptionStr)} />
                                </div>
                            </div>
                        </div>
                    </div>}
                    <S.Node
                      ref={(element) => {
                          this.element = element;
                      }}
                      data-tip data-for={this.props.node.getOptions().id} // Data for tooltip
                        borderColor={this.props.node.getOptions().extras["borderColor"]}
                        data-default-node-name={this.props.node.getOptions().name}
                        selected={this.props.node.isSelected()}
                        background={this.props.node.getOptions().color}
                        onDoubleClick={this.handleEditLiteral.bind(this)}>
                        <S.Title>
                            <S.TitleName>{this.props.node.getOptions().name}</S.TitleName>
                            <label data-no-drag>
                                <Toggle
                                    className='lock'
                                    checked={this.props.node.isLocked()}
                                    onChange={this.handleDeletableNode.bind(this, 'nodeDeletable')}
                                />
                                {!isArgumentNode && (
                                    <Toggle
                                        className='description'
                                        name='Description'
                                        checked={this.state.showDescription}
                                        onChange={this.handleDescription.bind(this)}
                                    />
                                )}
                            </label>
                        </S.Title>
                        <S.Ports>
                            <S.PortsContainer>{_.map(this.props.node.getInPorts(), this.generatePort)}</S.PortsContainer>
                            <S.PortsContainer>{_.map(this.props.node.getOutPorts(), this.generatePort)}</S.PortsContainer>
                        </S.Ports>
                    </S.Node>
                    {/** Error Tooltip */}
                    {(this.props.node.getOptions().extras["tip"] != undefined && this.props.node.getOptions().extras["tip"] != "") ?
                      <ReactTooltip
                        id={this.props.node.getOptions().id}
                        clickable
                        place="bottom"
                        className="error-tooltip"
                        arrowColor="rgba(255, 0, 0, .9)"
                        delayHide={100}
                        delayUpdate={50}
                        getContent={() =>
                          <div data-no-drag className="error-container">
                              <p className="error-title">Error</p>
                              <div className="markdown-body"
                                   dangerouslySetInnerHTML={this.renderText(this.props.node.getOptions().extras["tip"])} />
                              <button
                                type="button"
                                className="close"
                                data-dismiss="modal"
                                aria-label="Close"
                                        onClick={this.hideErrorTooltip.bind(this)}>
                                        <span aria-hidden="true">&times;</span>
                                    </button>
                                </div>
                            }
                            overridePosition={({ left, top }) => {
                                const currentNode = this.props.node;
                                const nodeDimension = { x: currentNode.width, y: currentNode.height };
                                const nodePosition = { x: currentNode.getX(), y: currentNode.getY() };
                                let newPositionX = nodePosition.x;
                                let newPositionY = nodePosition.y;
                                let offset = 0;

                                if (!this.props.shell.leftCollapsed) {
                                    // Some weird offset happened when left sidebar opened, need to add this
                                    let leftSidebar = document.getElementById('jp-left-stack');
                                    offset = leftSidebar.clientWidth + 2;
                                }

                                newPositionX = newPositionX - 184 + offset + (nodeDimension.x / 2);
                                newPositionY = newPositionY + 90 + nodeDimension.y;

                                const tooltipPosition = this.props.engine.getRelativePoint(newPositionX, newPositionY);

                                left = tooltipPosition.x;
                                top = tooltipPosition.y;
                                return { top, left }
                            }}
                        />
                        : null}
                </div>
            );
        }
        return (
            <S.Node
                borderColor={this.props.node.getOptions().extras["borderColor"]}
                data-default-node-name={this.props.node.getOptions().name}
                selected={this.props.node.isSelected()}
                background={this.props.node.getOptions().color}>
                <S.Title>
                    <S.TitleName>{this.props.node.getOptions().name}</S.TitleName>
                </S.Title>
                <S.Ports>
                    <S.PortsContainer>{_.map(this.props.node.getInPorts(), this.generatePort)}</S.PortsContainer>
                    <S.PortsContainer>{_.map(this.props.node.getOutPorts(), this.generatePort)}</S.PortsContainer>
                </S.Ports>
            </S.Node>
        );
    }
}
