define(['moment'], function (moment) {
  // add moment to window
  window.moment = moment;

  var utils = angular
    .module('angularJSApp.utils', [])
    .factory('moment', function () {
      return moment;
    })
    .factory('Util', [
      '$http',
      'AppConfig',
      '$state',
      '$uibModal',
      '$translate',
      '$sce',
      'ngToast',
      'moment',
      'MimeTypes',
      function ($http, AppConfig, $state, $uibModal, $translate, $sce, ngToast, moment, MimeTypes) {
        var _util = {
          generatePackageFeaturesConfig: function (
            packageName = 'FREE',
            numberOfUsers = 1,
            billingCycle,
            packageDetail
          ) {
            var packageFeaturesConfig = {
              commonFeatures: [
                { key: 'FEATURES.SUPPORTED_PROTECTED_FILES' },
                { key: 'FEATURES.SHARED_SPACE', paramKey: 'storageSpace' },
                { key: 'FEATURES.MAXIMUM_FILES_SIZE', paramKey: 'maximumFileSize' },
                {
                  key:
                    billingCycle === 'ANNUALLY'
                      ? 'FEATURES.SECURE_SHARES_PER_YEAR'
                      : 'FEATURES.SECURE_SHARES_PER_MONTH',
                  paramKey: 'secureShares',
                },
                { key: 'FEATURES.COPY_PROTECTION' },
                { key: 'FEATURES.TRACKING_DOCUMENTS' },
                { key: 'FEATURES.REVOKE_ACCESS_REMOTELY' },
                { key: 'FEATURES.SECURE_ONLINE_VIEWER' },
                { key: 'FEATURES.WATERMARK_VIEW_ONLINE' },
                { key: 'FEATURES.TWO_FACTOR_AUTH' },
                { key: 'FEATURES.PRINTING_CONTROL' },
                { key: 'FEATURES.EXPIRY_SETTING' },
              ],

              FREE: {
                storageSpace: '100MB',
                maximumFileSize: '1MB',
                secureShares: '5',
              },
              PERSONAL: {
                storageSpace: '5GB',
                maximumFileSize: '1GB',
                secureShares: '100',
              },
              TEAM: {
                storageSpacePerUser: 5, // GB per user
                secureSharesPerUser: 100,
                maximumFileSize: '2GB',
              },
              BUSINESS: {
                storageSpacePerUser: 5, // GB per user
                secureSharesPerUser: 100,
                maximumFileSize: '5GB',
              },
            };
            // Clone the package features config to avoid mutating the original object
            let packageValues = { ...(packageFeaturesConfig[packageName] || {}) };

            // Initialize base configurations with default values or fallbacks if none are configured
            let baseStorage = packageValues.storageSpace || (packageName === 'TEAM' || packageName === 'BUSINESS' ? '5GB' : '0B');
            let baseMaxFileSize = packageValues.maximumFileSize || (packageName === 'TEAM' ? '2GB' : packageName === 'BUSINESS' ? '5GB' : '0B');
            let baseSecureShares = packageValues.secureShares || (packageName === 'TEAM' || packageName === 'BUSINESS' ? '100' : '0');

            // If API packageDetail response is available, override default values dynamically
            if (packageDetail) {
              // Extract the total shared storage space from API payload ("storage" property in bytes or string)
              const apiStorage = packageDetail?.storage;
              if (apiStorage !== undefined && apiStorage !== null) {
                // Convert to human-readable size (e.g. 1GB) if value is a numeric byte count
                baseStorage = typeof apiStorage === 'number' ? _util.convertFileSize(apiStorage) : apiStorage;
              }

              // Extract the maximum allowed file upload size from API payload ("max_file_size" property)
              const apiMaxFileSize = packageDetail?.max_file_size;
              if (apiMaxFileSize !== undefined && apiMaxFileSize !== null) {
                // Convert numeric bytes to human-readable format (e.g. 500MB)
                baseMaxFileSize = typeof apiMaxFileSize === 'number' ? _util.convertFileSize(apiMaxFileSize) : apiMaxFileSize;
              }

              // Extract the base number of monthly/yearly secure shares from API payload ("share" property)
              const apiSecureShares = packageDetail?.share;
              if (apiSecureShares !== undefined && apiSecureShares !== null) {
                baseSecureShares = String(apiSecureShares);
              }
            }

            // Special handling for scale-out packages (TEAM and BUSINESS) where resources grow with number of users
            if (packageName === 'TEAM' || packageName === 'BUSINESS') {
              // Parse the number and unit from the base storage configuration string (e.g., "5GB" -> 5 and "GB")
              const matchStorage = String(baseStorage).match(/^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/);
              if (matchStorage) {
                const num = parseFloat(matchStorage[1]);
                const unit = matchStorage[2] || 'GB';
                // Multiply storage by the total number of users and append the unit without space (e.g. 25GB)
                packageValues.storageSpace = num * numberOfUsers + unit;
              } else {
                packageValues.storageSpace = baseStorage;
              }

              // Parse base secure shares count into integer
              const numShares = parseInt(baseSecureShares, 10) || 0;
              // Calculate scaled secure shares (scaled up by users, and by 12 months if billing cycle is ANNUALLY)
              if (billingCycle === 'ANNUALLY') {
                packageValues.secureShares = String(numShares * numberOfUsers * 12);
              } else {
                packageValues.secureShares = String(numShares * numberOfUsers);
              }

              packageValues.maximumFileSize = baseMaxFileSize;
            } else {
              // For standard packages (FREE, PERSONAL), map the API values directly to package configuration
              packageValues.storageSpace = baseStorage;
              packageValues.maximumFileSize = baseMaxFileSize;
              packageValues.secureShares = baseSecureShares;
            }

            // Return a list of features based on the selected package
            const features = [...packageFeaturesConfig.commonFeatures];

            // Replace parameters with actual values
            var featuresWithParams = features.map((feature) => {
              if (feature.paramKey && packageValues[feature.paramKey]) {
                return {
                  key: feature.key,
                  params: { value: packageValues[feature.paramKey] },
                };
              }
              return { key: feature.key };
            });

            return {
              features: featuresWithParams,
              totalFeatures: featuresWithParams.length,
              package: packageName,
              values: packageValues,
            };
          },
          capitalizeFirstLetter: function (str) {
            if (!str || typeof str !== 'string') return str;
            return str.charAt(0).toUpperCase() + str.slice(1);
          },
          createRequest: function (apiURL, method, params, cb, errCb) {
            if (!apiURL) {
              console.error('Invalid API');
              return;
            }

            var opts = {
              url: apiURL,
              method: method,
            };
            // Shorthand
            method === 'GET' || method === 'DELETE' ? (opts.params = params) : (opts.data = params);
            return $http(opts).then(
              function (response) {
                cb && cb(response.data);
                // redirect to login page if session timeout, invalid token (status 401 - unthorized)
                if (response.data.status === 401) {
                  $state.go('login');
                }
              },
              function (response) {
                errCb && errCb(response.data);
                // session timeout
                if (response.status === 401) {
                  $state.go('login');
                }
              }
            );
          },

          /**
           * Get value of specific param. Example: /pagename?name=tmhao#/child => return <tmhao>
           * @returns {Array}
           */
          getParameter: function () {
            var vars = [],
              hash;
            var path = window.location.href.slice(window.location.href.indexOf('?') + 1);
            var rPath = path;
            // Remove hash if have
            if (path.indexOf(location.hash) !== -1) {
              rPath = path.substring(0, path.indexOf(location.hash));
            }

            var hashes = rPath.split('&');
            for (var i = 0; i < hashes.length; i++) {
              hash = hashes[i].split('=');
              vars.push(hash[0]);
              vars[hash[0]] = hash[1];
            }
            return vars;
          },

          createToast: function (opts) {
            $translate([opts.content]).then(function (translations) {
              ngToast.create({
                className: opts.className,
                content: translations[opts.content],
              });
            });
          },
          convertFileSize: function (bytes) {
            var thresh = 1024;
            if (bytes < thresh) return bytes + 'B';
            var units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
            var u = -1;
            do {
              bytes /= thresh;
              ++u;
            } while (bytes >= thresh);
            var result = bytes.toFixed(1);
            if (result.endsWith('.0')) {
              result = bytes.toFixed(0);
            }
            return result + units[u];
          },

          // Get moment object
          // @param {Object|String} date : Can be date object or string format date
          // @param {String} format
          // @return {Object} Moment object
          getMoment: function (date, format, timezone) {
            var d = angular.isObject(date)
              ? moment.utc(date)
              : moment.utc(date, format || AppConfig.UTIL_FORMAT_DATE);

            return timezone ? d.add(timezone, 'hours') : d;
          },
          // Format date with specific format
          // @param {Object|String} date : Can be date object or string format date
          // @param {String} format
          // @return {String} String date with specific format
          formatDate: function (date, timezone, format, lang) {
            switch (lang) {
              case 'vi':
              case 'ja':
                moment.locale(lang);
                break;
              default:
                moment.locale('en');
                break;
            }
            return this.getMoment(date, AppConfig.UTIL_FORMAT_DATE, timezone).format(
              format || AppConfig.UTIL_FORMAT_DATE
            );
          },
          formatDateBasic: function (date, format) {
            return moment(date).format(format || AppConfig.UTIL_FORMAT_DATE);
          },
          formatDateBasedOnDiff: function (date1, date2, lang, timeZone) {
            var d1 = date1,
              d2 = date2;
            if (typeof date2 === undefined || date2 === '') {
              d1 = moment();
              d2 = this.getMoment(date1);
            }
            if (d1.isSame(d2, 'day')) {
              return this.formatDate(moment(date1), timeZone, 'LTS', lang);
            }

            return this.formatDate(moment(date1), timeZone, 'MM/DD/YYYY HH:mm:ss', lang);
          },
          formatBookendDate: function (date, timezone, format) {
            var d = moment(date, format || AppConfig.MAX_EXPIRY_DATE_FORMAT);
            timezone && d.subtract(timezone, 'hours');
            return d.format(AppConfig.BOOKEND_DATE_FORMAT);
          },

          translate: function (opt) {
            return $sce.getTrustedHtml($translate.instant(opt));
          },
          // Default is using calendar format
          // Convert from calendar date to bookend date
          formatBookendDate: function (date, timezone, format) {
            var d = moment(date, format || AppConfig.CALENDAR_FORMAT);
            timezone && d.subtract(timezone, 'hours');

            return d.format(AppConfig.BOOKEND_DATE_FORMAT);
          },
          // Get group from mime type
          getGroupFromMimeType: function (file) {
            if (file === undefined) return null;

            var group = null;

            switch (file.file_type) {
              case 1:
              case 2:
              case 3:
              case 4:
                group = 'secure';

                break;

              default:
                if (file.content_type === undefined || file.content_type === null) return null;
                // Not in secure file
                _.forIn(MimeTypes, function (value, key) {
                  if (group === null) {
                    var list = value.types || value;

                    for (var i in list) {
                      if (file.content_type === list[i]) {
                        group = key;

                        break;
                      }
                    }
                  }
                });

                break;
            }
            return group;
          },
          /**
           * Message-Digest algorithm 5 (MD5)
           * @param {type} string
           */
          MD5: function (string) {
            function RotateLeft(lValue, iShiftBits) {
              return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
            }

            function AddUnsigned(lX, lY) {
              var lX4, lY4, lX8, lY8, lResult;
              lX8 = lX & 0x80000000;
              lY8 = lY & 0x80000000;
              lX4 = lX & 0x40000000;
              lY4 = lY & 0x40000000;
              lResult = (lX & 0x3fffffff) + (lY & 0x3fffffff);
              if (lX4 & lY4) {
                return lResult ^ 0x80000000 ^ lX8 ^ lY8;
              }
              if (lX4 | lY4) {
                if (lResult & 0x40000000) {
                  return lResult ^ 0xc0000000 ^ lX8 ^ lY8;
                } else {
                  return lResult ^ 0x40000000 ^ lX8 ^ lY8;
                }
              } else {
                return lResult ^ lX8 ^ lY8;
              }
            }

            function F(x, y, z) {
              return (x & y) | (~x & z);
            }

            function G(x, y, z) {
              return (x & z) | (y & ~z);
            }

            function H(x, y, z) {
              return x ^ y ^ z;
            }

            function I(x, y, z) {
              return y ^ (x | ~z);
            }

            function FF(a, b, c, d, x, s, ac) {
              a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
              return AddUnsigned(RotateLeft(a, s), b);
            }

            function GG(a, b, c, d, x, s, ac) {
              a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
              return AddUnsigned(RotateLeft(a, s), b);
            }

            function HH(a, b, c, d, x, s, ac) {
              a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
              return AddUnsigned(RotateLeft(a, s), b);
            }

            function II(a, b, c, d, x, s, ac) {
              a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
              return AddUnsigned(RotateLeft(a, s), b);
            }

            function ConvertToWordArray(string) {
              var lWordCount;
              var lMessageLength = string.length;
              var lNumberOfWords_temp1 = lMessageLength + 8;
              var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
              var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
              var lWordArray = Array(lNumberOfWords - 1);
              var lBytePosition = 0;
              var lByteCount = 0;
              while (lByteCount < lMessageLength) {
                lWordCount = (lByteCount - (lByteCount % 4)) / 4;
                lBytePosition = (lByteCount % 4) * 8;
                lWordArray[lWordCount] =
                  lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition);
                lByteCount++;
              }
              lWordCount = (lByteCount - (lByteCount % 4)) / 4;
              lBytePosition = (lByteCount % 4) * 8;
              lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
              lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
              lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
              return lWordArray;
            }

            function WordToHex(lValue) {
              var WordToHexValue = '',
                WordToHexValue_temp = '',
                lByte,
                lCount;
              for (lCount = 0; lCount <= 3; lCount++) {
                lByte = (lValue >>> (lCount * 8)) & 255;
                WordToHexValue_temp = '0' + lByte.toString(16);
                WordToHexValue =
                  WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
              }
              return WordToHexValue;
            }

            function Utf8Encode(string) {
              string = string.replace(/\r\n/g, '\n');
              var utftext = '';

              for (var n = 0; n < string.length; n++) {
                var c = string.charCodeAt(n);

                if (c < 128) {
                  utftext += String.fromCharCode(c);
                } else if (c > 127 && c < 2048) {
                  utftext += String.fromCharCode((c >> 6) | 192);
                  utftext += String.fromCharCode((c & 63) | 128);
                } else {
                  utftext += String.fromCharCode((c >> 12) | 224);
                  utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                  utftext += String.fromCharCode((c & 63) | 128);
                }
              }

              return utftext;
            }

            var x = Array();
            var k, AA, BB, CC, DD, a, b, c, d;
            var S11 = 7,
              S12 = 12,
              S13 = 17,
              S14 = 22;
            var S21 = 5,
              S22 = 9,
              S23 = 14,
              S24 = 20;
            var S31 = 4,
              S32 = 11,
              S33 = 16,
              S34 = 23;
            var S41 = 6,
              S42 = 10,
              S43 = 15,
              S44 = 21;

            string = Utf8Encode(string);

            x = ConvertToWordArray(string);

            a = 0x67452301;
            b = 0xefcdab89;
            c = 0x98badcfe;
            d = 0x10325476;

            for (k = 0; k < x.length; k += 16) {
              AA = a;
              BB = b;
              CC = c;
              DD = d;
              a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
              d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
              c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
              b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
              a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
              d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
              c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
              b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
              a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
              d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
              c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
              b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
              a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
              d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
              c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
              b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
              a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
              d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
              c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
              b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
              a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
              d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
              c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
              b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
              a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
              d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
              c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
              b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
              a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
              d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
              c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
              b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
              a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
              d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
              c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
              b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
              a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
              d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
              c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
              b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
              a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
              d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
              c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
              b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
              a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
              d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
              c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
              b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
              a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
              d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
              c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
              b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
              a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
              d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
              c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
              b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
              a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
              d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
              c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
              b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
              a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
              d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
              c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
              b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
              a = AddUnsigned(a, AA);
              b = AddUnsigned(b, BB);
              c = AddUnsigned(c, CC);
              d = AddUnsigned(d, DD);
            }

            var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);

            return temp.toLowerCase();
          },
          translateI18Next: function (path, opts) {
            return $sce.getTrustedHtml($translate.instant(path, opts));
          },
          replaceTranslate: function (path, obj) {
            return $translate.instant(path, obj).toString() === path
              ? 'N/A'
              : $translate.instant(path, obj).toString();
          },
          replaceTranslateStripError: function (path, pathChange, obj) {
            if ($translate.instant(path, obj).toString() === path) {
              path = pathChange;
            }
            return $translate.instant(path, obj).toString();
          },
        };

        return _util;
      },
    ])
    .factory('AppUtil', [
      'Util',
      function (Util) {
        return {
          getPasswordPolicyError: function (passwordSetting) {
            var passwordError = Util.replaceTranslate('USER.PASSWORD_POLICY.DEFAULT', {
              min: passwordSetting.min,
              max: passwordSetting.max,
            });
            if (
              passwordSetting.capital !== 0 ||
              passwordSetting.lower !== 0 ||
              passwordSetting.number !== 0 ||
              passwordSetting.special !== 0
            ) {
              passwordError += Util.replaceTranslate('USER.PASSWORD_POLICY.INCLUDE');

              if (passwordSetting.capital !== 0) {
                passwordError +=
                  Util.replaceTranslate('USER.PASSWORD_POLICY.CAPITAL', {
                    number: passwordSetting.capital,
                  }) + ',';
              }
              if (passwordSetting.lower !== 0) {
                passwordError +=
                  Util.replaceTranslate('USER.PASSWORD_POLICY.LOWER', {
                    number: passwordSetting.lower,
                  }) + ',';
              }
              if (passwordSetting.number !== 0) {
                passwordError +=
                  Util.replaceTranslate('USER.PASSWORD_POLICY.NUMBER', {
                    number: passwordSetting.number,
                  }) + ',';
              }
              if (passwordSetting.special !== 0) {
                passwordError +=
                  Util.replaceTranslate('USER.PASSWORD_POLICY.SPECIAL', {
                    number: passwordSetting.special,
                  }) + ',';
              }
              passwordError = passwordError.slice(0, passwordError.length - 1);
              passwordError += Util.replaceTranslate('USER.PASSWORD_POLICY.CHARACTERS');
            }

            return passwordError;
          },

          convertSecondToTime: function (secondInput) {
            var seconds = parseInt(secondInput, 10);

            var hrs = Math.floor(seconds / 3600);
            seconds -= hrs * 3600;
            var mnts = Math.floor(seconds / 60);
            seconds -= mnts * 60;

            var strHrs = hrs > 0 ? (hrs > 9 ? hrs : '0' + hrs) + ':' : '';
            var strMin = (mnts > 9 ? mnts : '0' + mnts) + ':';
            var strSec = seconds > 9 ? seconds : '0' + seconds;

            return strHrs + strMin + strSec;
          },
        };
      },
    ]);
});
