const TYPE_ALIASES = {
  bit: 'uint8_t',
  bool: 'uint8_t',
  char: 'int8_t',
  double: 'float',
  float: 'float',
  int: 'int16_t',
  int8: 'int8_t',
  int8_t: 'int8_t',
  int16: 'int16_t',
  int16_t: 'int16_t',
  int32: 'int32_t',
  int32_t: 'int32_t',
  long: 'int32_t',
  short: 'int16_t',
  'signed char': 'int8_t',
  'signed int': 'int16_t',
  'signed long': 'int32_t',
  'signed short': 'int16_t',
  uint8: 'uint8_t',
  uint8_t: 'uint8_t',
  uint16: 'uint16_t',
  uint16_t: 'uint16_t',
  uint32: 'uint32_t',
  uint32_t: 'uint32_t',
  'unsigned char': 'uint8_t',
  'unsigned int': 'uint16_t',
  'unsigned long': 'uint32_t',
  'unsigned short': 'uint16_t'
}

const TYPE_QUALIFIERS = {
  _I: true,
  _IO: true,
  _O: true,
  const: true,
  extern: true,
  register: true,
  static: true,
  volatile: true
}

const STRUCT_PATTERNS = [
  /typedef\s+struct(?:\s+[A-Za-z_]\w*)?\s*\{([\s\S]*?)\}\s*([A-Za-z_]\w*)\s*;/g,
  /struct\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}\s*;/g
]

const ENUM_PATTERNS = [
  /typedef\s+enum(?:\s+([A-Za-z_]\w*))?\s*\{([\s\S]*?)\}\s*([A-Za-z_]\w*)\s*;/g,
  /enum\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}\s*;/g
]

function normalizeLookupName(value) {
  return String(value || '')
    .replace(/^_+/, '')
    .replace(/[^A-Za-z0-9]/g, '')
    .toLowerCase()
}

