import { addXmlns } from "./Dom"
import { ForeignObject } from '@svgdotjs/svg.js'
import { BoundingRect, Node } from "../types"




interface ForeignObjectConfig {
    el: HTMLElement
    width?: number
    height?: number
}

interface ExtraContentConfig {
    addContentToHeader?: () => {
        el: HTMLElement
        cssText?: string
        height: number
    } | void
    addContentToFooter?: () => {
        el: HTMLElement
        cssText?: string
        height: number
    } | void
}

interface ExtraContentResult {
    cssTextList: string[]
    header: ForeignObject | null
    headerHeight: number
    footer: ForeignObject | null
    footerHeight: number
}

// Get bounding rect for node tree
export const getNodeTreeBoundingRect = (
    node: Node,
    x: number = 0,
    y: number = 0,
    paddingX: number = 0,
    paddingY: number = 0,
    excludeSelf: boolean = false,
    excludeGeneralization: boolean = false
): BoundingRect => {
    let minX: number = Infinity
    let maxX: number = -Infinity
    let minY: number = Infinity
    let maxY: number = -Infinity

    const walk = (root: Node, isRoot: boolean): void => {
        if (!(isRoot && excludeSelf) && root.group) {
            try {
                const { x, y, width, height } = root.group
                    .findOne('.smm-node-shape')
                    .rbox()
                if (x < minX) {
                    minX = x
                }
                if (x + width > maxX) {
                    maxX = x + width
                }
                if (y < minY) {
                    minY = y
                }
                if (y + height > maxY) {
                    maxY = y + height
                }
            } catch (e) { }
        }
        if (!excludeGeneralization && root._generalizationList.length > 0) {
            root._generalizationList.forEach(item => {
                walk(item.generalizationNode, false)
            })
        }
        if (root.children) {
            root.children.forEach(item => {
                walk(item, false)
            })
        }
    }
    walk(node, true)

    minX = minX - x + paddingX
    minY = minY - y + paddingY
    maxX = maxX - x + paddingX
    maxY = maxY - y + paddingY

    return {
        left: minX,
        top: minY,
        width: maxX - minX,
        height: maxY - minY
    }
}

// Get bounding rect for multiple nodes
export const getNodeListBoundingRect = (
    nodeList: Node[],
    x: number = 0,
    y: number = 0,
    paddingX: number = 0,
    paddingY: number = 0
): BoundingRect => {
    let minX: number = Infinity
    let maxX: number = -Infinity
    let minY: number = Infinity
    let maxY: number = -Infinity

    nodeList.forEach(node => {
        const { left, top, width, height } = getNodeTreeBoundingRect(
            node,
            x,
            y,
            paddingX,
            paddingY,
            false,
            true
        )
        if (left < minX) {
            minX = left
        }
        if (left + width > maxX) {
            maxX = left + width
        }
        if (top < minY) {
            minY = top
        }
        if (top + height > maxY) {
            maxY = top + height
        }
    })
    return {
        left: minX,
        top: minY,
        width: maxX - minX,
        height: maxY - minY
    }
}

// Create foreignObject node
export const createForeignObjectNode = ({ el, width, height }: ForeignObjectConfig): ForeignObject => {
    const foreignObject = new ForeignObject()
    if (width !== undefined) {
        foreignObject.width(width)
    }
    if (height !== undefined) {
        foreignObject.height(height)
    }
    // 使用 node 属性直接设置原生 DOM 元素
    foreignObject.node.appendChild(el)
    return foreignObject
}

// Format node generalization data
export const formatGetNodeGeneralization = (data: any) => {
    const generalization = data.generalization
    if (generalization) {
        return Array.isArray(generalization) ? generalization : [generalization]
    } else {
        return []
    }
}

// Sort node list by sortIndex
export const sortNodeList = (nodeList: Node[]): Node[] => {
    nodeList = [...nodeList]
    nodeList.sort((a, b) => {
        return a.sortIndex - b.sortIndex
    })
    return nodeList
}

// Check if node is outside canvas
export const checkNodeOuter = (mindMap: {
    elRect: { width: number, height: number },
    draw: {
        transform: () => {
            scaleX: number,
            scaleY: number,
            translateX: number,
            translateY: number
        }
    }
}, node: Node): {
    isOuter: boolean,
    offsetLeft: number,
    offsetTop: number
} => {
    let elRect = mindMap.elRect
    let { scaleX, scaleY, translateX, translateY } = mindMap.draw.transform()
    let { left, top, width, height } = node
    let right = (left + width) * scaleX + translateX
    let bottom = (top + height) * scaleY + translateY
    left = left * scaleX + translateX
    top = top * scaleY + translateY
    let offsetLeft = 0
    let offsetTop = 0
    if (left < 0) {
        offsetLeft = -left
    }
    if (right > elRect.width) {
        offsetLeft = -(right - elRect.width)
    }
    if (top < 0) {
        offsetTop = -top
    }
    if (bottom > elRect.height) {
        offsetTop = -(bottom - elRect.height)
    }
    return {
        isOuter: offsetLeft !== 0 || offsetTop !== 0,
        offsetLeft,
        offsetTop
    }
}

// Check if two node lists contain same nodes
export const checkNodeListIsEqual = (list1: Node[], list2: Node[]): boolean => {
    if (list1.length !== list2.length) return false
    for (let i = 0; i < list1.length; i++) {
        if (
            !list2.find(item => {
                return item.uid === list1[i].uid
            })
        ) {
            return false
        }
    }
    return true
}

// Handle extra content when getting SVG content
export const handleGetSvgDataExtraContent = ({
    addContentToHeader,
    addContentToFooter
}: ExtraContentConfig): ExtraContentResult => {
    const cssTextList: string[] = []
    let header: ForeignObject | null = null
    let headerHeight = 0
    let footer: ForeignObject | null = null
    let footerHeight = 0

    const handle = (fn: (() => {
        el: HTMLElement,
        cssText?: string,
        height: number
    } | void) | undefined, callback: (foreignObject: ForeignObject, height: number) => void): void => {
        if (typeof fn === 'function') {
            const res = fn()
            if (!res) return
            const { el, cssText, height } = res
            if (el instanceof HTMLElement) {
                addXmlns(el)
                const foreignObject = createForeignObjectNode({ el, height })
                callback(foreignObject, height)
            }
            if (cssText) {
                cssTextList.push(cssText)
            }
        }
    }

    handle(addContentToHeader, (foreignObject, height) => {
        header = foreignObject
        headerHeight = height
    })
    handle(addContentToFooter, (foreignObject, height) => {
        footer = foreignObject
        footerHeight = height
    })

    return {
        cssTextList,
        header,
        headerHeight,
        footer,
        footerHeight
    }
}