import { createDirective, client, noop, leftClick, addEvt, preventDraggable, position, stopAndPrevent, cleanEvt, ref, computed, watch, getCurrentInstance, h, Transition, getNormalizedVNodes, hSlot, nextTick, KeepAlive } from './__federation_expose_Modules-a9c6a6fc.js';
import { getModifierDirections, shouldStart } from './touch-27fc22f3.js';
import { clearSelection } from './selection-d249e9bd.js';

function parseArg (arg) {
  // delta (min velocity -- dist / time)
  // mobile min distance on first move
  // desktop min distance until deciding if it's a swipe or not
  const data = [ 0.06, 6, 50 ];

  if (typeof arg === 'string' && arg.length) {
    arg.split(':').forEach((val, index) => {
      const v = parseFloat(val);
      v && (data[ index ] = v);
    });
  }

  return data
}

const TouchSwipe = createDirective({
      name: 'touch-swipe',

      beforeMount (el, { value, arg, modifiers }) {
        // early return, we don't need to do anything
        if (modifiers.mouse !== true && client.has.touch !== true) {
          return
        }

        const mouseCapture = modifiers.mouseCapture === true ? 'Capture' : '';

        const ctx = {
          handler: value,
          sensitivity: parseArg(arg),
          direction: getModifierDirections(modifiers),

          noop,

          mouseStart (evt) {
            if (shouldStart(evt, ctx) && leftClick(evt)) {
              addEvt(ctx, 'temp', [
                [ document, 'mousemove', 'move', `notPassive${ mouseCapture }` ],
                [ document, 'mouseup', 'end', 'notPassiveCapture' ]
              ]);
              ctx.start(evt, true);
            }
          },

          touchStart (evt) {
            if (shouldStart(evt, ctx)) {
              const target = evt.target;
              addEvt(ctx, 'temp', [
                [ target, 'touchmove', 'move', 'notPassiveCapture' ],
                [ target, 'touchcancel', 'end', 'notPassiveCapture' ],
                [ target, 'touchend', 'end', 'notPassiveCapture' ]
              ]);
              ctx.start(evt);
            }
          },

          start (evt, mouseEvent) {
            client.is.firefox === true && preventDraggable(el, true);

            const pos = position(evt);

            ctx.event = {
              x: pos.left,
              y: pos.top,
              time: Date.now(),
              mouse: mouseEvent === true,
              dir: false
            };
          },

          move (evt) {
            if (ctx.event === void 0) {
              return
            }

            if (ctx.event.dir !== false) {
              stopAndPrevent(evt);
              return
            }

            const time = Date.now() - ctx.event.time;

            if (time === 0) {
              return
            }

            const
              pos = position(evt),
              distX = pos.left - ctx.event.x,
              absX = Math.abs(distX),
              distY = pos.top - ctx.event.y,
              absY = Math.abs(distY);

            if (ctx.event.mouse !== true) {
              if (absX < ctx.sensitivity[ 1 ] && absY < ctx.sensitivity[ 1 ]) {
                ctx.end(evt);
                return
              }
            }
            else if (absX < ctx.sensitivity[ 2 ] && absY < ctx.sensitivity[ 2 ]) {
              return
            }

            const
              velX = absX / time,
              velY = absY / time;

            if (
              ctx.direction.vertical === true
              && absX < absY
              && absX < 100
              && velY > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = distY < 0 ? 'up' : 'down';
            }

            if (
              ctx.direction.horizontal === true
              && absX > absY
              && absY < 100
              && velX > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = distX < 0 ? 'left' : 'right';
            }

            if (
              ctx.direction.up === true
              && absX < absY
              && distY < 0
              && absX < 100
              && velY > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = 'up';
            }

            if (
              ctx.direction.down === true
              && absX < absY
              && distY > 0
              && absX < 100
              && velY > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = 'down';
            }

            if (
              ctx.direction.left === true
              && absX > absY
              && distX < 0
              && absY < 100
              && velX > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = 'left';
            }

            if (
              ctx.direction.right === true
              && absX > absY
              && distX > 0
              && absY < 100
              && velX > ctx.sensitivity[ 0 ]
            ) {
              ctx.event.dir = 'right';
            }

            if (ctx.event.dir !== false) {
              stopAndPrevent(evt);

              if (ctx.event.mouse === true) {
                document.body.classList.add('no-pointer-events--children');
                document.body.classList.add('non-selectable');
                clearSelection();

                ctx.styleCleanup = withDelay => {
                  ctx.styleCleanup = void 0;

                  document.body.classList.remove('non-selectable');

                  const remove = () => {
                    document.body.classList.remove('no-pointer-events--children');
                  };

                  if (withDelay === true) { setTimeout(remove, 50); }
                  else { remove(); }
                };
              }

              ctx.handler({
                evt,
                touch: ctx.event.mouse !== true,
                mouse: ctx.event.mouse,
                direction: ctx.event.dir,
                duration: time,
                distance: {
                  x: absX,
                  y: absY
                }
              });
            }
            else {
              ctx.end(evt);
            }
          },

          end (evt) {
            if (ctx.event === void 0) {
              return
            }

            cleanEvt(ctx, 'temp');
            client.is.firefox === true && preventDraggable(el, false);
            ctx.styleCleanup !== void 0 && ctx.styleCleanup(true);
            evt !== void 0 && ctx.event.dir !== false && stopAndPrevent(evt);

            ctx.event = void 0;
          }
        };

        el.__qtouchswipe = ctx;

        if (modifiers.mouse === true) {
          // account for UMD too where modifiers will be lowercased to work
          const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true
            ? 'Capture'
            : '';

          addEvt(ctx, 'main', [
            [ el, 'mousedown', 'mouseStart', `passive${ capture }` ]
          ]);
        }

        client.has.touch === true && addEvt(ctx, 'main', [
          [ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ],
          [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll)
        ]);
      },

      updated (el, bindings) {
        const ctx = el.__qtouchswipe;

        if (ctx !== void 0) {
          if (bindings.oldValue !== bindings.value) {
            typeof bindings.value !== 'function' && ctx.end();
            ctx.handler = bindings.value;
          }

          ctx.direction = getModifierDirections(bindings.modifiers);
        }
      },

      beforeUnmount (el) {
        const ctx = el.__qtouchswipe;

        if (ctx !== void 0) {
          cleanEvt(ctx, 'main');
          cleanEvt(ctx, 'temp');

          client.is.firefox === true && preventDraggable(el, false);
          ctx.styleCleanup !== void 0 && ctx.styleCleanup();

          delete el.__qtouchswipe;
        }
      }
    }
);

