import React, { forwardRef, useCallback, useEffect, useState, useMemo } from 'react';
import deltaPoints from './Utils/delta';
import useRows from './useRows';
import Link from './Link';
import DeleteLinkButton from './DeleteLinkButton';

import {context} from './useDrop';

export default forwardRef(({
    getBlocks,
    onSelect: _onSelect,
    selected    
}, ref) => {
    const selectType = 'l',
        defaultArrowPosition = 'left',
        [newLink, setNewLink] = useState(null),
        [blocksMeta, setBlocksMeta] = useState(getBlocks()),
        blocksKey = blocksMeta.reduce((s, [bix,,{x,y,id}]) => `${s};${bix}:${id}-${x}-${y}`, ''),
        [linksMeta, registerLinks, unRegisterLinks, updateLinks, getLinks] = useRows(),
        linksKey = linksMeta.reduce((s, [lix, bix, rix, {ax, ay, relations}]) => `${s};${lix}-${bix}-${rix}:a${ax}x${ay},${(relations || []).join('-')}`, ''),
        getBlockByID = useCallback(iid => blocksMeta.find(([,,{id}]) => id === iid) || [,,{}]),
        getBlockByIX = useCallback(iix => blocksMeta.find(([bix]) => bix === iix) || [,,{}]),        
        getEndPoint = useCallback((points, x, y, tx, ty) => points 
            ? points.reduce((cLink, point) => (delta => (cLink && cLink.delta < delta && cLink)
            || {
                    x: point.x,
                    y: point.y,
                    delta,
                    position: point.position || defaultArrowPosition
            })(deltaPoints(x, y, point.x + tx, point.y + ty)), null)
            : {x: 0, y: 0, position: defaultArrowPosition}
        , []),
        onSelect = useCallback((e, bix, rix, bid) => {
            // console.log('select', bix, rix, bid)
            _onSelect([selectType, bix, rix, bid]);
        }, []),
        onDelete = useCallback((e, bix, rix, bid) => (([,,{removeRelation}]) => {
            e.stopPropagation();
            return removeRelation(rix, bid);
        })(getBlockByIX(bix)), [linksKey, blocksKey]),
        onAnchorDragStart = useCallback(() => {
            setNewLink({
                bix: context.drag[0],
                rix: context.drag[1],
                ax: context.drag[2],
                ay: context.drag[3],
                tx: context.drag[2],
                ty: context.drag[3],
                t: null,
            });
            _onSelect(null);
        }, []),
        onAnchorDragEnd = useCallback(() => {
            if(context.drag && context.over) {
                const from = blocksMeta.find(([bix]) => context.drag[0] === bix),
                    to = blocksMeta.find(([bix]) => context.over[1] === bix);
                from[2].addRelation(context.drag[1], to[2].id);
            }
            setNewLink(null);
        }, [blocksKey]),
        onAnchorDrag = useCallback(position => setNewLink(pState => ({...pState, tx: position[0], ty: position[1]})), []),
        onDragOver = useCallback((e, bix) => setNewLink(pState => ({...pState, t: bix})), []),
        onDragOut = useCallback((e, bix) => setNewLink(pState => ({...pState, t: null})), []),        
        {
            bix: nBix, 
            rix: nRix, 
            ax: nax, 
            ay: nay, 
            tx: ntx, 
            ty: nty,
            t: tBix
        } = newLink || [],
        targetBlock = useMemo(() => tBix && (([,,{positions, x: bx, y: by}]) => (({x, y, position}) => [x+bx, y+by, position])(getEndPoint(positions, nax, nay, bx, by)))(blocksMeta.find(([bix]) => tBix === bix) || []), [tBix, blocksKey]),
        links = useMemo(() => (linksMeta || []).reduce((lnks, [lix, bix, rix, {ax, ay, relations}]) => {
            (relations || []).forEach(bid => (([,,{getStyle, x: fx, y: fy}], [tbix,, {positions, x: tx, y: ty}]) => (({position, x: tpx, y: tpy}) => {
                if(tx !== undefined && ty !== undefined) {
                    lnks.push({
                        lix, 
                        bix, 
                        bid,
                        rix, 
                        key: `${lix}-${bid}`, 
                        width: getStyle('link.width'), 
                        x1: fx + ax, 
                        y1: fy + ay, 
                        x2: tx + tpx, 
                        y2: ty + tpy, 
                        targetPosition: position || defaultArrowPosition, 
                        color: getStyle('link.color')                
                    });
                }
            })(getEndPoint(positions, ax + fx, ay + fy, tx, ty)))(getBlockByIX(bix), getBlockByID(bid)))
            return lnks;
        }, []), [blocksKey, linksKey]),
        selectedLink = (selected && selected[0] === selectType)
            ? (links || []).find(({bix, bid, rix}) => selected[1] === bix && selected[2] === rix && selected[3] === bid)
            : null;

    useEffect(() => {
        if(ref) {
            ref.current = {
                onBlocksUpdate: setBlocksMeta,

                registerLinks, 
                unRegisterLinks, 
                updateLinks, 
                getLinks,

                onAnchorDragStart,
                onAnchorDragEnd,
                onAnchorDrag,
                onDragOver,
                onDragOut                
            };
            return () => {ref.current = null;}
        }
    }, [blocksKey, linksKey]);
    return (<React.StrictMode>
        {links.map(({lix, bix, bid, rix, key, width, x1, x2, y1, y2, targetPosition, color}) => (<Link 
            key = {key}
            selected = {selected && (selected[0] === selectType && selected[1] === bix && selected[2] === rix && selected[3] === bid)}
            width = {width}
            x1 = {x1}
            y1 = {y1}
            x2 = {x2}
            y2 = {y2}
            targetPosition = {targetPosition}
            color = {color}     
            onClick = {e => onSelect(e, bix, rix, bid)}          
            onClickDelete = {e => onDelete(e, bix, rix, bid)}
        />))}
        {newLink && (([,,{getStyle}]) => (<Link 
            key={`new-${nBix}-${nRix}`}
            width={getStyle('link.width')}
            x1={nax}
            y1={nay}
            x2={targetBlock ? targetBlock[0] : ntx}
            y2={targetBlock ? targetBlock[1] : nty}
            targetPosition={targetBlock ? targetBlock[2] : defaultArrowPosition}
            color={getStyle('link.color')}              
        />))(blocksMeta.find(([bix]) => nBix === bix) || [])}
        {selectedLink && (({bix, bid, rix, width, x1, x2, y1, y2, targetPosition, color}) => (<DeleteLinkButton 
            onDelete = {e => onDelete(e, bix, rix, bid)} 
            buttonSize = {15}
            bg = {'#fff'}
            border = {color}
            width = {width}
            x1 = {x1}
            x2 = {x2}
            y1 = {y1}
            y2 = {y2}
            targetPosition = {targetPosition}            
        />))(selectedLink)}
    </React.StrictMode>) 
})