/**
 * Generates a DOM safe UUID.
 * Starting with a letter is important to make it DOM safe.
 * @return {string} a random DOM safe UUID
 */
export function uuid() {
  return "cxxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0
    const v = c === "x" ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}

/**
 * Capitalises a string
 * @param string the string to capitalise
 * @return {string} the capitalised string
 */
export const capitalise = string => {
  if (!string) {
    return string
  }
  return string.substring(0, 1).toUpperCase() + string.substring(1)
}

/**
 * Computes a short hash of a string
 * @param string the string to compute a hash of
 * @return {string} the hash string
 */
export const hashString = string => {
  if (!string) {
    return "0"
  }
  let hash = 0
  for (let i = 0; i < string.length; i++) {
    let char = string.charCodeAt(i)
    hash = (hash << 5) - hash + char
    hash = hash & hash // Convert to 32bit integer
  }
  return hash.toString()
}

/**
 * Gets a key within an object. The key supports dot syntax for retrieving deep
 * fields - e.g. "a.b.c".
 * Exact matches of keys with dots in them take precedence over nested keys of
 * the same path - e.g. getting "a.b" from { "a.b": "foo", a: { b: "bar" } }
 * will return "foo" over "bar".
 * @param obj the object
 * @param key the key
 * @return {*|null} the value or null if a value was not found for this key
 */
export const deepGet = (obj, key) => {
  if (!obj || !key) {
    return null
  }
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
    return obj[key]
  }
  const split = key.split(".")
  for (let i = 0; i < split.length; i++) {
    obj = obj?.[split[i]]
  }
  return obj
}

/**
 * Sets a key within an object. The key supports dot syntax for retrieving deep
 * fields - e.g. "a.b.c".
 * Exact matches of keys with dots in them take precedence over nested keys of
 * the same path - e.g. setting "a.b" of { "a.b": "foo", a: { b: "bar" } }
 * will override the value "foo" rather than "bar".
 * If a deep path is specified and the parent keys don't exist then these will
 * be created.
 * @param obj the object
 * @param key the key
 * @param value the value
 */
export const deepSet = (obj, key, value) => {
  if (!obj || !key) {
    return
  }
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
    obj[key] = value
    return
  }
  const split = key.split(".")
  for (let i = 0; i < split.length - 1; i++) {
    const nextKey = split[i]
    if (obj && obj[nextKey] == null) {
      obj[nextKey] = {}
    }
    obj = obj?.[nextKey]
  }
  if (!obj) {
    return
  }
  obj[split[split.length - 1]] = value
}

/**
 * Deeply clones an object. Functions are not supported.
 * @param obj the object to clone
 */
export const cloneDeep = obj => {
  return JSON.parse(JSON.stringify(obj))
}
