import React, {useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect} from 'react';
import {batch, shallowEqual,connect, useSelector, useDispatch} from 'react-redux'
import * as TaskHeader from './TaskHeader'
import * as Immer from 'immer'
import * as Drag from './Drag'
import * as Data from './Data'
import * as Common from './common'
import * as Step from './Step'
import * as Steps2 from './Steps2'
import * as Palette from './Palette'
import * as Task from './Task'
import useResizeAware from 'react-resize-aware';
import * as Settings from './Settings'
import * as CreateTask from './CreateTask'
import * as ModelViewer from './ModelViewer'
import * as VersionControl from './VersionControl'
import * as SQLStepMenu from './SQLStepMenu'
import * as StepMenu from './StepMenu'
import * as Datasets from './Datasets'
import * as Materialization from './Materialization'

import * as Shared from 'placeholder-shared'
import * as Antd from 'antd'

import {
	PlusOutlined,
	DatabaseOutlined,
	PartitionOutlined,
	CaretDownOutlined,
	BranchesOutlined,
	RollbackOutlined,
	CodeOutlined,
	BookOutlined,
	SettingOutlined,
	TableOutlined,
	ThunderboltOutlined
} from '@ant-design/icons';

import * as _ from 'lodash'
import './Tree.scss'

var classnames = require('classnames')

interface MouseLocation {
	x: number;
	y: number;
}

const calculateOffset = (initialMouseLocation:MouseLocation, currentMouseLocation:MouseLocation) => {
	return {
		'x': currentMouseLocation.x - initialMouseLocation.x,
		'y': currentMouseLocation.y - initialMouseLocation.y
	}
}
export const getMainWindowOffset = (id:string) => {
	const { left, top} = document.getElementById(id).getBoundingClientRect()
	const pos = document.getElementsByClassName("mainWindow")[0].getBoundingClientRect()
	return {
		left:
			left - pos.left, top: top - pos.top 
	}
}
export const getMainWindowPos = () => {
	const {top,left}= document.getElementsByClassName("mainWindow")[0].getBoundingClientRect()
	return {top,left}
}

const useMouseScale = ()=> {
	const [scale, setScale ] = useState(1)
	const wheelCB = useCallback((e: React.WheelEvent) => {
		let newScale = scale + e.deltaY * -0.005
		newScale = Math.min(Math.max(.125, newScale), 4);
		//console.log("in mouse scale", scale, e.deltaY, newScale)
		setScale(newScale)
		e.preventDefault()
		e.stopPropagation()
	}, [setScale, scale])
	return [scale, wheelCB]
}

