system.directive('fulfillgelato', function () {
    return {
        controller: function ($scope, $http, Upload, $q) {
            this.__proto__ = new BaseFulfillController($scope, $http);
            let httpRequestCanceller = $q.defer();
            const axiosClient = axios.create({});
            const framedImageWorkerUrl = 'https://' + window.location.hostname + '/print-suppliers/framed-image';

            $scope.gelatoCurrentOrder = {};
            $scope.gelatoCatalogs = [];
            $scope.gelatoFilteredCatalogs = [];
            $scope.gelatoRegions = {};

            $scope.gelatoState = {
                loading: true,
                loadingProductInfo: false,
                typing: null,
                search_type: null,
                search: ''
            };

            $scope.fulFillLocale = null;
            $scope.fulFillCurrency = 'GBP';
            $scope.CURRENCIES = [
                'USD',
                'EUR',
                'GBP',
                'AUD',
                'CAD',
                'JPY'
            ];
            $scope.productTemplateIds = [];

            $scope.spreads = [
                'default',
                'back',
                'neck-inner',
                'neck-outer',
                'sleeve-left',
                'sleeve-right',
                'inside',
            ];

            const cacheVersion = '_v12272023';

            $scope.initializeGelato = function () {

                const fulFillCurrency = localStorage.getItem('gelato_order_currency');
                if (fulFillCurrency) {
                    $scope.fulFillCurrency = fulFillCurrency;
                }

                $(document).mouseup(function (e) {
                    let container = $(".gelato-data");
                    if (!container.is(e.target) && container.has(e.target).length === 0) {
                        if ($scope.gelatoState.searchingFor) {
                            $scope.gelatoState.searchingFor = null;
                        }
                    }
                });
            }

            $scope.changeCurrency = function() {
                localStorage.setItem('gelato_order_currency', $scope.fulFillCurrency);
            }

            $scope.gelato_openFulfillForm = async function (orderOrCard) {

                let order = angular.copy(orderOrCard);

                if (typeof orderOrCard.order_id != 'undefined') 
                {
                    $scope.loadingOrderInfo = true; 
                    order = await $scope.loadOrderInfo(orderOrCard.order_id, orderOrCard.locale_code);
                    $scope.loadingOrderInfo = false;

                    $scope.fulFillLocale = orderOrCard.locale_code;

                    safeApply();

                    if (!order) return toastr.error('Error! Cannot find order infomation...');

                } else {
                    if (typeof order.location != 'undefined' && order.location !== '') {
                        $scope.fulFillLocale = angular.copy(order.location);
                    } else {
                        $scope.fulFillLocale = null;
                    }
                }

                if (!$scope.gelatoCatalogs.length) $scope.gelato_loadCatalogs();

                $scope.gelatoCurrentOrder = angular.copy(order);
                $scope.getSuggestsPrinting($scope.gelatoCurrentOrder.code);
                if ($scope.gelatoCurrentOrder.attributes.customer_info) {
                    $scope.gelatoCurrentOrder.customer.phone = $scope.gelatoCurrentOrder.attributes.customer_info.phone;
                    $scope.gelatoCurrentOrder.customer.full_name = $scope.gelatoCurrentOrder.attributes.customer_info.full_name;
                }

                if ($scope.gelatoCurrentOrder.shipping_type === 'express')
                    $scope.gelatoCurrentOrder.shipping_type = 'express';
                else
                    $scope.gelatoCurrentOrder.shipping_type = 'standard';
                
                if ($scope.gelatoCurrentOrder.customer) {

                    if (typeof $scope.gelatoCurrentOrder.attributes.gift_info != 'undefined' && $scope.gelatoCurrentOrder.attributes.gift_info.name) {
                        $scope.gelatoCurrentOrder.customer.full_name = $scope.gelatoCurrentOrder.attributes.gift_info.name;
                        if ($scope.gelatoCurrentOrder.attributes.gift_info.phone) {
                            $scope.gelatoCurrentOrder.customer.phone = $scope.gelatoCurrentOrder.attributes.gift_info.phone;
                        }
                    }

                    $scope.gelatoCurrentOrder.customer.full_name = $scope.capitalizeFirstLetter($scope.gelatoCurrentOrder.customer.full_name);
                    if (window.user_email) {
                        $scope.gelatoCurrentOrder.customer.email = window.user_email;
                    }
                }

                for (let i = 0; i < $scope.gelatoCurrentOrder.items.length; i++) {
                    let item = $scope.gelatoCurrentOrder.items[i];
                    $scope.getCustomConfigs(item);
                    item.gelatoData = {
                        files: [
                            {
                                print_type: 'default',
                                url: null,
                                extension: null,
                                frame_url: null,
                                crop_params: null
                            }
                        ],
                        catalog: null,
                        states: {} // exp: loading = false
                    };
                }

                safeApply();

                loadGelatoRegions();
                gelato_loadZipcode();
                gelato_fetchProfiles();

                $scope.checkEstimateCosts($scope.gelatoCurrentOrder, toastr);

                $('#modalFulfillGelato').modal({
                    modal: true,
                    persist: true,
                    position: [30, 0],
                    autoPosition: true
                });
            }

            $scope.gelato_loadCatalogs = function () {
                $scope.gelatoState.loading = true;
                $http.get('/modules/print-suppliers/json/gelato_catalogs.json').then(function (response) {
                    let catalogsResponse = response.data;
                    if (catalogsResponse) {
                        $scope.gelatoCatalogs = catalogsResponse;
                        $scope.gelatoFilteredCatalogs = catalogsResponse.sort(function (a, b) {
                            if (a.title < b.title) { return -1; }
                            if (a.title > b.title) { return 1; }
                            return 0;
                        });
                    }
                    $scope.gelatoState.loading = false;
                }, function () {
                    console.warn(error);
                    toastr.error('Error load gelato catalogs!');
                    $scope.gelatoState.loading = false;
                });
            }

            $scope.gelato_searchCatalogs = function () {
                if ($scope.gelatoState.typing) {
                    clearTimeout($scope.gelatoState.typing);
                }
                $scope.gelatoState.typing = setTimeout(() => {
                    let search = $scope.gelatoState.search;
                    let catalogsFiltered = [];
                    if (search) {
                        $scope.gelatoCatalogs.forEach(catalog => {
                            if (catalog.title.toLowerCase().search(search.toLowerCase()) >= 0) {
                                catalogsFiltered.push(catalog);
                            }
                        });
                    } else {
                        catalogsFiltered = $scope.gelatoCatalogs;
                    }

                    $scope.gelatoFilteredCatalogs = catalogsFiltered.sort(function (a, b) {
                        if (a.title < b.title) { return -1; }
                        if (a.title > b.title) { return 1; }
                        return 0;
                    });

                    safeApply();

                }, 200);
            }

            $scope.chooseCatalog = function (item, catalog = null, callbackFilterCatalog = null) {
                item.gelatoData.states.loadingFilters = true;

                if (!catalog)
                    catalog = item.gelatoData.catalog;
                else
                    item.gelatoData.catalog = catalog;

                if (catalog.catalogUid) { // load detail catalog
                    item.gelatoData.filters = [];
                    item.gelatoData.filterChoosed = {};
                    item.gelatoData.productUid = null;
                    item.gelatoData.products = [];

                    let catalogFromCache = $scope.readFromCache('gelato_catalog_' + catalog.catalogUid + cacheVersion);
                    if (catalogFromCache) {
                        loadFiltersCatalog(catalogFromCache, item, callbackFilterCatalog);
                        item.gelatoData.states.loadingFilters = false;
                    } else {
                        const requestObj = $scope.buildLocaleData('/print-suppliers/gelato/api?end_point=catalogs/' + catalog.catalogUid, {}, $scope.fulFillLocale);
                        $http.get(requestObj.url, { params: requestObj.params }).then(function (response) {

                            if (response.data.catalogUid) {
                                $scope.writeToCache('gelato_catalog_' + catalog.catalogUid + cacheVersion, response.data);
                                loadFiltersCatalog(response.data, item, callbackFilterCatalog);
                            } else {
                                toastr.error('Error load detail gelato catalog!');
                                item.gelatoData.catalog = null;
                            }

                            item.gelatoData.states.loadingFilters = false;

                        }, function (error) {
                            toastr.error('Error load detail gelato catalog!');
                            item.gelatoData.catalog = null;
                        });
                    }
                }
            }

            $scope.changeGelatoData = function (item, callbackSuccess = null) {
                item.gelatoData.states.loadingProducts = true;
                item.gelatoData.productUid = null;

                let gelatoData = angular.copy(item.gelatoData);

                for (const key in gelatoData.filterChoosed) {
                    if (Object.hasOwnProperty.call(gelatoData.filterChoosed, key)) {
                        if (!gelatoData.filterChoosed[key]) delete gelatoData.filterChoosed[key];
                    }
                }

                httpRequestCanceller.resolve();
                httpRequestCanceller = $q.defer();

                const requestObj = $scope.buildLocaleData('/print-suppliers/gelato/api?method=POST&end_point=catalogs/' + gelatoData.catalog.catalogUid + '/products:search', {}, $scope.fulFillLocale);

                $http.post(requestObj.url, {
                    params: {
                        "attributeFilters": gelatoData.filterChoosed
                    },
                    ignore_localization: 1
                }, { timeout: httpRequestCanceller.promise }).then(onSuccess);

                function onSuccess(response) {
                    item.gelatoData.states.loadingProducts = false;
                    if (typeof response.data.products != 'undefined') {
                        item.gelatoData.products = response.data.products;
                    }
                    safeApply();
                    if (callbackSuccess) callbackSuccess();
                }
            }

            $scope.gelato_getDisplayNameProduct = function (product, item) {
                let display = [];
                for (const key in product.attributes) {
                    if (Object.hasOwnProperty.call(product.attributes, key)) {
                        const attributeValue = product.attributes[key];
                        let filter = item.gelatoData.filters.find(function (filter) {
                            return filter.productAttributeUid == key;
                        });
                        if (filter) {
                            let value = filter.values.find(function (value) {
                                return value.productAttributeValueUid == attributeValue;
                            });
                            if (value) {
                                display.push($scope.capitalizeFirstLetter(value.title));
                            }
                        }
                    }
                }
                return display.join(' ');
            }

            $scope.gelato_getAttribute = function (product, attributeKey, item) {
                let attribute = {};
                if (typeof product.attributes[attributeKey] != 'undefined') {
                    let attributeValue = product.attributes[attributeKey];
                    let filter = item.gelatoData.filters.find(function (filter) {
                        return filter.productAttributeUid == attributeKey;
                    });
                    if (filter) {
                        let value = filter.values.find(function (value) {
                            return value.productAttributeValueUid == attributeValue;
                        });
                        if (value) attribute = value;
                    }
                }
                return attribute;
            }

            $scope.chooseProduct = function (product, item)
            {
                resetActiveProduct(item);
                product.isActive = true;

                item.gelatoData.ColorHexCode = '#ffffff';
                try {
                    if (typeof product.dimensions['Colorhexcode'] != 'undefined' && product.dimensions['Colorhexcode'].value) {
                        item.gelatoData.ColorHexCode = product.dimensions['Colorhexcode'].value;
                    }
                } catch (error) {
                    console.warn('Lỗi đọc mã màu sản phẩm');
                }

                item.gelatoData.product = product;
                item.gelatoData.productUid = product.productUid;
            }

            $scope.gelato_uploadImages = async function (files, item, designFile) {
                let supportedFilesType = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
                let supportedFilesExtensions = ['jpg', 'jpeg', 'png', 'gif'];

                item.gelatoData.states.uploading = true;

                if (files) {
                    try {

                        let imagesUploaded = [];
                        let imageFile = null;
                        if (files && files.length) {
                            imageFile = files[0];

                            let fileExtension = imageFile.name.split('.').pop();
                            fileExtension = fileExtension.toLowerCase();
                            if (
                                supportedFilesExtensions.includes(fileExtension) &&
                                supportedFilesType.includes(imageFile.type)
                            ) {
                                imagesUploaded = await $scope.gelato_uploads(files);
                            } else {
                                toastr.error('Error! format supported: jpg, jpeg, png, gif');
                            }
                        }

                        if (imagesUploaded.length) {
                            $scope.$applyAsync(function () {
                                let imageUrl = imagesUploaded.pop();
                                if (imageUrl) {
                                    designFile.extension = imageFile.name.split('.').pop();
                                    designFile.url = imageUrl;
                                    designFile.frame_url = null;
                                    designFile.designPositions = {};
                                }
                                item.gelatoData.states.uploading = false;
                            });
                        }

                    } catch (error) {
                        console.log(error);
                        toastr.error('Error upload images!');
                    }
                }
            };

            $scope.gelato_uploads = function (files) {
                if (typeof googleCloudStorageBucket != 'undefined') {
                    baseFileManagerUrl = googleCloudStorageBucket;
                }
                return new Promise(function (resolve, reject) {
                    Upload.upload({
                        url: baseFileManagerUrl + '/upload',
                        data: { upload: files, type: 'design' }
                    }).then(function (resp) {
                        if (resp.data.status === 'successful') {
                            let upload = resp.data.upload;
                            resolve(upload);
                        } else {
                            reject(0);
                        }
                    }, function (resp) {
                        reject(0);
                    }, function (evt) {
                        $scope.progressPercentage = 100.0 * evt.loaded / evt.total;
                    });
                });
            };

            $scope.gelato_chooseDesign = function (design, designFile, item) {
                if (!design.is_design_comfirm) {
                    toastr.error('Design is not confirmed');
                    return;
                }
                if (designFile.url !== design.url) {
                    designFile.url = design.url;
                    designFile.extension = design.url.split('.').pop();
                    $scope.gelato_changeDesignUrl(designFile);
                }
            }

            $scope.orderGelato = function ()
            {
                safeApply();

                let printervalOrder = angular.copy($scope.gelatoCurrentOrder);
                let items = printervalOrder.items;

                let fullName = printervalOrder.customer.full_name;
                let partsName = fullName.split(' ');
                let lengthName = partsName.length;

                let lastName = partsName.pop();
                let firstName = partsName.join(' ');

                if (lengthName < 2) {
                    firstName = lastName;
                }

                let region = ' ';
                if (printervalOrder.state_name) {
                    region = printervalOrder.state_name;
                }

                // region to code
                for (const key in $scope.gelatoRegions) {
                    if (Object.hasOwnProperty.call($scope.gelatoRegions, key)) {
                        const gelatoRegion = $scope.gelatoRegions[key];
                        if (gelatoRegion.toLowerCase() === region.toLowerCase()) {
                            try {
                                let iso = key.split('_')[0];
                                if (iso.toLowerCase() === printervalOrder.country.iso.toLowerCase()) {
                                    region = key.split('_')[1];
                                    break;
                                }
                            } catch (error) {
                                console.warn("Error get code region!", error);
                            }
                        }
                    }
                }

                let city = ' ';
                if (printervalOrder.city_name) {
                    city = printervalOrder.city_name;
                }

                if (gelato_validateItem(items).validated) {

                    let orderReferenceId = "API";
                    if (printerval_locate && printerval_locate !== '') {
                        orderReferenceId += "_" + printerval_locate.toUpperCase();
                    }
                    orderReferenceId += "_" + printervalOrder.code;

                    let gelatoOrderData = {
                        //"orderType": 'draft',
                        "orderReferenceId": orderReferenceId,
                        "customerReferenceId": "customer-" + printervalOrder.customer.id,
                        "currency": $scope.fulFillCurrency,
                        "shipmentMethodUid": (printervalOrder.shipping_type === 'standard' ? 'normal' : 'express'),
                        "shippingAddress": {
                            "firstName": firstName,
                            "lastName": lastName,
                            "addressLine1": printervalOrder.delivery_address,
                            "addressLine2": printervalOrder.optional_address,
                            "state": region,
                            "city": city,
                            "postCode": printervalOrder.zip_code,
                            "country": printervalOrder.country.iso,
                            "email": printervalOrder.customer.email,
                            "phone": printervalOrder.customer.phone
                        }
                    };

                    let profiles = [];
                    let lineItems = [];
                    const mapItemPrintProvider = {};

                    for (let i = 0; i < items.length; i++) {
                        const itemData = items[i];
                        const gelatoData = items[i].gelatoData;

                        let files = [];
                        for (let j = 0; j < gelatoData.files.length; j++) {
                            let printFileUrl = gelatoData.files[j].url;

                            if (typeof gelatoData.files[j].frame_url !== 'undefined' && gelatoData.files[j].frame_url) {
                                printFileUrl = gelatoData.files[j].frame_url;
                            }

                            files.push({
                                type: gelatoData.files[j].print_type,
                                url: printFileUrl
                            });
                        }

                        let gelatoItem = {
                            "itemReferenceId": itemData.id,
                            "productUid": itemData.gelatoData.productUid,
                            "files": files,
                            "quantity": itemData.quantity
                        };

                        lineItems.push(gelatoItem);

                        profiles.push({
                            "product_id": itemData.product_id,
                            "product_sku_id": itemData.product_sku_id,
                            "is_template_product": isTemplateProduct(itemData.product_id),
                            "order_id": printervalOrder.id,
                            "order_item_id": itemData.id,
                            "options": {
                                "productUid": itemData.gelatoData.productUid,
                                "catalog": itemData.gelatoData.catalog,
                                "filterChoosed": itemData.gelatoData.filterChoosed,
                                "files": gelatoData.files
                            },
                            "type": 'gelato'
                        });

                        mapItemPrintProvider[itemData.id] = 'GELATO';
                    }

                    gelatoOrderData.items = lineItems;

                    const param = {
                        title: 'Create Order',
                        customText: 'Post order to Gelato!',
                        confirmButtonText: 'Ok',
                        cancelButtonText: 'Back',
                        arg: {gelatoOrderData, profiles}
                    };

                    $scope.callConfirmModal(param, async function (arg) {
                        loadingButton('#btnPostGelatoOrder');
                        await gelato_postProfiles(arg.profiles);
                        await gelato_postZipcode(arg.gelatoOrderData);
                        postToGelato(arg.gelatoOrderData, {mapItemPrintProvider});
                    });

                } else { // show error validate
                    document.getElementById("errorModalGelato").style.display = "block";
                }

            }

            $scope.gelato_changeDesignUrl = function (designFile)
            {
                designFile.extension = designFile.url.split('.').pop();
                designFile.crop_params = null;
                designFile.frame_url = null;
                designFile.designPositions = {};

                safeApply();
            }

            /* Design Location */
            $scope.designCanvasObject = {};
            $scope.Math = window.Math;

            let initPosition = {};
            let canvas = null;
            let frame = {padding: 50}

            $scope.gelato_openEditDesignModal = function (designFile, item)
            {
                $scope.currentDesignItem = item;
                $scope.currentDesignFile = designFile;
                $scope.loadingDesign = true;
                
                $("#DesignModalGelato").show();
                if(canvas) {
                    canvas.clear();
                    canvas.dispose();
                }

                axiosClient.get('https://preflight-cdn.live.gelato.tech/product/template?product_uid=' + $scope.currentDesignItem.gelatoData.productUid + '&type=json').then(response => {

                    let designTemplate = {};
                    try {
                        designTemplate.width = response.data.spreads[0].pages[0].width;
                        designTemplate.height = response.data.spreads[0].pages[0].height;
                        
                        /*if (response.data.spreads.length > 1) {
                            $scope.gelatoWarning = 'Sản phẩm này có thể in nhiều trang (mặt), vui lòng dùng file pdf tương ứng để in đủ số lượng. Hình ảnh chỉ hỗ trợ in trang đầu tiên!';
                        }*/

                        frame.width = frame.origin_width = designTemplate.width;
                        frame.height = frame.origin_height = designTemplate.height;
                        frame.ratio = designTemplate.width / designTemplate.height;

                        if(typeof $scope.currentDesignFile.designPositions == 'undefined' || !$scope.currentDesignFile.designPositions) {
                            $scope.currentDesignFile.designPositions = {
                                frame: {}
                            };
                        }

                        $scope.currentDesignFile.designPositions.frame = frame;

                    } catch (error) {
                        console.warn("Missing fileSpecifications data", error);
                    }

                    safeApply();

                    if(typeof frame.width != 'undefined' &&  typeof frame.height != 'undefined' && frame.width && frame.height) {
                        // calculate canvas size
                        let contentBlock = $('#DesignModalGelato .error-modal-content');
                        let canvasDimensions = {
                            width: contentBlock.width(),
                            height: $(window).height() - 200,
                            padding: 50
                        }

                        canvas = this.__canvas = new fabric.Canvas('cGelato');
                        canvas.setHeight(canvasDimensions.height);
                        canvas.setWidth(canvasDimensions.width);

                        canvas.setBackgroundColor({source: '/modules/print-suppliers/images/transparent.png', repeat: 'repeat'}, function () {
                            canvas.renderAll();
                        });

                        let borderColor = null;
                        try {
                            let bgColor = item.gelatoData.ColorHexCode;
                            borderColor = $scope.invertColor(bgColor, true);
                            canvas.setBackgroundColor(bgColor, canvas.renderAll.bind(canvas));
                        } catch (error) {

                        }

                        let fitDimension = 'width';
                        // đưa chiều rộng về bằng nhau giữa khung và canvas
                        let tempWidth = canvasDimensions['width'];
                        let tempHeight = tempWidth / frame.ratio;
                        // nếu chiều cao lớn hơn chiều cao của casvas cần căn theo chiều cao
                        if(tempHeight > canvasDimensions.height) {
                            fitDimension = 'height';
                        }

                        frame[fitDimension] = canvasDimensions[fitDimension] - 2 * canvasDimensions.padding;
                        // tính chiều còn lại theo tỉ lệ
                        if(fitDimension === 'width') {
                            frame['height'] = frame['width'] / frame.ratio;
                        } else {
                            frame['width'] = frame['height'] * frame.ratio;
                        }

                        // căn frame vào giữa canvas
                        frame.top = canvasDimensions.height / 2 - frame.height / 2;
                        frame.left = canvasDimensions.width / 2 - frame.width / 2;

                        let rect = new fabric.Rect({ // draw frame
                            top: frame.top,
                            left: frame.left,
                            width: frame.width,
                            height: frame.height,
                            fill: '',
                            stroke: (borderColor)? borderColor : '#333',
                            strokeWidth: 1,
                            strokeDashArray: [10, 5],
                            selectable: false
                        });

                        canvas.add(rect);

                        // draw design
                        fabric.Image.fromURL(designFile.url, function (oImg) {

                            $scope.loadingDesign = false;

                            oImg.id = 'designImage';
                            ['ml', 'mb', 'mr', 'mt', 'mtr'].forEach(control => {
                                oImg.setControlVisible(control, false);
                            });
                            oImg.borderColor = '#007bff';
                            oImg.cornerColor = '#007bff';
                            oImg.transparentCorners = false;

                            if ( // đã có chỉ số
                                typeof $scope.currentDesignFile.designPositions != 'undefined' &&
                                typeof $scope.currentDesignFile.designPositions.position != 'undefined' &&
                                typeof $scope.currentDesignFile.designPositions.position.top != 'undefined'
                            ) {

                                let positionInfo = $scope.currentDesignFile.designPositions.position;

                                oImg.scaleToWidth(frame['width']);

                                oImg.set({
                                    left: frame.left + frame.width / 2 - (oImg.width * oImg.scaleY / 2),
                                    top: frame.top + (frame.height / 2) - (oImg.height * oImg.scaleY / 2)
                                });

                                initPosition.top = frame.top;
                                initPosition.left = frame.left;
                                initPosition.scale = oImg.scaleX;
                                initPosition.angle = oImg.angle;

                                oImg.set({
                                    left: positionInfo.left,
                                    top: positionInfo.top,
                                    scaleX: positionInfo.scaleX,
                                    scaleY: positionInfo.scaleY,
                                    angle: positionInfo.angle
                                });

                                oImg.setCoords();

                            } else {

                                oImg.scaleToWidth(frame['width']);

                                oImg.set({
                                    left: frame.left + frame.width / 2 - (oImg.width * oImg.scaleY / 2),
                                    top: frame.top + (frame.height / 2) - (oImg.height * oImg.scaleY / 2)
                                });

                                initPosition.top = frame.top;
                                initPosition.left = frame.left;
                                initPosition.scale = oImg.scaleX;
                                initPosition.angle = oImg.angle;

                            }

                            canvas.add(oImg);
                            canvas.setActiveObject(oImg);

                            $scope.designCanvasObject = oImg;
                            // updateFrontValueDesign(oImg);

                            safeApply();
                        });

                        canvas.renderAll();

                        gelato_observe('object:moving');
                        gelato_observe('object:scaling');
                        gelato_observe('object:rotating');
                        gelato_observe('object:skewing');
                        gelato_observe('object:moved');
                        gelato_observe('object:scaled');
                        gelato_observe('object:rotated');
                        gelato_observe('object:skewed');
                    }

                    safeApply();

                });
    
            }

            $scope.gelato_applyDesignPosition = function ()
            {
                $scope.loadingDesign = true;

                canvas.getObjects().forEach(async function(o) {
                    if (o.id === 'designImage') {

                        let printervalPosition = {
                            left: o.left,
                            top: o.top,
                            scaleX: o.scaleX,
                            scaleY: o.scaleY,
                            angle: o.angle,
                            init: initPosition
                        }

                        if (typeof $scope.currentDesignFile.designPositions.position == 'undefined') {
                            $scope.currentDesignFile.designPositions.position = {};
                        }

                        $scope.currentDesignFile.designPositions.position = printervalPosition;

                        let newCropParams = buildCropParams($scope.currentDesignFile);
                        if (newCropParams) {
                            $scope.currentDesignFile.frame_url = await buildFramedDesign(newCropParams, $scope.currentDesignFile);
                        }

                        $('#DesignModalGelato').fadeOut();

                        $scope.loadingDesign = false;

                        safeApply();

                    }
                })
            }

            $scope.gelato_changeDesign2CenterV = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        o.centerV();
                        o.setCoords();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_changeDesign2CenterH = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        o.centerH();
                        o.setCoords();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_changeDesign2Left = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        let coords = o.getCoords();
                        let minX = null;
                        for (let i = 0; i < coords.length; i++) {
                            if(!minX) minX = coords[i].x;
                            if(coords[i].x < minX) minX = coords[i].x;
                        }

                        let delta = minX - frame.left;
                        o.left = o.left - delta;
                        o.setCoords();
                        canvas.renderAll();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_changeDesign2Right = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        let coords = o.getCoords();
                        let maxX = null;
                        for (let i = 0; i < coords.length; i++) {
                            if(!maxX) maxX = coords[i].x;
                            if(coords[i].x > maxX) maxX = coords[i].x;
                        }

                        let delta = frame.left + frame.width - maxX;
                        o.left = o.left + delta;
                        o.setCoords();
                        canvas.renderAll();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_changeDesign2Top = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        let coords = o.getCoords();
                        let minY = null;
                        for (let i = 0; i < coords.length; i++) {
                            if(!minY) minY = coords[i].y;
                            if(coords[i].y < minY) minY = coords[i].y;
                        }

                        let delta = minY - frame.top;
                        o.top = o.top - delta;
                        o.setCoords();
                        canvas.renderAll();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_changeDesign2Bottom = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {
                        let coords = o.getCoords();
                        let maxY = null;
                        for (let i = 0; i < coords.length; i++) {
                            if(!maxY) maxY = coords[i].y;
                            if(coords[i].y > maxY) maxY = coords[i].y;
                        }

                        let delta = frame.top + frame.height - maxY;
                        o.top = o.top + delta;
                        o.setCoords();
                        canvas.renderAll();
                        // updateFrontValueDesign(o);
                    }
                })
            }
            $scope.gelato_resetDesignPosition = function () {
                canvas.getObjects().forEach(function(o) {
                    if(o.id === 'designImage') {

                        o.rotate(0);
                        o.setCoords();
                        o.scaleToWidth(frame.width);
                        o.setCoords();
                        o.center();
                        o.setCoords();

                        // updateFrontValueDesign(o);
                    }
                })
            }

            $scope.parseInfoCrop = function (value, key, scale = null) {
                let defaultValue = parseFloat(initPosition[key]);
                value = parseFloat(value);

                if(key === 'scale') {
                    let ratio = 100 / defaultValue;
                    return ratio * value / 100;
                }

                if ((key === 'top' || key === 'left') && scale) {
                    return (frame[key] - value) / scale;
                }
            }

            $scope.addNewFile = function(item) {
                item.gelatoData.files.push({
                    print_type: 'default',
                    url: null,
                    extension: null,
                    frame_url: null,
                    crop_params: null
                });
            }

            $scope.removeFile = function(item, index) {
                if (item.gelatoData.files.length > 1) {
                    item.gelatoData.files.splice(index, 1);
                }
            }

            function gelato_postProfiles(profiles) {

                const promisePostTemplate = new Promise(function (resolve, reject) {
                    const requestObj = $scope.buildLocaleData('/print-suppliers/gelato/fulfill-template', profiles, $scope.fulFillLocale);
                    $http.post(requestObj.url, requestObj.params).then(function(response) {
                        resolve();
                    });
                });

                const promisePostProfile = new Promise(function (resolve, reject) {
                    const requestObj = $scope.buildLocaleData('/print-suppliers/profile', profiles, $scope.fulFillLocale);
                    $http.post(requestObj.url, requestObj.params).then(function (response) {
                        if (response.data.status === "successful") {
                            resolve(response.data.data);
                        } else {
                            toastr.error('Error save profile. Please contact technical team.');
                            reject();
                        }
                    }, function (error) {
                        toastr.error('Error save profile. Please contact technical team.');
                        reject(error);
                    });
                });

                return Promise.all([promisePostProfile, promisePostTemplate]);
            }

            function gelato_postZipcode(gelatoOrderData) {
                return new Promise(function (resolve) {
                    const zipcodeData = {
                        country_id: $scope.gelatoCurrentOrder.country_id,
                        code: gelatoOrderData.shippingAddress.postCode,
                        city: gelatoOrderData.shippingAddress.city,
                        region: $scope.gelatoCurrentOrder.state_name
                    };
                    const requestObj = $scope.buildLocaleData('/print-suppliers/zipcode', zipcodeData, $scope.fulFillLocale);
                    $http.post(requestObj.url, requestObj.params).then(function (response) {
                        resolve(response.data.data);
                    }, function (error) {
                        resolve(error);
                    });
                });
            }

            function buildCropParams(designFile) {
                if (typeof designFile.designPositions.position != 'undefined' && designFile.designPositions.position) {

                    let designPosition = designFile.designPositions.position;
                    let designFrame = designFile.designPositions.frame;
                    let cropParams = null;

                    if (typeof designPosition != 'undefined' && typeof designFrame != 'undefined') {
                        cropParams = {
                            i: designFile.url,
                            fw: designFrame.width / designPosition.scaleX,
                            fh: designFrame.height / designPosition.scaleX,
                            x: $scope.parseInfoCrop(designPosition.left, 'left', designPosition.scaleX),
                            y: $scope.parseInfoCrop(designPosition.top, 'top', designPosition.scaleX),
                        }
                    }

                    designFile.crop_params = cropParams;

                    safeApply();

                    return cropParams;
                }
            }

            async function buildFramedDesign(cropParams, designFile = null) {
                let framedImage = new URL(framedImageWorkerUrl);

                framedImage.search = new URLSearchParams(cropParams);
                framedImage = framedImage.toString();

                return new Promise(function(resolve) {

                    var xhttp = new XMLHttpRequest();
                    xhttp.onreadystatechange = function() {
                        if (this.readyState == 4 && this.status == 200) {
                            const response = JSON.parse(xhttp.responseText);
                            if (response.imageUrl) {
                                if (designFile) designFile.frame_url = response.imageUrl;
                                resolve(response.imageUrl);                            
                            }
                        }
                    };
                    xhttp.open("GET", framedImage, true);
                    xhttp.send();
                });
            }

            function loadGelatoRegions() 
            {
                $http.get('/modules/print-suppliers/json/gelato_regions.json')
                    .then(function (response) {
                        $scope.gelatoRegions = response.data;
                    });
            }

            function gelato_observe(eventName) {
                canvas.on(eventName, function(opt){
                    canvas.getObjects().forEach(function(o) {
                        if(o.id === 'designImage') {
                            // updateFrontValueDesign(o);
                        }
                    })
                });
            }

            function gelato_loadZipcode()
            {
                const requestObj = $scope.buildLocaleData('/print-suppliers/zipcode', {
                    country_id: $scope.gelatoCurrentOrder.country_id,
                    code: $scope.gelatoCurrentOrder.zip_code
                }, $scope.fulFillLocale);

                $http.get(requestObj.url, { params: requestObj.params }).then(function (response) {
                        if (response.data.status === 'successful') {
                            let _code = response.data.result;
                            if (!$scope.gelatoCurrentOrder.state_name && _code.region) {
                                $scope.gelatoCurrentOrder.state_name = _code.region;

                                if (!parseInt(_code.verified)) {
                                    $('#gelato_inputRegion').addClass('warning');
                                    $('#gelato_warningBox').addClass('warning-box');
                                }

                            }
                            if (!$scope.gelatoCurrentOrder.city_name && _code.city) {
                                $scope.gelatoCurrentOrder.city_name = _code.city;
                                if (!parseInt(_code.verified)) {
                                    $('#gelato_inputCity').addClass('warning');
                                    $('#gelato_warningBox').addClass('warning-box');
                                }
                            }
                        }
                    }, function (error) {
                        toastr.error('Error load zipcode!');
                    });
            }

            function gelato_fetchProfiles()
            {
                $scope.getDesignJobOrder($scope.gelatoCurrentOrder);

                const requestObj = $scope.buildLocaleData('/print-suppliers/get-profile', {
                    type: 'gelato',
                    order_items: $scope.gelatoCurrentOrder.items
                }, $scope.fulFillLocale);

                $http.post(requestObj.url, requestObj.params).then(function (response) {
                    if (response.data.status === 'successful') {
                        $scope.gelato_profiles = {
                            profiles: response.data.profiles,
                            designs: response.data.designs
                        };
                        $scope.productTemplateIds = response.data.product_template_ids;
                        gelato_applyProfiles();
                    }
                }, function (error) {
                    toastr.error('Error load profiles!');
                });
            }

            function gelato_applyProfiles()
            {
                $scope.gelatoCurrentOrder.items.forEach(async printOrderItem =>
                {
                    let profile = null;
                    let designs;

                    /*
                    * Load profile & designs for product
                    * */
                    if (isTemplateProduct(printOrderItem.product_id)) {
                        profile = $scope.gelato_profiles.profiles.find(function (item) {
                            return parseInt(item.product_id) === parseInt(printOrderItem.product_id) && item.is_template_product;
                        });
                        designs = $scope.findDesign(printOrderItem, true, $scope.gelato_profiles.designs);
                    } else {
                        profile = $scope.gelato_profiles.profiles.find(function (item) {
                            return parseInt(item.product_sku_id) === parseInt(printOrderItem.product_sku_id) && !item.is_template_product;
                        });
                        designs = $scope.findDesign(printOrderItem, false, $scope.gelato_profiles.designs);
                    }

                    /*
                    * Add available design to item
                    * */
                    printOrderItem.gelatoData.patternDesigns = [];
                    if (designs && designs.length) {

                        printOrderItem.gelatoData.patternDesigns = designs;

                        /*
                        * Select first pattern design to default
                        * */
                        try {

                            let defaultFile = findFileByKey(printOrderItem.gelatoData.files);

                            defaultFile.print_type = 'default';
                            defaultFile.extension = printOrderItem.gelatoData.patternDesigns[0].url.split('.').pop();
                            defaultFile.url = printOrderItem.gelatoData.patternDesigns[0].url;

                            $scope.gelato_changeDesignUrl(printOrderItem);

                        } catch (error) {
                            console.warn('Cannot select first design url to default print file!', error);
                        }

                        setTimeout(() => {
                            $('[data-magnify=gallery]').magnify({
                                zIndex: 10000
                            });
                        }, 500);
                    }

                    profile = angular.copy(profile);
                    if (profile && profile.options)
                    {
                        if (typeof profile.options.catalog != 'undefined' && profile.options.catalog) {

                            let foundCatalog = $scope.gelatoFilteredCatalogs.find(filteredCatalog => {
                                return filteredCatalog.catalogUid === profile.options.catalog.catalogUid;
                            })

                            if (foundCatalog) {
                                $scope.chooseCatalog(printOrderItem, foundCatalog, function () {
                                    if (profile.options.filterChoosed) {
                                        printOrderItem.gelatoData.filterChoosed = profile.options.filterChoosed;
                                        $scope.changeGelatoData(printOrderItem, function () {
                                            if (profile.options.productUid) { // find product

                                                let foundProduct = printOrderItem.gelatoData.products.find(product => {
                                                    return product.productUid === profile.options.productUid;
                                                });

                                                if (foundProduct) {
                                                    $scope.chooseProduct(foundProduct, printOrderItem);
                                                }
                                            }
                                        });
                                    }

                                    /*
                                    * Apply profile design
                                    * */
                                    if (profile.options.files && profile.options.files.length) {
                                        printOrderItem.gelatoData.files = angular.copy(profile.options.files);
                                        safeApply();
                                    }

                                });
                            }

                        }
                    }

                });
            }

            function findFileByKey(files, key = 'default') {
                return files.find(function(file) {
                    return file.print_type === key;
                })
            }

            function isTemplateProduct(productId) {
                return $scope.productTemplateIds.includes(productId);
            }

            function isValidHttpUrl(string) {
                let url;
                try {
                    url = new URL(string);
                } catch (_) {
                    return false;
                }
                return url.protocol === "http:" || url.protocol === "https:";
            }

            function gelato_validateItem(items) {
                let validated = true;
                let messages = {};

                for (let i = 0; i < items.length; i++) {
                    const itemData = items[i];
                    messages[itemData.product_sku_id] = [];

                    if (typeof itemData.gelatoData.productUid == 'undefined' || !itemData.gelatoData.productUid) {
                        validated = false;
                        messages[itemData.product_sku_id].push(itemData.name + ' || Missing Gelato product.');
                    }

                    for (let j = 0; j < itemData.gelatoData.files.length; j++) {
                        let designFile = itemData.gelatoData.files[j];
                        if (typeof designFile.url == 'undefined' || !designFile.url) {
                            validated = false;
                            messages[itemData.product_sku_id].push(itemData.name + ' || Missing design file.');
                            break;
                        }
                    }

                }

                $scope.gelatoState.messages = messages;

                return {
                    validated,
                    messages
                };
            }

            function postToGelato(gelatoOrder, additionalData = {})
            {
                toastr.options.positionClass = "toast-bottom-left";
                const requestObj = $scope.buildLocaleData('/print-suppliers/gelato/api?method=POST&end_point=orders&base_api=https://order.gelatoapis.com/v4/', gelatoOrder, $scope.fulFillLocale);

                $http.post(requestObj.url, { params: requestObj.params }).then(function (response)
                {
                    if (response.data.id) {
                        toastr.success('Successfully');
                        $('#modalFulfillGelato').modal('hide');
                        afterFulfillAction(requestObj.params, response.data, additionalData).then(() => {/* do some thing */});
                    }
                    stopLoadingButton('#btnPostGelatoOrder');
                    safeApply();
                }).catch(function() {
                    toastr.error('Has an Error! Please contact technical team.');
                    toastr.warning($scope.getFailFulfillMessage('gelato'));
                    $('#modalFulfillGelato').modal('hide');
                });
            }

            function resetActiveProduct(item) {
                item.gelatoData.products.forEach(product => {
                    product.isActive = false;
                });
            }

            let loadingButton = function (selector, text = 'Processing...') {
                $(selector).attr('dt-text', $(selector).text());
                $(selector).text(text);
                $(selector).attr("disabled", true);
            }

            let stopLoadingButton = function (selector) {
                $(selector).html($(selector).attr('dt-text'));
                $(selector).attr("disabled", false);
            }

            function loadFiltersCatalog(data, item, callbackFilterCatalog = null) {
                let productAttributes = data.productAttributes;
                let filtered = {};
                productAttributes.forEach(attribute => {
                    switch (attribute.productAttributeUid) {
                        case 'ProductStatus': filtered.ProductStatus = 'activated'; break;
                        case 'State': filtered.State = 'published'; break;
                        default: item.gelatoData.filters.push(attribute);
                    }
                });

                item.gelatoData.filters = item.gelatoData.filters.sort(function (a, b) {
                    if (a.productAttributeUid < b.productAttributeUid) {
                        return -1;
                    }
                    if (a.productAttributeUid > b.productAttributeUid) {
                        return 1;
                    }
                    return 0;
                });

                item.gelatoData.filterChoosed = filtered;

                safeApply();

                if (callbackFilterCatalog) { callbackFilterCatalog(); }
            }

            async function afterFulfillAction(gelatoOrderPayload, gelatoOrderResponse, additionalData = {}) {
                // log
                logGelatoFulFill(gelatoOrderPayload);

                let productGelatoIds = [];
                if (typeof gelatoOrderResponse.items !== 'undefined' && gelatoOrderResponse.items.length) {
                    for (let i = 0; i < gelatoOrderResponse.items.length; i++) {
                        productGelatoIds.push(gelatoOrderResponse.items[i].id);
                    }
                }

                const orderMeta = {
                    order_id: $scope.gelatoCurrentOrder.id,
                    resource_id: gelatoOrderResponse.id,
                    key: 'gelato_product_ids',
                    value: productGelatoIds.join(',')
                };

                if (additionalData && typeof additionalData.mapItemPrintProvider) {
                    orderMeta.print_provider = additionalData.mapItemPrintProvider;
                }

                // attach product id
                const requestObj = $scope.buildLocaleData('/print-suppliers/fulfill/attach-order-meta', orderMeta, $scope.fulFillLocale);
                $http.post(requestObj.url, requestObj.params);

            }

            function logGelatoFulFill(gelatoOrder) {

                let logData = {
                    actor_email: user_email,
                    target_id: $scope.gelatoCurrentOrder.id,
                    event_type: 'GELATO-FULFILLMENT',
                    data: gelatoOrder
                };

                const requestObj = $scope.buildLocaleData('/print-suppliers/fulfill/log', logData, $scope.fulFillLocale);
                $http.post(requestObj.url, requestObj.params);

            }

            $scope.gelato_copyProfileData = function(orderItem)
            {
                try 
                {
                    const profile = getSingleProfile(orderItem);

                    $scope.copyToClipboard(JSON.stringify(profile));
                    toastr.success('Copied profile!');

                } catch (error) {
                    console.warn(error);
                    toastr.error('Error, cannot copy profile!');
                }
            }

            $scope.gelato_pasteProfileData = function(orderItem)
            {
                try {
                    navigator.clipboard.readText()
                    .then(text => {

                        // const text = `{"product_id":23947,"is_template_product":false,"product_sku_id":2301060,"order_id":4695,"order_item_id":4695,"options":{"artworks":{"front":{"extension":"jpg","url":"https://assets.printerval.com/2023/05/10/wallpaperflare-8029884d02286ee23e29e3cf4af9443c.jpg"},"back":{}},"color":"Fire Red/ Arctic White","type":"Jacket","size":null,"sku":{"id":2,"sku":2602024,"brand":"AWDis","product_type":"Jacket","style":"AWJH043","product_name":"VARSITY JACKET","color":"Fire Red/ Arctic White","size":"M","selected":true}},"type":"print-logistic"}`;

                        profile = JSON.parse(text);
                        applySingleProfile(profile, orderItem);
                    })
                    .catch(err => {
                        console.error('Failed to read clipboard contents: ', err);
                    });
                } catch (error) {
                    toastr.error('Error, cannot paste profile!');
                    toastr.error(error);
                }
            }

            const getSingleProfile = function(itemData) 
            {
                const gelatoData = itemData.gelatoData;
                const printervalOrder = angular.copy($scope.gelatoCurrentOrder);
                const files = [];

                for (let j = 0; j < gelatoData.files.length; j++) 
                {
                    let printFileUrl = gelatoData.files[j].url;

                    if (typeof gelatoData.files[j].frame_url !== 'undefined' && gelatoData.files[j].frame_url) {
                        printFileUrl = gelatoData.files[j].frame_url;
                    }

                    files.push({
                        type: gelatoData.files[j].print_type,
                        url: printFileUrl
                    });
                }

                return {
                    "product_id": itemData.product_id,
                    "product_sku_id": itemData.product_sku_id,
                    "is_template_product": isTemplateProduct(itemData.product_id),
                    "order_id": printervalOrder.id,
                    "order_item_id": itemData.id,
                    "options": {
                        "productUid": itemData.gelatoData.productUid,
                        "catalog": itemData.gelatoData.catalog,
                        "filterChoosed": itemData.gelatoData.filterChoosed,
                        "files": gelatoData.files
                    },
                    "type": 'gelato'
                };
            }

            const applySingleProfile = function(profile, orderItem)
            {
                if (typeof profile.options.catalog != 'undefined' && profile.options.catalog) {

                    const foundCatalog = $scope.gelatoFilteredCatalogs.find(filteredCatalog => {
                        return filteredCatalog.catalogUid === profile.options.catalog.catalogUid;
                    })

                    if (foundCatalog) {
                        $scope.chooseCatalog(orderItem, foundCatalog, function () {
                            if (profile.options.filterChoosed) {
                                orderItem.gelatoData.filterChoosed = profile.options.filterChoosed;
                                $scope.changeGelatoData(orderItem, function () {
                                    if (profile.options.productUid) { // find product

                                        const foundProduct = orderItem.gelatoData.products.find(product => {
                                            return product.productUid === profile.options.productUid;
                                        });

                                        if (foundProduct) {
                                            $scope.chooseProduct(foundProduct, orderItem);
                                        }
                                    }
                                });
                            }

                            /*
                            * Apply profile design
                            * */
                            if (profile.options.files && profile.options.files.length) {
                                orderItem.gelatoData.files = angular.copy(profile.options.files);
                                safeApply();
                            }

                        });
                    }

                }
            }

            function safeApply() {
                if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') {
                    $scope.$apply();
                }
            }

            $scope.initializeGelato();
        }
    };
});
