import { createStyleMixin, draggableMixin, componentNameValidationMixin } from '../../mixins';
import { createBaseClass } from '../../baseClasses/createBaseClass';
import { compose } from '../../helpers';
import { forwardAttrs, getComponentName } from '../../helpers/componentHelpers';
import { isNumericValue, sanitizeCountryCodePrefix as sanitizeValue } from './helpers';
import { PhoneFieldClass } from '../phone-fields/descope-phone-field/PhoneFieldClass';
import { PhoneFieldInputBoxClass } from '../phone-fields/descope-phone-input-box-field/PhoneFieldInputBoxClass';
import { injectStyle } from '@descope-ui/common/components-helpers';

export const componentName = getComponentName('hybrid-field');

const attrs = {
  shared: [
    'label',
    'bordered',
    'full-width',
    'label-type',
    'size',
    'disabled',
    'readonly',
    'required',
    'st-host-direction',
    'st-error-message-icon',
    'st-error-message-icon-size',
    'st-error-message-icon-padding',
  ],
  email: [
    'placeholder',
    'data-errormessage-value-missing-email',
    'data-errormessage-pattern-mismatch-email',
    'external-input',
  ],
  phone: {
    countryCode: [
      'phone-input-label',
      'country-input-label',
      'country-input-placeholder',
      'restrict-countries',
      'default-code',
      'phone-minlength',
      'phone-format-value',
      'phone-strict-validation',
      'data-errormessage-value-missing-phone',
    ],
    inputBox: [
      'restrict-countries',
      'default-code',
      'phone-minlength',
      'phone-format-value',
      'phone-strict-validation',
      'data-errormessage-value-missing-phone',
    ],
  },
};

const attrMap = {
  email: {
    'data-errormessage-value-missing-email': 'data-errormessage-value-missing',
    'data-errormessage-pattern-mismatch-email': 'data-errormessage-pattern-mismatch',
  },
  phone: {
    'phone-input-label': 'label',
    'phone-minlength': 'minlength',
    'phone-format-value': 'format-value',
    'phone-strict-validation': 'strict-validation',
    'data-errormessage-value-missing-phone': 'data-errormessage-value-missing',
  },
};

const EMAIL_FIELD = 'descope-email-field';
const PHONE_FIELD = 'descope-phone-field';
const PHONE_INPUT_BOX_FIELD = 'descope-phone-input-box-field';

const BaseClass = createBaseClass({
  componentName,
  baseSelector: 'div',
});

class RawHybridField extends BaseClass {
  #isReportedValidity = false;