const useMouseDrag = (offsetName:string, reset:boolean) => {
	const [isDragging, setDragging] =  useState(false)
	const dispatch = useDispatch()
	const [lastMouseLocation, setLastMouseLocation] = useState(
		null)
		const lastMouseLocationRef = useRef(lastMouseLocation)

	let toThrottle = (newMouseLocation: MouseLocation) => {
		if (isDragging && lastMouseLocationRef.current) {
			let offset = calculateOffset(lastMouseLocationRef.current, newMouseLocation)
			if (offset.x || offset.y) {
				//console.log("change ui offset", lastMouseLocationRef.current, newMouseLocation, offset)
				dispatch(Shared.State.changeUIOffset(offsetName, offset))
				setLastMouseLocation(newMouseLocation)
			}
		}
	}
	let throttledFunc = useCallback(_.throttle(toThrottle, 25), [
		isDragging
	])
	/*
	let throttledFunc = useCallback(toThrottle, [
		isDragging
	])
	*/
	const mouseMoveFunc = useCallback((e: React.MouseEvent) => {
		let newMouseLocation: MouseLocation = {
			x: e.pageX, y: e.pageY
		}
		throttledFunc(newMouseLocation)
	}, [throttledFunc])
    const [hasDragged, setHasDragged] = useState({x:0,y:0})
	const onMouseDown = useCallback((e:React.MouseEvent) => {
		console.log("on mouse down e", e)
		//dispatch(Shared.State.getShowStepAction(0))
		e.preventDefault()
		//e.stopPropagation()
		if (e.buttons == 1){
			setDragging(true)
		}
		if (reset) {
			const {x,y, width,height, top} = document.getElementsByClassName("mainWindow")[0].getBoundingClientRect()
			dispatch(Shared.State.changeUIOffset(offsetName, {x:0,y:0,initialX:e.pageX - x, initialY:e.pageY - y}))
		}
		setLastMouseLocation({
			x: e.pageX,
			y: e.pageY
		})
        setHasDragged({ x: e.clientX, y: e.clientY })
	}, [reset,setDragging,offsetName, dispatch, setLastMouseLocation])
	const onMouseUp = useCallback((e:React.MouseEvent) => {
		//console.log("on mouse up")
        const inTolerance = (diff) => {
            return Math.abs(diff) < 3 

		}
		let insideStep = false;
		for (let elem in (e as any).path){
			const classList = (e as any).path[elem].classList
			if (classList && classList.contains('StepNodeContainer')){
				insideStep = true;
			}
		}
		
        if (e.button == 0 && !insideStep &&  !e.shiftKey &&  inTolerance(e.clientX  -  hasDragged.x)&&  inTolerance(e.clientY -  hasDragged.y)) {
			dispatch(Shared.State.changeUIState('mainWindowHide', true))
			dispatch(Shared.State.getSetCurrentStepGraph(0))
        }
		e.stopPropagation()
		e.preventDefault()
		setDragging(false)
		setLastMouseLocation(null)
	}, [setDragging, setLastMouseLocation,dispatch,hasDragged.x,hasDragged.y])

	const onMouseLeave = useCallback((e:React.MouseEvent)=> {
		e.stopPropagation()
		e.preventDefault()
		setDragging(false)
		setLastMouseLocation(null)
	}, [setDragging, setLastMouseLocation])

	let offsets = useSelector(state => (state as any).UIState[offsetName])
	if (!offsets) {
		offsets = [0,0]
	}
	useLayoutEffect(() => {
		lastMouseLocationRef.current = lastMouseLocation
	})
	return [onMouseDown, onMouseUp, mouseMoveFunc, onMouseLeave, offsets]
}

type iconProviderFuncType = (key:string, node:Shared.RequestTree.RequestNode<any>) => any;
type titleProviderFuncType = (key:string, node:Shared.RequestTree.RequestNode<any>) => any;
export const getTreeNodeForRequestNode = (requestNode,
	iconProviderFunc: iconProviderFuncType, titleProviderFunc: titleProviderFuncType) => {
	let children = []
	let key = null
	let isLeaf = false

	switch (requestNode.nodeType) {
		case Shared.RequestTree.RequestNodeType.folder: {
			let folderID = requestNode.nodeID
			key = Shared.RequestTree.getKey(Shared.RequestTree.RequestNodeType.folder, folderID)
			for (let child in requestNode.children) {
				children.push(getTreeNodeForRequestNode(requestNode.children[child], iconProviderFunc,
					titleProviderFunc))
			}
			break;
		}
		case Shared.RequestTree.RequestNodeType.request: {
			let taskID = requestNode.nodeID
			key = Shared.RequestTree.getKey(Shared.RequestTree.RequestNodeType.request, taskID)
			isLeaf = true;
			for (let child in requestNode.children) {
				children.push(getTreeNodeForRequestNode(requestNode.children[child], iconProviderFunc, titleProviderFunc))
			}
			break;
		}
		case Shared.RequestTree.RequestNodeType.task: {
			let taskID = requestNode.nodeID
			key = Shared.RequestTree.getKey(Shared.RequestTree.RequestNodeType.task, taskID)
			for (let child in requestNode.children) {
				children.push(getTreeNodeForRequestNode(requestNode.children[child], iconProviderFunc,
					titleProviderFunc))
			}
			break;
		}
		case Shared.RequestTree.RequestNodeType.api: {
			let apiID = requestNode.nodeID
			key = Shared.RequestTree.getKey(Shared.RequestTree.RequestNodeType.api, apiID)
			for (let child in requestNode.children) {
				children.push(getTreeNodeForRequestNode(requestNode.children[child], iconProviderFunc,
					titleProviderFunc))
			}
			break;
		}
		default:
			console.assert(false)
	}
	let icon = iconProviderFunc(key, requestNode)
	let title = titleProviderFunc(key, requestNode)
	return <Antd.Tree.TreeNode className={classnames('TreeNode')}
		icon={icon}
		key={key}
		title={title}>
		{children}
	</Antd.Tree.TreeNode>
}

