import assign from 'object-assign'

export const merge = (a, b) => {
  let result = assign({}, a, b)
  for (const key in a) {
    if (!a[key] || typeof b[key] !== 'object') continue
    assign(result, {
      [key]: assign(a[key], b[key]),
    })
  }
  return result
}

// sort object-value responsive styles
const sort = obj => {
  const next = {}
  Object.keys(obj)
    .sort((a, b) => a.localeCompare(b, undefined, {
      numeric: true,
      sensitivity: 'base',
    }))
    .forEach(key => {
      next[key] = obj[key]
    })
  return next
}

const defaults = {
  breakpoints: [40, 52, 64].map(n => n + 'em'),
}
const createMediaQuery = n => `@media screen and (min-width: ${n})`
const getValue = (n, scale) => get(scale, n, n)

export const get = (obj, key, def, p, undef) => {
  key = key && key.split ? key.split('.') : [key]
  for (p = 0; p < key.length; p++) {
    obj = obj ? obj[key[p]] : undef
  }
  return obj === undef ? def : obj
}

export const createParser = config => {
  const cache = {}
  const parse = props => {
    let styles = {}
    let shouldSort = false
    const isCacheDisabled = props.theme && props.theme.disableStyledSystemCache

    for (const key in props) {
      if (!config[key]) continue
      const sx = config[key]
      const raw = props[key]
      const scale = get(props.theme, sx.scale, sx.defaults)

      if (typeof raw === 'object') {
        cache.breakpoints =
          (!isCacheDisabled && cache.breakpoints) ||
          get(props.theme, 'breakpoints', defaults.breakpoints)
        if (Array.isArray(raw)) {
          cache.media = (!isCacheDisabled && cache.media) || [
            null,
            ...cache.breakpoints.map(createMediaQuery),
          ]
          styles = merge(
            styles,
            parseResponsiveStyle(cache.media, sx, scale, raw, props)
          )
          continue
        }
        if (raw !== null) {
          styles = merge(
            styles,
            parseResponsiveObject(cache.breakpoints, sx, scale, raw, props)
          )
          shouldSort = true
        }
        continue
      }

      assign(styles, sx(raw, scale, props))
    }

    // sort object-based responsive styles
    if (shouldSort) {
      styles = sort(styles)
    }

    return styles
  }
  parse.config = config
  parse.propNames = Object.keys(config)
  parse.cache = cache

  const keys = Object.keys(config).filter(k => k !== 'config')
  if (keys.length > 1) {
    keys.forEach(key => {
      parse[key] = createParser({ [key]: config[key] })
    })
  }

  return parse
}

const parseResponsiveStyle = (mediaQueries, sx, scale, raw, _props) => {
  let styles = {}
  raw.slice(0, mediaQueries.length).forEach((value, i) => {
    const media = mediaQueries[i]
    const style = sx(value, scale, _props)
    if (!media) {
      assign(styles, style)
    } else {
      assign(styles, {
        [media]: assign({}, styles[media], style),
      })
    }
  })
  return styles
}

const parseResponsiveObject = (breakpoints, sx, scale, raw, _props) => {
  let styles = {}
  for (let key in raw) {
    const breakpoint = breakpoints[key]
    const value = raw[key]
    const style = sx(value, scale, _props)
    if (!breakpoint) {
      assign(styles, style)
    } else {
      const media = createMediaQuery(breakpoint)
      assign(styles, {
        [media]: assign({}, styles[media], style),
      })
    }
  }
  return styles
}

export const createStyleFunction = ({
  properties,
  property,
  scale,
  transform = getValue,
  defaultScale,
}) => {
  properties = properties || [property]
  const sx = (value, scale, _props) => {
    const result = {}
    const n = transform(value, scale, _props)
    if (n === null) return
    properties.forEach(prop => {
      result[prop] = n
    })
    return result
  }
  sx.scale = scale
  sx.defaults = defaultScale
  return sx
}

// new v5 API
export const system = (args = {}) => {
  const config = {}
  Object.keys(args).forEach(key => {
    const conf = args[key]
    if (conf === true) {
      // shortcut definition
      config[key] = createStyleFunction({
        property: key,
        scale: key,
      })
      return
    }
    if (typeof conf === 'function') {
      config[key] = conf
      return
    }
    config[key] = createStyleFunction(conf)
  })

  const parser = createParser(config)
  return parser
}

export const compose = (...parsers) => {
  let config = {}
  parsers.forEach(parser => {
    if (!parser || !parser.config) return
    assign(config, parser.config)
  })
  const parser = createParser(config)

  return parser
}