function useCache () {
  const cache = new Map();

  return {
    getCache: function (key, obj) {
        return cache[ key ] === void 0
          ? (cache[ key ] = obj)
          : cache[ key ]
      },

    getCacheWithFn: function (key, fn) {
        return cache[ key ] === void 0
          ? (cache[ key ] = fn())
          : cache[ key ]
      }
  }
}

const usePanelChildProps = {
  name: { required: true },
  disable: Boolean
};

const PanelWrapper = {
  setup (_, { slots }) {
    return () => h('div', {
      class: 'q-panel scroll',
      role: 'tabpanel'
    }, hSlot(slots.default))
  }
};

const usePanelProps = {
  modelValue: {
    required: true
  },

  animated: Boolean,
  infinite: Boolean,
  swipeable: Boolean,
  vertical: Boolean,

  transitionPrev: String,
  transitionNext: String,
  transitionDuration: {
    type: [ String, Number ],
    default: 300
  },

  keepAlive: Boolean,
  keepAliveInclude: [ String, Array, RegExp ],
  keepAliveExclude: [ String, Array, RegExp ],
  keepAliveMax: Number
};

const usePanelEmits = [ 'update:modelValue', 'beforeTransition', 'transition' ];

function usePanel () {
  const { props, emit, proxy } = getCurrentInstance();
  const { getCacheWithFn } = useCache();

  let panels, forcedPanelTransition;

  const panelIndex = ref(null);
  const panelTransition = ref(null);

  function onSwipe (evt) {
    const dir = props.vertical === true ? 'up' : 'left';
    goToPanelByOffset((proxy.$q.lang.rtl === true ? -1 : 1) * (evt.direction === dir ? 1 : -1));
  }

  const panelDirectives = computed(() => {
    // if props.swipeable
    return [ [
      TouchSwipe,
      onSwipe,
      void 0,
      {
        horizontal: props.vertical !== true,
        vertical: props.vertical,
        mouse: true
      }
    ] ]
  });

  const transitionPrev = computed(() =>
    props.transitionPrev || `slide-${ props.vertical === true ? 'down' : 'right' }`
  );

  const transitionNext = computed(() =>
    props.transitionNext || `slide-${ props.vertical === true ? 'up' : 'left' }`
  );

  const transitionStyle = computed(
    () => `--q-transition-duration: ${ props.transitionDuration }ms`
  );

  const contentKey = computed(() => (
    typeof props.modelValue === 'string' || typeof props.modelValue === 'number'
      ? props.modelValue
      : String(props.modelValue)
  ));

  const keepAliveProps = computed(() => ({
    include: props.keepAliveInclude,
    exclude: props.keepAliveExclude,
    max: props.keepAliveMax
  }));

  const needsUniqueKeepAliveWrapper = computed(() =>
    props.keepAliveInclude !== void 0
    || props.keepAliveExclude !== void 0
  );

  watch(() => props.modelValue, (newVal, oldVal) => {
    const index = isValidPanelName(newVal) === true
      ? getPanelIndex(newVal)
      : -1;

    if (forcedPanelTransition !== true) {
      updatePanelTransition(
        index === -1 ? 0 : (index < getPanelIndex(oldVal) ? -1 : 1)
      );
    }

    if (panelIndex.value !== index) {
      panelIndex.value = index;
      emit('beforeTransition', newVal, oldVal);
      nextTick(() => {
        emit('transition', newVal, oldVal);
      });
    }
  });

  function nextPanel () { goToPanelByOffset(1); }
  function previousPanel () { goToPanelByOffset(-1); }

  function goToPanel (name) {
    emit('update:modelValue', name);
  }

  function isValidPanelName (name) {
    return name !== void 0 && name !== null && name !== ''
  }

  function getPanelIndex (name) {
    return panels.findIndex(panel => {
      return panel.props.name === name
        && panel.props.disable !== ''
        && panel.props.disable !== true
    })
  }

  function getEnabledPanels () {
    return panels.filter(panel => {
      return panel.props.disable !== ''
        && panel.props.disable !== true
    })
  }

  function updatePanelTransition (direction) {
    const val = direction !== 0 && props.animated === true && panelIndex.value !== -1
      ? 'q-transition--' + (direction === -1 ? transitionPrev.value : transitionNext.value)
      : null;

    if (panelTransition.value !== val) {
      panelTransition.value = val;
    }
  }

  function goToPanelByOffset (direction, startIndex = panelIndex.value) {
    let index = startIndex + direction;

    while (index > -1 && index < panels.length) {
      const opt = panels[ index ];

      if (
        opt !== void 0
        && opt.props.disable !== ''
        && opt.props.disable !== true
      ) {
        updatePanelTransition(direction);
        forcedPanelTransition = true;
        emit('update:modelValue', opt.props.name);
        setTimeout(() => {
          forcedPanelTransition = false;
        });
        return
      }

      index += direction;
    }

    if (props.infinite === true && panels.length > 0 && startIndex !== -1 && startIndex !== panels.length) {
      goToPanelByOffset(direction, direction === -1 ? panels.length : -1);
    }
  }

  function updatePanelIndex () {
    const index = getPanelIndex(props.modelValue);

    if (panelIndex.value !== index) {
      panelIndex.value = index;
    }

    return true
  }

  function getPanelContentChild () {
    const panel = isValidPanelName(props.modelValue) === true
      && updatePanelIndex()
      && panels[ panelIndex.value ];

    return props.keepAlive === true
      ? [
          h(KeepAlive, keepAliveProps.value, [
            h(
              needsUniqueKeepAliveWrapper.value === true
                ? getCacheWithFn(contentKey.value, () => ({ ...PanelWrapper, name: contentKey.value }))
                : PanelWrapper,
              { key: contentKey.value, style: transitionStyle.value },
              () => panel
            )
          ])
        ]
      : [
          h('div', {
            class: 'q-panel scroll',
            style: transitionStyle.value,
            key: contentKey.value,
            role: 'tabpanel'
          }, [ panel ])
        ]
  }

  function getPanelContent () {
    if (panels.length === 0) {
      return
    }

    return props.animated === true
      ? [ h(Transition, { name: panelTransition.value }, getPanelContentChild) ]
      : getPanelContentChild()
  }

  function updatePanelsList (slots) {
    panels = getNormalizedVNodes(
      hSlot(slots.default, [])
    ).filter(
      panel => panel.props !== null
        && panel.props.slot === void 0
        && isValidPanelName(panel.props.name) === true
    );

    return panels.length
  }

  function getPanels () {
    return panels
  }

  // expose public methods
  Object.assign(proxy, {
    next: nextPanel,
    previous: previousPanel,
    goTo: goToPanel
  });

  return {
    panelIndex,
    panelDirectives,

    updatePanelsList,
    updatePanelIndex,

    getPanelContent,
    getEnabledPanels,
    getPanels,

    isValidPanelName,

    keepAliveProps,
    needsUniqueKeepAliveWrapper,

    goToPanelByOffset,
    goToPanel,

    nextPanel,
    previousPanel
  }
}

export { usePanel, usePanelChildProps, usePanelEmits, usePanelProps };
