import * as Arrays from "./arrays";

/**
 * Returns closest element that satisfies the predicate, going from child to parent.
 * @param node {Node} Node from which the search starts.
 * @param predicate {(node: Node) => boolean} Search predicate
 */
export function closest(node: Node, predicate: (node: Node) => boolean): Node {
    do {
        if (predicate(node)) {
            return node;
        }

        node = node.parentNode;
    }
    while (node);
}

/**
 * Returns first element that satisfies the predicate, going from parent to children.
 * @param node {Node} Node from which the search starts.
 * @param predicate {(node: Node) => boolean} Search predicate
 * @returns 
 */
export function findFirst(node: Node, predicate: (node: Node) => boolean): Node {
    if (predicate(node)) {
        return node;
    }

    const childNodes = Arrays.coerce<Node>(node.childNodes);

    for (const childNode of childNodes) {
        const childNodeMeetingCriteria = findFirst(childNode, predicate);

        if (childNodeMeetingCriteria) {
            return childNodeMeetingCriteria;
        }
    }

    return null;
}

/**
 * Returns a stack of ancestors of specified starting element.
 * @param element - Starting element.
 */
export function parents(element: HTMLElement): HTMLElement[] {
    const stack = selfAndParents(element);

    if (stack.length > 1) {
        return stack.slice(1);
    }

    return [];
}

/**
 * Returns a stack of ancestor elements and the specified starting element itself.
 * @param element - Starting element.
 */
export function selfAndParents(element: HTMLElement): HTMLElement[] {
    const stack = [];

    while (element) {
        stack.push(element);
        element = element.parentElement;
    }

    return stack;
}

/**
 * Determines if the element is actually visible, regardless of it's presence in the document.
 * @param element - An element being checked.
 */
export function isVisible(element: HTMLElement): boolean {
    const box = element.getBoundingClientRect();
    return box.width > 0 && box.height > 0;
};

/**
 * Returns a collection of focusable element hosted by specified element.
 * @param element - Host element.
 */
export function getFocusableElements(element: HTMLElement): HTMLElement[] {
    return Arrays.coerce<HTMLElement>(element.querySelectorAll("*")).filter(x => x.tabIndex >= 0 && isVisible(x));
};

export enum AriaAttributes {
    /**
     * Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed.
     */
    expanded = "aria-expanded",

    /**
     * Defines a string value that labels the current element.
     */
    label = "aria-label",

    /**
     * Identifies the element (or elements) whose contents or presence are controlled by the current element.
     */
    controls = "aria-controls",

    /**
     * Indicates whether the element is exposed to an accessibility API.
     */
    hidden = "aria-hidden",

    /**
     * Indicates the current “selected” state of various widgets. See related aria-checked and aria-pressed.
     */
    selected = "aria-selected",

    /**
     * Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element.
     */
    hasPopup = "aria-haspopup",
}

export enum Attributes {
    Href = "href",
    Src = "src",
    Target = "target",
    Download = "download",
    TabIndex = "tabindex",
    Role = "role",
    Rel = "rel",
    Type = "type",
    Integrity = "integrity",
    ContentEditable = "contentEditable",
    Language = "lang",
    Direction = "dir",
    Charset = "charset"
}

export enum DataAttributes {
    /**
     * Indicates type of togglable. Allowed values: `popup`,`dropdown`,`collapsible`.
     */
    Toggle = "data-toggle",

    /**
     * Indicates how the togglable gets triggered. Allowed values: `click`,`hover`,`focus`,`keydown`.
     */
    TriggerEvent = "data-trigger-event",

    /**
     * 
     */
    Target = "data-target",

    /**
     * 
     */
    Dismiss = "data-dismiss",

    /**
     * Indicates togglables auto-closing behavior. Allowed values: `true`,`inside`,`outside`,`false.`
     */
    AutoClose = "data-auto-close"
}

export enum AriaRoles {
    Listbox = "listbox",
    Option = "option"
}

/**
 * Hyperlink relationship between a linked resource and the current page.
 */
export enum HyperlinkRels {
    /**
     * Instructs the browser not to set `window.opener` property when opening a new window.
     */
    NoOpener = "noopener",

    /**
     * Instructs the browser not to send `Referrer` HTTP header when opening navigating to linked page.
     */
    NoReferrer = "noreferrer",

    /**
     * Indicates that the linked resource is not endorsed by the author.
     */
    NoFollow = "nofollow"
}

export enum NavigationTarget {
    /**
     * Opens the linked document in a new window or tab.
     */
    Blank = "_blank",

    /**
     * Opens the linked document in the same frame as it was clicked.
     */
    Self = "_self",
}