// Lib
import React from 'react';
import {DragDropContext} from 'react-beautiful-dnd';
import Observer from '@researchgate/react-intersection-observer';
import {withTheme, injectSheet} from '../../lib/theme';
import StyleGrid from '../../lib/StyleGrid';
import BlockRoot from './BlockRoot';
import BlockSelection from './BlockSelection';
import {ID} from "@formanta/js";

const styling = theme => ({
    wrapper: {
        display: 'flex',
        flexDirection: 'column',
        [StyleGrid.mdUp]: {
            flexDirection: 'row',
        }
    },
    targets: {
        [StyleGrid.mdUp]: {
            marginRight: theme.gutter.base * 4 + 'px',
            width: '80%',
            flexGrow: 2,
        }
    },
    sources: {
        display: 'flex',
        alignSelf: 'flex-start',
        width: '100%',
        //maxWidth: '100vw',
        [StyleGrid.mdUp]: {
            width: 'auto',
            height: '100%'
        },
        '& .doctree--blocks-wrapper': {}
    },
});

class BlockTree extends React.Component {

    state = {
        // state based styles for sticky BlockSelection
        styleSelection: {},
    };

    constructor(props) {
        super(props);

        const blks = {...this.props.blocks};
        this.evts = {};

        // create data binding fn's
        for(let root in blks) {
            this.evts[root] = [];
            if(blks.hasOwnProperty(root)) {
                blks[root].forEach((elem) => {
                    elem.uuid = ID();
                    elem.addSave = this.addEvt;
                    elem.rmSave = this.rmEvt;
                });
            }
        }

        this.observerOpts = {
            onChange: this.toggleSticky,
            root: '#control-panel--content',
            rootMargin: '0% 0% -100%',
        };

        this.state = {
            blocks: blks,
            root_blocks: this.props.root_blocks
        };
    }

    componentWillUnmount() {
        if(this.props.rmSave) {
            this.props.rmSave();
        }
    }

    componentDidMount() {
        if(this.props.addSave) {
            this.props.addSave(() => {
                this.triggerAll();
                let data = {};
                for(let root_name in this.state.blocks) {
                    //todo optimize sanitizing/data crawling loop stacks / here and in trigger, evt. registering etc.
                    if(this.state.blocks.hasOwnProperty(root_name)) {
                        data[root_name] = {children: []};
                        this.state.blocks[root_name].forEach((elem) => {
                            // shallow copy for not really destroying `saveHandler` bindings
                            const e = {...elem};
                            delete e.addSave;
                            delete e.rmSave;
                            data[root_name].children.push(e);
                        });
                    }
                }

                return data;
            });
        }
    }

    triggerAll = () => {
        // crawl data from all registered events and update the data with current data inside of block
        for(let root in this.evts) {
            this.evts[root].forEach((evt, i) => {
                const dat = evt();
                this.updateBlockData(root, i, dat);
            });
        }
    };

    updateBlockData(root, i, data) {
        // update just the data, silently at first, must call set state afterward (e.g. like in onDragEnd)
        // eslint-disable-next-line
        this.state.blocks[root][i].data = {...data};
    }

    /**
     * @param {string} r root_name
     * @param {number} i index
     * @param evt
     */
    addEvt = (r, i, evt) => {
        this.evts[r][i] = evt;
    };

    /**
     * @param {string} r root_name
     * @param {number} i index
     */
    rmEvt = (r, i) => {
        if(this.evts[r] && this.evts[r][i]) {
            this.evts[r].splice(i, 1);
        }
    };

    deleteItem = (root_name, item_index) => {
        let blocks = {...this.state.blocks};
        if(blocks[root_name] && blocks[root_name][item_index]) {
            this.triggerAll();
            this.rmEvt(root_name, item_index);
            blocks[root_name].splice(item_index, 1);
            this.setState({
                blocks
            });
        }
    };

    onDragEnd = result => {
        const {destination, source} = result;

        if(!destination) {
            return;
        }

        const {droppableId: srcId, index: srcI} = source;
        const {droppableId: destId, index: destI} = destination;

        if(
            destId === srcId &&
            destI === srcI
        ) {
            return;
        }

        // todo: make it contextual based, e.g. on reorder only trigger snapshot for that root and not all
        // get changed data snapshot
        this.triggerAll();

        // get all blocks `{root_name: [{}, {}, ], }`
        let blocks = {...this.state.blocks};

        if('BlockSelection' === srcId) {
            // when dragging item from `BlockSelection`:
            // copy and add new, not move
            const {blockSelection} = this.props;
            const dest_root = [...blocks[destId]];

            const sel_blocks = [...blockSelection];
            const elem = {...sel_blocks[srcI]};
            elem.uuid = ID();
            elem.addSave = this.addEvt;
            elem.rmSave = this.rmEvt;

            dest_root.splice(destI, 0, elem);

            blocks[destId] = dest_root;

            this.setState({
                blocks
            });
            return;
        }
        // else reorder or move & reorder

        // save choosen for manipulation
        const src_block = blocks[srcId][srcI];

        if(destId === srcId) {
            // on same source and target root: only reorder
            const src_root = [...blocks[srcId]];
            // delete from old pos
            src_root.splice(srcI, 1);
            // add in new pos
            src_root.splice(destI, 0, src_block);
            blocks[srcId] = src_root;
        } else {
            // move from one to another
            const src_root = [...blocks[srcId]];
            const dest_root = [...blocks[destId]];

            // delete from old pos
            src_root.splice(srcI, 1);
            // add in new pos
            dest_root.splice(destI, 0, src_block);

            blocks[srcId] = src_root;
            blocks[destId] = dest_root;
        }

        this.setState({
            blocks
        });
    };

    toggleSticky = (evt) => {
    };

    render() {
        const {t, blockSelection, classes} = this.props;

        return (
            <DragDropContext onDragEnd={this.onDragEnd}>
                <div className={classes.wrapper}>
                    <div className={classes.targets}>
                        {this.state.root_blocks ? this.state.root_blocks.map((name) => (
                            <BlockRoot
                                key={name}
                                name={name}
                                deleteItem={this.deleteItem}
                                blocks={this.state.blocks[name]}
                                t={t}
                            />
                        )) : null}
                    </div>
                    <Observer {...this.observerOpts}>
                        <div className={classes.sources}>
                            {blockSelection ?
                                <BlockSelection
                                    blocks={blockSelection}
                                    t={t}
                                /> : null}
                        </div>
                    </Observer>
                </div>
            </DragDropContext>
        );
    }
}

export default withTheme(injectSheet(styling)(BlockTree));