import React, { useEffect, useReducer, useRef } from 'react'
import { useModel } from 'umi'

interface State {
    childrenDivs: Element[],//实例
    children?: JSX.Element[],//jsxElement
    configs?: { w: number, h: number, t: number, l: number }[],//配置
    size?: number,//屏幕宽度
    newColumn?: number//本次展示列数
}
interface Action {
    type: 'addRef' | 'children' | 'configs' | 'size' | 'newColumn',
    params?: any,
}
function reducer(state: State, action: Action) {
    let { type, params } = action
    switch (type) {
        case 'addRef':
            let arr = state.childrenDivs
            arr[params.index] = params.ref//替换指定位置的实例
            arr = arr?.filter((item: Element) => { return item })//清空空实例
            return { ...state, childrenDivs: arr }
        case 'children':
            return { ...state, children: params.children }
        case 'configs':
            if (JSON.stringify(params.configs) === JSON.stringify(state.configs)) {//传入的配置和老配置一样不更新
                return { ...state }
            }
            return { ...state, configs: params.configs }
        case 'newColumn':
            return { ...state, newColumn: params.newColumn }
        case 'size':
            return { ...state, size: params.size }
        default:
            return { ...state }
    }
}
/**
 *  
 **高阶函数处理瀑布流传入JSX.ELEMENT
* @example
 *<HocWaterFallBox>
 *   <div>//必要的div包裹,缺少会报错
 *     {
 *       getData?.data?.items?.map((item: any, index: number) => {
 *         return <div key={item.id} className={action.id === item.id ?{...}:{...}}>//必要的div包裹,缺少会报错,不要在此div设置统一className，此处className只在配置选中需求时JS控制添加，当盒子存在className不会做样式更新处理更新注意！
 *             <Img src='...' /> //要渲染的盒子模型
 *            </div>
 *        })
 *      }
 *   </div>
 *</HocWaterFallBox>  
 * @param {number} column 一行展示多少列，column和childrenWidth二选一，设置了column自动计算子元素的宽度
 * @param {number} childrenWidth 默认子元素宽度300，可自行设置支持number，设置了子元素宽列就以宽度计算
 * @param {number}  width 容器宽默认100%，可自行设置支持number
 * @param {number} margin 子元素左右间隔默认20px，可自行设置支持number，底部间隔默认为0
 * @param  { JSX.Element} props JSX.Element
 * @return { JSX.Element} JSX.Element
 */
