define([
  '../core',
  '../var/rnotwhite',
  '../var/strundefined',
  '../core/access',
  './support',
  '../selector',
], function(jQuery, rnotwhite, strundefined, access, support) {
  var nodeHook,
    boolHook,
    attrHandle = jQuery.expr.attrHandle;

  jQuery.fn.extend({
    attr: function(name, value) {
      return access(this, jQuery.attr, name, value, arguments.length > 1);
    },

    removeAttr: function(name) {
      return this.each(function() {
        jQuery.removeAttr(this, name);
      });
    },
  });

  jQuery.extend({
    attr: function(elem, name, value) {
      var hooks,
        ret,
        nType = elem.nodeType;

      // don't get/set attributes on text, comment and attribute nodes
      if (!elem || nType === 3 || nType === 8 || nType === 2) {
        return;
      }

      // Fallback to prop when attributes are not supported
      if (typeof elem.getAttribute === strundefined) {
        return jQuery.prop(elem, name, value);
      }

      // All attributes are lowercase
      // Grab necessary hook if one is defined
      if (nType !== 1 || !jQuery.isXMLDoc(elem)) {
        name = name.toLowerCase();
        hooks =
          jQuery.attrHooks[name] ||
          (jQuery.expr.match.bool.test(name) ? boolHook : nodeHook);
      }

      if (value !== undefined) {
        if (value === null) {
          jQuery.removeAttr(elem, name);
        } else if (
          hooks &&
          'set' in hooks &&
          (ret = hooks.set(elem, value, name)) !== undefined
        ) {
          return ret;
        } else {
          elem.setAttribute(name, value + '');
          return value;
        }
      } else if (
        hooks &&
        'get' in hooks &&
        (ret = hooks.get(elem, name)) !== null
      ) {
        return ret;
      } else {
        ret = jQuery.find.attr(elem, name);

        // Non-existent attributes return null, we normalize to undefined
        return ret == null ? undefined : ret;
      }
    },

    removeAttr: function(elem, value) {
      var name,
        propName,
        i = 0,
        attrNames = value && value.match(rnotwhite);

      if (attrNames && elem.nodeType === 1) {
        while ((name = attrNames[i++])) {
          propName = jQuery.propFix[name] || name;

          // Boolean attributes get special treatment (#10870)
          if (jQuery.expr.match.bool.test(name)) {
            // Set corresponding property to false
            elem[propName] = false;
          }

          elem.removeAttribute(name);
        }
      }
    },

    attrHooks: {
      type: {
        set: function(elem, value) {
          if (
            !support.radioValue &&
            value === 'radio' &&
            jQuery.nodeName(elem, 'input')
          ) {
            var val = elem.value;
            elem.setAttribute('type', value);
            if (val) {
              elem.value = val;
            }
            return value;
          }
        },
      },
    },
  });

  // Hooks for boolean attributes
  boolHook = {
    set: function(elem, value, name) {
      if (value === false) {
        // Remove boolean attributes when set to false
        jQuery.removeAttr(elem, name);
      } else {
        elem.setAttribute(name, name);
      }
      return name;
    },
  };
  jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g), function(i, name) {
    var getter = attrHandle[name] || jQuery.find.attr;

    attrHandle[name] = function(elem, name, isXML) {
      var ret, handle;
      if (!isXML) {
        // Avoid an infinite loop by temporarily removing this function from the getter
        handle = attrHandle[name];
        attrHandle[name] = ret;
        ret = getter(elem, name, isXML) != null ? name.toLowerCase() : null;
        attrHandle[name] = handle;
      }
      return ret;
    };
  });
});
