const BYTE_MIN = 0
const BYTE_MAX = 0xFF

const CONTROL_LABELS = {
  0x00: 'NUL',
  0x01: 'SOH',
  0x02: 'STX',
  0x03: 'ETX',
  0x04: 'EOT',
  0x05: 'ENQ',
  0x06: 'ACK',
  0x07: 'BEL',
  0x08: 'BS',
  0x09: '\\t',
  0x0A: '\\n',
  0x0B: 'VT',
  0x0C: '\\f',
  0x0D: '\\r',
  0x0E: 'SO',
  0x0F: 'SI',
  0x10: 'DLE',
  0x11: 'DC1',
  0x12: 'DC2',
  0x13: 'DC3',
  0x14: 'DC4',
  0x15: 'NAK',
  0x16: 'SYN',
  0x17: 'ETB',
  0x18: 'CAN',
  0x19: 'EM',
  0x1A: 'SUB',
  0x1B: 'ESC',
  0x1C: 'FS',
  0x1D: 'GS',
  0x1E: 'RS',
  0x1F: 'US',
  0x20: 'SP',
  0x7F: 'DEL'
}

function formatHexByte(value) {
  return `0x${(Number(value) & BYTE_MAX).toString(16).toUpperCase().padStart(2, '0')}`
}

function getDefaultRows() {
  return [
    { label: '字符', meta: '', value: '--', copyValue: '' },
    { label: 'HEX', meta: '', value: '--', copyValue: '' },
    { label: 'DEC', meta: '', value: '--', copyValue: '' }
  ]
}

function createEmptyResultState() {
  return {
    asciiCodeErrorText: '',
    asciiCodeResultRows: getDefaultRows()
  }
}

function splitValueTokens(text) {
  return String(text || '')
    .trim()
    .split(/[\s,;，；]+/)
    .map((token) => token.trim())
    .filter(Boolean)
}

function tokenLooksNumeric(token) {
  return /^0x/i.test(token) || /^\d+$/.test(token)
}

function tokenIsValidNumeric(token) {
  return /^0x[0-9a-f]+$/i.test(token) || /^\d+$/.test(token)
}

function shouldParseAsNumeric(text) {
  const tokens = splitValueTokens(text)
  if (!tokens.length) return false

  return tokens.every(tokenLooksNumeric)
}

function parseNumericToken(token) {
  if (!tokenIsValidNumeric(token)) {
    throw new Error('数值格式无效')
  }

  const value = /^0x/i.test(token)
    ? parseInt(token.slice(2), 16)
    : Number(token)

  if (!Number.isInteger(value) || value < BYTE_MIN || value > BYTE_MAX) {
    throw new Error('数值范围需为 0 - 255')
  }

  return value
}

function parseNumericBytes(text) {
  return splitValueTokens(text).map(parseNumericToken)
}

function parseTextBytes(text) {
  const bytes = []

  Array.from(String(text || '')).forEach((char) => {
    const value = char.codePointAt(0)
    if (!Number.isInteger(value) || value < BYTE_MIN || value > BYTE_MAX) {
      throw new Error('字符需在 0 - 255 范围内')
    }
    bytes.push(value)
  })

  return bytes
}

function formatChar(value) {
  const byte = Number(value) & BYTE_MAX
  if (Object.prototype.hasOwnProperty.call(CONTROL_LABELS, byte)) return CONTROL_LABELS[byte]
  if (byte >= 0x21 && byte <= 0x7E) return String.fromCharCode(byte)

  return `\\x${byte.toString(16).toUpperCase().padStart(2, '0')}`
}

function formatDisplayText(bytes = []) {
  if (!bytes.length) return '--'
  if (bytes.every((byte) => {
    const value = Number(byte) & BYTE_MAX
    return value >= 0x21 && value <= 0x7E
  })) {
    return bytes.map((byte) => String.fromCharCode(Number(byte) & BYTE_MAX)).join('')
  }

  return bytes.map(formatChar).join(' ')
}

function bytesToText(bytes = []) {
  return bytes.map((byte) => String.fromCharCode(Number(byte) & BYTE_MAX)).join('')
}

function createResultRows(bytes = []) {
  if (!bytes.length) return getDefaultRows()

  const hexText = bytes.map(formatHexByte).join(' ')
  const decText = bytes.map((byte) => String(Number(byte) & BYTE_MAX)).join(' ')
  const charText = formatDisplayText(bytes)

  return [
    {
      copyValue: bytesToText(bytes),
      label: '字符',
      value: charText
    },
    {
      copyValue: hexText,
      label: 'HEX',
      value: hexText
    },
    {
      copyValue: decText,
      label: 'DEC',
      value: decText
    }
  ]
}

function buildState(source = {}) {
  const inputText = String(source.asciiCodeInputText || '')
  const trimmedText = inputText.trim()

  if (!trimmedText) {
    return {
      asciiCodeInputText: inputText,
      ...createEmptyResultState()
    }
  }

  try {
    const numericMode = shouldParseAsNumeric(trimmedText)
    const bytes = numericMode ? parseNumericBytes(trimmedText) : parseTextBytes(inputText)

    return {
      asciiCodeErrorText: '',
      asciiCodeInputText: inputText,
      asciiCodeResultRows: createResultRows(bytes)
    }
  } catch (error) {
    return {
      asciiCodeErrorText: error && error.message ? error.message : '转换失败',
      asciiCodeInputText: inputText,
      asciiCodeResultRows: getDefaultRows()
    }
  }
}

function createInitialState() {
  return buildState({
    asciiCodeInputText: ''
  })
}

function updateState(state, changedData = {}) {
  return buildState({
    ...state,
    ...changedData
  })
}

function clearInput(state = {}) {
  return updateState(state, {
    asciiCodeInputText: ''
  })
}

module.exports = {
  clearInput,
  createInitialState,
  formatHexByte,
  updateState
}