function HocWaterFallBox(props: { children: JSX.Element, childrenWidth?: number, width?: number, margin?: number, column?: number }) {
    const { childrenWidth = 300, width, margin = 20, column } = props
    const [state, dispatch] = useReducer(reducer, { childrenDivs: [], children: null, configs: [] })
    let { collapsed } = useModel('@@initialState', model => ({ collapsed: model?.initialState?.collapsed }))
    let div: { current: HTMLDivElement | null } = useRef(null)
    let { childrenDivs, configs, children, size, newColumn } = state
    //监听浏览器宽度变化
    useEffect(() => {
        window.onresize = function () {
            dispatch({ type: 'size', params: { size: document.body.scrollWidth } })
        }
        return () => {
            window.onresize = null
        }
    }, [])
    //计算配置
    useEffect(() => {
        let children = props?.children?.props?.children
        let bodyWidth: number = 0//父容器的宽
        let newColumn: number = column ? column : 0 //列
        let allW: number = 0//统一宽
        let childConfg: { w: number, h: number, t: number, l: number }[] = []//每个children的样式配置
        let allH: number[] = []//全部列的高
        let time: any = null//定时器
        function set() {
            if (div?.current) {//计算容器宽和列
                bodyWidth = width ? width : div?.current?.clientWidth//容器宽有自定义就使用自定义没有就计算当前容器的宽
                if (newColumn) {//假如自定义了列数执行
                    allW = (bodyWidth - (newColumn - 1) * margin) / newColumn//计算所有children的宽
                } else {//否则走自定义children宽逻辑
                    if (bodyWidth <= childrenWidth) {//假如容器宽小于等于渲染元素的宽退出操作避免报错
                        return
                    }
                    newColumn = Math.floor((bodyWidth - childrenWidth) / (childrenWidth + margin)) + 1//计算列
                }
            }
            childrenDivs.forEach((child: Element, index: number) => {//循环拿到的实例
                let w = allW ? allW : child?.clientWidth || 0//children的宽，自定义列就走计算的宽，没有就计算实例的宽，初始为0
                let h = child?.scrollHeight || 0//children的高，从实例计算，初始为0
                let t = 0//children的top值初始为0
                let l = 0//children的left值初始为0
                if (index < newColumn) {//假如下标小于列的值为第一行逻辑
                    allH.push(h)//添加第一行所有children的高
                    l = index === 0 ? 0 : (allW ? allW : childrenWidth) * index + margin * index//计算第一行每个children的left值
                } else {//否则不是第一行走以下逻辑
                    let minH: number = Math.min(...allH)//取当前最小高度的children的值
                    let eq = allH.indexOf(minH)//取当前最小高度children的位置
                    allH[eq] = allH[eq] + h + margin//更新列的高
                    t = minH ? minH + margin : 0//设置当前children的top
                    l = eq === 0 ? 0 : (allW ? allW : childrenWidth) * eq + margin * eq//设置当前children的left值
                }
                childConfg.push({ w, h, t, l })//向配置中添加本次计算
            })
            dispatch({ type: 'configs', params: { configs: childConfg } })//存放本次计算的所有children配置
            dispatch({ type: 'newColumn', params: { newColumn: newColumn } })//存放本次计算的列值
        }
        if (childrenDivs?.length === children?.length) {//假如获取到实例的个数和本次传入的jsxElement个数相等表示已经全部取得实例可以进行计算
            time = setTimeout(() => { //设置定时器，避免渲染过慢导致获取相应的数据不正确
                set()
            }, 200)
        }
        return () => {//每次卸载清除定时器以免内存泄漏
            time = null
            clearTimeout(time)
        }
    }, [childrenDivs, div, childrenWidth, width, column, margin, size, collapsed])//此处依赖值变化需要更新计算
    /**处理结果 */
    useEffect(() => {
        let childrens: JSX.Element[] = props?.children?.props?.children || []
        let allH: number[] = []//全部列的高
        let newChild: { [name: string]: string }[] = []
        let oldChild: { [name: string]: string }[] = []
        childrens?.map((child: any) => {
            newChild.push({ key: child?.key, style: child?.props?.style })
        })
        state?.children?.props?.children?.map((child: any) => {
            oldChild.push({ key: child?.key.replace(/(\/.*)|(")/ig, ''), style: child?.props?.style })
        })
        if (JSON.stringify(newChild) === JSON.stringify(oldChild)) {
            return
        }
        if (!props?.children?.props?.children) {//当没拿到全部数据不处理
            return
        }
        let configss = configs
        if (configss.every((item: { w: number, h: number, t: number, l: number }) => item.l === 0) && configss.length > 0) {//配置没有处理完不处理
            return
        }
        configss?.forEach((conf: { h: number }, index: number) => {//计算全部列的高
            allH[index % newColumn] = allH.length === newColumn ? allH[index % newColumn] + conf.h + margin : conf.h + margin
        })
        let children = React.cloneElement(//创建新的JSX.ELEMENT
            props.children,
            {
                children: React.Children.map(childrens, (child: JSX.Element, index: number) => {
                    return React.cloneElement(
                        child,
                        {
                            style: { width: configss[index]?.w || childrenWidth, position: 'absolute', left: configss[index]?.l, top: configss[index]?.t,  },
                            ref: (ref: HTMLDivElement) => { dispatch({ type: 'addRef', params: { index, ref } }) },
                            key: JSON.stringify(child.key)
                        },
                    )
                }),
                ref: div,
                style: { width: width || '100%', position: 'relative', height: allH.length > 0 ? Math.max(...allH) : '100%' },
            },
        )
        dispatch({ type: 'children', params: { children } })
    }, [props.children, configs, newColumn])
    return children
}
export default HocWaterFallBox