  #selectionStart;

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
    <div class="wrapper">
      <descope-email-field external-input="${this.isExternalInput}"></descope-email-field>
      <descope-phone-field allow-alphanumeric-input="true"></descope-phone-field>
      <descope-phone-input-box-field allow-alphanumeric-input="true"></descope-phone-input-box-field>
    </div>
		`;

    injectStyle(
      `
      :host {
        display: inline-flex;
        box-sizing: border-box;
        padding: 0;
        gap: 0;
      }
      .wrapper {
        display: grid;
        width: 100%;
      }
      descope-email-field,
      descope-phone-field,
      descope-phone-input-box-field {
        grid-area: 1/1;
        width: 100%;
        height: 100%;
      }
      .hidden {
        visibility: hidden;
      }
      `,
      this
    );
  }

  get isExternalInput() {
    return this.getAttribute('external-input') === 'true';
  }

  get emailInputEle() {
    return this.isExternalInput ? this.emailInput.externalInput : this.emailInput.inputElement;
  }

  get phoneVariant() {
    const isInputBox = this.getAttribute('phone-variant') === 'inputBox';
    return isInputBox ? this.phoneInputBoxInput : this.phoneCountryCodeInput;
  }

  get defaultCode() {
    const firstItem = this.phoneCountryCodeInput.countryCodes?.[0] || '';
    return this.getAttribute('default-code') || firstItem;
  }

  get activeInputEle() {
    if (this.activeInput === this.emailInput) {
      return this.emailInputEle;
    }
    return this.phoneVariant.phoneNumberInputEle;
  }

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

  set value(val) {
    this.handleActiveInput(val);
    this.activeInput.value = val;
  }

  async init() {
    super.init?.();

    this.initInputs();
    this.updateAttrs();
    this.toggleInputVisibility();

    await this.waitForInputs();

    this.initInputEles();
    this.overrideEmailInputType();
    this.overrideEmailSetAttribute();
  }

  waitForInputs() {
    return new Promise((resolve) => {
      const check = setInterval(() => {
        if (this.emailInputEle) {
          clearInterval(check);
          resolve();
        }
      });
    });
  }

  initInputs() {
    this.emailInput = this.shadowRoot.querySelector(EMAIL_FIELD);
    this.phoneCountryCodeInput = this.shadowRoot.querySelector(PHONE_FIELD);
    this.phoneInputBoxInput = this.shadowRoot.querySelector(PHONE_INPUT_BOX_FIELD);

    this.inputs = [this.emailInput, this.phoneCountryCodeInput, this.phoneInputBoxInput];

    this.activeInput = this.emailInput;
  }

  // we need to listen on the inner `input` element itself, otherwise we don't always get an `input` event
  initInputEles() {
    const inputEles = [
      this.emailInputEle,
      this.phoneCountryCodeInput.phoneNumberInputEle,
      this.phoneInputBoxInput.phoneNumberInputEle,
    ];

    inputEles.forEach((ele) => {
      ele.addEventListener('input', this.onValueChange.bind(this));
    });
  }

  overrideEmailInputType() {
    this.emailInputEle.addEventListener('focus', () => {
      this.emailInputEle.setAttribute('type', 'text');
    });
    this.emailInputEle.addEventListener('blur', () => {
      setTimeout(() => {
        this.emailInputEle.setAttribute('type', 'email');
      });
    });
  }

  // We want to prevent Vaadin from changing the input type to `email`
  // otherwise, we cannot get the selectionStart from the input.
  overrideEmailSetAttribute() {
    const origEmailSetAttr = this.emailInput.setAttribute.bind(this.emailInputEle);
    this.emailInputEle.setAttribute = (name, value) => {
      if (name !== 'type' || value !== 'email' || !this.emailInput.hasAttribute('focused')) {
        return origEmailSetAttr(name, value);
      }
      return false;
    };
  }

  updateAttrs() {
    forwardAttrs(this, this.emailInput, { includeAttrs: attrs.email, mapAttrs: attrMap.email });

    forwardAttrs(this, this.phoneCountryCodeInput, {
      includeAttrs: attrs.phone.countryCode,
      mapAttrs: attrMap.phone,
    });

    forwardAttrs(this, this.phoneInputBoxInput, {
      includeAttrs: attrs.phone.inputBox,
      mapAttrs: attrMap.phone,
    });

    this.inputs.forEach((input) => forwardAttrs(this, input, { includeAttrs: attrs.shared }));

    setTimeout(() => this.phoneCountryCodeInput.setAttribute('default-code', this.defaultCode));
  }

  onValueChange() {
    this.#selectionStart = this.activeInputEle.selectionStart;
    this.handleActiveInput(this.activeInput.value);
  }

  handleActiveInput(val) {
    const expectedActiveInput = isNumericValue(val) ? this.phoneVariant : this.emailInput;

    if (this.activeInput !== expectedActiveInput) {
      this.setActiveInput(expectedActiveInput);
    }
  }

  setActiveInput(expectedActiveInput) {
    const val = this.activeInput.value;
    this.activeInput = expectedActiveInput;
    this.setActiveInputValue(val);
    if (this.#isReportedValidity) this.activeInput.reportValidity();
    this.setActiveInputSelectionStart();
    this.toggleInputVisibility();
  }

  setActiveInputSelectionStart() {
    setTimeout(() => {
      this.activeInputEle.focus();
      this.activeInputEle.setSelectionRange?.(this.#selectionStart, this.#selectionStart);
    });
  }

  setActiveInputValue(val) {
    const sanitizedVal = sanitizeValue(val);
    const isPhoneField = this.activeInput.localName === PHONE_FIELD;
    const value = isPhoneField
      ? `${this.phoneCountryCodeInput.countryCodeItems}-${sanitizedVal}`
      : sanitizedVal;
    this.activeInput.value = value;
  }

  toggleInputVisibility() {
    this.inputs.forEach((input) => {
      input !== this.activeInput ? input.classList.add('hidden') : input.classList.remove('hidden');
    });
  }

  reportValidity() {
    this.#isReportedValidity = true;

    const ele = this.activeInputEle;

    // On reportValidity we want to set the caret at the end of the input value.
    // Since checkValidity triggers `focus` on the input, it sets the caret at the start of the input,
    // regardless the existing value.
    const origInputType = ele.type;
    ele.setAttribute('type', 'text');
    setTimeout(() => {
      ele.setSelectionRange?.(ele.value.length, ele.value.length);
      ele.setAttribute('type', origInputType);
    });

    return this.activeInput.reportValidity();
  }

  checkValidity() {
    return this.activeInput.checkValidity();
  }
}

export const HybridFieldClass = compose(
  createStyleMixin({ componentNameOverride: getComponentName('input-wrapper') }),
  createStyleMixin({
    mappings: {
      hostWidth: { selector: () => ':host', property: 'width' },
      hostDirection: [
        { selector: () => 'descope-email-field', property: 'direction' },
        { selector: () => 'descope-phone-field', property: 'direction' },
        { selector: () => 'descope-phone-input-box-field', property: 'direction' },
      ],
      errorMessageIcon: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageIcon,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageIcon,
        },
      ],
      errorMessageIconSize: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageIconSize,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageIconSize,
        },
      ],
      errorMessageIconPadding: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageIconPadding,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageIconPadding,
        },
      ],
      errorMessageIconRepeat: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageIconRepeat,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageIconRepeat,
        },
      ],
      errorMessageIconPosition: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageIconPosition,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageIconPosition,
        },
      ],
      errorMessageFontSize: [
        {
          selector: () => PhoneFieldClass.componentName,
          property: PhoneFieldClass.cssVarList.errorMessageFontSize,
        },
        {
          selector: () => PhoneFieldInputBoxClass.componentName,
          property: PhoneFieldInputBoxClass.cssVarList.errorMessageFontSize,
        },
      ],
    },
  }),
  draggableMixin,
  componentNameValidationMixin
)(RawHybridField);
