import find from 'lodash/find';
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import isEmpty from 'lodash/isEmpty';
import first from 'lodash/first';
import { t } from 'Framework/i18n';
import { prepareImageObjectData, prepareObjectData } from 'App/Modules/Designer/Utils/Transformers';
import { Services } from '../ServiceLocator';
import { getConf } from '../configuration';
import { getUrl } from '../Utils/getUrl';
import {
    getPreviewUrlFromImageData,
    isEditMode,
    isHardModified,
    isColorPreviewCompatible,
} from '../Reducers/ImageGallery/Selectors';
import {
    fetchTags,
    uploadImage,
    setSelectedImageByIdentifier,
    fetchGallery,
    setModeToEdit,
    setModeToAdd,
    updateUploadDelayed,
} from '../Reducers/ImageGallery/Actions';
import {
    createColorData,
    getDefaultTextColor,
    getFontSizeMin,
    hasPlaceHolderImagesForActiveDesignArea,
    getVectorizedImageColor,
    getElementLockState,
    getSelectedDesignProductionMethod,
    getDesignDataForSelectedDesignArea,
} from '../Reducers/DesignArea/Selectors';
import { getDefaultFontParams } from '../Reducers/Fonts/Selectors';
import { createTextPlaceholder } from '../Reducers/ImageGallery/Modifiers';
import { updateLinkedImagesWithImageData } from '../Reducers/DesignData/Actions';
import { checkDesignElementRestrictions } from '../Reducers/DesignArea/DesignElementSelectors';
import { FontFaceRepository } from '../Modules/Designer/Utils/FontFaceRepository';

function getCanvasImageScalingWithImageData(canvasImage, imageData) {
    const currentPreview = canvasImage.imageData.preview;
    const nextPreview = imageData.preview;
    return canvasImage.scaling * (currentPreview.width / nextPreview.width);
}

function updateCanvasImageWithImageData(canvasImage, imageData) {
    return canvasImage.updateOptions(
        prepareImageObjectData({
            imageData,
            src: imageData.preview.url?.startsWith('http')
                ? imageData.preview.url
                : `${getConf('network.host')}${imageData.preview.url}`,
            scaling: getCanvasImageScalingWithImageData(canvasImage, imageData),
        })
    );
}

function updateLinkedCanvasImagesWithImageData(imageIdentifier, imageData) {
    Services.store.dispatch(updateLinkedImagesWithImageData(imageIdentifier, imageData));
    return Services.designData.updateCanvasDataFromState();
}

export default class DesignerService {
    static serviceName = 'DesignerService';

    constructor() {
        this.imageEditDialogContinueClickHandler = null;
        this.imageEditDialogCloseHandler = null;
    }

    async openImageEditDialog(identifier, canvas, options = {}) {
        if (!identifier && !this.checkDesignElementRestrictions('image')) {
            return;
        }

        const { placeholderImageUpdate = false, submitOnExitClick = false } = options;

        const imageEditDialogIdentifier = 'imageEditDialog';

        const userImages = get(Services.store.state, 'imageGallery.userImages', []);

        const originalImage = (identifier && find(userImages, { identifier })) || null;

        if (originalImage) {
            Services.store.dispatch(setSelectedImageByIdentifier(identifier));
            Services.store.dispatch(setModeToEdit());
        } else {
            const firstUserImage = first(userImages);
            firstUserImage && !firstUserImage.isPlaceHolderImage
                ? Services.store.dispatch(setSelectedImageByIdentifier(firstUserImage.identifier))
                : Services.store.dispatch(setSelectedImageByIdentifier(null));
            Services.store.dispatch(setModeToAdd());
        }

        this.imageEditDialogContinueClickHandler = async () => {
            const editMode = isEditMode(Services.store.state);
            let updatePosition = false;

            if (editMode && !placeholderImageUpdate && !canvas.selectedObject) {
                throw new Error(
                    'imageEditDialogContinueClickHandler must be called in edit mode while either there is an active canvas object or placeholderImageUpdate is true'
                );
            }

            if (!isColorPreviewCompatible(Services.store.state)) {
                return;
            }

            if (placeholderImageUpdate && hasPlaceHolderImagesForActiveDesignArea(Services.store.state)) {
                const placeholderImageObject = canvas.objects.find(
                    obj => obj.type === 'Image' && obj.imageData.isPlaceHolderImage
                );
                updatePosition = { x: placeholderImageObject.x, y: placeholderImageObject.y };
                placeholderImageObject && placeholderImageObject.remove();
            }
            Services.designData.commitChanges();
            const {
                imageGallery: { selectedImage: image },
            } = Services.store.state;

            await Services.designData.editFile({ ...image.operations }, image, { immediateCommit: true });

            if (editMode && canvas.selectedObject) {
                originalImage && !isHardModified(image, originalImage)
                    ? await updateLinkedCanvasImagesWithImageData(originalImage.identifier, image)
                    : await updateCanvasImageWithImageData(canvas.selectedObject, image);
            } else {
                await this.addImageFromUrl(canvas, getPreviewUrlFromImageData(image.preview), image, {
                    updatePosition,
                });
            }

            Services.ui.hide(imageEditDialogIdentifier);
        };

        this.imageEditDialogCloseHandler = submitOnExitClick
            ? this.imageEditDialogContinueClickHandler
            : () => {
                  const {
                      imageGallery: { selectedImage },
                  } = Services.store.state;

                  if (
                      selectedImage &&
                      originalImage &&
                      selectedImage.identifier === originalImage.identifier &&
                      !isEqual(selectedImage.operations, originalImage.operations)
                  ) {
                      Services.designData.editFile(originalImage.operations, selectedImage, {
                          immediateCommit: true,
                      });
                  }

                  Services.ui.hide(imageEditDialogIdentifier);
              };

        await Promise.all([
            !Services.store.state.imageGallery.tags.length && this.loadTags(),
            !Services.store.state.imageGallery.gallery.length && this.loadImages(),
        ]);

        Services.ui.show(imageEditDialogIdentifier);
    }

