import { injectStyle } from '@descope-ui/common/components-helpers';
import { createBaseInputClass } from '../../../baseClasses/createBaseInputClass';
import { forwardAttrs, observeAttributes } from '../../../helpers/componentHelpers';
import { NewPasswordClass } from '../NewPasswordClass';
import { componentName } from './componentName';

const passwordAttrPrefixRegex = /^password-/;
const confirmAttrPrefixRegex = /^confirm-/;

const removeAttrPrefix = (attr, prefix) => attr.replace(prefix, '');

const passwordInputAttrs = ['password-label', 'password-placeholder'];
const confirmInputAttrs = ['confirm-label', 'confirm-placeholder'];
const policyPanelAttrs = [
  'has-validation',
  'policy-label',
  'available-policies',
  'active-policies',
  'data-password-policy-value-minlength',
  'data-password-policy-value-passwordstrength',
  'manual-visibility-toggle',
];
const commonAttrs = [
  'has-confirm',
  'disabled',
  'bordered',
  'size',
  'full-width',
  'maxlength',
  'invalid',
  'readonly',
  'draggable',
  'autocomplete',
  'label-type',
  'manual-visibility-toggle',
];

const inputRelatedAttrs = [].concat(
  commonAttrs,
  passwordInputAttrs,
  confirmInputAttrs,
  policyPanelAttrs
);

const BaseInputClass = createBaseInputClass({ componentName, baseSelector: 'div' });

class NewPasswordInternal extends BaseInputClass {
  static get observedAttributes() {
    return [].concat(BaseInputClass.observedAttributes || [], inputRelatedAttrs);
  }

  constructor() {
    super();

    this.innerHTML = `
			<div class="wrapper"></div>
		`;

    this.wrapperEle = this.querySelector('.wrapper');
  }

  get value() {
    return this.passwordInput?.value || '';
  }

  set value(val) {
    if (val === this.value) return;
    this.passwordInput.value = val;
  }

  get hasConfirm() {
    return this.getAttribute('has-confirm') === 'true';
  }

  onHasConfirmChange(hasConfirm) {
    this.confirmInput.classList.toggle('hidden', !hasConfirm);
  }

  onHasValidationChange(hasValidation) {
    this.policyPanel.classList.toggle('hidden', !hasValidation);
  }

  get hasValidation() {
    return this.getAttribute('has-validation') === 'true';
  }

  getValidity() {
    if (this.isRequired && !this.value) {
      return { valueMissing: true };
    }

    if (this.value && !this.policyPanel.isValid) {
      return { typeMismatch: true };
    }

    if (this.hasConfirm && this.confirmInput && this.value !== this.confirmInput.value) {
      return { patternMismatch: true };
    }

    const min = this.getAttribute('minlength');
    const minVal = parseInt(min, 10) || 0;
    const minValid = this.value.length >= minVal;
    if (!minValid) {
      return { tooShort: true };
    }

    return {};
  }

  init() {
    this.addEventListener('focus', (e) => {
      // we want to ignore focus events we are dispatching
      if (e.isTrusted) {
        this.passwordInput.focus();
      }
    });
    super.init?.();
    this.renderInputs(this.hasConfirm, this.hasValidation);
  }

  renderInputs() {
    const hasExternalInput = this.getAttribute('external-input') === 'true';
    const template = `
      <div class="password-input-container">
        <descope-password
          autocomplete="new-password"
          manual-visibility-toggle="true"
          data-id="password"
          external-input="${hasExternalInput}"
        ></descope-password>
        <descope-policy-validation></descope-policy-validation>
      </div>
      <descope-password
        autocomplete="new-password"
        manual-visibility-toggle="true"
        external-input="${hasExternalInput}"
        data-id="confirm"
      ></descope-password>
    `;

    this.wrapperEle.innerHTML = template;

    this.passwordInput = this.querySelector('[data-id="password"]');
    this.confirmInput = this.querySelector('[data-id="confirm"]');
    this.policyPanel = this.querySelector('descope-policy-validation');
    this.passwordStrength = this.querySelector('descope-password-strength');

    this.inputs = [this.passwordInput, this.confirmInput];

    this.initInputs();

    // sync input value to policy validation panel
    this.passwordInput.addEventListener('input', (e) => {
      this.passwordStrength?.addEventListener(
        'score-changed',
        ({ detail }) => {
          this.policyPanel.setAttribute('data-password-policy-actual-passwordstrength', detail);
        },
        { once: true }
      );
      this.passwordStrength?.setAttribute('value', e.target.value);
      this.policyPanel.setAttribute('value', e.target.value);
    });

    forwardAttrs(this, this.policyPanel, {
      includeAttrs: policyPanelAttrs,
      mapAttrs: {
        'policy-label': 'label',
        'available-policies': 'data',
      },
    });
  }

