/** @module util */
import T from "i18n-react";
import $ from "jquery";
import React from "react";
import rmlOpts from "rmlopts";

import { Icon, TreeNode } from "@devowl-wp/react-folder-tree";
import { createRequestFactory } from "@devowl-wp/utils";

import addUrlParam from "./addUrlParam.js";
import hooks from "./hooks.js";

export const untrailingslashit = (str) =>
    str.endsWith("/") || str.endsWith("\\") ? untrailingslashit(str.slice(0, -1)) : str;
export const trailingslashit = (str) => `${untrailingslashit(str)}/`;
export const IS_DARKMODE = $("link#dark_mode-css").length > 0;
export const textDots = (string, max = 20) => (string && string.length > max ? `${string.slice(0, max)}...` : string);

export const ICON_OBJ_FOLDER_CLOSED = <Icon type="folder" />;
export const ICON_OBJ_FOLDER_OPEN = <Icon type="folder-open" />;
export const ICON_OBJ_FOLDER_COLLECTION = <i className="rmlicon-collection" />;
export const ICON_OBJ_FOLDER_GALLERY = <i className="rmlicon-gallery" />;

/**
 * This is a replacement of the `ajax` function and will be the main request method in V5.
 */
const { urlBuilder, request } = createRequestFactory({
    restNamespace: "realmedialibrary/v1",
    restNonce: rmlOpts.restNonce,
    restQuery: rmlOpts.restQuery,
    restRoot: rmlOpts.restRoot,
});

/**
 * @param {string} path
 * @see http://planetozh.com/blog/2008/04/javascript-basename-and-dirname/
 */
export function dirname(path) {
    return path.replace(/\\/g, "/").replace(/\/[^/]*$/, "");
}

/**
 * @param {string} path
 * @see http://planetozh.com/blog/2008/04/javascript-basename-and-dirname/
 */
