import { extend } from '../../vue';
import { NAME_LINK } from '../../constants/components';
import { EVENT_NAME_CLICK } from '../../constants/events';
import { PROP_TYPE_STRING, PROP_TYPE_BOOLEAN, PROP_TYPE_ARRAY_STRING, PROP_TYPE_OBJECT_STRING } from '../../constants/props';
import { concat } from '../../utils/array';
import { isTag, attemptFocus, attemptBlur } from '../../utils/dom';
import { getRootEventName, stopEvent } from '../../utils/events';
import { isBoolean, isUndefined, isEvent, isFunction } from '../../utils/inspect';
import { sortKeys, omit } from '../../utils/object';
import { makeProp, makePropsConfigurable, pluckProps } from '../../utils/props';
import { computeTag, isRouterLink, computeRel, computeHref } from '../../utils/router';
import { attrsMixin } from '../../mixins/attrs';
import { listenOnRootMixin } from '../../mixins/listen-on-root';
import { listenersMixin } from '../../mixins/listeners';
import { normalizeSlotMixin } from '../../mixins/normalize-slot';

// --- Constants ---

const ROOT_EVENT_NAME_CLICKED = getRootEventName(NAME_LINK, 'clicked');

// --- Props ---

// `<router-link>` specific props
const routerLinkProps = {
  activeClass: makeProp(PROP_TYPE_STRING),
  append: makeProp(PROP_TYPE_BOOLEAN, false),
  event: makeProp(PROP_TYPE_ARRAY_STRING),
  exact: makeProp(PROP_TYPE_BOOLEAN, false),
  exactActiveClass: makeProp(PROP_TYPE_STRING),
  exactPath: makeProp(PROP_TYPE_BOOLEAN, false),
  exactPathActiveClass: makeProp(PROP_TYPE_STRING),
  replace: makeProp(PROP_TYPE_BOOLEAN, false),
  routerTag: makeProp(PROP_TYPE_STRING),
  to: makeProp(PROP_TYPE_OBJECT_STRING)
};

// `<nuxt-link>` specific props
const nuxtLinkProps = {
  noPrefetch: makeProp(PROP_TYPE_BOOLEAN, false),
  // Must be `null` to fall back to the value defined in the
  // `nuxt.config.js` configuration file for `router.prefetchLinks`
  // We convert `null` to `undefined`, so that Nuxt.js will use the
  // compiled default
  // Vue treats `undefined` as default of `false` for Boolean props,
  // so we must set it as `null` here to be a true tri-state prop
  prefetch: makeProp(PROP_TYPE_BOOLEAN, null)
};

// All `<b-link>` props
const props = makePropsConfigurable(sortKeys({
  ...nuxtLinkProps,
  ...routerLinkProps,
  active: makeProp(PROP_TYPE_BOOLEAN, false),
  disabled: makeProp(PROP_TYPE_BOOLEAN, false),
  href: makeProp(PROP_TYPE_STRING),
  // Must be `null` if no value provided
  rel: makeProp(PROP_TYPE_STRING, null),
  // To support 3rd party router links based on `<router-link>` (i.e. `g-link` for Gridsome)
  // Default is to auto choose between `<router-link>` and `<nuxt-link>`
  // Gridsome doesn't provide a mechanism to auto detect and has caveats
  // such as not supporting FQDN URLs or hash only URLs
  routerComponentName: makeProp(PROP_TYPE_STRING),
  target: makeProp(PROP_TYPE_STRING, '_self')
}), NAME_LINK);

// --- Main component ---