export const TranslateLayer = (props:{ uiOffsetName:string, children:React.ReactNode})=> {
	const dragStep = useSelector((state) => (state as any).UIState.dragStep)
	const {children , uiOffsetName} = props
	const [scale, wheelCB] = useMouseScale()
	let [mouseDown, mouseUp, mouseMove, mouseLeave,offsets] = useMouseDrag(uiOffsetName, false)
	const portDragging =  (dragStep && dragStep.type == Drag.DragPayloadType.port) 
	const onMouseDown = useCallback((e) =>{
			mouseDown(e)
	}, [mouseDown])

	useEffect(() => {
		const mouseUpCB = (e: any) => {
			mouseUp(e)
		}
		window.addEventListener('mouseup', mouseUpCB)
		return () => {
			window.removeEventListener('mouseup',mouseUpCB)
		}
	}, [mouseUp])
	useEffect(() => {
		const mouseMoveCB = (e: any) => {
			//console.log("in mouse move",e)
			mouseMove(e)
		}
		window.addEventListener('mousemove', mouseMoveCB)
		return () => {
			window.removeEventListener('mousemove',mouseMoveCB)
		}
	}, [mouseMove])
	useEffect(() => {
		const mouseLeaveCB = (e: any) => {
			mouseLeave()
		}
		window.addEventListener('mouseLeave', mouseLeaveCB)
		return () => {
			window.removeEventListener('mouseLeave', mouseLeaveCB)
		}
	}, [mouseLeave])
	const onDragCB = useCallback((e) => {
		console.log ("is dragging", e.pageX, e.pageY)
		//mouseMove2(e)
	}, [])
	const onDragOverCB = (e:React.MouseEvent) =>{
		e.preventDefault()
		return 
	}
	useEffect(() => {
		divRef.current.addEventListener('wheel', wheelCB, {passive:false})
		return () => {
			divRef.current.removeEventListener('wheel', wheelCB)
		}
	})
	const divRef = useRef(null)
	const isBackdropActivated = useSelector(state => (state as any).UIState.isBackdropActivated)
	return <div
	ref={divRef}
		onDragOver={onDragOverCB}
		onDrag={onDragCB}
		onMouseDown={onMouseDown}
		onWheel={wheelCB as any}
		/*
		onDragStart={onDragStartCB}
		onDragEnd={onDragEndCB}
		*/
		className={Common.classNames({
			'treeDivContainer':true,
			positionAbsolute:true,
			isBackdropActivatedTree:isBackdropActivated
		})}
	>
		<div className='TaskWrapper TranslateLayer ' style={{ 'transform': `scale(${scale}, ${scale}) translate(${offsets.x}px, ${offsets.y}px` }}>
			{children}
		</div>
	</div>
}


