// @flow
import nodePath from 'path'

type LabelFormatOptions = {
  name: string,
  path: string
}

const invalidClassNameCharacters = /[!"#$%&'()*+,./:;<=>?@[\]^`|}~{]/g

const sanitizeLabelPart = (labelPart: string) =>
  labelPart.trim().replace(invalidClassNameCharacters, '-')

function getLabel(
  identifierName?: string,
  labelFormat?: string | (LabelFormatOptions => string),
  filename: string
) {
  if (!identifierName) return null

  const sanitizedName = sanitizeLabelPart(identifierName)

  if (!labelFormat) {
    return sanitizedName
  }

  if (typeof labelFormat === 'function') {
    return labelFormat({
      name: sanitizedName,
      path: filename
    })
  }

  const parsedPath = nodePath.parse(filename)
  let localDirname = nodePath.basename(parsedPath.dir)
  let localFilename = parsedPath.name

  if (localFilename === 'index') {
    localFilename = localDirname
  }

  return labelFormat
    .replace(/\[local\]/gi, sanitizedName)
    .replace(/\[filename\]/gi, sanitizeLabelPart(localFilename))
    .replace(/\[dirname\]/gi, sanitizeLabelPart(localDirname))
}

export function getLabelFromPath(path: *, state: *, t: *) {
  return getLabel(
    getIdentifierName(path, t),
    state.opts.labelFormat,
    state.file.opts.filename
  )
}

const getObjPropertyLikeName = (path, t) => {
  if (
    (!t.isObjectProperty(path) && !t.isObjectMethod(path)) ||
    path.node.computed
  ) {
    return null
  }
  if (t.isIdentifier(path.node.key)) {
    return path.node.key.name
  }

  if (t.isStringLiteral(path.node.key)) {
    return path.node.key.value.replace(/\s+/g, '-')
  }

  return null
}

function getDeclaratorName(path, t) {
  // $FlowFixMe
  const parent = path.findParent(
    p =>
      p.isVariableDeclarator() ||
      p.isAssignmentExpression() ||
      p.isFunctionDeclaration() ||
      p.isFunctionExpression() ||
      p.isArrowFunctionExpression() ||
      p.isObjectProperty() ||
      p.isObjectMethod()
  )
  if (!parent) {
    return ''
  }

  // we probably have a css call assigned to a variable
  // so we'll just return the variable name
  if (parent.isVariableDeclarator()) {
    if (t.isIdentifier(parent.node.id)) {
      return parent.node.id.name
    }
    return ''
  }

  if (parent.isAssignmentExpression()) {
    let { left } = parent.node
    if (t.isIdentifier(left)) {
      return left.name
    }
    if (t.isMemberExpression(left)) {
      let memberExpression = left
      let name = ''
      while (true) {
        if (!t.isIdentifier(memberExpression.property)) {
          return ''
        }

        name = `${memberExpression.property.name}${name ? `-${name}` : ''}`

        if (t.isIdentifier(memberExpression.object)) {
          return `${memberExpression.object.name}-${name}`
        }

        if (!t.isMemberExpression(memberExpression.object)) {
          return ''
        }
        memberExpression = memberExpression.object
      }
    }
    return ''
  }

  // we probably have an inline css prop usage
  if (parent.isFunctionDeclaration()) {
    return parent.node.id.name || ''
  }

  if (parent.isFunctionExpression()) {
    if (parent.node.id) {
      return parent.node.id.name || ''
    }
    return getDeclaratorName(parent, t)
  }

  if (parent.isArrowFunctionExpression()) {
    return getDeclaratorName(parent, t)
  }

  // we could also have an object property
  const objPropertyLikeName = getObjPropertyLikeName(parent, t)

  if (objPropertyLikeName) {
    return objPropertyLikeName
  }

  let variableDeclarator = parent.findParent(p => p.isVariableDeclarator())
  if (!variableDeclarator || !variableDeclarator.get('id').isIdentifier()) {
    return ''
  }
  return variableDeclarator.node.id.name
}

function getIdentifierName(path: *, t: *) {
  let objPropertyLikeName = getObjPropertyLikeName(path.parentPath, t)

  if (objPropertyLikeName) {
    return objPropertyLikeName
  }

  // $FlowFixMe
  let classOrClassPropertyParent = path.findParent(
    p => t.isClassProperty(p) || t.isClass(p)
  )

  if (classOrClassPropertyParent) {
    if (
      t.isClassProperty(classOrClassPropertyParent) &&
      classOrClassPropertyParent.node.computed === false &&
      t.isIdentifier(classOrClassPropertyParent.node.key)
    ) {
      return classOrClassPropertyParent.node.key.name
    }
    if (
      t.isClass(classOrClassPropertyParent) &&
      classOrClassPropertyParent.node.id
    ) {
      return t.isIdentifier(classOrClassPropertyParent.node.id)
        ? classOrClassPropertyParent.node.id.name
        : ''
    }
  }

  let declaratorName = getDeclaratorName(path, t)
  // if the name starts with _ it was probably generated by babel so we should ignore it
  if (declaratorName.charAt(0) === '_') {
    return ''
  }
  return declaratorName
}
