import { Gesture } from "./Gesture.js";
import { Transform } from "./Transform.js";
import { NodeDetails, Connection, Node, NodeEditorOptions, NodeTestData } from "./index";
import "../asset/style.scss"

export class NodeEditor{
    protected nodes: Record<string, Node> = {};
    protected types: Record<string, any> = {};
    protected options: NodeEditorOptions = {};
    protected element: HTMLElement;
    protected containerElement: HTMLDivElement;
    protected addList: HTMLUListElement;
    protected details: NodeDetails;
    protected svg: SVGElement;
    protected gesture: Gesture;
    protected activeConnection: Connection;
    protected selectedNode?: Node;
    protected testData: Record<string,Record<string, NodeTestData>> = {};
    
    constructor(element: HTMLElement, options: NodeEditorOptions = {}){
        this.element = element;
        
        //Create container
        this.containerElement = document.createElement("div");
        this.containerElement.className = "e-node-container";
        this.element.appendChild(this.containerElement);
        this.element.NodeEditor = this;
         
        //Create addable nodes
        this.addList = document.createElement("ul");
        this.addList.className = 'e-node-add';
        this.element.appendChild(this.addList);
        this.containerElement.addEventListener('dragover', e => {
            e.preventDefault();
        });
        
        this.containerElement.addEventListener('drop', e => {
            let data = JSON.parse(e.dataTransfer.getData('application/e-node-add'));
            this.addNode(data.type, e.offsetX - data.x, e.offsetY - data.y);
        });
        
        this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        this.containerElement.appendChild(this.svg);
        //Loading config
        this.options.url = element.getAttribute('data-url') ?? options.url;
        this.options.testUrl = options.testUrl ?? this.options.url + "/test";
        this.options.runUrl = options.testUrl ?? this.options.url + "/run";
        
        //Add gestures
        this.element.classList.add('e-gesture');
        this.gesture = new Gesture(this.containerElement);

        //Create details panel
        this.details = new NodeDetails(this);
        
        //Load Nodes
        this.load();
        setTimeout(() =>  console.log(this.getData()), 200);
    }
    
    async load(){
        const response = await fetch(this.options.url, {
            headers: {'Content-Type': 'application/json'}
        });
        
        if(!response.ok){
            throw Error('Cant load nodes', response);
        }
        const data = await response.json();
        this.clearData();
        this.setData(data);
    }

    async run(isTest = false){
        const response = await fetch(isTest ? this.options.testUrl : this.options.runUrl, {
            headers: {'Content-Type': 'application/json'}
        });
        
        if(!response.ok){
            throw Error('Cant load nodes', response);
        }

        await this.load();
    }

    clearData(){
        for(let name in this.nodes){
            this.nodes[name].remove();
        }
    }
    
    setData(data){
        if(!data.types){
            throw Error('There is no type information.');
        }
        this.types = data.types;
        //Build Nodes
        for (const key in data.nodes) {
            const nodeData = data.nodes[key];
            if(!this.types[nodeData.type]){
                throw Error('There is no type information for the "'+nodeData.type+'" node.');
            }
            this.nodes[key] = new Node(this.types[nodeData.type], nodeData, this, key);
        }
        //Build connections
        if(data.connections){
            for(const key in data.connections){
                for(const key2 in data.connections[key]){
                    const k = data.connections[key][key2];
                    const source = this.nodes[k[0]].outputs[k[1]];
                    const target = this.nodes[key].inputs[key2];
                    if(source && target){
                        const connection = new Connection(source, target);
                    }
                }
            }
        }
        if(data.test){
            this.testData = data.test;
        }
        //Add avaialble nodes to add
        for (let i=0; i<data.add.length; i++){
            const li = document.createElement('li');
            li.setAttribute('draggable','true');
            li.innerHTML = data.add[i];
            li.ondragstart = this.addDragStart;
            let key = data.add[i];
            li.addEventListener('dragstart', e => {
                e.dataTransfer.setData('application/e-node-add', JSON.stringify({
                    x: e.offsetX,
                    y: e.offsetY,
                    type: key
                }));
            });
            this.addList.appendChild(li);
        }
    }
    
