import React, { useState, useMemo, createContext, useId, useCallback, useRef, useContext, useEffect } from 'react';
import pathForRect from './Utils/pathForRect';

import useStyles from './useStyles';
import useDrag from './useBlockDrag';
import useDrop from './useDrop';
import Row from './Row';
import useRows from './useRows';
import { Context as BlocksContext } from './Blocks';
import Anchor from './Anchor';
import Gradient from './Gradient';
import DropShadow from './DropShadow';

import './Block.scss';
import Tooltip from './Tooltip';

export const Context = createContext();
export default ({
    id,
    title,
    tooltip,
    position,
    relations,
    // rows,
    styles,   
    children,
    
    onMove: _onMove,
    onMoveEnd,
    onMoveStart,
    onSelect: _onSelect,
}) => {
    const baseClass = 'reneco-components-scheme-block',
        blockIX = useId(),
        ix = useRef(null),
        selectedType = 'b',
        {
            onSelect: blocksSelect, 
            selected: _selected,
            registerBlock, 
            unregisterBlock,
            updateBlock,
            getBlocks,

            onAnchorDragStart,
            onAnchorDragEnd,
            onAnchorDrag: _onAnchorDrag,
            onDragOver,
            onDragOut,
            registerLinks,
            unRegisterLinks,
            updateLinks,
            getLinks                
        } = useContext(BlocksContext),

        selected = _selected && (_selected[0] === selectedType && _selected[1] === blockIX),
        [hover, __setHover] = useState(false),
        _hover = useRef(hover),
        setHover = hover => {
            _hover.current = hover;
            __setHover(hover);
        },
        [hoverRow, setHoverRow] = useState(null),
        [_getStyle, clearStyles] = useStyles(styles),  
        getStyle = (style, isBG) => _getStyle(`${selected ? `selected.`:''}${_hover.current ? 'hover.' : ''}${style}`, isBG),
        getRowStyle = (style, hover, rowName, isBG) => getStyle(`row${hover ? '.hover' : ''}${rowName ? `.${rowName}` : ''}.${style}`, isBG),
        paddingVertical = getStyle('header.paddingVertical') || 0,
        paddingHorizontal = getStyle('header.paddingHorizontal') || 0,        
        borderWidth = getStyle('borderWidth') || 0,
        borderColor = getStyle('borderColor') || '#eee',
        topRadius = getStyle('topRadius') || 0,
        bottomRadius = getStyle('bottomRadius') || 0,  
        background = getStyle('background', true) || [],      
        dropShadow = getStyle('dropShadow') || '',
        headerBackground = getStyle('header.background', true) || [],
        [rowsMeta, _registerRow, _unregisterRow, _updateRow, getRows, getRowByIX] = useRows(),
        addRelation = (rowix, id) => (([,,,{addRelation: rAddRelation}]) => rAddRelation(id))(getRowByIX(rowix)),
        removeRelation = (rowix, id) => (([,,,{removeRelation: rRemoveRelation}]) => rRemoveRelation(id))(getRowByIX(rowix)),
        contentWidth = rowsMeta.reduce((w, [,,{width}]) => Math.max(w, width), 0),
        contentHeight = rowsMeta.reduce((h, [,,{height}]) => h+height, 0), 
        headerHeight = title ? (rowsMeta[0] && rowsMeta[0][2].height) : 0,   
        anchorRadius = getRowStyle('connector.radius') || 0,    
        [width, height] = useMemo(() => ((hasAnchor, fix) => {
            return [
                Math.round(contentWidth + borderWidth + paddingHorizontal*2 + (hasAnchor ? anchorRadius/2 : 0)) + fix,
                Math.round(contentHeight + (borderWidth)*2 + topRadius/2 + bottomRadius/2) + fix,
            ]
        })((rowsMeta || []).findIndex(([,,,{relatable}]) => relatable) > -1, borderWidth%2 ? 0 : .5), [contentWidth, contentHeight, styles]),
        gradientIDBlock = `${blockIX}-block`,
        gradientIDShadow = `${blockIX}-shadow`,
        gradientIDheader = `${blockIX}-header`,
  
        boxPath = pathForRect({
            x: 0,
            y: 0,
            width,
            height
        }, topRadius, bottomRadius),
        onSelect = useCallback(e => {
            blocksSelect && blocksSelect([selectedType, blockIX]);
            _onSelect && _onSelect();
        }, [_onSelect, blockIX]),
        updateRelatedLinks = useCallback((x = -1, y = -1, width) => {
            updateBlock(blockIX, ([$box, data]) => [$box, {
                ...data,
                x, 
                y,
                ...(width ? {positions: getTargetPositions()} : {})
            }], true);
            if(width) {
                const links = getLinks();
                links.filter(([,bid]) => bid === blockIX).forEach(([lid, bid, rid, opts]) => {
                    updateLinks(lid, [bid, rid, {...opts, ax: width || opts.ax}]);
                });
            }
        }, [blockIX]),         
        onMove = useCallback(([x, y]) => updateRelatedLinks(x, y), []),        
        [$positionBlock, transformString, [x, y]] = useDrag(position, borderWidth, {
            onMove,
            onMoveEnd,
            onMoveStart,  
            onSelect        
        }),
        [] = useDrop(blockIX, 'block', $positionBlock, {onDragOver, onDragOut }),
        registerRow = useCallback(meta => {
            const [rowix,, bbox, opts] = meta,
                // blocks = getBlocks(),
                rix = _registerRow(rowsMeta => {
                        registerLinks([`${blockIX}-${rowix}`, blockIX, rowix, {
                            ay: rowsMeta.slice(0, rix).reduce((h, [,,{height}]) => h+height, 0) + bbox.height/2,
                            ax: width,
                            relations: opts?.relations
                        }]);      
                    // console.log(blocks);
                    return meta;
                });
            return rix;
        }, [blockIX]),
        unregisterRow = useCallback(ix => {
            unRegisterLinks(`${blockIX}-${ix}`);
            return _unregisterRow(ix);
        }, [blockIX]),
        updateRow = useCallback((ix, meta) => {
            const [,, opts] = meta;
            updateLinks(`${blockIX}-${ix}`, ([,,props]) => [blockIX, ix, {
                ...props, 
                relations: opts?.relations
            }]);
            return _updateRow(ix, meta);
        }, [blockIX]),       
        onAnchorDrag = useCallback(([px, py]) => _onAnchorDrag([px+x, py+y]), [x,y]),
        getTargetPositions = useCallback(() => [
            {
                x: (width + borderWidth/2)/2,
                y: borderWidth/2,
                position: 'top'
            },
            {
                x: width+borderWidth,
                y: (height + borderWidth/2)/2,
                position: 'right'
            },
            {
                x: (width + borderWidth/2)/2,
                y: height+borderWidth,
                position: 'bottom'
            },
            {
                x: borderWidth/2,
                y: (height + borderWidth/2)/2,
                position: 'left'
            }            
        ], [width, borderWidth, height]);

    useEffect(() => ((eMouseOver, eMouseLeave) => {
        ix.current = registerBlock([blockIX, $positionBlock, {
            getStyle,
            getRowStyle,
            addRelation,
            removeRelation,
            id, 
            positions: getTargetPositions(),
        }]);
        // console.log('register', blockIX, getTargetPositions())
        const _setHover = () => setHover(true),
            _unsetHover = () => setHover(false);
        $positionBlock.current?.addEventListener(eMouseOver, _setHover);
        $positionBlock.current?.addEventListener(eMouseLeave, _unsetHover);
        return () => {
            unregisterBlock(blockIX);
            $positionBlock.current?.removeEventListener(eMouseOver, _setHover);
            $positionBlock.current?.removeEventListener(eMouseLeave, _unsetHover);
        }
    })('mouseover', 'mouseleave'), []);
    useEffect(() => {
        if(relations) {
            registerLinks([blockIX, blockIX, null, relations]);
            return () => {
                unRegisterLinks(blockIX);
            }            
        }
    }, [(relations || []).join('-')]);
    useEffect(() => {
        updateRelatedLinks(x, y, width);
        // console.log('links', getLinks())
        updateBlock(blockIX, ([$box, data]) => [$box, {
            ...data,
            x, 
            y,
            id,
            positions: getTargetPositions(),
            addRelation,
            removeRelation,            
        }]);
        // console.log('update', blockIX, getTargetPositions());
    }, [id, width, height, x, y]);
    // console.log(styles?.background, background)
    let h = headerHeight;
    return (<g
        // ref={drag}
        ref={$positionBlock}
        transform={transformString}
    >

        <defs>
            {background.length > 1 && <Gradient colors={background} id={gradientIDBlock} />}
            {headerBackground.length > 1 && <Gradient colors={headerBackground} id={gradientIDheader} />}
            {dropShadow && <DropShadow dropShadow={dropShadow} id={gradientIDShadow} />}
        </defs>
        <path
            className={`${baseClass}-bg`}
            stroke={borderColor}
            strokeWidth={borderWidth}
            fill={background.length > 1 ? `url(#${gradientIDBlock})`: background[0]}
            d={boxPath}
            filter={dropShadow && `url(#${gradientIDShadow})` || undefined}
        />
        <Context.Provider value={{
            getRowStyle,
            registerRow,
            unregisterRow,
            updateRow,
            rowsMeta,
            width: contentWidth,
            height,
            borderColor,
            borderWidth
        }}>
            {title && <g className={`${baseClass}-title`}>
                {<path
                    className={`${baseClass}-title-bg`}
                    fill={headerBackground.length > 1 ? `url(#${gradientIDheader})`: headerBackground[0]}
                    stroke={getStyle('header.borderColor') || ''}
                    strokeWidth={getStyle('header.borderWidth') || 0}
                    d={pathForRect({
                        x:0, y:0, width, height: headerHeight
                    }, topRadius, height === headerHeight && bottomRadius)}
                />}
                <g transform={`translate(${paddingHorizontal}, ${(paddingVertical)})`}>
                    <Row 
                        className={`${baseClass}-title-text`}
                        content={title}    
                        paddingVertical={paddingVertical}
                        paddingHorizontal={paddingHorizontal}
                        align={getStyle('header.textAlign')}
                        color={getStyle('header.textColor')}
                        decoration={getStyle('header.textDecoration')}
                        fontFamily={getStyle('header.fontFamily')}
                        fontSize={getStyle('header.fontSize')}
                        fontWeight={getStyle('header.fontWeight')}
                        fontStyle={getStyle('header.fontStyle')}
                        icon={getStyle('icon')}
                        iconSize={getStyle('iconSize')}                            
                    />
                </g>
            </g>}     
            {tooltip && <Tooltip 
                blockWidth = {width}
                right={topRadius}
                fontStyle = {getStyle('header.counter.fontStyle') || ''}
                fontWeight = {getStyle('header.counter.fontWeight') || ''}
                fontSize = {getStyle('header.counter.fontSize') || ''}
                fontFamily = {getStyle('header.counter.fontFamily') || ''}
                paddingHorizontal = {getStyle('header.counter.paddingHorizontal') || ''}
                paddingVertical = {getStyle('header.counter.paddingVertical') || ''}
                topRadius = {getStyle('header.counter.topRadius') || ''}
                bottomRadius = {0}
                borderColor = {getStyle('header.counter.borderColor') || ''}
                text = {tooltip}
            />}
            <g transform={`translate(${paddingHorizontal}, ${(paddingVertical)})`}>
                {children}   
                {(title ? rowsMeta.slice(1) : rowsMeta).map(([rix,,{height}, {relatable, name, onClick, onOver, onLeave}], ix) => {
                    const positionY = h + height/2 - anchorRadius/2;
                    h += height;
                    return (<React.Fragment key={rix}>
                        {onClick && <path 
                            className={`${baseClass}-handle`}
                            d={pathForRect({
                                x: 0,
                                y: h - height - paddingVertical,
                                width: width - 2*paddingHorizontal,
                                height: height
                            }, 0, 0)}
                            onClick={onClick}
                            onMouseOver={onOver}
                            onMouseLeave={onLeave}                            
                        />}
                        {relatable && <Anchor 
                            y={positionY}
                            x={width - paddingHorizontal}
                            radius={anchorRadius}
                            dropShadow={getRowStyle('connector.dropShadow', hoverRow === ix, name)}
                            background={getRowStyle('connector.background', hoverRow === ix, name)}
                            borderWidth={getRowStyle('connector.borderWidth', hoverRow === ix, name)}
                            borderColor={getRowStyle('connector.borderColor', hoverRow === ix, name)}                            
                            onMoveStart={onAnchorDragStart}
                            onMove={onAnchorDrag}
                            onMoveEnd={onAnchorDragEnd}
                            // setDragContext={setAnchorDragContext([rix, positionY])}
                            context={[blockIX, rix, x + width, y+positionY+paddingVertical]}
                        />}
                    </React.Fragment>)
                })}   
            </g>
        </Context.Provider>   
    </g>)
}