/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 ~ Copyright 2019 Adobe
 ~
 ~ Licensed under the Apache License, Version 2.0 (the "License");
 ~ you may not use this file except in compliance with the License.
 ~ You may obtain a copy of the License at
 ~
 ~     http://www.apache.org/licenses/LICENSE-2.0
 ~
 ~ Unless required by applicable law or agreed to in writing, software
 ~ distributed under the License is distributed on an "AS IS" BASIS,
 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
'use strict';

/**
 * Variant selector component.
 */
class VariantSelector {
    constructor(config) {
        this._element = config.element;
        this._product = config.product;

        this._state = {
            // Currently selected variant
            variant: {},

            // Currently selected variant attributes
            attributes: {},

            // Currently selected variant attributes for Beacon tracking
            selections: {},

            // Reference to buttons
            buttons: [],

            // List of product variants
            variantData: []
        };

        // Parse variant data
        this._state.variantData = JSON.parse(this._element.dataset.variants);

        // Add click event handlers to variant selection buttons
        this._state.buttons = this._element.querySelectorAll(VariantSelector.selectors.variantButtons);
        this._state.buttons.forEach(function(button) {
            button.addEventListener('click', this._onSelectVariant.bind(this));
        }, this);

        this._state.useUid = this._product.hasAttribute('data-uid-cart');

        // Update button state on variant change
        this._element.addEventListener(VariantSelector.events.variantChanged, this._updateButtonActiveClass.bind(this));

        // Check for initial variant sku in hash and listen for url changes
        this._initFromHash();
        window.addEventListener('popstate', this._initFromHash.bind(this));
    }

    /**
     * Select variant from the current location hash. This method will update
     * the internal state and emit a variantchanged event, so all components can
     * update accordingly.
     */
    _initFromHash() {
        let sku = window.location.hash.replace('#', '');
        if (!sku) return;

        // Find variant with given sku
        this._state.variant = this._findSelectedVariant(sku);

        // If variant is valid, store variant attributes and emit event to update
        // buttons and parent components.
        if (this._state.variant) {
            this._state.attributes =
                this._state.useUid && this._state.variant.hasOwnProperty('variantAttributesUid')
                    ? { ...this._state.variant.variantAttributesUid }
                    : { ...this._state.variant.variantAttributes };

            Object.entries(this._state.attributes).forEach(([attribute, id]) => {
                this._state.selections[attribute] = this._element.querySelector(
                    `button[data-id="${id}"]`
                ).dataset.title;
            });

            this._emitVariantChangedEvent();
        }
    }

    /**
     * Emit a variantchanged event with a copy of the current component state.
     */
    _emitVariantChangedEvent() {
        let variantEvent = new CustomEvent(VariantSelector.events.variantChanged, {
            bubbles: true,
            detail: this._state
        });
        this._element.dispatchEvent(variantEvent);
    }

    /**
     * Variant changed event handler that adds or removes active styles on variant
     * selection buttons based on the internal state.
     */
    _updateButtonActiveClass() {
        this._state.buttons.forEach(function(button) {
            const attributeIdentifier = button.closest('div.tileList__root').dataset.id;
            const valueIdentifier = button.dataset.id;

            if (
                this._state.attributes[attributeIdentifier] &&
                this._state.attributes[attributeIdentifier] == valueIdentifier
            ) {
                if (button.classList.contains('swatch__root')) {
                    button.classList.add('swatch__root_selected');
                    button.innerHTML = VariantSelector.buttonCheckIcon; // Add check icon
                } else {
                    button.classList.add('tile__root_selected');
                }
            } else {
                if (button.classList.contains('swatch__root')) {
                    button.classList.remove('swatch__root_selected');
                    button.innerHTML = '';
                } else {
                    button.classList.remove('tile__root_selected');
                }
            }
        }, this);
    }

    /**
     * Returns the currently selected variant that matches either the sku given
     * as argument or the current variant attribute selection stored in the
     * internal state or null if no matching variant exists.
     */
    _findSelectedVariant(sku) {
        // Iterate variants
        for (let variant of this._state.variantData) {
            let match = true;

            // If sku is given, match with sku
            if (sku && variant.sku == sku) {
                return variant;
            }

            // Iterate variant attributes
            if (sku) continue;
            for (let key in variant.variantAttributes) {
                let selectedValue = this._state.attributes[key];
                if (this._state.useUid) {
                    match = match && selectedValue == variant.variantAttributesUid[key];
                } else {
                    match = match && selectedValue == variant.variantAttributes[key];
                }
            }

            // Return variant if all variant attributes match
            if (match) return variant;
        }

        // Return null if nothing matches
        return null;
    }

    /**
     * Click event handler for a variant selection button that selects a new
     * variant and stores it in the internal state and dispatches a variantchanged
     * event.
     */
    _onSelectVariant(event) {
        // Get value identifier from button
        const button = event.target.closest('button');
        const valueIdentifier = button.dataset.id;
        const valueLabel = button.dataset.title;

        // Get attribute identifier from parent
        const parent = button.closest('div.tileList__root');
        const attributeIdentifier = parent.dataset.id;

        // Store selected variant
        this._state.attributes[attributeIdentifier] = valueIdentifier;

        // Store selection
        this._state.selections[attributeIdentifier] = valueLabel;

        // Find selected variant based on selected variants attributes
        this._state.variant = this._findSelectedVariant.bind(this)();
        this._emitVariantChangedEvent();

        // Update hash only if a proper variant was found
        if (this._state.variant) {
            this._setHash(this._state.variant.sku);
        }

        // Don't reload page on click
        event.preventDefault();
    }

    /**
     * Set location hash by pushing to the history state.
     */
    _setHash(sku) {
        history.pushState(null, null, '#' + sku);
    }
}

VariantSelector.selectors = {
    self: '.productFullDetail__options[data-variants]',
    variantButtons: '.productFullDetail__options button',
    product: '[data-cmp-is=product]'
};

VariantSelector.events = {
    variantChanged: 'variantchanged'
};

VariantSelector.buttonCheckIcon =
    '<span class="icon__root"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></span>';

(function(document) {
    function onDocumentReady() {
        // Initialize variant selector
        const variantSelectorCmp = document.querySelector(VariantSelector.selectors.self);
        const productCmp = document.querySelector(VariantSelector.selectors.product);
        if (variantSelectorCmp) new VariantSelector({ element: variantSelectorCmp, product: productCmp });
    }

    if (document.readyState !== 'loading') {
        onDocumentReady();
    } else {
        document.addEventListener('DOMContentLoaded', onDocumentReady);
    }
})(window.document);

export default VariantSelector;