    async loadTags() {
        return await Services.store.dispatch(fetchTags());
    }

    async loadImages() {
        return await Services.store.dispatch(fetchGallery());
    }

    async uploadFile(file) {
        await Services.store.dispatch(setSelectedImageByIdentifier(null));
        const timeout = setTimeout(() => {
            Services.store.dispatch(updateUploadDelayed(true));
        }, 10000);
        const response = await Services.store.dispatch(uploadImage(file));
        clearTimeout(timeout);
        const url = get(response, 'data.preview.url');
        const term = get(response, 'data.message');
        if (term) {
            Services.ui.alertMessage(t(term));
        }
        if (!url) {
            throw new Error('Preview image url is not specified!');
        }
        Services.store.dispatch(updateUploadDelayed(false));
        return getUrl(url);
    }

    async addImageFromUrl(canvas, url, imageData, options = {}) {
        canvas.clearSelection();

        const {
            enlargeImage = false,
            updatePosition = false,
            scaleToHalf = true,
            autoSelect = true,
        } = options;

        const locked = getElementLockState(Services.store.state);

        const item = await canvas.addImage(
            prepareObjectData({ src: url, imageData, locked, autoFit: locked })
        );

        item.selected = !!autoSelect;

        item.fitCanvas(enlargeImage);

        if (scaleToHalf && item.scaling !== 1) {
            item.scaling *= 0.5;
        }
        if (updatePosition) {
            item.x = updatePosition.x;
            item.y = updatePosition.y;
        }
    }

    async addText(canvas, textContent = 'My Text', autoColorCount = 1, options = {}) {
        if (!this.checkDesignElementRestrictions('text')) {
            return;
        }

        canvas.clearSelection();

        const state = Services.store.state;

        const {
            fontSizeMin = getFontSizeMin(state),
            locked = getElementLockState(state),
            fontItem = find(state.fonts, { isDefault: true }) || state.fonts[0],
            htmlContent = this.createColorizedTextContent(textContent, autoColorCount),
            fitCanvas = true,
        } = options;

        if (!fontItem) {
            Services.ui.reportError(t('common.noFontsAvailable'));
        } else {
            await FontFaceRepository.collectFromHTML(htmlContent);
            const text = canvas.addText({
                content: htmlContent,
                fontSize: 16,
                fontSizeMin,
                fontItem,
                locked,
                autoFit: locked,
            });

            text.selected = true;

            if (!locked && fitCanvas) {
                this.fitTextToCanvas(text);
            }
        }
    }

    fitTextToCanvas(text) {
        const canvas = text.canvas;
        text.alignTo('center', 'center');
        text.fontSize = Math.max(text.fontSizeMin, Math.min(canvas.size.width, canvas.size.height) * 0.4);
        if (text.fontSize > text.fontSizeMin && text.getScaledSize().width > canvas.size.width) {
            text.fitCanvas();
        }
    }

    checkDesignElementRestrictions(targetElementType) {
        const state = Services.store.state;
        const designData = getDesignDataForSelectedDesignArea(state);
        const designProductionMethod = getSelectedDesignProductionMethod(state);

        const result = checkDesignElementRestrictions(designData, designProductionMethod, targetElementType);

        if (result) {
            Services.ui.reportError(
                t(
                    `designElementRestrictions.addElementMessages.${result.type}${
                        result.max === 1 ? '_1' : ''
                    }`,
                    result
                )
            );
            return false;
        }
        return true;
    }

    /**
     * @param {string} str
     * @param {number} [colorCount=1]
     * @param {object} [options={}]
     * @returns {string}
     */
    createColorizedTextContent(str, colorCount = 1, options = {}) {
        const { withPlaceholder = true } = options;

        const state = Services.store.state;
        const fontParams = getDefaultFontParams(state);
        const chunkLength = Math.ceil(str.length / colorCount);

        const content = str
            .match(new RegExp(`.{1,${chunkLength}}`, 'g'))
            .map((chunk, idx) => {
                const color = getDefaultTextColor(state, colorCount > 1 ? idx : null);

                const lastImageColor = getVectorizedImageColor(state);
                const colorData = isEmpty(lastImageColor)
                    ? createColorData(color.identifier, color.paletteIdentifier, color.custom, color.value)
                    : lastImageColor;

                return `<span style='color: ${colorData.value}; ${fontParams}' data-color='${colorData.identifier}'>${chunk}</span>`;
            })
            .join('');

        return withPlaceholder ? createTextPlaceholder(content) : content;
    }
}
