const {
  persistGroups,
  readStoredGroups
} = require('./persistence.js')
const {
  getWxApi
} = require('../../utils/base-utils.js')
const {
  normalizeStorageCodeInfoCard,
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  normalizeGroup
} = require('../../domain/parameter-groups/model.js')
const storageAccessService = require('../storage-access/service.js')
const {
  PROTOCOL_MODE,
  normalizeProtocolMode
} = require('../../domain/protocol-mode.js')

const STORAGE_CODE_INFO_KEY = 'parameter-groups-code-info'
let initialized = false
const subscribers = []
const DEFAULT_STORAGE_CODE_INFO_CARD = normalizeStorageCodeInfoCard(null)
let state = {
  activeProtocolMode: PROTOCOL_MODE.STORAGE_ACCESS,
  parameterDataTypeOptions: DATA_TYPE_OPTIONS,
  parameterGroupsByProtocol: {
    [PROTOCOL_MODE.MODBUS_RTU]: [],
    [PROTOCOL_MODE.STORAGE_ACCESS]: []
  },
  parameterRegisterTypeOptions: REGISTER_TYPE_OPTIONS,
  storageCodeInfoCard: DEFAULT_STORAGE_CODE_INFO_CARD
}

function getActiveProtocolMode() {
  return normalizeProtocolMode(state.activeProtocolMode)
}

function getActiveGroups() {
  if (getActiveProtocolMode() === PROTOCOL_MODE.NONE) return []

  return state.parameterGroupsByProtocol[getActiveProtocolMode()] || []
}

function getGroupsForProtocol(protocolMode = getActiveProtocolMode()) {
  const normalizedProtocolMode = normalizeProtocolMode(protocolMode)
  if (normalizedProtocolMode === PROTOCOL_MODE.NONE) return []

  return state.parameterGroupsByProtocol[normalizedProtocolMode] || []
}

function getCodeInfoContext(card = state.storageCodeInfoCard) {
  return card && card.codeInfoContext
    ? card.codeInfoContext
    : {}
}

function normalizeGroupsForProtocol(parameterGroups = [], protocolMode = getActiveProtocolMode(), options = {}) {
  const codeInfoContext = protocolMode === PROTOCOL_MODE.STORAGE_ACCESS
    ? getCodeInfoContext(options.storageCodeInfoCard)
    : {}

  return Array.isArray(parameterGroups)
    ? parameterGroups.map((group) => normalizeGroup({
      ...group,
      codeInfoContext
    }))
    : []
}

function notify() {
  const nextState = getState()

  subscribers.slice().forEach((subscriber) => {
    subscriber(nextState)
  })
}

function persistActiveGroups(options = {}) {
  if (options.persist === false) return

  persistGroups(getActiveGroups(), getActiveProtocolMode())
}

function setState(changedData, options = {}) {
  state = {
    ...state,
    ...changedData
  }

  persistActiveGroups(options)
  notify()
}

function setGroups(parameterGroups, options = {}) {
  const protocolMode = normalizeProtocolMode(options.protocolMode || getActiveProtocolMode())
  if (protocolMode === PROTOCOL_MODE.NONE) {
    notify()
    return
  }
  const normalizedGroups = normalizeGroupsForProtocol(parameterGroups, protocolMode)
  const parameterGroupsByProtocol = {
    ...state.parameterGroupsByProtocol,
    [protocolMode]: normalizedGroups
  }

  state = {
    ...state,
    parameterGroupsByProtocol
  }

  if (options.persist !== false) persistGroups(normalizedGroups, protocolMode)
  notify()
}

function persistStorageCodeInfoCard(card) {
  const wxApi = getWxApi()
  if (typeof wxApi.setStorageSync !== 'function') return

  try {
    wxApi.setStorageSync(STORAGE_CODE_INFO_KEY, JSON.stringify(card || null))
  } catch (error) {}
}

function readStorageCodeInfoCard() {
  const wxApi = getWxApi()
  if (typeof wxApi.getStorageSync !== 'function') return DEFAULT_STORAGE_CODE_INFO_CARD

  try {
    const jsonText = wxApi.getStorageSync(STORAGE_CODE_INFO_KEY)
    return jsonText ? normalizeStorageCodeInfoCard(JSON.parse(jsonText)) : DEFAULT_STORAGE_CODE_INFO_CARD
  } catch (error) {
    return DEFAULT_STORAGE_CODE_INFO_CARD
  }
}

