import { iconExists, loadIcon } from 'iconify-icon';

export type Node = HTMLElement | SVGElement | Text | undefined;
export type NodeChildren = Array<Node>;

export type Class = string | Array<Class> | Record<string, boolean> | undefined;
type Listener<K extends keyof HTMLElementEventMap> = | ((ev: HTMLElementEventMap[K]) => any) | {
    options?: boolean | AddEventListenerOptions;
    listener: (ev: HTMLElementEventMap[K]) => any;
} | undefined;

export interface NodeProperties
{
    attributes?: Record<string, string | undefined | boolean>;
    text?: string;
    class?: Class;
    style?: Record<string, string | undefined | boolean | number> | string;
    listeners?: { 
        [K in keyof HTMLElementEventMap]?: Listener<K>
    };
}

export const cancelPropagation = (e: Event) => e.stopImmediatePropagation();
export function dom<K extends keyof HTMLElementTagNameMap>(tag: K, properties?: NodeProperties, children?: NodeChildren): HTMLElementTagNameMap[K]
{
    const element = document.createElement(tag);

    if(children && children.length > 0)
        for(const c of children) if(c !== undefined) element.appendChild(c);

    if(properties?.attributes)
        for(const [k, v] of Object.entries(properties.attributes))
            if(typeof v === 'string') element.setAttribute(k, v);
            else if(typeof v === 'boolean') element.toggleAttribute(k, v);

    if(properties?.text)
        element.textContent = properties.text;

    if(properties?.listeners)
    {
        for(let [k, v] of Object.entries(properties.listeners)) 
        {
            const key = k as keyof HTMLElementEventMap, value = v as Listener<typeof key>;
            if(typeof value === 'function') 
                element.addEventListener(key, value);
            else if(value)
                element.addEventListener(key, value.listener, value.options);
        }
    }

    styling(element, properties ?? {});

    return element;
}
export function svg<K extends keyof SVGElementTagNameMap>(tag: K, properties?: NodeProperties, children?: Omit<NodeChildren, 'HTMLElement' | 'Text'>): SVGElementTagNameMap[K]
{
    const element = document.createElementNS("http://www.w3.org/2000/svg", tag);

    if(children && children.length > 0)
        for(const c of children) if(c !== undefined) element.appendChild(c);

    if(properties?.attributes)
        for(const [k, v] of Object.entries(properties.attributes))
            if(typeof v === 'string') element.setAttribute(k, v);
            else if(typeof v === 'boolean') element.toggleAttribute(k, v);

    if(properties?.text)
        element.textContent = properties.text;

    styling(element, properties ?? {});

    return element;
}
export function text(data: string): Text
{
    return document.createTextNode(data);
}
export function styling(element: SVGElement | HTMLElement, properties: { 
    class?: Class;
    style?: Record<string, string | undefined | boolean | number> | string; 
}): SVGElement | HTMLElement
{
    if(properties?.class)
    {
        element.setAttribute('class', mergeClasses(properties.class));
    }

    if(properties?.style)
    {
        if(typeof properties.style === 'string')
        {
            element.setAttribute('style', properties.style);
        }
        else
            for(const [k, v] of Object.entries(properties.style)) if(v !== undefined && v !== false) element.attributeStyleMap.set(k, v);
    }

    return element;
}
export interface IconProperties
{
    mode?: string;
    inline?: boolean;
    noobserver?: boolean;
    width?: string|number;
    height?: string|number;
    flip?: string;
    rotate?: number|string;
    style?: Record<string, string | undefined> | string;
    class?: Class;
}
const iconCache: Map<IconProperties & { name: string }, HTMLElement> = new Map();
export function icon(name: string, properties?: IconProperties): HTMLElement
{
    const key = { ...properties, name };

    if(iconCache.has(key))
        return iconCache.get(key)!.cloneNode() as HTMLElement;

    const el = document.createElement('iconify-icon');

    if(!iconExists(name))
        loadIcon(name);
    
    el.setAttribute('icon', name);

    properties?.mode && el.setAttribute('mode', properties?.mode.toString());
    properties?.inline && el.toggleAttribute('inline', properties?.inline);
    properties?.noobserver && el.toggleAttribute('noobserver', properties?.noobserver);
    properties?.width && el.setAttribute('width', properties?.width.toString());
    properties?.height && el.setAttribute('height', properties?.height.toString());
    properties?.flip && el.setAttribute('flip', properties?.flip.toString());
    properties?.rotate && el.setAttribute('rotate', properties?.rotate.toString());

    if(properties?.class)
    {
        el.setAttribute('class', mergeClasses(properties.class));
    }
    if(properties?.style)
    {
        if(typeof properties.style === 'string')
        {
            el.setAttribute('style', properties.style);
        }
        else
            for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) el.attributeStyleMap.set(k, v);
    }

    iconCache.set(key, el.cloneNode() as HTMLElement);
    return el;
}

export function mergeClasses(classes: Class): string
{
    if(typeof classes === 'string')
    {
        return classes.trim();
    }
    else if(Array.isArray(classes))
    {
        return classes.map(e => mergeClasses(e)).join(' ');
    }
    else if(classes)
    {
        return Object.entries(classes).filter(e => e[1]).map(e => e[0].trim()).join(' ');
    }
    else
    {
        return '';
    }
}