import {
  createStyleMixin,
  draggableMixin,
  createProxy,
  proxyInputMixin,
  componentNameValidationMixin,
  externalInputMixin,
} from '../../mixins';
import { compose } from '../../helpers';
import { getComponentName } from '../../helpers/componentHelpers';
import passwordDraggableMixin from './passwordDraggableMixin';
import {
  resetInputLabelPosition,
  resetInputCursor,
  useHostExternalPadding,
  inputFloatingLabelStyle,
} from '../../helpers/themeHelpers/resetHelpers';

export const componentName = getComponentName('password');

const customMixin = (superclass) =>
  class PasswordFieldMixinClass extends superclass {
    static get observedAttributes() {
      return ['manual-visibility-toggle'];
    }

    get manualToggleVisibility() {
      return this.getAttribute('manual-visibility-toggle') === 'true';
    }

    init() {
      super.init?.();
      this.handleCaretOnVisibilityChange();
      this.origSetPasswordVisible = this.baseElement._setPasswordVisible;
      this.origSetFocused = this.baseElement._setFocused;
      this.baseElement._setFocused = this.setFocus.bind(this);

      this.initExternalInput();
    }

    initExternalInput() {
      const externalInput = this.createExternalInput();
      if (externalInput) {
        this.handlePasswordVisibility(externalInput);
      }
    }

    get caretPosition() {
      return this.value?.length || 0;
    }

    handleCaretOnVisibilityChange() {
      const origTogglePasswordVisibility = this.baseElement._togglePasswordVisibility.bind(
        this.baseElement
      );
      this.baseElement._togglePasswordVisibility = () => {
        setTimeout(() => {
          origTogglePasswordVisibility();
          this.inputElement.setSelectionRange(this.caretPosition, this.caretPosition);
          return false;
        });
      };
    }

    // We use `manual-visibility-toggle` to to toggle the password's visibility
    // even if the input field is focused-out. However, on focusout - Vaadin resets
    // password visibility. Since we don't want to override Vaadin's native setFocus
    // function (which is part of their FocusMixin).
    // So, here we override the part of logic that runs within Vaadin's setFocus that reports
    // the password visibility state. This logic is part of our `manual-visibility-toggle` only.
    setFocus(focused) {
      if (this.manualToggleVisibility) {
        this.baseElement._setPasswordVisible = () => {};
      }
      this.origSetFocused.call(this.baseElement, focused);
      this.baseElement._setPasswordVisible = this.origSetPasswordVisible.bind(this.baseElement);
    }

    resetPasswordVisibilityToggle() {
      this.baseElement._setPasswordVisible = this.origSetPasswordVisible;
      this.baseElement._setFocused = this.origSetFocus;
    }

    attributeChangedCallback(attrName, oldValue, newValue) {
      super.attributeChangedCallback?.(attrName, oldValue, newValue);
    }

    // override vaadin's password visibility toggle.
    // we need this override in order to to resolve the external input `focus` race condition,
    // which is caused due to the focus sync between the two inputs.
    handlePasswordVisibility(externalInput) {
      // disable vaadin's `__boundRevealButtonMouseDown` mouse-down event lisetener
      const origBoundRevealButtonMouseDown = this.baseElement.__boundRevealButtonMouseDown;
      this.baseElement
        .querySelector('vaadin-password-field-button')
        .removeEventListener('mousedown', origBoundRevealButtonMouseDown);

      // disable vaadin's `_passwordVisibleChanged` observer
      this.baseElement._passwordVisibleChanged = () => {};

      // override vaadin's `_togglePasswordVisibility`
      this.baseElement._togglePasswordVisibility = () => {
        const currVisibility = externalInput.getAttribute('type');
        if (currVisibility === 'password') {
          this.showPasswordVisibility(externalInput);
        } else {
          this.hidePasswordVisibility(externalInput);
        }
      };
    }

    showPasswordVisibility(input) {
      // handle input element's type
      input.setAttribute('type', 'text');
      // handle vaadin's `password-visible` attribute
      this.setAttribute('password-visible', 'true');
    }

    hidePasswordVisibility(input) {
      // handle input element's type
      input.setAttribute('type', 'password');
      // handle vaadin's `password-visible` attribute
      this.setAttribute('password-visible', 'false');
    }
  };

const {
  host,
  inputField,
  inputElement,
  inputElementPlaceholder,
  revealButtonContainer,
  revealButtonIcon,
  label,
  requiredIndicator,
  errorMessage,
  helperText,
} = {
  host: { selector: () => ':host' },
  inputField: { selector: '::part(input-field)' },
  inputElement: { selector: '> input' },
  inputElementPlaceholder: { selector: () => ':host input:placeholder-shown' },
  revealButtonContainer: { selector: '::part(reveal-button)' },
  revealButtonIcon: { selector: '::part(reveal-button)::before' },
  label: { selector: '::part(label)' },
  requiredIndicator: { selector: '[required]::part(required-indicator)::after' },
  helperText: { selector: '::part(helper-text)' },
  errorMessage: { selector: '::part(error-message)' },
};