    getData(){
        const data = {
            connections: {},
            nodes: {}
        };
        for(let name in this.nodes){
            let node = this.nodes[name];
            data.nodes[name] = node.getData();
            let connections = {};
            const inputs = node.getInputs();
            for(let key in inputs){
                let input = inputs[key];
                if(input.getConnection()){
                    let source = input.getConnection().getSource();
                    connections[key] = [
                        source.getNode().getName(), source.getKey()
                    ];
                }
            }
            if(Object.keys(connections).length){
                data.connections[name] = connections;
            }
        }
        console.log(data);
        return data;
    }

    getTestData(){
        return this.testData;
    }
    
    async save(){
        const data = this.getData();
        const response = await fetch(this.options.url, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
        if(!response.ok){
            throw Error('NodeEditor: Save failed.', response);
        }
    }
    
    async addNode(type, x, y){
        let i = 1;
        while(this.nodes[type + i]){
            i++;
        }
        const name = type + i;
        const typeData = await this.getTypeData(type);
        console.log(typeData);
        this.nodes[name] = new Node(typeData,{
            'x': x,
            'y': y,
            'type': type
        }, this, name);
    }
    
    /**
     * Returns the details for a given node type.
     * If it is not available it will fetch from the server.
     * @param name Type name
     * @returns The details of the type.
     */
    async getTypeData(name: string){
        if(!this.types[name]){
            const response = await fetch(this.options.url + '/type/' + name, {
                headers: {'Content-Type': 'application/json'}
            });

            if(!response.ok){
                throw Error('Cant load type', response);
            }
            
            const data = await response.json();
            
            this.types[name] = data;
        }
        return this.types[name];
    }
    
    /**
     * The svg layer which contains the lines (Connections) between nodes.
     */
    public getSvg(){
        return this.svg;
    }
    
    /**
     * The container dom element.
     */
    public getContainerElement(){
        return this.containerElement;
    }
    
    /**
     * The transformation utility.
     */
    public getTransform(): Transform{
        return this.getGesture().getTransform();
    }
    
    /**
     * Te active connection which is movable with the mouse.
     * @returns The connection
     */
    public getActiveConnection(){
        return this.activeConnection;
    }
    
    /**
     * Sets a connection active, so the end will be controllable with the cursor
     * @param connection Connection to modify
     */
    public setActiveConnection(connection: Connection): void{
        this.activeConnection = connection;
    }

    /**
     * Returns the wrapper of the node editor.
     * @returns HTMLElement
     */
    getElement(){
        return  this.element;
    }

    getGesture(): Gesture{
        return this.gesture;
    }

    /**
     * Selects a node end shows the details panel for it
     * @param node Node to select
     */
    selectNode(node: Node): void{
        for(const node of Object.values(this.nodes)){
            node.setSelected(false);
        }
        node.setSelected(true);
        this.selectedNode = node;
        this.details.show(node);
    }

    /**
     * Deselect node node and hides the details panel
     */
    deselectNode(){
        this.selectedNode.setSelected(false);
        this.selectedNode = null;
        this.details.hide();
    }

    removeSelectedNode(){
        this.selectedNode.remove();
        this.deselectNode();
    }

    /**
     * Creates a color by hashing a string
     * @param text String to hash
     * @returns Color in hexa
     */
    static createColorFromString(text: string): string{
        let hash = 0;
        if (text.length === 0) return "";
        for (var i = 0; i < text.length; i++) {
            var char = text.charCodeAt(i);
            hash = ((hash<<5)-hash)+char;
            hash = hash & hash; // Convert to 32bit integer
        }
        const hashText = (hash & 0xFFFFFF).toString(16);
        return "#000000".substring(0, 7 - hashText.length) + hashText;
    }


    /**
     * Creates an element
     * @param type Name of the dom type, eg `div`
     * @param className Classname, null if not needed
     * @param text The text inside the node
     * @param target Target to append
     * @returns The created Dom element
     */
    static createElement(type: string, className: string,  text?: string, target?: HTMLElement): HTMLElement{
        const element = document.createElement(type);
        if(className){
            element.className = className;
        }
        if(text){
            element.innerText = text;
        }
        if(target){
            target.append(element);
        }
        return element;
    }

}