  // the inputs are not required but we still want it to have a required
  // indicator in case the root component is required
  handleIndicatorStyle() {
    this.inputs.forEach((input) => {
      injectStyle(
        `
				:host ::part(required-indicator)::after {
					content: var(${NewPasswordClass.cssVarList.inputsRequiredIndicator});
          width: 1em;
          text-align: center;
				}
        :host ::part(required-indicator) {
          display: inline-flex;
        }
				`,
        input
      );
    });
  }

  get isInvalid() {
    return this.hasAttribute('invalid') && this.getAttribute('invalid') !== 'false';
  }

  // for some reason, Vaadin is removing the invalid attribute on several events,
  // e.g. focus, input, etc..., so we need to make sure the inputs will stay invalid
  // if the root component is invalid
  handleInputsInvalidAttribute() {
    this.inputs.forEach((input) => {
      input &&
        observeAttributes(
          input,
          (changedAttributes) => {
            if (changedAttributes.includes('invalid')) {
              const inputInvalidValue = input.getAttribute('invalid');
              const rootInvalidValue = this.getAttribute('invalid');

              if (this.isInvalid && rootInvalidValue !== inputInvalidValue) {
                input.setAttribute('invalid', 'true');
              }
            }
          },
          {}
        );
    });
  }

  initInputs() {
    this.handleIndicatorStyle();
    this.handleInputsInvalidAttribute();
    this.handleFocusEventsDispatching(this.inputs);
  }

  async togglePasswordStrength() {
    if (this.activePolicies.includes('passwordstrength')) {
      if (!this.passwordStrength) {
        await import('@descope-ui/descope-password-strength');
        this.passwordStrength = document.createElement('descope-password-strength');
        this.passwordStrength.setAttribute('options', 'Very weak,Weak,Average,Strong,Very strong');
        this.passwordStrength.setAttribute('value', this.passwordInput.value || '');
        this.passwordStrength.setAttribute(
          'st-host-direction',
          this.getAttribute('st-host-direction')
        );
        this.passwordStrength.setAttribute('full-width', 'true');
        this.querySelector('.password-input-container').insertBefore(
          this.passwordStrength,
          this.policyPanel
        );
      }
    } else if (this.passwordStrength) {
      this.passwordStrength.remove();
      this.passwordStrength = null;
    }
  }

  get activePolicies() {
    return (this.getAttribute('active-policies') || '').split(',');
  }

  toggleBooleanAttribute(ele, name, value) {
    value === null ? ele?.removeAttribute(name) : ele?.setAttribute(name, value);
  }

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

    if (oldValue !== newValue) {
      if (attrName === 'has-validation') {
        this.onHasValidationChange(newValue === 'true');
      } else if (attrName === 'has-confirm') {
        this.onHasConfirmChange(newValue === 'true');
      } else if (commonAttrs.includes(attrName)) {
        this.inputs.forEach((input) => this.toggleBooleanAttribute(input, attrName, newValue));
      } else if (passwordInputAttrs.includes(attrName)) {
        this.toggleBooleanAttribute(
          this.passwordInput,
          removeAttrPrefix(attrName, passwordAttrPrefixRegex),
          newValue
        );
      } else if (confirmInputAttrs.includes(attrName)) {
        this.toggleBooleanAttribute(
          this.confirmInput,
          removeAttrPrefix(attrName, confirmAttrPrefixRegex),
          newValue
        );
      }
      if (attrName === 'active-policies') {
        this.togglePasswordStrength();
      }
    }
  }
}

export default NewPasswordInternal;
