/**
 * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
 * Node module: @schukai/monster
 * This file is licensed under the AGPLv3 License.
 * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
 */

import { extend } from "../data/extend.mjs";
import { Base } from "../types/base.mjs";
import { getGlobalObject } from "../types/global.mjs";
import {equipWithInternal} from "../types/internal.mjs";
import { isArray } from "../types/is.mjs";
import { ATTRIBUTE_HREF, ATTRIBUTE_SRC } from "./constants.mjs";
import { Resource } from "./resource.mjs";
import { Data } from "./resource/data.mjs";
import { Stylesheet } from "./resource/link/stylesheet.mjs";
import { Script } from "./resource/script.mjs";

export { ResourceManager };

/**
 * The ResourceManager is a singleton that manages all resources.
 *
 * @license AGPLv3
 * @since 1.25.0
 * @copyright schukai GmbH
 * @memberOf Monster.DOM
 * @summary A Resource class
 */
class ResourceManager extends Base {
    /**
     *
     * @param {Object} options
     * throw {Error} unsupported document type
     */
    constructor(options) {
        super(options);
        equipWithInternal.call(this);

        if (!(this.getOption("document") instanceof Document)) {
            throw new Error("unsupported document type");
        }
    }

    /**
     * @deprecated since 3.15.0 use getInternal instead
     * @property {string} baseurl
     */
    getOption(key) {
        return this.getInternal(key);
    }

    /**
     * @property {string} baseurl
     */
    getBaseURL() {
        this.getOption("document")?.baseURL;
    }

    /**
     * @property {string} baseurl
     * @deprecated since 3.15.0 use internalDefaults instead
     */
    get defaults() {
        return this.internalDefaults;
    }

    /**
     *
     * @property {HTMLDocument} document=document Document
     * @property {Object} resources
     * @property {Array} resources.scripts=[] array with {@link Monster.DOM.Resource.Script} objects
     * @property {Array} resources.stylesheets=[] array with {@link Monster.DOM.Resource.Link.Stylesheet} objects
     * @property {Array} resources.data=[] array with {@link Monster.DOM.Resource.Data} objects
     */
    get internalDefaults() {
        return Object.assign({},  {
            document: getGlobalObject("document"),
            resources: {
                scripts: [],
                stylesheets: [],
                data: [],
            },
        });
    }

    /**
     * Append Tags to DOM
     *
     * @return {Monster.DOM.ResourceManager}
     * @throws {Error} unsupported resource definition
     */
    connect() {
        runResourceMethod.call(this, "connect");
        return this;
    }

    /**
     * Check if available
     *
     * @return {Promise}
     * @throws {Error} unsupported resource definition
     */
    available() {
        return Promise.all(runResourceMethod.call(this, "available"));
    }

    /**
     * Add a script
     *
     * @param {string|URL} url
     * @param [Object|undefined} options
     * @return {Monster.DOM.ResourceManager}
     * @see Monster.DOM.Resource.Script
     */
    addScript(url, options) {
        return addResource.call(this, "scripts", url, options);
    }

    /**
     * Add Stylesheet
     *
     * @param {string|URL} url
     * @param [Object|undefined} options
     * @return {Monster.DOM.ResourceManager}
     * @see Monster.DOM.Resource.Link.Stylesheet
     */
    addStylesheet(url, options) {
        return addResource.call(this, "stylesheets", url, options);
    }

    /**
     * Add Data Tag
     *
     * @param {string|URL} url
     * @param [Object|undefined} options
     * @return {Monster.DOM.ResourceManager}
     * @see Monster.DOM.Resource.Data
     */
    addData(url, options) {
        return addResource.call(this, "data", url, options);
    }
}

/**
 * @private
 * @param {string} method
 * @return {Array}
 */
function runResourceMethod(method) {
    const self = this;

    const result = [];

    for (const type of ["scripts", "stylesheets", "data"]) {
        const resources = self.getOption(`resources.${type}`);
        if (!isArray(resources)) {
            continue;
        }

        for (const resource of resources) {
            if (!(resource instanceof Resource)) {
                throw new Error("unsupported resource definition");
            }

            result.push(resource[method]());
        }
    }

    return result;
}

/**
 *
 * @param {string} type
 * @param {string|URL} url
 * @param [Object|undefined} options
 * @return {Monster.DOM.ResourceManager}
 * @private
 */
function addResource(type, url, options) {
    const self = this;

    if (url instanceof URL) {
        url = url.toString();
    }

    options = options || {};

    let resource;
    switch (type) {
        case "scripts":
            resource = new Script(extend({}, options, { [ATTRIBUTE_SRC]: url }));
            break;
        case "stylesheets":
            resource = new Stylesheet(extend({}, options, { [ATTRIBUTE_HREF]: url }));
            break;
        case "data":
            resource = new Data(extend({}, options, { [ATTRIBUTE_SRC]: url }));
            break;
        default:
            throw new Error(`unsupported type ${type}`);
    }

    self.getOption("resources")?.[type].push(resource);
    return self;
}