function setStorageCodeInfo(codeInfo, options = {}) {
  const card = normalizeStorageCodeInfoCard(codeInfo)
  storageAccessService.updateSyncedDeviceCaps(card.codeInfoContext || {})
  const storageGroups = state.parameterGroupsByProtocol[PROTOCOL_MODE.STORAGE_ACCESS] || []
  const normalizedStorageGroups = normalizeGroupsForProtocol(storageGroups, PROTOCOL_MODE.STORAGE_ACCESS, {
    storageCodeInfoCard: card
  })

  state = {
    ...state,
    parameterGroupsByProtocol: {
      ...state.parameterGroupsByProtocol,
      [PROTOCOL_MODE.STORAGE_ACCESS]: normalizedStorageGroups
    },
    storageCodeInfoCard: card
  }

  if (options.persist !== false) persistGroups(normalizedStorageGroups, PROTOCOL_MODE.STORAGE_ACCESS)
  if (options.persist !== false) persistStorageCodeInfoCard(card)
  notify()
}

function updateGroups(mapper, options = {}) {
  if (typeof mapper !== 'function') return

  const protocolMode = normalizeProtocolMode(options.protocolMode || getActiveProtocolMode())
  if (protocolMode === PROTOCOL_MODE.NONE) return
  setGroups(getGroupsForProtocol(protocolMode).map((group, index) => mapper(group, index)), {
    ...options,
    protocolMode
  })
}

function switchProtocolMode(protocolMode, options = {}) {
  const normalizedProtocolMode = normalizeProtocolMode(protocolMode)
  init(normalizedProtocolMode)

  if (state.activeProtocolMode === normalizedProtocolMode) {
    if (options.notify !== false) notify()
    return
  }

  state = {
    ...state,
    activeProtocolMode: normalizedProtocolMode
  }

  if (options.notify !== false) notify()
}

function init(protocolMode = state.activeProtocolMode) {
  const normalizedProtocolMode = normalizeProtocolMode(protocolMode)
  if (initialized) return

  const storageCodeInfoCard = readStorageCodeInfoCard()
  storageAccessService.updateSyncedDeviceCaps(storageCodeInfoCard.codeInfoContext || {})
  const protocolOrder = normalizedProtocolMode === PROTOCOL_MODE.MODBUS_RTU
    ? [PROTOCOL_MODE.MODBUS_RTU, PROTOCOL_MODE.STORAGE_ACCESS]
    : [PROTOCOL_MODE.STORAGE_ACCESS, PROTOCOL_MODE.MODBUS_RTU]
  const parameterGroupsByProtocol = {
    [PROTOCOL_MODE.MODBUS_RTU]: [],
    [PROTOCOL_MODE.STORAGE_ACCESS]: []
  }

  protocolOrder.forEach((targetProtocolMode) => {
    parameterGroupsByProtocol[targetProtocolMode] = normalizeGroupsForProtocol(
      readStoredGroups(targetProtocolMode),
      targetProtocolMode,
      {
        storageCodeInfoCard
      }
    )
  })

  state = {
    ...state,
    activeProtocolMode: normalizedProtocolMode,
    storageCodeInfoCard,
    parameterGroupsByProtocol
  }
  initialized = true
}

function getState() {
  return {
    activeProtocolMode: getActiveProtocolMode(),
    parameterDataTypeOptions: DATA_TYPE_OPTIONS,
    parameterGroups: getActiveGroups(),
    parameterRegisterTypeOptions: REGISTER_TYPE_OPTIONS,
    storageCodeInfoCard: state.storageCodeInfoCard
  }
}

function getGroups() {
  return getActiveGroups()
}

function findGroup(groupId, protocolMode = getActiveProtocolMode()) {
  return getGroupsForProtocol(protocolMode).find((group) => group.id === groupId)
}

function subscribe(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  init()
  subscribers.push(subscriber)
  subscriber(getState())

  return () => {
    const index = subscribers.indexOf(subscriber)
    if (index >= 0) subscribers.splice(index, 1)
  }
}

module.exports = {
  DATA_TYPE_OPTIONS,
  REGISTER_TYPE_OPTIONS,
  findGroup,
  getGroups,
  getState,
  init,
  setGroups,
  setState,
  setStorageCodeInfo,
  switchProtocolMode,
  subscribe,
  updateGroups
}
