import {
    Services
} from '#service';

import {
    devAssert
} from '#utils/log';

import {
    getServicePromise
} from './service-helpers';

/**
 * A map of services that delay rendering. The key is the name of the service
 * and the value is a DOM query which is used to check if the service is needed
 * in the current document.
 * Do not add a service unless absolutely necessary.
 *
 * \   \  /  \  /   / /   \     |   _  \     |  \ |  | |  | |  \ |  |  / _____|
 *  \   \/    \/   / /  ^  \    |  |_)  |    |   \|  | |  | |   \|  | |  |  __
 *   \            / /  /_\  \   |      /     |  . `  | |  | |  . `  | |  | |_ |
 *    \    /\    / /  _____  \  |  |\  \----.|  |\   | |  | |  |\   | |  |__| |
 *     \__/  \__/ /__/     \__\ | _| `._____||__| \__| |__| |__| \__|  \______|
 *
 * The equivalent of this list is used for server-side rendering (SSR) and any
 * changes made to it must be made in coordination with caches that implement
 * SSR. For more information on SSR see bit.ly/amp-ssr.
 *
 * @const {!Object<string, string>}
 */
const SERVICES = {
    'amp-dynamic-css-classes': '[custom-element=amp-dynamic-css-classes]',
    'variant': 'amp-experiment',
};

/**
 * Base class for render delaying services.
 * This should be extended to ensure the service
 * is properly handled
 *
 * @interface
 */
export class RenderDelayingService {
    /**
     * Function to return a promise for when
     * it is finished delaying render, and is ready.
     * NOTE: This should simply return Promise.resolve,
     * if your service does not need to perform any logic
     * after being registered.
     * @return {!Promise}
     */
    whenReady() {}
}

/**
 * Maximum milliseconds to wait for all extensions to load before erroring.
 * @const
 */
const LOAD_TIMEOUT = 3000;

/**
 * Detects any render delaying services that are required on the page, and
 * returns a promise with a timeout.
 * @param {!Window} win
 * @return {!Promise<!Array<*>>} resolves to an Array that has the same length
 *     as the detected render delaying services
 */
export function waitForServices(win) {
    const promises = includedServices(win).map((serviceId) => {
        const serviceReadyPromise = getServicePromise(win, serviceId).then(
            (service) => {
                if (service && isRenderDelayingService(service)) {
                    return service.whenReady().then(() => {
                        return service;
                    });
                }
                return service;
            }
        );

        return Services.timerFor(win).timeoutPromise(
            LOAD_TIMEOUT,
            serviceReadyPromise,
            `Render timeout waiting for service ${serviceId} to be ready.`
        );
    });
    return Promise.all(promises);
}

/**
 * Returns true if the page has a render delaying service.
 * @param {!Window} win
 * @return {boolean}
 */
export function hasRenderDelayingServices(win) {
    return includedServices(win).length > 0;
}

/**
 * Function to determine if the passed
 * Object is a Render Delaying Service
 * @param {!Object} service
 * @return {boolean}
 */
export function isRenderDelayingService(service) {
    const maybeRenderDelayingService = /** @type {!RenderDelayingService}*/ (
        service
    );
    return typeof maybeRenderDelayingService.whenReady == 'function';
}

/**
 * Detects which, if any, render-delaying extensions are included on the page.
 * @param {!Window} win
 * @return {!Array<string>}
 */
export function includedServices(win) {
    /** @const {!Document} */
    const doc = win.document;
    devAssert(doc.body);

    return Object.keys(SERVICES).filter((service) => {
        return doc.querySelector(SERVICES[service]);
    });
}