import { createCanvasRepository } from 'redhotmagma-graphics-editor/CanvasAdapters/PaperJs/CanvasRepository';
import { CanvasEvents } from 'redhotmagma-graphics-editor/CanvasInterface/Canvas';
import { applyFontSizeMin } from 'redhotmagma-graphics-editor/CanvasAdapters/PaperJs/Modifiers/FontSizeMin';

import { isProduction } from 'App/configuration';
import { prepareCanvasData } from './Utils/Transformers';
import { updateProviders } from './withActiveCanvas';
import { createDebounceManager } from 'Utils/Function/createDebounceManager';

const CHANGE_DEBOUNCE_TIMEOUT = 200;

export const setupDesigner = designDataService => {
    // use a WeakMap for storing the canvas objects
    // so the GC can free them up when the key object is no more there
    const canvasMap = new WeakMap();

    const canvasRepository = createCanvasRepository(canvasMap);

    const canvasSerializer = canvas => canvas.toObjectWithMetric();
    designDataService.registerCanvasSerializer(canvasSerializer);

    // setup object change listener for newly created canvases
    // to store the serialized canvas data when something has changed
    const canvasChangeListener = designDataService.createCanvasChangeListener({
        syncCanvasChange: () => canvasChangeDebounceManager.sync(),
    });

    const canvasChangeDebounceManager = createDebounceManager(canvasChangeListener, CHANGE_DEBOUNCE_TIMEOUT);

    canvasRepository.onCreate = canvas => {
        let lastLength = canvas.getLength();
        canvas.on(CanvasEvents.OnObjectChange, (targetCanvas, eventData) => {
            // check if the canvas is the active canvas
            // events from non-actives should be ignored in order to not modify the active design data illegally
            if (canvas !== canvasRepository.getActiveCanvas()) {
                // eslint-disable-next-line no-console
                console.log('The canvas is not active, skipping interaction.');
                return;
            }

            const length = canvas.getLength();
            if (lastLength !== length) {
                lastLength = length;
                canvasChangeDebounceManager.invokeSync(canvas, eventData);
            } else {
                canvasChangeDebounceManager.invokeDebounced(canvas, eventData);
            }
        });
    };

    // expose the canvasRepository for easier debugging in dev mode
    if (!isProduction()) {
        window.canvasRepository = canvasRepository;
    }

    // request (get or create) the associated canvas when the user selects a design area
    designDataService.registerDesignAreaChangeListener(
        ({ selectedDesignArea, prevSelectedDesignArea, canvasSize }) => {
            if (
                prevSelectedDesignArea &&
                selectedDesignArea &&
                prevSelectedDesignArea.identifier === selectedDesignArea.identifier &&
                canvasMap.has(prevSelectedDesignArea)
            ) {
                const canvas = canvasMap.get(prevSelectedDesignArea);
                canvasMap.delete(prevSelectedDesignArea);
                canvasMap.set(selectedDesignArea, canvas);
                canvas.setSize(canvasSize);
            }

            // naively check that a "valid" design area is selected
            if (selectedDesignArea.identifier && canvasSize.width > 0 && canvasSize.height > 0) {
                const canvas = canvasRepository.requestCanvas(selectedDesignArea, canvasSize);
                updateProviders(canvas);
            } else {
                // clear the selection on the active canvas (so the tools will be hidden)
                const canvas = canvasRepository.getActiveCanvas();
                if (canvas) {
                    canvas.clearSelection();
                }
            }
        }
    );

    designDataService.registerUpdateCanvasData((canvasData, designArea = null, canvasSize = null) => {
        const canvas = designArea
            ? canvasRepository.requestCanvas(designArea, canvasSize, { activate: false })
            : canvasRepository.getActiveCanvas();
        return canvas.updateFromObject(prepareCanvasData(canvasData || {}));
    });

    // update the minimum font size when the design production method changes
    designDataService.registerDesignProductionMethodChangeListener(() => {
        const canvas = canvasRepository.getActiveCanvas();
        applyFontSizeMin(canvas, designDataService.getFontSizeMin());
    });

    // this function is responsible for loading the given canvas data
    // 1. to the active canvas (when no designArea provided)
    // 2. to the given designArea (canvasSize must be also provided)
    designDataService.registerLoadCanvasData((canvasData, designArea = null, canvasSize = null) => {
        const canvas = designArea
            ? canvasRepository.requestCanvas(designArea, canvasSize, { activate: false })
            : canvasRepository.getActiveCanvas();
        return canvas.fromObject(prepareCanvasData(canvasData || {}));
    });

    // get the associated canvas for the given designArea, if there is one already
    designDataService.registerGetCanvas(designArea =>
        canvasMap.has(designArea) ? canvasMap.get(designArea) : false
    );

    designDataService.registerInitCanvas(
        async ({ designArea, prevDesignArea, canvasSize, maskData, colorOverlayData }) => {
            let canvas;
            let isNew;
            if (canvasMap.has(prevDesignArea)) {
                canvas = canvasMap.get(prevDesignArea);
                canvasMap.delete(prevDesignArea);
                canvasMap.set(designArea, canvas);
                canvas.setSize(canvasSize);
                isNew = false;
            } else {
                canvas = canvasRepository.requestCanvas(designArea, canvasSize);
                isNew = true;
            }
            try {
                await canvas.useMask(maskData);
            } catch (e) {
                // eslint-disable-next-line no-console
                console.error('Error while applying mask:', e);
                // eslint-disable-next-line no-console
                console.error('Mask data:', maskData);
            }
            canvas.useColorOverlay(colorOverlayData);
            return {
                canvas,
                designArea,
                isNew,
            };
        }
    );

    designDataService.registerRemoveCanvas(designArea => {
        if (canvasMap.has(designArea)) {
            canvasMap.get(designArea).dispose();
            canvasMap.delete(designArea);
        }
    });
};

export default setupDesigner;