// @vue/component
const BLink = /*#__PURE__*/extend({
  name: NAME_LINK,
  // Mixin order is important!
  mixins: [attrsMixin, listenersMixin, listenOnRootMixin, normalizeSlotMixin],
  inheritAttrs: false,
  props,
  computed: {
    computedTag() {
      // We don't pass `this` as the first arg as we need reactivity of the props
      const {
        to,
        disabled,
        routerComponentName
      } = this;
      return computeTag({
        to,
        disabled,
        routerComponentName
      }, this);
    },
    isRouterLink() {
      return isRouterLink(this.computedTag);
    },
    computedRel() {
      // We don't pass `this` as the first arg as we need reactivity of the props
      const {
        target,
        rel
      } = this;
      return computeRel({
        target,
        rel
      });
    },
    computedHref() {
      // We don't pass `this` as the first arg as we need reactivity of the props
      const {
        to,
        href
      } = this;
      return computeHref({
        to,
        href
      }, this.computedTag);
    },
    computedProps() {
      const {
        event,
        prefetch,
        routerTag
      } = this;
      return this.isRouterLink ? {
        ...pluckProps(omit({
          ...routerLinkProps,
          ...(this.computedTag === 'nuxt-link' ? nuxtLinkProps : {})
        }, ['event', 'prefetch', 'routerTag']), this),
        // Only add these props, when actually defined
        ...(event ? {
          event
        } : {}),
        ...(isBoolean(prefetch) ? {
          prefetch
        } : {}),
        // Pass `router-tag` as `tag` prop
        ...(routerTag ? {
          tag: routerTag
        } : {})
      } : {};
    },
    computedAttrs() {
      const {
        bvAttrs,
        computedHref: href,
        computedRel: rel,
        disabled,
        target,
        routerTag,
        isRouterLink
      } = this;
      return {
        ...bvAttrs,
        // If `href` attribute exists on `<router-link>` (even `undefined` or `null`)
        // it fails working on SSR, so we explicitly add it here if needed
        // (i.e. if `computedHref` is truthy)
        ...(href ? {
          href
        } : {}),
        // We don't render `rel` or `target` on non link tags when using `vue-router`
        ...(isRouterLink && routerTag && !isTag(routerTag, 'a') ? {} : {
          rel,
          target
        }),
        tabindex: disabled ? '-1' : isUndefined(bvAttrs.tabindex) ? null : bvAttrs.tabindex,
        'aria-disabled': disabled ? 'true' : null
      };
    },
    computedListeners() {
      return {
        // Transfer all listeners (native) to the root element
        ...this.bvListeners,
        // We want to overwrite any click handler since our callback
        // will invoke the user supplied handler(s) if `!this.disabled`
        click: this.onClick
      };
    }
  },
  methods: {
    onClick(event) {
      const eventIsEvent = isEvent(event);
      const isRouterLink = this.isRouterLink;
      const suppliedHandler = this.bvListeners.click;
      if (eventIsEvent && this.disabled) {
        // Stop event from bubbling up
        // Kill the event loop attached to this specific `EventTarget`
        // Needed to prevent `vue-router` for doing its thing
        stopEvent(event, {
          immediatePropagation: true
        });
      } else {
        // Router links do not emit instance `click` events, so we
        // add in an `$emit('click', event)` on its Vue instance
        //
        // seems not to be required for Vue3 compat build
        /* istanbul ignore next: difficult to test, but we know it works */
        if (isRouterLink) {
          var _event$currentTarget$;
          (_event$currentTarget$ = event.currentTarget.__vue__) === null || _event$currentTarget$ === void 0 ? void 0 : _event$currentTarget$.$emit(EVENT_NAME_CLICK, event);
        }
        // Call the suppliedHandler(s), if any provided
        concat(suppliedHandler).filter(h => isFunction(h)).forEach(handler => {
          handler(...arguments);
        });
        // Emit the global `$root` click event
        this.emitOnRoot(ROOT_EVENT_NAME_CLICKED, event);
        // TODO: Remove deprecated 'clicked::link' event with next major release
        this.emitOnRoot('clicked::link', event);
      }
      // Stop scroll-to-top behavior or navigation on
      // regular links when href is just '#'
      if (eventIsEvent && !isRouterLink && this.computedHref === '#') {
        stopEvent(event, {
          propagation: false
        });
      }
    },
    focus() {
      attemptFocus(this.$el);
    },
    blur() {
      attemptBlur(this.$el);
    }
  },
  render(h) {
    const {
      active,
      disabled
    } = this;
    return h(this.computedTag, {
      class: {
        active,
        disabled
      },
      attrs: this.computedAttrs,
      props: this.computedProps,
      // We must use `nativeOn` for `<router-link>`/`<nuxt-link>` instead of `on`
      [this.isRouterLink ? 'nativeOn' : 'on']: this.computedListeners
    }, this.normalizeSlot());
  }
});

export { BLink, nuxtLinkProps, props, routerLinkProps };
