import demoProducts from './demoProducts';
import {performCancellableRequest} from './abortableRequest';
import {v4 as uuid} from 'uuid';

/**
 * Structure of a Product object.
 *
 * {
 *     id: string,
 *     name: string,
 *     description: string,
 *     shortDescription: string,
 *     price: number,
 *     attributes: [
 *          {
 *              id: string,
 *              name: string,
 *              value: string
 *          }
 *          ...
 *     ],
 *     reviews: [
 *         {
 *             id: string,
 *             title: string,
 *             rating: number,
 *             resources: string | undefined
 *         }
 *     ]
 * }
 */


let products;

/**
 * Perform a Fisher Yates shuffle on the given array.
 *
 * @param array The array to shuffle.
 * @returns {*} The shuffled array.
 */
const shuffle = (array) => {
    let currentIndex = array.length, temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element.
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }

    return array;
}

/**
 * Write the products to the localstorage.
 *
 * @param newProducts The array of products to persist.
 */
const persistProducts = (newProducts) => {
    products = newProducts;
    localStorage.products = JSON.stringify(newProducts);
}

/**
 * Retrieve all products on the website asynchronously.
 *
 * @param abortController An AbortController that can be used to perform cleanup and cancel the asynchronous request.
 * @returns {Promise<Product[]>}  An array of products, wrapped in a Promise,
 * the promise WON'T resolve when it is cancelled. This means the then() callback won't ever be executed.
 */
export const getProducts = async (abortController = undefined) => {
    if (!products) {
        const storage = localStorage.products;
        products = storage ? JSON.parse(storage) : shuffle(demoProducts).slice(0, 10);
        persistProducts(products);
    }

    return performCancellableRequest(() => products, abortController);
}

/**
 * Retrieve a specific product using its id.
 *
 * @param id The id of the product that is to be retrieved.
 * @param abortController An AbortController that can be used to perform cleanup and cancel the asynchronous request.
 * @returns {Promise<Product[]>}  A single product, wrapped in a Promise,
 * the promise WON'T resolve when it is cancelled. This means the then() callback won't ever be executed.
 */
export const getProductById = async (id, abortController = undefined) => {
    const products = await getProducts(abortController);
    return performCancellableRequest(() => products.find(p => p.id === id), abortController);
}

/**
 * Add a new random product to the website.
 */
export const addNewProduct = async () => {
    let p;
    do {
        p = shuffle(demoProducts)[0];
    }
    while (!!products.find(p2 => p2.id === p.id));

    products.push(p);
    persistProducts(products);
}

/**
 * Delete the product with the given id.
 *
 * @param productId The id of the product that is to be deleted.
 */
export const deleteProduct = async (productId) => {
    products = products.filter(p => p.id !== productId);
    persistProducts(products);
}

/**
 * Add a new attribute to the given product.
 *
 * @param productId The id of the product to which the attribute must be added.
 * @param name The name of the new attribute.
 * @param value The value of the new attribute.
 */
export const addAttribute = (productId, name, value) => {
    const p = products.find(p => p.id === productId);
    p.attributes.push({name, value, id: uuid()});
    persistProducts(products);
}

/**
 * Remove an attribute from the given product.
 *
 * @param productId The id of the product from which the attribute must be removed.
 * @param attributeId The id of the attribute that is to be removed.
 */
export const removeAttribute = (productId, attributeId) => {
    const p = products.find(p => p.id === productId);
    p.attributes = p.attributes.filter(a => a.id !== attributeId);
    persistProducts(products);
}
