import { iphone, mobile } from "./environment";
import window from "./global/window";
import {
  applyInputValue,
  checkVal,
  clearOptionalTail,
  HandleNativePlaceholder,
  writeBuffer
} from "./inputHandling";
import { keys } from "./keycode.js";
import {
  caret,
  determineNewCaretPosition,
  getBuffer,
  getBufferTemplate,
  getLastValidPosition,
  isMask,
  resetMaskSet,
  seekNext,
  seekPrevious,
  translatePosition
} from "./positioning";
import { handleRemove, isComplete, isSelection, isValid } from "./validation";
import { getPlaceholder, getTest } from "./validation-tests";

export { EventHandlers };

var EventHandlers = {
  keyEvent: function (e, checkval, writeOut, strict, ndx) {
    const inputmask = this.inputmask,
      opts = inputmask.opts,
      $ = inputmask.dependencyLib,
      maskset = inputmask.maskset,
      input = this,
      $input = $(input),
      c = e.key,
      pos = caret.call(inputmask, input),
      kdResult = opts.onKeyDown.call(
        this,
        e,
        getBuffer.call(inputmask),
        pos,
        opts
      );
    if (kdResult !== undefined) return kdResult;

    // backspace, delete, and escape get special treatment
    if (
      c === keys.Backspace ||
      c === keys.Delete ||
      (iphone && c === keys.BACKSPACE_SAFARI) ||
      (e.ctrlKey && c === keys.x && !("oncut" in input))
    ) {
      // backspace/delete
      e.preventDefault(); // stop default action but allow propagation
      handleRemove.call(inputmask, input, c, pos);
      writeBuffer(
        input,
        getBuffer.call(inputmask, true),
        maskset.p,
        e,
        input.inputmask._valueGet() !== getBuffer.call(inputmask).join("")
      );
    } else if (c === keys.End || c === keys.PageDown) {
      // when END or PAGE_DOWN pressed set position at lastmatch
      e.preventDefault();
      const caretPos = seekNext.call(
        inputmask,
        getLastValidPosition.call(inputmask)
      );
      caret.call(
        inputmask,
        input,
        e.shiftKey ? pos.begin : caretPos,
        caretPos,
        true
      );
    } else if ((c === keys.Home && !e.shiftKey) || c === keys.PageUp) {
      // Home or page_up
      e.preventDefault();
      caret.call(inputmask, input, 0, e.shiftKey ? pos.begin : 0, true);
    } else if (
      ((opts.undoOnEscape && c === keys.Escape) ||
        (false && c === keys.z && e.ctrlKey)) &&
      e.altKey !== true
    ) {
      // escape && undo && #762
      checkVal(input, true, false, inputmask.undoValue.split(""));
      $input.trigger("click");
    } else if (
      c === keys.Insert &&
      !(e.shiftKey || e.ctrlKey) &&
      inputmask.userOptions.insertMode === undefined
    ) {
      // insert
      if (!isSelection.call(inputmask, pos)) {
        opts.insertMode = !opts.insertMode;
        caret.call(inputmask, input, pos.begin, pos.begin);
      } else opts.insertMode = !opts.insertMode;
    } else if (opts.tabThrough === true && c === keys.Tab) {
      if (e.shiftKey === true) {
        pos.end = seekPrevious.call(inputmask, pos.end, true);
        if (getTest.call(inputmask, pos.end - 1).match.static === true) {
          pos.end--;
        }
        pos.begin = seekPrevious.call(inputmask, pos.end, true);
        if (pos.begin >= 0 && pos.end > 0) {
          e.preventDefault();
          caret.call(inputmask, input, pos.begin, pos.end);
        }
      } else {
        pos.begin = seekNext.call(inputmask, pos.begin, true);
        pos.end = seekNext.call(inputmask, pos.begin, true);
        if (pos.end < maskset.maskLength) pos.end--;
        if (pos.begin <= maskset.maskLength) {
          e.preventDefault();
          caret.call(inputmask, input, pos.begin, pos.end);
        }
      }
    } else if (!e.shiftKey) {
      if (opts.insertModeVisual && opts.insertMode === false) {
        if (c === keys.ArrowRight) {
          setTimeout(function () {
            const caretPos = caret.call(inputmask, input);
            caret.call(inputmask, input, caretPos.begin);
          }, 0);
        } else if (c === keys.ArrowLeft) {
          setTimeout(function () {
            const caretPos = {
              begin: translatePosition.call(
                inputmask,
                input.inputmask.caretPos.begin
              ),
              end: translatePosition.call(
                inputmask,
                input.inputmask.caretPos.end
              )
            };
            if (inputmask.isRTL) {
              caret.call(
                inputmask,
                input,
                caretPos.begin + (caretPos.begin === maskset.maskLength ? 0 : 1)
              );
            } else {
              caret.call(
                inputmask,
                input,
                caretPos.begin - (caretPos.begin === 0 ? 0 : 1)
              );
            }
          }, 0);
        }
      } else {
        inputmask.keyEventHook === undefined || inputmask.keyEventHook(e);
      }
    }

    inputmask.isComposing = c == keys.Process || c == keys.Unidentified;
    inputmask.ignorable =
      c.length > 1 &&
      !(input.tagName.toLowerCase() === "textarea" && c == keys.Enter);
    return EventHandlers.keypressEvent.call(
      this,
      e,
      checkval,
      writeOut,
      strict,
      ndx
    );
  },
  keypressEvent: function (e, checkval, writeOut, strict, ndx) {
    const inputmask = this.inputmask || this,
      opts = inputmask.opts,
      $ = inputmask.dependencyLib,
      maskset = inputmask.maskset;

    let input = inputmask.el,
      $input = $(input),
      c = e.key;

    if (
      checkval !== true &&
      !(e.ctrlKey && e.altKey && !inputmask.ignorable) &&
      (e.ctrlKey || e.metaKey || inputmask.ignorable)
    ) {
      if (c === keys.Enter) {
        if (inputmask.undoValue !== inputmask._valueGet(true)) {
          inputmask.undoValue = inputmask._valueGet(true);
          // e.preventDefault();

          setTimeout(function () {
            $input.trigger("change");
          }, 0);
        }
      }
      // inputmask.skipInputEvent = true; //skip the input as otherwise the skipped char could be picked up for validation by the inputfallback
    } else if (c) {
      // special treat the decimal separator
      // if ((k === 44 || k === 46) && e.location === 3 && opts.radixPoint !== "") k = opts.radixPoint.charCodeAt(0);
      let pos = checkval
          ? {
              begin: ndx,
              end: ndx
            }
          : caret.call(inputmask, input),
        forwardPosition;

      // allow for character substitution
      if (!checkval) c = opts.substitutes[c] || c;
      maskset.writeOutBuffer = true;
      const valResult = isValid.call(
        inputmask,
        pos,
        c,
        strict,
        undefined,
        undefined,
        undefined,
        checkval
      );
      if (valResult !== false) {
        resetMaskSet.call(inputmask, true);
        forwardPosition =
          valResult.caret !== undefined
            ? valResult.caret
            : seekNext.call(
                inputmask,
                valResult.pos.begin ? valResult.pos.begin : valResult.pos
              );
        maskset.p = forwardPosition; // needed for checkval
      }

      forwardPosition =
        opts.numericInput && valResult.caret === undefined
          ? seekPrevious.call(inputmask, forwardPosition)
          : forwardPosition;
      if (writeOut !== false) {
        setTimeout(function () {
          opts.onKeyValidation.call(input, c, valResult);
        }, 0);
        if (maskset.writeOutBuffer && valResult !== false) {
          const buffer = getBuffer.call(inputmask);
          writeBuffer(input, buffer, forwardPosition, e, checkval !== true);
        }
      }

      e.preventDefault();

      if (checkval) {
        if (valResult !== false) valResult.forwardPosition = forwardPosition;
        return valResult;
      }
    }
  },
  pasteEvent: async function (e) {
    function handlePaste(
      inputmask,
      input,
      inputValue,
      pastedValue,
      onBeforePaste
    ) {
      let caretPos = caret.call(inputmask, input, undefined, undefined, true),
        valueBeforeCaret = inputValue.substr(0, caretPos.begin),
        valueAfterCaret = inputValue.substr(caretPos.end, inputValue.length);

      if (
        valueBeforeCaret ==
        (inputmask.isRTL
          ? getBufferTemplate.call(inputmask).slice().reverse()
          : getBufferTemplate.call(inputmask)
        )
          .slice(0, caretPos.begin)
          .join("")
      )
        valueBeforeCaret = "";
      if (
        valueAfterCaret ==
        (inputmask.isRTL
          ? getBufferTemplate.call(inputmask).slice().reverse()
          : getBufferTemplate.call(inputmask)
        )
          .slice(caretPos.end)
          .join("")
      )
        valueAfterCaret = "";

      pastedValue = valueBeforeCaret + pastedValue + valueAfterCaret;
      if (inputmask.isRTL && opts.numericInput !== true) {
        pastedValue = pastedValue.split("");
        for (const c of getBufferTemplate.call(inputmask)) {
          if (pastedValue[0] === c) pastedValue.shift();
        }
        pastedValue = pastedValue.reverse().join("");
      }

      let pasteValue = pastedValue;
      if (typeof onBeforePaste === "function") {
        pasteValue = onBeforePaste.call(inputmask, pasteValue, opts);
        if (pasteValue === false) {
          return false;
        }
        if (!pasteValue) {
          pasteValue = inputValue;
        }
      }
      checkVal(input, true, false, pasteValue.toString().split(""), e);
    }

    const input = this,
      inputmask = this.inputmask,
      opts = inputmask.opts;
    let inputValue = inputmask._valueGet(true),
      pastedValue;

    inputmask.skipInputEvent = true;
    if (e.clipboardData && e.clipboardData.getData) {
      pastedValue = e.clipboardData.getData("text/plain");
    } else if (window.clipboardData && window.clipboardData.getData) {
      // IE
      pastedValue = window.clipboardData.getData("Text");
    }
    handlePaste(inputmask, input, inputValue, pastedValue, opts.onBeforePaste);
    e.preventDefault();
  },
  inputFallBackEvent: function (e) {
    // fallback when keypress is not triggered
    const inputmask = this.inputmask,
      opts = inputmask.opts,
      $ = inputmask.dependencyLib;

    // console.log(e.inputType);

    function analyseChanges(inputValue, buffer, caretPos) {
      let frontPart = inputValue.substr(0, caretPos.begin).split(""),
        backPart = inputValue.substr(caretPos.begin).split(""),
        frontBufferPart = buffer.substr(0, caretPos.begin).split(""),
        backBufferPart = buffer.substr(caretPos.begin).split(""),
        fpl =
          frontPart.length >= frontBufferPart.length
            ? frontPart.length
            : frontBufferPart.length,
        bpl =
          backPart.length >= backBufferPart.length
            ? backPart.length
            : backBufferPart.length,
        bl,
        i,
        action = "",
        data = [],
        marker = "~",
        placeholder;

      // align buffers
      while (frontPart.length < fpl) frontPart.push(marker);
      while (frontBufferPart.length < fpl) frontBufferPart.push(marker);
      while (backPart.length < bpl) backPart.unshift(marker);
      while (backBufferPart.length < bpl) backBufferPart.unshift(marker);

      const newBuffer = frontPart.concat(backPart),
        oldBuffer = frontBufferPart.concat(backBufferPart);

      // console.log("N " + newBuffer);
      // console.log("O " + oldBuffer);

      for (i = 0, bl = newBuffer.length; i < bl; i++) {
        placeholder = getPlaceholder.call(
          inputmask,
          translatePosition.call(inputmask, i)
        );
        switch (action) {
          case "insertText":
            if (
              oldBuffer[i - 1] === newBuffer[i] &&
              caretPos.begin == newBuffer.length - 1
            ) {
              data.push(newBuffer[i]);
            }
            i = bl;
            break;
          case "insertReplacementText":
            if (newBuffer[i] === marker) {
              // extend selection
              caretPos.end++;
            } else {
              // breakout loop
              i = bl;
            }
            break;
          case "deleteContentBackward":
            if (newBuffer[i] === marker) {
              caretPos.end++;
            } else {
              // breakout loop
              i = bl;
            }
            break;
          default:
            if (newBuffer[i] !== oldBuffer[i]) {
              if (
                (newBuffer[i + 1] === marker ||
                  newBuffer[i + 1] === placeholder ||
                  newBuffer[i + 1] === undefined) &&
                ((oldBuffer[i] === placeholder &&
                  oldBuffer[i + 1] === marker) ||
                  oldBuffer[i] === marker)
              ) {
                // basic insert
                action = "insertText";
                data.push(newBuffer[i]);
                caretPos.begin--;
                caretPos.end--;
              } else if (
                oldBuffer[i + 1] === marker &&
                oldBuffer[i] === newBuffer[i + 1]
              ) {
                // insert between
                action = "insertText";
                data.push(newBuffer[i]);
                caretPos.begin--;
                caretPos.end--;
              } else if (
                newBuffer[i] !== placeholder &&
                newBuffer[i] !== marker &&
                (newBuffer[i + 1] === marker ||
                  (oldBuffer[i] !== newBuffer[i] &&
                    oldBuffer[i + 1] ===
                      newBuffer[i + 1])) /* single char replacement */
              ) {
                // replace selection
                action = "insertReplacementText";
                data.push(newBuffer[i]);
                caretPos.begin--;
              } else if (newBuffer[i] === marker) {
                // delete~backspace
                action = "deleteContentBackward";
                if (
                  isMask.call(
                    inputmask,
                    translatePosition.call(inputmask, i),
                    true
                  ) ||
                  oldBuffer[i] === opts.radixPoint
                )
                  caretPos.end++;
              } else {
                i = bl;
              }
            }
            break;
        }
      }

      return {
        action,
        data,
        caret: caretPos
      };
    }

    let input = this,
      inputValue = input.inputmask._valueGet(true),
      buffer = (
        inputmask.isRTL
          ? getBuffer.call(inputmask).slice().reverse()
          : getBuffer.call(inputmask)
      ).join(""),
      caretPos = caret.call(inputmask, input, undefined, undefined, true),
      changes;

    if (buffer !== inputValue) {
      changes = analyseChanges(inputValue, buffer, caretPos);
      if (
        (input.inputmask.shadowRoot || input.ownerDocument).activeElement !==
        input
      ) {
        input.focus();
      }
      writeBuffer(input, getBuffer.call(inputmask));
      caret.call(inputmask, input, caretPos.begin, caretPos.end, true);

      // Japanese IME hack #2662
      if (
        !mobile &&
        inputmask.skipNextInsert &&
        e.inputType === "insertText" &&
        changes.action === "insertText" &&
        inputmask.isComposing
      ) {
        return false;
      }
      if (
        e.inputType === "insertCompositionText" &&
        changes.action === "insertText" &&
        inputmask.isComposing
      ) {
        inputmask.skipNextInsert = true;
      } else {
        inputmask.skipNextInsert = false;
      }

      switch (changes.action) {
        case "insertText":
        case "insertReplacementText":
          changes.data.forEach(function (entry, ndx) {
            const keypress = new $.Event("keypress");
            keypress.key = entry;
            inputmask.ignorable = false; // make sure ignorable is ignored ;-)
            EventHandlers.keypressEvent.call(input, keypress);
          });
          setTimeout(function () {
            // #2195 trigger keyup to help some other plugins to track changes
            inputmask.$el.trigger("keyup");
          }, 0);
          break;
        case "deleteContentBackward":
          var keydown = new $.Event("keydown");
          keydown.key = keys.Backspace;
          EventHandlers.keyEvent.call(input, keydown);
          break;
        default:
          applyInputValue(input, inputValue);
          caret.call(inputmask, input, caretPos.begin, caretPos.end, true);
          break;
      }

      e.preventDefault();
    }
  },
  setValueEvent: function (e) {
    const inputmask = this.inputmask,
      $ = inputmask.dependencyLib;
    let input = this,
      value = e && e.detail ? e.detail[0] : arguments[1];

    if (value === undefined) {
      value = input.inputmask._valueGet(true);
    }

    applyInputValue(input, value, new $.Event("input"));

    if ((e.detail && e.detail[1] !== undefined) || arguments[2] !== undefined) {
      caret.call(inputmask, input, e.detail ? e.detail[1] : arguments[2]);
    }
  },
  focusEvent: function (e) {
    const inputmask = this.inputmask,
      opts = inputmask.opts,
      input = this,
      nptValue = inputmask && inputmask._valueGet();

    if (opts.showMaskOnFocus) {
      if (nptValue !== getBuffer.call(inputmask).join("")) {
        writeBuffer(
          input,
          getBuffer.call(inputmask),
          seekNext.call(inputmask, getLastValidPosition.call(inputmask))
        );
      } /* else if (mouseEnter === false) { //only executed on focus without mouseenter
					caret(input, seekNext(getLastValidPosition()));
				} */
    }
    if (
      opts.positionCaretOnTab === true &&
      inputmask.mouseEnter === false &&
      (!isComplete.call(inputmask, getBuffer.call(inputmask)) ||
        getLastValidPosition.call(inputmask) === -1)
    ) {
      EventHandlers.clickEvent.apply(input, [e, true]);
    }
    inputmask.undoValue = inputmask && inputmask._valueGet(true);
  },
  invalidEvent: function (e) {
    this.inputmask.validationEvent = true;
  },
  mouseleaveEvent: function () {
    const inputmask = this.inputmask,
      opts = inputmask.opts,
      input = this;
    inputmask.mouseEnter = false;
    if (
      opts.clearMaskOnLostFocus &&
      (input.inputmask.shadowRoot || input.ownerDocument).activeElement !==
        input
    ) {
      HandleNativePlaceholder(input, inputmask.originalPlaceholder);
    }
  },
  clickEvent: function (e, tabbed) {
    const inputmask = this.inputmask;
    inputmask.clicked++;

    const input = this;
    if (
      (input.inputmask.shadowRoot || input.ownerDocument).activeElement ===
      input
    ) {
      const newCaretPosition = determineNewCaretPosition.call(
        inputmask,
        caret.call(inputmask, input),
        tabbed
      );
      if (newCaretPosition !== undefined) {
        caret.call(inputmask, input, newCaretPosition);
      }
    }
  },
  cutEvent: function (e) {
    const inputmask = this.inputmask,
      maskset = inputmask.maskset,
      input = this,
      pos = caret.call(inputmask, input),
      // correct clipboardData
      clipData = inputmask.isRTL
        ? getBuffer.call(inputmask).slice(pos.end, pos.begin)
        : getBuffer.call(inputmask).slice(pos.begin, pos.end),
      clipDataText = inputmask.isRTL
        ? clipData.reverse().join("")
        : clipData.join("");
    if (window.navigator && window.navigator.clipboard)
      window.navigator.clipboard.writeText(clipDataText);
    else if (window.clipboardData && window.clipboardData.getData) {
      // IE
      window.clipboardData.setData("Text", clipDataText);
    }
    handleRemove.call(inputmask, input, keys.Delete, pos);
    writeBuffer(
      input,
      getBuffer.call(inputmask),
      maskset.p,
      e,
      inputmask.undoValue !== inputmask._valueGet(true)
    );
  },
  blurEvent: function (e) {
    const inputmask = this.inputmask,
      opts = inputmask.opts,
      $ = inputmask.dependencyLib;
    inputmask.clicked = 0;

    const $input = $(this),
      input = this;
    if (input.inputmask) {
      HandleNativePlaceholder(input, inputmask.originalPlaceholder);
      let nptValue = input.inputmask._valueGet(),
        buffer = getBuffer.call(inputmask).slice();

      if (nptValue !== "") {
        if (opts.clearMaskOnLostFocus) {
          if (
            getLastValidPosition.call(inputmask) === -1 &&
            nptValue === getBufferTemplate.call(inputmask).join("")
          ) {
            buffer = [];
          } else {
            // clearout optional tail of the mask
            clearOptionalTail.call(inputmask, buffer);
          }
        }
        if (isComplete.call(inputmask, buffer) === false) {
          setTimeout(function () {
            $input.trigger("incomplete");
          }, 0);
          if (opts.clearIncomplete) {
            resetMaskSet.call(inputmask, false);
            if (opts.clearMaskOnLostFocus) {
              buffer = [];
            } else {
              buffer = getBufferTemplate.call(inputmask).slice();
            }
          }
        }

        writeBuffer(input, buffer, undefined, e);
      }

      nptValue = inputmask._valueGet(true);
      if (inputmask.undoValue !== nptValue) {
        if (
          nptValue != "" ||
          inputmask.undoValue != getBufferTemplate.call(inputmask).join("") ||
          (inputmask.undoValue == getBufferTemplate.call(inputmask).join("") &&
            inputmask.maskset.validPositions.length > 0)
        ) {
          inputmask.undoValue = nptValue;
          $input.trigger("change");
        }
      }
    }
  },
  mouseenterEvent: function () {
    const inputmask = this.inputmask,
      { showMaskOnHover } = inputmask.opts,
      input = this;
    inputmask.mouseEnter = true;
    if (
      (input.inputmask.shadowRoot || input.ownerDocument).activeElement !==
      input
    ) {
      const bufferTemplate = (
        inputmask.isRTL
          ? getBufferTemplate.call(inputmask).slice().reverse()
          : getBufferTemplate.call(inputmask)
      ).join("");
      if (showMaskOnHover) {
        HandleNativePlaceholder(input, bufferTemplate);
      }
    }
  },
  submitEvent: function () {
    // trigger change on submit if any
    const inputmask = this.inputmask,
      opts = inputmask.opts;

    if (inputmask.undoValue !== inputmask._valueGet(true)) {
      inputmask.$el.trigger("change");
    }
    if (
      /* opts.clearMaskOnLostFocus && */ getLastValidPosition.call(
        inputmask
      ) === -1 &&
      inputmask._valueGet &&
      inputmask._valueGet() === getBufferTemplate.call(inputmask).join("")
    ) {
      inputmask._valueSet(""); // clear masktemplete on submit and still has focus
    }
    if (
      opts.clearIncomplete &&
      isComplete.call(inputmask, getBuffer.call(inputmask)) === false
    ) {
      inputmask._valueSet("");
    }
    if (opts.removeMaskOnSubmit) {
      inputmask._valueSet(inputmask.unmaskedvalue(), true);
      setTimeout(function () {
        writeBuffer(inputmask.el, getBuffer.call(inputmask));
      }, 0);
    }
  },
  resetEvent: function () {
    const inputmask = this.inputmask;

    inputmask.refreshValue = true; // indicate a forced refresh when there is a call to the value before leaving the triggering event fn
    setTimeout(function () {
      applyInputValue(inputmask.el, inputmask._valueGet(true));
    }, 0);
  }
};