function stripComments(source) {
  return String(source || '')
    .replace(/\/\*[\s\S]*?\*\//g, '')
    .replace(/\/\/.*$/gm, '')
}

function normalizeTypeText(typeText) {
  return String(typeText || '')
    .replace(/\*/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function resolveType(typeText, aliases) {
  const normalized = normalizeTypeText(typeText)
  if (!normalized) return ''

  const compact = normalized
    .split(/\s+/)
    .filter((token) => !TYPE_QUALIFIERS[token])
    .join(' ')
    .trim()

  if (!compact || /^struct\b/.test(compact) || compact.indexOf('*') >= 0) {
    return ''
  }

  if (aliases[compact]) return aliases[compact]

  const tokens = compact.split(/\s+/).filter(Boolean)
  for (const token of tokens) {
    if (aliases[token]) return aliases[token]
  }

  return ''
}

function getEnumTypeNames(enumInfo = {}) {
  return [
    enumInfo.name,
    enumInfo.typedefName,
    enumInfo.tagName ? `enum ${enumInfo.tagName}` : '',
    enumInfo.tagName
  ]
    .filter(Boolean)
    .filter((name, index, list) => list.indexOf(name) === index)
}

function normalizeEnumTypeKey(value) {
  return normalizeTypeText(value).toLowerCase()
}

function createEnumTypeMap(enums = []) {
  return enums.reduce((map, enumInfo) => {
    getEnumTypeNames(enumInfo).forEach((typeName) => {
      map[normalizeTypeText(typeName)] = enumInfo
      map[normalizeEnumTypeKey(typeName)] = enumInfo
    })

    return map
  }, {})
}

function createAliasMap(source, enums = findEnums(source)) {
  const aliases = {
    ...TYPE_ALIASES
  }
  const definePattern = /^\s*#\s*define\s+([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*$/gm
  let defineMatch

  while ((defineMatch = definePattern.exec(source))) {
    const name = defineMatch[1]
    const value = defineMatch[2]
    if (aliases[value]) aliases[name] = aliases[value]
  }

  const typedefPattern = /typedef\s+(?!struct\b)([^;{}]+?)\s+([A-Za-z_]\w*)\s*;/g
  let typedefMatch

  while ((typedefMatch = typedefPattern.exec(source))) {
    const resolvedType = resolveType(typedefMatch[1], aliases)
    if (resolvedType) aliases[typedefMatch[2]] = resolvedType
  }

  const typedefStructPattern = /typedef\s+struct(?:\s+([A-Za-z_]\w*))?\s*\{[\s\S]*?\}\s*([A-Za-z_]\w*)\s*;/g
  let structTypedefMatch

  while ((structTypedefMatch = typedefStructPattern.exec(source))) {
    const tagName = structTypedefMatch[1]
    const typedefName = structTypedefMatch[2]
    if (tagName && typedefName) aliases[`struct ${tagName}`] = typedefName
  }

  enums.forEach((enumInfo) => {
    getEnumTypeNames(enumInfo).forEach((typeName) => {
      aliases[typeName] = enumInfo.dataType || 'uint16_t'
    })
  })

  return aliases
}

function tokenizeEnumExpression(expression, symbols = {}) {
  const source = String(expression || '').trim()
  const tokens = []
  let index = 0

  while (index < source.length) {
    const char = source[index]
    if (/\s/.test(char)) {
      index += 1
      continue
    }

    const numberMatch = source.slice(index).match(/^(?:0x[0-9a-f]+|\d+)(?:u|U|l|L|ul|UL|uL|Ul|lu|LU|lU|Lu)?/i)
    if (numberMatch) {
      const raw = numberMatch[0].replace(/(?:u|U|l|L|ul|UL|uL|Ul|lu|LU|lU|Lu)+$/i, '')
      tokens.push({
        type: 'number',
        value: raw.toLowerCase().startsWith('0x') ? parseInt(raw, 16) : Number(raw)
      })
      index += numberMatch[0].length
      continue
    }

    const identifierMatch = source.slice(index).match(/^[A-Za-z_]\w*/)
    if (identifierMatch) {
      const name = identifierMatch[0]
      if (!Object.prototype.hasOwnProperty.call(symbols, name)) return null
      tokens.push({
        type: 'number',
        value: Number(symbols[name])
      })
      index += name.length
      continue
    }

    const twoCharOperator = source.slice(index, index + 2)
    if (twoCharOperator === '<<' || twoCharOperator === '>>') {
      tokens.push({
        type: 'operator',
        value: twoCharOperator
      })
      index += 2
      continue
    }

    if ('+-*/%&|^~()'.indexOf(char) >= 0) {
      tokens.push({
        type: char === '(' || char === ')' ? 'paren' : 'operator',
        value: char
      })
      index += 1
      continue
    }

    return null
  }

  return tokens
}

function createEnumExpressionParser(tokens) {
  let index = 0
  const precedence = {
    '|': 1,
    '^': 2,
    '&': 3,
    '<<': 4,
    '>>': 4,
    '+': 5,
    '-': 5,
    '*': 6,
    '/': 6,
    '%': 6
  }

  function peek() {
    return tokens[index] || null
  }

  function consume() {
    const token = tokens[index] || null
    index += 1

    return token
  }

  function applyBinaryOperator(operator, left, right) {
    if (operator === '+') return left + right
    if (operator === '-') return left - right
    if (operator === '*') return left * right
    if (operator === '/') return right === 0 ? NaN : Math.trunc(left / right)
    if (operator === '%') return right === 0 ? NaN : left % right
    if (operator === '<<') return left << right
    if (operator === '>>') return left >> right
    if (operator === '&') return left & right
    if (operator === '^') return left ^ right
    if (operator === '|') return left | right

    return NaN
  }

  function parsePrimary() {
    const token = consume()
    if (!token) return NaN
    if (token.type === 'number') return Number(token.value)
    if (token.value === '(') {
      const value = parseExpression(1)
      const endToken = consume()
      return endToken && endToken.value === ')' ? value : NaN
    }

    return NaN
  }

  function parseUnary() {
    const token = peek()
    if (token && token.type === 'operator' && ['+', '-', '~'].indexOf(token.value) >= 0) {
      consume()
      const value = parseUnary()
      if (token.value === '+') return value
      if (token.value === '-') return -value

      return ~value
    }

    return parsePrimary()
  }

  function parseExpression(minPrecedence) {
    let left = parseUnary()

    while (Number.isFinite(left)) {
      const token = peek()
      const tokenPrecedence = token && token.type === 'operator' ? precedence[token.value] : 0
      if (!tokenPrecedence || tokenPrecedence < minPrecedence) break

      consume()
      const right = parseExpression(tokenPrecedence + 1)
      left = applyBinaryOperator(token.value, left, right)
    }

    return left
  }

  return {
    parse() {
      const value = parseExpression(1)

      return index === tokens.length && Number.isFinite(value) ? Math.trunc(value) : null
    }
  }
}

function evaluateEnumExpression(expression, symbols = {}) {
  const tokens = tokenizeEnumExpression(expression, symbols)
  if (!tokens || !tokens.length) return null

  return createEnumExpressionParser(tokens).parse()
}

function inferEnumDataType(options = []) {
  const values = options.map((option) => Number(option.value)).filter((value) => Number.isFinite(value))
  const minValue = values.length ? Math.min.apply(null, values) : 0
  const maxValue = values.length ? Math.max.apply(null, values) : 0

  if (minValue < 0) {
    if (minValue >= -0x8000 && maxValue <= 0x7FFF) return 'int16_t'

    return 'int32_t'
  }
  if (maxValue <= 0xFFFF) return 'uint16_t'

  return 'uint32_t'
}

function parseEnumOptions(body, globalSymbols = {}) {
  const options = []
  const symbols = {
    ...globalSymbols
  }
  let currentValue = -1

  String(body || '').split(',').forEach((item) => {
    const text = item.trim()
    if (!text) return

    const match = text.match(/^([A-Za-z_]\w*)\s*(?:=\s*(.+))?$/)
    if (!match) return

    const name = match[1]
    const explicitValue = match[2] ? evaluateEnumExpression(match[2], symbols) : null
    currentValue = explicitValue === null ? currentValue + 1 : explicitValue
    symbols[name] = currentValue
    options.push({
      label: name,
      name,
      value: currentValue
    })
  })

  return {
    options,
    symbols
  }
}

function findEnums(source) {
  const enums = []
  const globalSymbols = {}

  ENUM_PATTERNS.forEach((pattern) => {
    pattern.lastIndex = 0
    let match

    while ((match = pattern.exec(source))) {
      const isTypedef = pattern === ENUM_PATTERNS[0]
      const tagName = isTypedef ? match[1] : match[1]
      const body = isTypedef ? match[2] : match[2]
      const typedefName = isTypedef ? match[3] : ''
      const parsed = parseEnumOptions(body, globalSymbols)
      if (!parsed.options.length) continue

      Object.assign(globalSymbols, parsed.symbols)
      const name = typedefName || tagName || 'enum'
      const enumInfo = {
        dataType: inferEnumDataType(parsed.options),
        name,
        options: parsed.options,
        tagName,
        typedefName,
        typeNames: []
      }
      enumInfo.typeNames = getEnumTypeNames(enumInfo)
      enums.push(enumInfo)
    }
  })

  const seen = {}

  return enums.filter((enumInfo) => {
    const key = enumInfo.typeNames.map(normalizeEnumTypeKey).join('|')
    if (!key || seen[key]) return false
    seen[key] = true

    return true
  })
}

function findStruct(source) {
  for (const pattern of STRUCT_PATTERNS) {
    pattern.lastIndex = 0
    const match = pattern.exec(source)
    if (!match) continue

    if (pattern === STRUCT_PATTERNS[0]) {
      return {
        body: match[1],
        name: match[2]
      }
    }

    return {
      body: match[2],
      name: match[1]
    }
  }

  return null
}

function findStructs(source) {
  const structs = []

  STRUCT_PATTERNS.forEach((pattern) => {
    pattern.lastIndex = 0
    let match

    while ((match = pattern.exec(source))) {
      if (pattern === STRUCT_PATTERNS[0]) {
        structs.push({
          body: match[1],
          name: match[2]
        })
      } else {
        structs.push({
          body: match[2],
          name: match[1],
          tagName: match[1]
        })
      }
    }
  })

  const seen = {}

  return structs.filter((item) => {
    const key = item.name
    if (!key || seen[key]) return false
    seen[key] = true

    return true
  })
}

function parseArrayDimensions(suffix) {
  const dimensions = []
  const pattern = /\[([^\]]*)\]/g
  let match

  while ((match = pattern.exec(suffix || ''))) {
    const text = String(match[1] || '').trim()
    const value = Number(text)
    if (!Number.isInteger(value) || value < 1) {
      throw new Error('数组长度需为正整数')
    }
    dimensions.push(value)
  }

  return dimensions
}

function splitDeclarations(body) {
  return String(body || '')
    .split(';')
    .map((item) => item.trim())
    .filter(Boolean)
}

function splitDeclarators(statement) {
  return String(statement || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean)
}

function parseFirstDeclarator(text) {
  const match = String(text || '').match(/^(.+?)\s+(\**\s*(?:[A-Za-z_]\w*)?(?:\s*\[[^\]]*\])*(?:\s*:\s*\d+)?)$/)
  if (!match) return null

  return {
    declarator: match[2],
    typeText: match[1]
  }
}

function parseDeclarator(text) {
  const rawText = String(text || '')
  const bitWidthMatch = rawText.match(/:\s*(\d+)\s*$/)
  const cleaned = rawText
    .replace(/=.*/, '')
    .replace(/:\s*\d+\s*$/, '')
    .replace(/\*/g, '')
    .trim()

  if (!cleaned && bitWidthMatch) {
    return {
      arrayDimensions: [],
      bitWidth: Number(bitWidthMatch[1]),
      name: ''
    }
  }

  const match = cleaned.match(/^([A-Za-z_]\w*)\s*((?:\[[^\]]*\])*)$/)
  if (!match) return null

  return {
    arrayDimensions: parseArrayDimensions(match[2]),
    bitWidth: bitWidthMatch ? Number(bitWidthMatch[1]) : null,
    name: match[1]
  }
}

module.exports = {
  createAliasMap,
  createEnumTypeMap,
  findStruct,
  findStructs,
  findEnums,
  getEnumTypeNames,
  normalizeLookupName,
  normalizeTypeText,
  parseDeclarator,
  parseFirstDeclarator,
  resolveType,
  splitDeclarations,
  splitDeclarators,
  stripComments
}