export const Tree = (props:{}) => {
	const currentTask = useSelector(state => (state as any).currentTask)
	const currentStepGraph = useSelector(state => (state as any).currentStepGraph)
	const currentStepGraphData = useSelector(state => (state as any).steps[currentStepGraph])
	const dragStep = useSelector(state  => (state as any).UIState.dragStep)
	const hasShownModal = useSelector(state  => (state as any).UIState.hasShownModal)
	//console.log('hasShownModal: ', hasShownModal);
	const dispatch = useDispatch()
	const edgesInfo = Task.useGetEdges()


	const graphViewMode = useSelector(state => Shared.Common.getValueSafe(state, ['UIState', 'graphViewMode'], 'graph'))

	const mainWindowActiveTab = useSelector(state => Shared.Common.getValueSafe((state as any),['UIState', 'mainWindowActiveTab'], 'graph'))

	//console.log("got run state", runState)
	//let [sizes, setSizes] = useState(new Step.SizeData())

	const [resizeListener, taskSize] = useResizeAware();
	const onResize = useCallback((width: number, height: number) => {
		console.log("GOT RESIZE OF TASK ", width, height)
		/*
		setScaleX(taskSize.width/width)
		setScaleY(taskSize.height/height)
		*/
	}, [taskSize])
	const onDropCB = (e) =>  {
		const mainWindowPos = getMainWindowPos()
		const x = e.nativeEvent.pageX - mainWindowPos.left
		const y = e.nativeEvent.pageY - mainWindowPos.top
	}
	const onChangeTab = useCallback((activeKey) => {
		const keyToShow = parseInt(activeKey) || activeKey == '0' ? parseInt(activeKey) : activeKey
		batch(() => {
			dispatch(Shared.State.getShowStepAction(keyToShow))
		})
	},[dispatch])
	const getGraphTab = () => {
		return <Common.DivWithClassName className='rowFlexBox flexGrow height100Percent width100Percent'>
			<Datasets.DatasetTree/>
			<Common.DivWithClassName className='columnFlexBox flexGrow height100Percent '>
				<Common.DivWithClassName className='rowFlexBox flexGrow'>
					<Common.DivWithClassName style={{ width: '70%' }} className='flexGrow columnFlexBox'>
						<Common.DivWithClassName className='flexStay'>
							<Palette.StatePalette />
						</Common.DivWithClassName>
						<Common.DivWithClassName className={
							Common.classNames({
								flexGrow: true,
								positionRelative: true,
								displayNone: graphViewMode == 'datasets'
							})}
						>
							<TranslateLayer uiOffsetName={"TreeOffset"}>
								<Drag.DropTargetContainer
									className='height100Percent width100Percent columnFlexBox flexGrow'
									onDrop={onDropCB}>
									{resizeListener}
									<Common.DivWithClassName className={classnames({
										'TreeLayerContainer': true,
										'TreeTaskItems': true,
									})}
									>
										{taskItem}
									</Common.DivWithClassName>
								</Drag.DropTargetContainer>
							</TranslateLayer>
						</Common.DivWithClassName>
						{graphViewMode == 'datasets' &&
							<Datasets.Datasets />
						}
					</Common.DivWithClassName>
				</Common.DivWithClassName>
				{currentStepGraph != 0 && graphViewMode == 'graph' && <Data.DataViewer showStepOnly={true} stepCounter={currentStepGraph} />}
			</Common.DivWithClassName>
		</Common.DivWithClassName>
	}

	const getModelViewer = ()=>{
		return null;
		//return <ModelViewer.ModelViewer/>
	}
	let taskItem = <Task.StateGraphForTask drawMode={Task.DrawMode.normal} dagreOptions={Common.TreeLayoutOptionsMain} taskID={currentTask} onResize={onResize} />
	//let UIState: {[k: string]: any} = {hasShownModal: false};
	const shownModal = ()=>{
		dispatch(Shared.State.changeUIState('hasShownModal', true))
	}
	const getBrowser = ()=> {
		return null;
		//return <ModelViewer.ModelViewer/>
	}

	const currentStep = useSelector(state => (state as any).currentStep)
	const currentStepType = useSelector(state => currentStep && Number.isInteger(currentStep) && (state as any).steps[currentStep].operation.type)
	const tabs = useSelector(state => (state as any).sqlTabs)
	const steps = useSelector(state => (state as any).steps)
	const materializations = useSelector(state => (state as any).materializations)
	const getModelTabs = () => {
		return tabs.map((pane) => {
			if (!Number.isInteger(pane) && pane.startsWith('materialization')) {
				const mCounter = Materialization.getMaterializationCounter(pane)
				const materialization = materializations[mCounter]
			return < Antd.Tabs.TabPane tab={<span><ThunderboltOutlined/>{materialization.name}</span>} key={(pane).toString()} closable={true}
					className='columnFlexBox flexGrow'
				>
					{currentStep == pane &&
						<Materialization.MaterializationEditor />
					}
				</ Antd.Tabs.TabPane>

			}
			else{
				const stepData = steps[pane]
				return < Antd.Tabs.TabPane tab={Shared.Runner.getName(stepData)} key={(pane).toString()} closable={true}
					className='columnFlexBox flexGrow'
				>
					{currentStep == pane && currentStepType == Shared.Runner.StepTypes.sql && <SQLStepMenu.FullSQLStepMenu />}
					{currentStep == pane && currentStepType == Shared.Runner.StepTypes.sourceInput && <StepMenu.StateSourceStepMenu />}
				</ Antd.Tabs.TabPane>
			}
		})
	}
	const createMaterialization = Step.useCreateMaterialization()
	const onEdit = useCallback((targetKey,action) => {
		if (action == 'add') {
			/*
			dispatch((dispatch, getState) => {
				//dispatch(Shared.State.changeUIState('mainWindowHide', false))
				let newStepCounter = Step.getNextID(getState().treeData[1])
				const operation = Steps2.StepDict[Shared.Runner.StepTypes.sql].newOperation()

				batch(() => {
					dispatch(Shared.State.getCreateStepAction(operation, currentTask, newStepCounter, "", true, 0, 0))
					dispatch(Shared.State.getUpdateStepOperationAction(newStepCounter, 'sql',
						`SELECT * FROM \$\{someTable\}`
						, false))
					dispatch(Shared.State.getShowStepAction(newStepCounter))
				})
			})
			*/
		}
		else if (action == 'remove') {
			dispatch(Shared.State.removeStepFromTabs(parseInt(targetKey)? parseInt(targetKey): targetKey))
		}

	},[dispatch])
	const closeTabs = <Common.DivWithClassName className='rowFlexBox flexStay'><Antd.Button disabled={!currentStepGraph && !currentStep} onClick={(e) => {
		dispatch(Shared.State.previousTabAction())
	}} size='small'>
		<PartitionOutlined rotate={180} /> 
		{currentStep ? 'Back to Graph' : currentStepGraph ? `Go to ${Shared.Runner.getName(currentStepGraphData)}` : ''} (⌘-E)
		</Antd.Button>
		<Antd.Dropdown.Button size='small' 
		overlay={<Antd.Menu>
			<Antd.Menu.Item onClick={() => {
				dispatch(Shared.State.closeAllTabs())
			}}>Close All Tabs</Antd.Menu.Item>
		</Antd.Menu>
		}>
		</Antd.Dropdown.Button>
	</Common.DivWithClassName>
	const dropdownMenu = <Antd.Menu>
		<Antd.Menu.Item onClick={() => {
			dispatch(Shared.State.changeUIState('isAddSourceModalVisible', true));
		}}>
			<DatabaseOutlined/> Add Sources
		 </Antd.Menu.Item>
		<Antd.Menu.Item onClick={() => {
			dispatch((dispatch, getState) => {
				let newStepCounter = Step.getNextID(getState().treeData[1])
				const operation = Steps2.StepDict[Shared.Runner.StepTypes.sql].newOperation()

				batch(() => {
					dispatch(Shared.State.getCreateStepAction(operation, currentTask, newStepCounter, "", true, 0, 0))
					dispatch(Shared.State.getUpdateStepOperationAction(newStepCounter, 'sql',
						`SELECT * FROM \$\{someTable\}`
						, false))
					dispatch(Shared.State.getShowStepAction(newStepCounter))
					dispatch(Step.postAddActions([], newStepCounter))
				})
			})

		}}>
			<CodeOutlined/> Create New Model
			</Antd.Menu.Item>
		<Antd.Menu.Divider />
		<Antd.Menu.Item onClick={() => {
			createMaterialization()
		}}>
			<ThunderboltOutlined /> Create new materialization template 
		 </Antd.Menu.Item>
	</Antd.Menu>
	const addIcon = <Antd.Dropdown trigger={['click']} overlay={dropdownMenu}>
		<span>+</span>
	</Antd.Dropdown>

	return <Common.DivWithClassName className=' flexGrow columnFlexBox'>
			<Antd.Tabs onEdit={onEdit} 
			tabPosition='top'
			addIcon={addIcon}
			tabBarExtraContent={closeTabs} 
			size="small" onChange={onChangeTab}
			//defaultActiveKey={"3"}
			activeKey={currentStep.toString()} hideAdd={false} 
			type='editable-card' 
			className='columnFlexBox  TabsParent flexGrow'>
				<Antd.Tabs.TabPane
				forceRender={true}
				closable={false}
				style={{outline:'none'}}
				  className='height100Percent flexGrow columnFlexBox'
					tab={
						<span>
							<BranchesOutlined />
							Browser</span>
					}
					key={0}
				>
					{getGraphTab()}
				</Antd.Tabs.TabPane>
				{getModelTabs()}
				{/*
				<Antd.Tabs.TabPane
				className='height100Percent flexGrow columnFlexBox'
				style={{outline:'none'}}
					tab={
						<span>
							<CodeOutlined/>
							Model Viewer
						</span>
					}
					key="modelViewer"
				>
					{getModelViewer()}
				</Antd.Tabs.TabPane>
				*/}
				{/*}
				<Antd.Tabs.TabPane
				className='height100Percent flexGrow columnFlexBox BrowserTab'
				style={{outline:'none'}}
					tab={
						<span>
							<BookOutlined/>
							Browser
						</span>
					}
					key="browser"
				>
					{getBrowser()}
				</Antd.Tabs.TabPane>
				*/}
			</Antd.Tabs>
			
				<Antd.Modal

				title="Data Warehouse Settings"
				visible={!hasShownModal}
				width="80%"
				onOk={shownModal}
				onCancel={shownModal}
			>
				<Settings.SnowflakeSettings />
				</Antd.Modal>
		</Common.DivWithClassName>
}

