const {
  CRC_ALGORITHM_PRESETS,
  calculateCrc
} = require('../../utils/crc.js')
const {
  HASH_ALGORITHM_PRESETS,
  calculateHash
} = require('./hash.js')
const {
  formatBytes,
  stringToUtf8Bytes
} = require('../../utils/binary-utils.js')

const INPUT_TYPE_OPTIONS = [
  { key: 'hex', label: 'HEX' },
  { key: 'string', label: 'String' },
  { key: 'base64', label: 'Base64' }
]
const ALGORITHM_PRESETS = CRC_ALGORITHM_PRESETS
  .map((preset) => ({ ...preset, kind: 'crc' }))
  .concat(HASH_ALGORITHM_PRESETS)
const CRC_CONFIG_FIELDS = [
  'crcInitialValue',
  'crcPoly',
  'crcWidth',
  'crcXorOut'
]

function clonePreset(preset) {
  return {
    ...preset
  }
}

function getPreset(index) {
  return clonePreset(ALGORITHM_PRESETS[Number(index)] || ALGORITHM_PRESETS[0])
}

function getCustomPresetIndex() {
  return Math.max(0, ALGORITHM_PRESETS.findIndex((preset) => preset.custom))
}

function getInputType(index) {
  return INPUT_TYPE_OPTIONS[Number(index)] || INPUT_TYPE_OPTIONS[0]
}

function createPresetState(presetIndex = 0) {
  const preset = getPreset(presetIndex)
  const kind = preset.kind || 'crc'

  return {
    crcAlgorithmCollapsed: !(preset.custom || kind === 'hmac' || kind === 'pbkdf2'),
    crcAlgorithmKind: kind,
    crcInitialValue: preset.init || 'FFFF',
    crcPoly: preset.poly || '1021',
    crcPresetIndex: Number(presetIndex),
    crcReflectIn: !!preset.reflectIn,
    crcReflectOut: !!preset.reflectOut,
    crcShowBinResult: kind === 'crc',
    crcShowCrcConfig: kind === 'crc',
    crcShowHmacKey: kind === 'hmac',
    crcShowPbkdf2Config: kind === 'pbkdf2',
    crcWidth: String(preset.width || 16),
    crcXorOut: preset.xorOut || '0000'
  }
}

function createInitialState() {
  return {
    ...createPresetState(0),
    ...createEmptyResultState(),
    crcDataLengthText: '0 bytes',
    crcDataText: '',
    crcFileName: '',
    crcFileSizeText: '',
    crcHmacKey: '',
    crcInputTypeIndex: 0,
    crcInputTypeOptions: INPUT_TYPE_OPTIONS,
    crcPbkdf2Iterations: '1000',
    crcPbkdf2Length: '32',
    crcPbkdf2Salt: '',
    crcPresetOptions: ALGORITHM_PRESETS.map(clonePreset)
  }
}

function createEmptyResultState() {
  return {
    crcResultBase64: '',
    crcResultBin: '',
    crcResultBinLines: splitBinaryResult(''),
    crcResultHex: ''
  }
}

function normalizeHexData(text) {
  return String(text || '')
    .replace(/0x/gi, '')
    .replace(/[\s,_:-]/g, '')
}

function parseHexBytes(text) {
  const hexText = normalizeHexData(text)
  if (!hexText) return []
  if (!/^[0-9A-F]+$/i.test(hexText)) throw new Error('HEX 数据包含非法字符')
  if (hexText.length % 2 !== 0) throw new Error('HEX 数据长度必须为偶数字符')

  const bytes = []
  for (let index = 0; index < hexText.length; index += 2) {
    bytes.push(parseInt(hexText.slice(index, index + 2), 16))
  }

  return bytes
}

function parseBase64Bytes(text) {
  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  let normalized = String(text || '').replace(/\s/g, '')
  if (!normalized) return []
  if (normalized.length % 4 === 1 || /[^A-Za-z0-9+/=]/.test(normalized)) {
    throw new Error('Base64 数据无效')
  }
  while (normalized.length % 4 !== 0) {
    normalized += '='
  }

  const bytes = []

  for (let index = 0; index < normalized.length; index += 4) {
    const chars = normalized.slice(index, index + 4)
    const values = chars.split('').map((char) => (char === '=' ? 0 : alphabet.indexOf(char)))
    if (values.some((value) => value < 0)) throw new Error('Base64 数据无效')

    const triple = (values[0] << 18) | (values[1] << 12) | (values[2] << 6) | values[3]
    bytes.push((triple >> 16) & 0xFF)
    if (chars[2] !== '=') bytes.push((triple >> 8) & 0xFF)
    if (chars[3] !== '=') bytes.push(triple & 0xFF)
  }

  return bytes
}

function parseInputBytes(dataText, inputTypeIndex) {
  const inputType = getInputType(inputTypeIndex)
  if (inputType.key === 'hex') return parseHexBytes(dataText)
  if (inputType.key === 'base64') return parseBase64Bytes(dataText)

  return stringToUtf8Bytes(dataText)
}

function splitBinaryResult(value) {
  const text = String(value === undefined || value === null ? '' : value)
  if (!text || text.length <= 32) return [
    {
      id: 'bin-line-0',
      text
    }
  ]

  const lineLength = Math.ceil(text.length / 2)

  return [
    {
      id: 'bin-line-0',
      text: text.slice(0, lineLength)
    },
    {
      id: 'bin-line-1',
      text: text.slice(lineLength)
    }
  ]
}

function getConfigFromState(state) {
  const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}

  return {
    init: state.crcInitialValue,
    key: preset.key || '',
    poly: state.crcPoly,
    reflectIn: !!state.crcReflectIn,
    reflectOut: !!state.crcReflectOut,
    useLookupTable: !!preset.key && !preset.custom,
    width: state.crcWidth,
    xorOut: state.crcXorOut
  }
}

function getHashConfigFromState(state) {
  const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}

  return {
    hmacKey: state.crcHmacKey,
    key: preset.key || '',
    pbkdf2Iterations: state.crcPbkdf2Iterations,
    pbkdf2Length: state.crcPbkdf2Length,
    pbkdf2Salt: state.crcPbkdf2Salt
  }
}

function calculateFromState(state, fileBytes) {
  const bytes = fileBytes
    ? Array.prototype.slice.call(fileBytes)
    : parseInputBytes(state.crcDataText, state.crcInputTypeIndex)
  const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}
  const result = (preset.kind || 'crc') === 'crc'
    ? calculateCrc(bytes, getConfigFromState(state))
    : calculateHash(bytes, getHashConfigFromState(state))

  return {
    crcDataLengthText: formatBytes(bytes.length),
    crcResultBase64: result.base64,
    crcResultBin: result.bin,
    crcResultBinLines: splitBinaryResult(result.bin),
    crcResultHex: result.hex
  }
}

module.exports = {
  calculateFromState,
  createEmptyResultState,
  createInitialState,
  createPresetState,
  CRC_CONFIG_FIELDS,
  getCustomPresetIndex
}