export function basename(path) {
    return path.replace(/\\/g, "/").replace(/.*\//, "");
}

/**
 * Check if a given filename is a hidden filename.
 *
 * @param {string} filename
 */
export function isHiddenFile(filename) {
    const base = basename(filename);
    return base.startsWith(".") || ["desktop.ini"].indexOf(base.toLowerCase()) > -1;
}

/**
 * Resolve the icon for a given string or object.
 *
 * @param {string} icon
 */
export function resolveIcon(icon) {
    const result = {
        icon: undefined,
    };

    switch (icon) {
        case "folder":
            return ICON_OBJ_FOLDER_CLOSED;
        case "folder-open":
            return ICON_OBJ_FOLDER_OPEN;
        case "collection":
            return ICON_OBJ_FOLDER_COLLECTION;
        case "gallery":
            return ICON_OBJ_FOLDER_GALLERY;
        default:
            if (typeof icon !== "string") {
                return icon;
            }

            /**
             * Set the tree node icon by string.
             *
             * @event module:util/hooks#tree/node/icon
             * @param {object} result Set "icon" to the React element
             * @param {string} icon The icon string
             * @since 4.6.0
             */
            hooks.call("tree/node/icon", [result, icon]);
            return result.icon ? result.icon : <Icon type={icon} />;
    }
}

/**
 * Replacement for $(document).ready(). Some WordPress environments do not
 * support completely the above function when in conflict with another plugin.
 *
 * @see https://matthiasweb.freshdesk.com/a/tickets/1545
 * @see http://youmightnotneedjquery.com/
 */
export const ready = (fn) =>
    (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading")
        ? fn()
        : document.addEventListener("DOMContentLoaded", fn);

/**
 * Creates a React component (span) with the translated markdown.
 *
 * @param {string} key The key in rmlOpts.lang
 * @param {object} [params] The parameters
 * @param {object|string('maxWidth')} [spanWrapperProps] Wraps an additinal span wrapper with custom attributes
 * @see https://github.com/alexdrel/i18n-react
 * @returns {React.Element} Or null if key not found
 */
export function i18n(key, params, spanWrapperProps) {
    if (rmlOpts && rmlOpts.others.lang && rmlOpts.others.lang[key]) {
        const span = <T.span text={rmlOpts.others.lang[key]} {...params} />;

        // Predefined span wrapper props
        if (typeof spanWrapperProps === "string") {
            switch (spanWrapperProps) {
                case "maxWidth":
                    spanWrapperProps = { style: { display: "inline-block", maxWidth: 200 } };
                    break;
                default:
                    break;
            }
        }

        return spanWrapperProps ? <span {...spanWrapperProps}>{span}</span> : span;
    }
    return key;
}

/**
 * Get URL parameter of current url.
 *
 * @param {string} name The parameter name
 * @param {string} [url=window.location.href]
 * @returns {string|null}
 */
export function urlParam(name, url = window.location.href) {
    const results = new RegExp(`[?&]${name}=([^&#]*)`).exec(url);
    return (results && results[1]) || null;
}

/**
 * Handle tree node defaults for loaded folder items and new items.
 *
 * @param {object[]} folders The folders
 * @returns object[]
 */
export function applyNodeDefaults(arr) {
    return arr.map(
        ({
            id,
            name,
            cnt,
            children,
            contentCustomOrder,
            forceCustomOrder,
            lastOrderBy,
            orderAutomatically,
            lastSubOrderBy,
            subOrderAutomatically,
            ...rest
        }) =>
            ((node) => {
                // Update node
                switch (node.properties.type) {
                    case 0:
                        node.iconActive = "folder-open";
                        break;
                    case 1:
                        node.icon = "collection";
                        break;
                    case 2:
                        node.icon = "gallery";
                        break;
                    default:
                        break;
                }

                /**
                 * A tree node is fetched from the server and should be prepared
                 * for the {@link module:store/TreeNode~TreeNode} class.
                 *
                 * @event module:util/hooks#tree/node
                 * @param {object} node The node object
                 */
                hooks.call("tree/node", [node]);
                return node;
            })(
                $.extend({}, TreeNode.defaultProps, {
                    // Default node
                    id,
                    title: name,
                    icon: "folder",
                    count: cnt,
                    childNodes: children ? applyNodeDefaults(children) : [],
                    properties: rest,
                    className: {},
                    contentCustomOrder,
                    forceCustomOrder,
                    lastOrderBy: lastOrderBy ? lastOrderBy : "",
                    orderAutomatically: !!orderAutomatically,
                    lastSubOrderBy: lastSubOrderBy ? lastSubOrderBy : "",
                    subOrderAutomatically: !!subOrderAutomatically,
                    $visible: true,
                }),
            ),
    );
}

/**
 * Execute the REST query to fetch the category tree.
 *
 * @returns {object} The original AJAX result and the tree result prepared for AIO
 */
export async function fetchTree() {
    const { tree, ...rest } = await request({
        location: {
            path: "/tree",
        },
    });
    return { tree: applyNodeDefaults(tree), ...rest };
}

/**
 * Allows you to find an object path.
 *
 * @param {object} obj The object
 * @param {string} path The path
 * @returns {mixed|undefined}
 */
export function findDeep(obj, path) {
    const paths = path.split(".");
    let current = obj;
    for (var i = 0; i < paths.length; ++i) {
        if (current[paths[i]] == undefined) {
            return undefined;
        } else {
            current = current[paths[i]];
        }
    }
    return current;
}

/**
 * Transform bytes to humand readable string.
 *
 * @param {int} bytes The bytes
 * @returns {string}
 * @see https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
 */
export function humanFileSize(bytes, si = true) {
    const thresh = si ? 1000 : 1024;
    if (Math.abs(bytes) < thresh) {
        return `${bytes} B`;
    }
    const units = si
        ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
        : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
    let u = -1;
    do {
        bytes /= thresh;
        ++u;
    } while (Math.abs(bytes) >= thresh && u < units.length - 1);
    return `${bytes.toFixed(1)} ${units[u]}`;
}

/**
 * Transform seconds to readable HH:mm:ss.
 *
 * @param {int} totalSec The seconds
 * @returns {string}
 */
export function secondsFormat(totalSec) {
    const hours = Math.floor(totalSec / 3600);
    const minutes = Math.floor((totalSec - hours * 3600) / 60);
    const seconds = totalSec - hours * 3600 - minutes * 60;
    return `${hours < 10 ? `0${hours}` : hours}:${minutes < 10 ? `0${minutes}` : minutes}:${
        seconds < 10 ? `0${seconds}` : seconds
    }`;
}

/**
 * Export Data URI to blob instance.
 *
 * @param {string} sUri
 * @returns {Blob}
 */
export function dataUriToBlob(sUri) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    let byteString;
    if (sUri.split(",")[0].indexOf("base64") >= 0) {
        byteString = window.atob(sUri.split(",")[1]);
    } else {
        byteString = unescape(sUri.split(",")[1]);
    }

    // separate out the mime component
    const type = sUri.split(",")[0].split(":")[1].split(";")[0];

    // write the bytes of the string to a typed array
    const ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new window.Blob([ia], { type });
}

/**
 * Detects if an element is in view port.
 *
 * @param {jQuery|HTMLElement} el
 * @returns {boolean}
 */
export function inViewPort(el, allowFromBottom) {
    const elementTop = $(el).offset().top;
    const height = $(el).outerHeight();
    const elementBottom = elementTop + height;
    const viewportTop = $(window).scrollTop();
    const viewportBottom = viewportTop + $(window).height();
    if (allowFromBottom && viewportTop > elementBottom - viewportTop) {
        return true;
    }
    return elementBottom > viewportTop && elementTop < viewportBottom;
}

/**
 * Check if Material WP is activated.
 *
 * @returns {boolean}
 */
export function isMaterialWp() {
    return $("body").hasClass("material-wp");
}

function materialWpWidthRules(calc) {
    return (
        `width: -webkit-calc(${calc}) !important;` +
        `width: -moz-calc(${calc}) !important;` +
        `width: calc(${calc}) !important;`
    );
}

/**
 * Resize handler for opposite when Material WP is active.
 *
 * @returns {boolean}
 */
export function materialWpResizeOpposite(containerId, oppositeId, width, injectStyle) {
    const adminBarWidth = $("#adminmenu").width();
    return injectStyle(
        `${containerId}-styleOpposite`,
        `@media only screen and (min-width: 1224px) {
            body:not(.wp-customizer) #${oppositeId} {' +
                ${materialWpWidthRules(`100% - ${width}px - ${adminBarWidth + 20}px`)}
            }
        }
        @media only screen and (max-width: 1223px) and (min-width: 990px) {
            body:not(.wp-customizer) #${oppositeId} {' +
                ${materialWpWidthRules(`100% - ${width}px - ${adminBarWidth + 40}px`)}
            }
        }
        @media only screen and (min-width: 700px) {
          body.aiot-wp-material.activate-aiot .rml-container {
        	margin-left: ${adminBarWidth + 20}px;
          }
        }
        @media only screen and (max-width: 1223px) {
          body.aiot-wp-material.activate-aiot .rml-container {
            margin-left: ${adminBarWidth + 40}px;
          }
        }
        body #wpcontent #wpbody #${oppositeId}.mwp-expanded {' +
            ${materialWpWidthRules(`100% - ${width}px - 50px`)}
        }`,
    );
}

/**
 * Get the next media modal z-index.
 *
 * @returns int
 */
export function getMediaDialogNextZIndex() {
    // Beaver builder
    if ($("body").hasClass("fl-builder") /* Beaver Builder */ || $("body").hasClass("et-fb") /* Divi Page Builder */) {
        return 9999992;
    }

    return 160001;
}

export {
    /**
     * @type module:util/addUrlParam
     */
    addUrlParam,
    /**
     * @type module:util/hooks
     */
    hooks,
    /**
     * The localized Real Media Library script object.
     *
     * @type object
     */
    rmlOpts,
    /**
     * @type function
     */
    urlBuilder,
    /**
     * @type function
     */
    request,
};
