// 包含 {tag}#{id}.{class ... 如有多个}[data-attr=... 如有多个]
// example: div#xxx.aaa.bbb[data-ccc="111",data-ddd="222"]
type ElemInfo = string;
type Children = ElemResult[];
type ElemResult = ({
    elem: ElemInfo
    children: Children
} | string);

function getDataAttributes(elem: Element) {
    const dataset = (elem as HTMLElement).dataset
    if (!dataset) {
        return ''
    }

    // @ts-ignore type
    const dataEntries: Array<[string, string]> = Array.from(
        // @ts-ignore type
        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call
        Object.entries(dataset)
    )
        // @ts-ignore type
        .sort(([aA], [aB]) => {
            return aA > aB ? 1
                : aA < aB ? -1
                    : 0
        })

    return dataEntries
        .map(([key, value]) => `data-${key}="${value}"`)
        .join(',')
}

function formatElemInfo(elem: Element): string {
    const tag = elem.tagName.toLowerCase()
    const id = elem.id ? `#${elem.id}` : ''

    const classList = Array.from(elem.classList)
        .sort((cA, cB) => (
            cA > cB ? 1
                : cA < cB ? -1
                    : 0
        ))

    const classes = elem.classList.length > 0 ? `.${classList.join('.')}` : ''
    const dataAttributes = getDataAttributes(elem)

    return `${tag}${id}${classes}${dataAttributes ? `[${dataAttributes}]` : ''}`
}

function getDomElemStructure(node: Node): ElemResult {
    if (node.nodeType !== Node.ELEMENT_NODE) {
        return node.nodeValue || ''
    }

    const elem = node as Element
    const elemInfo = formatElemInfo(elem)
    const tagName = elem.tagName.toLowerCase()
    const children: Children = []
    if (
        tagName === 'input'
        || tagName === 'textarea'
    ) {
        const input = elem as HTMLInputElement
        if (input.type === 'checkbox' || input.type === 'radio') {
            children.push(`[${input.type} = ${input.checked}]`)
        }
        else {
            // @ts-ignore type
            children.push(input.value || '')
        }
    }
    for (const child of elem.childNodes) {
        children.push(getDomElemStructure(child))
    }
    return {elem: elemInfo, children}
}

function printStructure(structure: ElemResult | ElemResult[], result: string[] = [], indent = 0) {
    if (Array.isArray(structure)) {
        for (const child of structure) {
            if (indent === 0 && typeof child === 'string') {
                result.push(`=== ${child} ===`)
            }
            else {
                printStructure(child, result, indent)
            }
        }
        return result
    }

    const getIndentText = () => '  '.repeat(indent)
    if (typeof structure === 'string') {
        if (!structure) {
            return result
        }

        result.push(getIndentText() + structure)
        return result
    }

    result.push(getIndentText() + structure.elem)
    for (const child of structure.children) {
        printStructure(child, result, indent + 1)
    }
    return result
}

type TargetElem =
    | Node
    | JQuery<HTMLElement>
    | string
    | TargetElem[]

export function printDomElem(target: TargetElem): string[] {
    const structure: ElemResult[] = []
    // @ts-ignore jquery type
    if (target.jquery) {
        return printDomElem((target as JQuery<HTMLElement>).get());
    }
    if (!(target instanceof Array)) {
        return printDomElem([target]);
    }
    for (const elem of target) {
        if (!elem) {
            continue
        }

        if (typeof elem === 'string') {
            structure.push(elem)
            continue
        }

        if (elem instanceof Node) {
            structure.push(getDomElemStructure(elem))
            continue
        }

        (elem as JQuery<HTMLElement>).get().forEach(elem => {
            structure.push(getDomElemStructure(elem))
        })
    }
    return printStructure(structure)
}

export function expectDomElem(node: TargetElem) {
    return expect(printDomElem(node))
}