export var StateTree = Tree

export const GetNextNumberForName = (dict, namePrefix) => {
	let highestNumber = 0;
	for (let key in dict) {
		let res = dict[key]['name'].match(`^${namePrefix} ([0-9]+)$`)
		if (res && parseInt(res[1]) > highestNumber)
			highestNumber = parseInt(res[1])
	}
	return highestNumber + 1
}

export const AddNewNode = (currentState:any, tree:Shared.RequestTree.RequestTree, parentKey:string, type:Shared.RequestTree.RequestNodeType) => {

    //get new node 
    let newNode = new Shared.RequestTree.RequestNode<any>(type, 0)
    let promiseChain = null
    switch(type) {
        case Shared.RequestTree.RequestNodeType.folder:
			break;
        case Shared.RequestTree.RequestNodeType.task:{
                let getNextTaskNumber = GetNextNumberForName(currentState['tasks'], 'Task')
                let newTaskID = CreateTask.CreateTaskFunc(false, `Task ${getNextTaskNumber}`)
                newNode.nodeID = newTaskID
            break;
            }
        case Shared.RequestTree.RequestNodeType.request:{
            break;
		}
		default:
			console.assert(false)
    }

    //then save and add new tree 
    let newTree = tree.clone()

        //get location 
    let children = null
    if (!parentKey) {
        children = newTree.children
    }
    else {
        newTree.callForKey(parentKey, (n, index, parentChildren) => {
            children = n.children
        })
    }
    children.unshift(newNode)
    Shared.RequestTree.SaveRequestTree(newTree)
    return newNode;
}

