class Constants
  #=============================================================================
  # String lengths
  #=============================================================================

  # Min characters for short text fields
  NAME_MIN_LENGTH = 2
  # Max characters for short text fields
  NAME_MAX_LENGTH = 255
  # Max characters for short text fields, after which they get truncated
  NAME_TRUNCATION_LENGTH = 25
  # Max edge length
  MAX_EDGE_LENGTH = 75
  # Max character for listing projects in dropdown
  MAX_NAME_TRUNCATION = 150
  # Max characters for short text fields, in dropdownList
  NAME_TRUNCATION_LENGTH_DROPDOWN = 20
  # Max characters for long text fields
  TEXT_MAX_LENGTH = 10000
  # Max characters for rich text fields (in html format)
  RICH_TEXT_MAX_LENGTH = 1000000
  # Max characters for color field (given in HEX format)
  COLOR_MAX_LENGTH = 7
  # Max characters for text in dropdown list element
  DROPDOWN_TEXT_MAX_LENGTH = 15
  # Max characters for text in modal list element
  MODAL_TEXT_MAX_LENGTH = 55
  # Max characters limit for (on most operating systems, it's ~255 characters,
  # but this is with a bit more safety margin)
  FILENAME_MAX_LENGTH = 100
  # Max characters for filenames, after which they get truncated
  FILENAME_TRUNCATION_LENGTH = 50
  # Max characters for names of exported files and folders, after which they get
  # truncated
  EXPORTED_FILENAME_TRUNCATION_LENGTH = 20

  USER_INITIALS_MAX_LENGTH = 4
  # Password 'key stretching' factor
  PASSWORD_STRETCH_FACTOR = 10
  # Standard max length for email
  EMAIL_MAX_LENGTH = 254
  # Some big value which is still supported by all databases, no matter what
  # data type is used
  INFINITY = ((2**32) / 2) - 1

  # Prevents integer overflow for reminder delta seconds
  MAX_NUMBER_OF_REMINDER_WEEKS = 816

  #=============================================================================
  # Query/display limits
  #=============================================================================

  # General limited/unlimited query/display elements for pages
  SEARCH_LIMIT = 20
  # General global search limit for object groups
  GLOBAL_SEARCH_PREVIEW_LIMIT = 4
  SEARCH_NO_LIMIT = -1
  # General limited query/display elements for popup modals
  MODAL_SEARCH_LIMIT = 5
  # Comments limited query/display elements for pages
  COMMENTS_SEARCH_LIMIT = 10
  # Activity limited query/display elements for pages
  ACTIVITY_AND_NOTIF_SEARCH_LIMIT = 20
  # Infinite Scroll load limit (elements per page)
  INFINITE_SCROLL_LIMIT = 20
  # Maximum number of users that can be invited in a single action
  INVITE_USERS_LIMIT = 20
  # Maximum nr. of search results for atwho (smart annotations)
  ATWHO_SEARCH_LIMIT = 10
  # Max characters for repository name in Atwho modal
  ATWHO_REP_NAME_LIMIT = 16
  # Results limited query/display elements for pages
  RESULTS_PER_PAGE_LIMIT = 10
  #Experiments more button appears
  EXPERIMENT_LONG_DESCRIPTION = 80
  # Infinite scroll default elements per page
  DEFAULT_ELEMENTS_PER_PAGE = 20
  # Default navigator width
  DEFAULT_NAV_WIDTH = 348

  #=============================================================================
  # File and data memory size
  #=============================================================================

  # Max table JSON size in MB
  TABLE_JSON_MAX_SIZE_MB = 20
  # Max uploaded user picture avatar size in MB
  AVATAR_MAX_SIZE_MB = 0.2
  # PDF preview file limit in MB
  PDF_PREVIEW_MAX_SIZE_MB = 10


  #=============================================================================
  # Application space
  #=============================================================================

  # Minimal space needed for team (in B)
  MINIMAL_TEAM_SPACE_TAKEN = 1.megabyte
  # Additional space of each file is added to its estimated size to account for
  # DB indexes size etc.
  ASSET_ESTIMATED_SIZE_FACTOR = 1.1

  #=============================================================================
  # Format sizes
  #=============================================================================

  # Picture size formats
  LARGE_PIC_FORMAT = [800, 600].freeze
  MEDIUM_PIC_FORMAT = [300, 300].freeze
  THUMB_PIC_FORMAT = [100, 100].freeze
  ICON_PIC_FORMAT = [40, 40].freeze
  ICON_SMALL_PIC_FORMAT = [30, 30].freeze

  # Hands-on-table number of starting columns and rows
  HANDSONTABLE_INIT_COLS_CNT = 5
  HANDSONTABLE_INIT_ROWS_CNT = 5

  # Word reports format. All units in Twips.
  # A twip is 1/20 of a point. Word documents are printed at 72dpi. 1in == 72pt == 1440 twips.
  # Here is default A4
  REPORT_DOCX_WIDTH = 12240
  REPORT_DOCX_HEIGHT = 15840
  REPORT_DOCX_MARGIN_TOP = 720
  REPORT_DOCX_MARGIN_RIGHT = 720
  REPORT_DOCX_MARGIN_BOTTOM = 720
  REPORT_DOCX_MARGIN_LEFT = 720

  # Word borders in eighth point units.
  # A eighth point is 1/8 of a point. A border size of 4 is equivalent to 0.5pt.
  REPORT_DOCX_TABLE_BORDER_SIZE = 4

  # All font size in half points
  REPORT_DOCX_REPORT_TITLE_SIZE = 36
  REPORT_DOCX_EXPERIMENT_TITLE_SIZE = 32
  REPORT_DOCX_MY_MODULE_TITLE_SIZE = 28
  REPORT_DOCX_STEP_TITLE_SIZE = 22
  REPORT_DOCX_STEP_ELEMENTS_TITLE_SIZE = 20

  #=============================================================================
  # Styling
  #=============================================================================

  # Dropdown top offset from the parent
  DROPDOWN_TOP_OFFSET_PX = 20

  #=============================================================================
  # Date and time
  #=============================================================================

  # URL expire time, used for presigned file URLs, because outsiders shouldn't
  # have access to them, but some buffer time is needed for file to be loaded
  URL_SHORT_EXPIRE_TIME = 30
  # Same as URL_EXPIRE_TIME, except for cases where the URL migth be used in
  # another page, and hence the URL mustn't expire by then (e.g. when generating
  # report and than using same HTML code in PDF, and consequently same file
  # URL); it expires in exactly one day
  URL_LONG_EXPIRE_TIME = 86_400

  DEFAULT_DATE_FORMAT = '%m/%d/%Y'.freeze

  SUPPORTED_DATE_FORMATS = [
    # US formats
    '%m/%d/%Y', '%m.%d.%Y', '%m. %d. %Y', '%m-%d-%Y', '%-m/%-d/%Y',
    '%-m.%-d.%Y', '%-m. %-d. %Y', '%-m-%-d-%Y',
    # European formats
    '%d/%m/%Y', '%d.%m.%Y', '%d. %m. %Y', '%d-%b-%Y', '%Y-%m-%d',
    '%d.%b.%Y', '%Y/%b/%d', '%d, %B, %Y', '%B, %d, %Y', '%-d/%-m/%Y',
    '%-d.%-m.%Y', '%-d. %-m. %Y', '%d-%m-%Y', '%Y-%-m-%-d', '%-d-%b-%Y',
    '%Y-%b-%-d', '%-d, %B, %Y', '%B, %-d, %Y'
  ].freeze

  #=============================================================================
  # Application colors
  #
  # NOTE: Don't use shortened syntax, e.g. #000 for #000000, as some Gems need
  # full syntax!
  #=============================================================================

  TAG_COLORS = %i(
    #C4D3A0
    #5EC66F
    #46C3C8
    #A3CCE4
    #3B99FD
    #104DA9
    #6F2DC1
    #FF69B4
    #DF3562
    #AD0015
    #FF5C00
    #E9A845
    #B06500
    #663300
    #1D2939
    #98A2B3
    #DCE0E7
  ).freeze

  # Theme colors
  BRAND_PRIMARY = '#104da9'.freeze # $brand-primary

  # Grayscale colors
  COLOR_WHITE = '#ffffff'.freeze # $color-white
  COLOR_CONCRETE = '#f0f0f6'.freeze # $color-concrete
  COLOR_ALTO = '#d0d0d8'.freeze # $color-alto
  COLOR_SILVER_CHALICE = '#a0a0a8'.freeze # $color-silver-chalice
  COLOR_VOLCANO = '#404048'.freeze # $color-volcano
  COLOR_BLACK = '#231f20'.freeze # $color-black

  # Fonts
  FONT_FAMILY_BASE = 'Inter,"Open Sans",Arial,Helvetica,sans-serif;'.freeze # $font-family-base

  #=============================================================================
  # External URLs
  #=============================================================================

  HTTP = 'http://'.freeze
  TUTORIALS_URL = ENV.fetch('VIDEO_TUTORIALS_URL', "#{HTTP}goo.gl/YH3fXA").freeze
  SUPPORT_URL = ENV.fetch('KNOWLEDGE_CENTER_URL', 'https://scinote-3850750.hs-sites.com/en/knowledge').freeze
  FREE_TRIAL_URL = 'https://www.scinote.net/free-trial/?utm_source=SciNoteShare&utm_medium=FreeTrialButton&utm_campaign=Q3_2023'.freeze
  # Default user picture avatar
  DEFAULT_AVATAR_URL = '/images/:style/missing.svg'.freeze

  ACADEMY_BL_LINK = 'https://scinote.net/academy/?utm_source=SciNote%20software%20BL&utm_medium=SciNote%20software%20BL'.freeze

  PWA_URL = 'https://:pwa_domain/teams/:team_id/projects/:project_id/experiments/:experiment_id/tasks/:task_id/protocol/:protocol_id/:step_id?domain=:domain'.freeze

  TWO_FACTOR_URL = {
    google: {
      android: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2',
      ios: 'https://apps.apple.com/us/app/google-authenticator/id388497605'
    },
    microsoft: {
      android: 'https://play.google.com/store/apps/details?id=com.azure.authenticator',
      ios: 'https://apps.apple.com/us/app/microsoft-authenticator/id983156458'
    },
    two_fa: {
      android: 'https://play.google.com/store/apps/details?id=com.twofasapp',
      ios: 'https://apps.apple.com/us/app/2fa-authenticator-2fas/id1217793794'
    },
  }

  SCINOTE_FLUICS_URL = 'https://www.scinote.net/fluics/'.freeze
  SCINOTE_ZEBRA_DOWNLOAD_URL = 'https://www.zebra.com/us/en/software/printer-software/browser-print.html'.freeze
  SCINOTE_ZEBRA_BLOG_URL = 'https://www.scinote.net/blog/connect-zebra-printers/'.freeze
  SCINOTE_ZEBRA_SUPPORT_URL = 'https://www.zebra.com/us/en/about-zebra/contact-zebra/contact-tech-support.html'.freeze
  TWO_FACTOR_RECOVERY_CODE_COUNT = 6
  TWO_FACTOR_RECOVERY_CODE_LENGTH = 12

  API_KEY_EXPIRES_IN = 1.year

  #=============================================================================
  # Protocol importers
  #=============================================================================

  PROTOCOLS_ENDPOINTS = {
    protocolsio: {
      v3: 'ProtocolsIo::V3'
    }
  }.freeze

  PROTOCOLS_IO_URL = 'https://www.protocols.io/'.freeze

  PROTOCOLS_IO_V3_API = {
    base_uri: 'https://www.protocols.io/api/v3/',
    default_timeout: 10,
    debug_level: :debug,
    sort_mappings: {
      alpha_asc: { order_field: :name, order_dir: :asc },
      alpha_desc: { order_field: :name, order_dir: :desc },
      newest: { order_field: :date, order_dir: :desc },
      oldest: { order_field: :date, order_dir: :asc }
    },
    endpoints: {
      protocols: {
        default_query_params: {
          filter: :public,
          key: '',
          order_field: :activity,
          order_dir: :desc,
          page_size: 50,
          page_id: 0,
          fields: 'id,title,authors,created_on,uri,stats,published_on'
        }
      },
      publications: {
        default_query_params: {
          latest: 20
        }
      }
    },
    source_id: 'protocolsio/v3'
  }.freeze

  PROTOCOLS_DESC_TAGS = %w(a img i br).freeze

  #=============================================================================
  # Other
  #=============================================================================

  FILE_TEXT_FORMATS = %w(doc docm docx dot dotm dotx odt rtf wopitest wopitestx).freeze

  FILE_TABLE_FORMATS = %w(csv ods xls xlsb xlsm xlsx).freeze

  FILE_PRESENTATION_FORMATS =
    %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).freeze

  WOPI_EDITABLE_FORMATS = %w(
    docx docm odt xlsx xlsm xlsb ods pptx ppsx odp wopitest wopitestx
  ).freeze

  WOPI_TOKEN_VALIDITY = 10.hours.to_i

  TEXT_EXTRACT_FILE_TYPES = [
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
    'application/pdf',
    'application/rtf',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'application/vnd.oasis.opendocument.presentation',
    'application/vnd.oasis.opendocument.spreadsheet',
    'application/vnd.oasis.opendocument.text',
    'application/vnd.ms-excel',
    'application/vnd.ms-powerpoint',
    'application/vnd.ms-word',
    'text/plain'
  ].freeze

  TEXT_EXTRACT_MAX_FILE_SIZE = ENV.fetch('TEXT_EXTRACT_MAX_FILE_SIZE_MB', 50).to_i.megabytes

  PREVIEWABLE_FILE_TYPES = TEXT_EXTRACT_FILE_TYPES

  # default preview timeout to 15 minutes
  PREVIEW_TIMEOUT_SECONDS = ENV['PREVIEW_TIMEOUT_SECONDS'] ? ENV['PREVIEW_TIMEOUT_SECONDS'].to_i : 900

  PREVIEW_MAX_FILE_SIZE = ENV.fetch('PREVIEW_MAX_FILE_SIZE_MB', 50).to_i.megabytes

  WHITELISTED_IMAGE_TYPES = [
    'gif', 'jpeg', 'pjpeg', 'png', 'x-png', 'svg+xml', 'bmp', 'tiff', 'jpg'
  ].freeze

  WHITELISTED_IMAGE_TYPES_EDITABLE = %w(
    jpeg pjpeg png
  ).freeze

  config = Sanitize::Config::RELAXED.deep_dup
  config[:attributes][:all] << 'id'
  config[:attributes][:all] << 'contenteditable'
  config[:attributes]['img'] << 'data-mce-token'
  config[:attributes]['img'] << 'data-source-type'
  config[:attributes]['a'] << 'data-turbolinks'
  config[:protocols]['img']['src'] << 'data'
  INPUT_SANITIZE_CONFIG = Sanitize::Config.freeze_config(config)

  REPOSITORY_DEFAULT_PAGE_SIZE = 10
  REPOSITORY_LIST_ITEMS_PER_COLUMN = 500
  REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN = 50
  REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN = 50
  REPOSITORY_NUMBER_TYPE_DEFAULT_DECIMALS = 2
  REPOSITORY_NUMBER_TYPE_MAX_DECIMALS = 10

  REPOSITORY_DATETIME_REMINDER_PRESET = 10

  # Repository default table state
  REPOSITORY_TABLE_DEFAULT_STATE = {
    'time' => 0,
    'start' => 0,
    'length' => REPOSITORY_DEFAULT_PAGE_SIZE,
    'order' => [[3, 'asc']], # Default sorting by 'name' column
    'columns' => [],
    'assigned' => 'assigned',
    'ColReorder' => [*0..10]
  }
  11.times do |i|
    REPOSITORY_TABLE_DEFAULT_STATE['columns'] << {
      'visible' => (i < 7 && i != 4), # relationship column is hidden by default
      'searchable' => (i >= 1 && i != 4), # Checkboxes and relationship column is not searchable
      'search' => { 'search' => '',
                    'smart' => true,
                    'regex' => false,
                    'caseInsensitive' => true }
    }
  end
  REPOSITORY_TABLE_DEFAULT_STATE.freeze

  # Repository default table state
  REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE = {
    'time' => 0,
    'start' => 0,
    'length' => REPOSITORY_DEFAULT_PAGE_SIZE,
    'order' => [[1, 'asc']], # Default sorting by 'ID' column
    'columns' => [],
    'assigned' => 'assigned',
    'ColReorder' => [*0..4]
  }

  REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['columns'] = REPOSITORY_TABLE_DEFAULT_STATE['columns'][0..4].deep_dup
  REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['columns'][4]['visible'] = true

  REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE.freeze

  # For default custom column template, any searchable default
  # column can be reused
  REPOSITORY_TABLE_STATE_CUSTOM_COLUMN_TEMPLATE =
    REPOSITORY_TABLE_DEFAULT_STATE['columns'][1].deep_dup
                                                .freeze

  EXPORTABLE_ZIP_EXPIRATION_DAYS = 7

  REPOSITORY_LIST_ITEMS_DELIMITERS_MAP = {
    return: "\n",
    comma: ',',
    semicolon: ';',
    space: ' '
  }.freeze

  REPOSITORY_LIST_ITEMS_DELIMITERS_ICON_MAP = {
    auto: "＊",
    return: "↵",
    comma: ',',
    semicolon: ';',
    space: '⎵'
  }.freeze

  IMPORT_REPOSITORY_ITEMS_LIMIT = 2000
  IMPORT_REPOSITORY_ITEMS_MIN_LIMIT = 2

  DEFAULT_TEAM_REPOSITORIES_LIMIT = 6

  # Very basic regex to check for validity of emails
  BASIC_EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP

  TINY_MCE_ASSET_REGEX = /data-mce-token="(\w+)"/

  # Team name for default admin user
  DEFAULT_PRIVATE_TEAM_NAME = 'My projects'.freeze

  TEMPLATES_PROJECT_NAME = 'SciNote Examples'.freeze

  # Interval time for polling status state
  FAST_STATUS_POLLING_INTERVAL = 5000
  SLOW_STATUS_POLLING_INTERVAL = 10000

  STATUS_COLORS = {
    not_started: '#fffff',
    in_progress: '#3070ED',
    done: '#5EC66F'
  }

  # Infinite date, for use in sorting
  INFINITE_DATE = Date.new(9999, 1, 1).freeze

  # Interval time for polling asset changes when editing with SciNote Edit
  ASSET_POLLING_INTERVAL = 5000

  ASSET_SYNC_TOKEN_EXPIRATION = 1.year
  ASSET_SYNC_URL = ENV['ASSET_SYNC_URL'].freeze

  # Grover timeout in ms
  GROVER_TIMEOUT_MS = 450000

  # SciNote Edit supported versions
  MIN_SCINOTE_EDIT_VERSION = ENV['MIN_SCINOTE_EDIT_VERSION'].freeze
  MAX_SCINOTE_EDIT_VERSION = ENV['MAX_SCINOTE_EDIT_VERSION'].freeze

  # SciNote Edit unsupported extensions
  SCINOTE_EDIT_RESTRICTED_EXTENSIONS = %w(
    ACTION APP BIN COMMAND CSH OSX WORKFLOW DMG BAT BIN CAB CMD COM CPL EX_ EXE GADGET INF1 INS INX ISU JOB
    JSE LNK MSC MSI MSP MST PAF PIF PS1 REG RGS SCR SCT SHB SHS U3P VB VBE VBS VBSCRIPT WS WSF WSH
  ).freeze

  SCINOTE_EDIT_LATEST_JSON_URL = ENV['SCINOTE_EDIT_LATEST_JSON_URL'].freeze

  # quick search
  QUICK_SEARCH_LIMIT = 5
  QUICK_SEARCH_SEARCHABLE_OBJECTS = %w(project experiment my_module protocol repository_row
                                       report project_folder result label_template).freeze

  #                             )       \   /      (
  #                            /|\      )\_/(     /|\
  #   *                       / | \    (/\|/\)   / | \                      *
  #   |`.____________________/__|__o____\`|'/___o__|__\___________________.'|
  #   |                           '^`    \|/   '^`                          |
  #   |                                   V                                 |
  #   |       _____ _                 _                            __       |
  #   |      |_   _| |__   __ _ _ __ | | __   _   _  ___  _   _   (  |      |
  #   |        | | | '_ \ / _` | '_ \| |/ /  | | | |/ _ \| | | |  |  )      |
  #   |        | | | | | | (_| | | | |   <   | |_| | ( | | |_| |  \_/       |
  #   |        |_| |_| |_|\__,_|_| |_|_|\_\   \__, |\___/ \_,|_|   _        |
  #   |                                       |___/               (_)       |
  #   |                                                                     |
  #   |   Special Thank You for supporting SciNote on Kicstarter goes       |
  #   |   to the following supporters                                       |
  #   | ._________________________________________________________________. |
  #   |'               l    /\ /     \\            \ /\   l                `|
  #   *                l  /   V       ))            V   \ l                 *
  #                    l/            //                  \I
  KICKSTARTER_SUPPORTERS = [
    'Manuela Lanzafame',
    'Fluckiger Rudolf',
    'Emily Gleason',
    'Benjamin E Doremus',
    'Chord Pet Wearable',
    'Chris Taylor',
    'Abraham White',
    'Ryotaro Eguchi',
    'Simon Waldherr',
    'Isaac Sandaljian',
    'Markus Rademacher'
  ].freeze
end