export const PasswordClass = compose(
  createStyleMixin({
    mappings: {
      hostWidth: { ...host, property: 'width' },
      hostMinWidth: { ...host, property: 'min-width' },
      hostDirection: { ...host, property: 'direction' },
      fontSize: [{}, host],
      fontFamily: [label, inputField, errorMessage, helperText],
      inputHeight: { ...inputField, property: 'height' },
      inputHorizontalPadding: [
        { ...inputElement, property: 'padding-left' },
        { ...inputElement, property: 'padding-right' },
      ],
      inputBackgroundColor: { ...inputField, property: 'background-color' },

      inputBorderStyle: { ...inputField, property: 'border-style' },
      inputBorderWidth: { ...inputField, property: 'border-width' },
      inputBorderColor: { ...inputField, property: 'border-color' },
      inputBorderRadius: { ...inputField, property: 'border-radius' },

      inputOutlineColor: { ...inputField, property: 'outline-color' },
      inputOutlineStyle: { ...inputField, property: 'outline-style' },
      inputOutlineOffset: { ...inputField, property: 'outline-offset' },
      inputOutlineWidth: { ...inputField, property: 'outline-width' },

      labelFontSize: { ...label, property: 'font-size' },
      labelFontWeight: { ...label, property: 'font-weight' },
      labelTextColor: [
        { ...label, property: 'color' },
        { ...label, property: '-webkit-text-fill-color' },
        { ...requiredIndicator, property: 'color' },
        { ...requiredIndicator, property: '-webkit-text-fill-color' },
      ],
      labelRequiredIndicator: { ...requiredIndicator, property: 'content' },
      errorMessageTextColor: { ...errorMessage, property: 'color' },
      errorMessageIcon: { ...errorMessage, property: 'background-image' },
      errorMessageIconSize: { ...errorMessage, property: 'background-size' },
      errorMessageIconPadding: { ...errorMessage, property: 'padding-inline-start' },
      errorMessageIconRepeat: { ...errorMessage, property: 'background-repeat' },
      errorMessageIconPosition: { ...errorMessage, property: 'background-position' },
      errorMessageFontSize: { ...errorMessage, property: 'font-size' },

      inputPlaceholderTextColor: [
        { ...inputElementPlaceholder, property: 'color' },
        { selector: () => ':host ::slotted(input:placeholder-shown)', property: 'color' },
      ],
      inputValueTextColor: [
        { ...inputElement, property: 'color' },
        { selector: () => ':host ::slotted(input)', property: 'color' },
      ],

      revealButtonOffset: [
        { ...revealButtonContainer, property: 'margin-right' },
        { ...revealButtonContainer, property: 'margin-left' },
      ],
      revealButtonSize: { ...revealButtonContainer, property: 'font-size' },
      revealButtonColor: { ...revealButtonIcon, property: 'color' },

      labelPosition: { ...label, property: 'position' },
      labelTopPosition: { ...label, property: 'top' },
      labelHorizontalPosition: [
        { ...label, property: 'left' },
        { ...label, property: 'right' },
      ],
      inputTransformY: { ...label, property: 'transform' },
      inputTransition: { ...label, property: 'transition' },
      marginInlineStart: { ...label, property: 'margin-inline-start' },
      placeholderOpacity: [
        { selector: '> input:placeholder-shown', property: 'opacity' },
        { ...inputElement, property: 'opacity' },
      ],
      inputVerticalAlignment: { ...inputField, property: 'align-items' },
      valueInputHeight: { ...inputElement, property: 'height' },
    },
  }),
  draggableMixin,
  externalInputMixin({
    inputType: 'password',
    inputName: 'password',
    includeAttrs: ['disabled', 'readonly', 'pattern', 'type', 'autocomplete'],
    noBlurDispatch: true,
  }),
  proxyInputMixin({ proxyProps: ['value', 'selectionStart'], useProxyTargets: true }),
  componentNameValidationMixin,
  passwordDraggableMixin,
  customMixin
)(
  createProxy({
    slots: ['', 'suffix'],
    wrappedEleName: 'vaadin-password-field',
    style: () => `
			:host {
				display: inline-block;
				max-width: 100%;
				min-width: 10em;
        box-sizing: border-box;
        position: relative;
			}
			${useHostExternalPadding(PasswordClass.cssVarList)}
			${resetInputCursor('vaadin-password-field')}
      ${resetInputLabelPosition('vaadin-password-field')}

			vaadin-password-field {
				width: 100%;
				box-sizing: border-box;
				padding: 0;
			}
			vaadin-password-field > input {
        -webkit-mask-image: none;
				box-sizing: border-box;
        opacity: 1;
			}
			vaadin-password-field::part(input-field) {
				box-sizing: border-box;
				padding: 0;
			}
			vaadin-password-field[focus-ring]::part(input-field) {
				box-shadow: none;
			}
      :host ::slotted(input) {
				min-height: 0;
      }
      :host([readonly]) ::slotted(input:placeholder-shown) {
				opacity: 0;
			}
			vaadin-password-field::before {
				height: initial;
			}
			vaadin-password-field::part(input-field)::after {
				opacity: 0;
			}
			vaadin-password-field-button {
				cursor: pointer;
			}
			:host([readonly]) vaadin-password-field-button {
				pointer-events: none;
			}
			vaadin-password-field-button[focus-ring] {
				box-shadow: 0 0 0 2px var(${PasswordClass.cssVarList.inputOutlineColor});
			}

      ${inputFloatingLabelStyle()}

      ::part(reveal-button) {
        align-self: center;
      }

      vaadin-password-field[external-input="true"] > input:not(:placeholder-shown) {
        opacity: 0;
      }
		`,
    excludeAttrsSync: ['tabindex', 'style'],
    componentName,
  })
);
