local prime_node_id = luatexbase.new_whatsit'prime'

local func = luatexbase.new_luafunction'prime_helper:w'
token.set_lua("prime_helper:w", func, 'protected')

lua.get_functions_table()[func] = function(id)
  check_math(id)
  write_whatsit_wrapped(prime_node_id, 100, token.scan_int(), 0)
end

local prime_lookup = {
  [0x2032] = {
    [0x2032] = 0x2033,
    [0x2033] = 0x2034,
    [0x2034] = 0x2057,
  },
  [0x2035] = {
    [0x2035] = 0x2036,
    [0x2036] = 0x2037,
  },
}

local accent_t = node.id'accent'
local math_char_t = node.id'math_char'
local sub_mlist_t = node.id'sub_mlist'
local noad_t = node.id'noad'
local radical_t = node.id'radical'

math_whatsit_processors[prime_node_id] = function(_style, n, parent_head, parent)
  assert(parent)
  local char = n.value
  local pre_sup, pre_sub
  local prev = parent.prev
  local prev_id = prev and prev.id
  if prev_id == noad_t or prev_id == accent_t or prev_id == radical_t then
    pre_sup, pre_sub = prev.sup, prev.sub
  else
    node.free(n)
    parent.nucleus = node.new(sub_mlist_t)
    prev = parent
  end
  if pre_sub == nil then
    prev.sub, parent.sub = parent.sub, nil
  elseif parent.sub then
    tex.error'Double subscript ignored'
  end
  local post_sup = parent.sup

  local merge_lookup = prime_lookup[char]
  -- First check for the cases where no sub_mlist is needed:
  local done
  if post_sup == nil then
    if pre_sup == nil then
      local new_sup = node.new(math_char_t)
      new_sup.fam, new_sup.char = main_fam, char
      prev.sup = new_sup

      done = true
    elseif pre_sup.id == math_char_t and pre_sup.fam == main_fam then
      local merged = merge_lookup[pre_sup.char]
      if merged then
        pre_sup.char = merged

        done = true
      end
    end
  end

  if not done then
    local mlist -- Some sub_mlist node which will become the new sup
    local new_head, current_tail

    if pre_sup ~= nil then
      if pre_sup.id == sub_mlist_t then
        mlist, new_head = pre_sup, pre_sup.head
        current_tail = node.tail(new_head)
      else
        new_head = node.new(noad_t, 0)
        new_head.nucleus = pre_sup
        current_tail = new_head
      end
    end

    if current_tail and current_tail.id == noad_t then
      local nucleus = current_tail.nucleus
      if nucleus.id == math_char_t and nucleus.fam == main_fam then
        local oldchar = nucleus.char
        local nextchar = merge_lookup[oldchar]
        if nextchar then
          nucleus.char, done = nextchar, true
        end
      end
    end
    
    if not done then
      local noad = node.new(noad_t, 0)
      local nucleus = node.new(math_char_t)
      nucleus.fam, nucleus.char = main_fam, char
      noad.nucleus = nucleus
      new_head, current_tail = node.insert_after(new_head, current_tail, noad)
    end

    if post_sup ~= nil then
      if post_sup.id == sub_mlist_t then
        local post_head = post_sup.head
        current_tail.next, post_head.prev = post_head, current_tail

        if mlist then
          post_sup.head = nil
        else
          mlist = post_sup
          parent.sup = nil
        end
      else
        local noad = node.new(noad_t, 0)
        noad.nucleus = post_sup
        parent.sup = nil
        node.insert_after(new_head, current_tail, noad)
      end
    end

    if not mlist then
      mlist = node.new(sub_mlist_t)
    end

    mlist.head = new_head
    prev.sup = mlist
  end

  if parent ~= prev then
    node.remove(prev, parent)
    node.free(parent)
  end
  return parent_head, prev
end
