import { injectStyle } from '@descope-ui/common/components-helpers';
import { createBaseInputClass } from '../../baseClasses/createBaseInputClass';
import {
  createStyleMixin,
  draggableMixin,
  componentNameValidationMixin,
  portalMixin,
} from '../../mixins';
import { forwardAttrs, getComponentName } from '../../helpers/componentHelpers';
import { compose } from '../../helpers';
import {
  isValidEpoch,
  formatEpoch,
  isNumber,
  getCurrentTime,
  overrideConstructedStylesheet,
  dateToEpoch,
  parseDateString,
} from './helpers';
import { formats } from './formats';
import { calendarIcon } from './icons';
import {
  counterConfig,
  DEFAULT_FORMAT,
  DIVIDER,
  MOBILE_DEVICE_INTERACTION_TIMEOUT_MS,
  NATIVE_FORMAT,
  valRange,
} from './consts';
import { DateCounter } from './DateCounterClass';
import { TextFieldClass } from '../descope-text-field/TextFieldClass';
import { newDate } from './date-utils';

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

// we set baseSelector to `vaadin-popover` as a temporary hack, so our portalMixin will
// be able to process this component's overlay. The whole process needs refactoring as soon as possible.
const BASE_SELECTOR = 'vaadin-popover';
const BaseInputClass = createBaseInputClass({ componentName, baseSelector: BASE_SELECTOR });

const dateFieldAttrs = [
  'format',
  'opened',
  'initial-value',
  'readonly',
  'disable-calendar',
  'utc-time',
];
const calendarAttrs = ['years-range', 'calendar-months', 'calendar-weekdays'];
const observedAttrs = [...dateFieldAttrs, ...calendarAttrs];

class RawDateFieldClass extends BaseInputClass {
  epoch = '';

  format = DEFAULT_FORMAT;

  selectedCounterIdx = 0;

  updateCountersDisplay() {
    this.inputElement.value = this.countersValue;
  }

  updateValue() {
    if (this.isCountersOutOfRange) {
      this.resetEpoch();
    } else {
      const date = formats[this.format].getDate(this.inputElement.value);
      this.updateEpoch(dateToEpoch(date, this.isUtcTime));
    }
  }

  onDateCounterChange = () => {
    this.updateCountersDisplay();
    this.updateValue();
    // update validity
    this.#dispatchInput();
  };

  updateEpoch(epoch) {
    this.epoch = dateToEpoch(epoch, this.isUtcTime);
  }

  dateCounters = [
    new DateCounter(counterConfig.MONTH, this.onDateCounterChange.bind(this)),
    new DateCounter(counterConfig.DAY, this.onDateCounterChange.bind(this)),
    new DateCounter(counterConfig.YEAR, this.onDateCounterChange.bind(this)),
  ];

  static get observedAttributes() {
    return [].concat(BaseInputClass.observedAttributes || [], observedAttrs);
  }

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
    <div>
      <descope-text-field inputmode="numeric">
        <span slot="suffix" class="toggle-calendar">
          ${calendarIcon}
        </span>
      </descope-text-field>
      <vaadin-popover></vaadin-popover>
    </div>
    `;

    injectStyle(
      `
      :host {
        display: inline-block;
        box-sizing: border-box;
        user-select: none;
        max-width: 100%;
      }

      :host ::slotted {
        padding: 0;
      }

      .toggle-calendar {
        cursor: pointer;
        display: flex;
        align-self: center;
        z-index: 1;
        height: 100%;
        align-items: center;
      }

      :host([readonly="true"]) .toggle-calendar {
        pointer-events: none;
      }

      .hidden {
        display: none;
      }
        `,
      this
    );

    this.inputElement = this.shadowRoot.querySelector('descope-text-field');
    this.popoverToggleButton = this.inputElement.querySelector('.toggle-calendar');
  }

  get validationTarget() {
    return this.inputElement;
  }

  get opened() {
    return this.getAttribute('opened') === 'true';
  }

  // returns the input's value as a epoch
  get displayValueEpoch() {
    const date = formats[this.format].getDate(this.inputElement.value);

    if (!isValidEpoch(date?.getTime())) {
      return null;
    }

    return date.getTime();
  }

  get sortedCounters() {
    return this.format
      .split(DIVIDER)
      .map((placeholder) => this.dateCounters.find((dc) => dc.placeholder === placeholder));
  }

  get activeCounter() {
    return this.sortedCounters[this.selectedCounterIdx];
  }

  get countersValue() {
    return this.sortedCounters.map((dc) => dc.stringValue).join(DIVIDER);
  }

  get overlay() {
    return this.baseElement.shadowRoot?.querySelector('vaadin-popover-overlay');
  }

  get backdrop() {
    return this.overlay.shadowRoot?.querySelector('#backdrop');
  }

  get calendar() {
    return this.overlay?.querySelector('descope-calendar');
  }

  get isRTL() {
    const computedStyleDirection = getComputedStyle(this.baseElement).getPropertyValue('direction');

    if (computedStyleDirection) {
      return computedStyleDirection === 'rtl';
    }

    // Fallback: If for some reason computed style was not calculated in time, fallback to check on attribute
    return this.getAttribute('st-host-direction') === 'rtl';
  }

  get value() {
    if (this.isInvalidDate()) {
      return '';
    }
    return this.epoch;
  }

  set value(val) {
    const value = parseInt(val, 10);
    if (!Number.isNaN(value)) {
      this.updateEpoch(value);
      this.updateDateCounters(new Date(value));
    } else {
      this.resetEpoch();
      this.resetDateCounters();
      this.clearInputEle();
    }
  }

  resetEpoch() {
    this.epoch = '';
  }

  get isCountersEmpty() {
    return this.dateCounters.every((dc) => dc.isEmpty);
  }

  get isCountersOutOfRange() {
    return this.dateCounters.some((dc) => !dc.isInRange(dc.numberValue));
  }

  get disableCalendar() {
    return this.getAttribute('disable-calendar') === 'true';
  }

  get isUtcTime() {
    return this.getAttribute('utc-time') === 'true';
  }

  get isSelectAll() {
    const inputEle = this.inputElement.baseElement.inputElement;
    return inputEle.value.length === inputEle.selectionStart + inputEle.selectionEnd;
  }

  reportValidity() {
    this.inputElement.reportValidity();
  }

  #dispatchInput() {
    this.inputElement.baseElement.dispatchEvent(new Event('input', { bubbles: true }));
  }

  updateInputDisplay() {
    this.inputElement.value = formatEpoch(newDate(this.countersValue).getTime(), this.format);
  }

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

    this.updateFormatPattern();
    this.initPopover();
    this.onDateCounterChange();
    this.initInputElement();

    setTimeout(() => {
      this.#overrideOverlaySettings();
    }, 0);
  }

  initInputElement() {
    this.inputElement.getValidity = this.getValidity.bind(this);
    this.inputElement.baseElement.checkValidity = this.checkValidity.bind(this);

    this.popoverToggleButton.addEventListener('click', this.onPopoverToggle.bind(this));

    this.inputElement.addEventListener('focus', this.onFocus.bind(this));
    this.inputElement.addEventListener('blur', this.onBlur.bind(this));
    this.inputElement.addEventListener('click', this.handleMouseCaretPositionChange.bind(this));
    this.inputElement.addEventListener('keydown', this.handleKeyboard.bind(this));
    this.inputElement.addEventListener('beforeinput', this.handleInput.bind(this));
    this.inputElement.addEventListener('pointerdown', this.onPointerDown.bind(this));
    this.inputElement.addEventListener('paste', this.onPaste.bind(this));

    forwardAttrs(this, this.inputElement, {
      includeAttrs: [
        'label',
        'label-type',
        'placeholder',
        'disabled',
        'readonly',
        'bordered',
        'required',
        'full-width',
        'st-host-direction',
        'pattern',
        'bordered',
        'data-errormessage-value-missing',
        'data-errormessage-pattern-mismatch',
        'data-errormessage-range-underflow',
        'data-errormessage-range-overflow',
        'st-error-message-icon',
        'st-error-message-icon-size',
        'st-error-message-icon-padding',
      ],
    });
  }

  handleInput(e) {
    e.preventDefault();

    this.handleSelectAll();

    if (e.data && isNumber(e.data)) {
      this.parseDigits(e.data);
      this.updateCountersDisplay();
    } else if (e.inputType) {
      this.handleNavKeys(e);
    }
  }

  initPopover() {
    this.baseElement.trigger = ['click'];
    this.baseElement.withBackdrop = true;
    this.baseElement.renderer = this.#popoverRenderer.bind(this);

    // block popover events from focusing/blurring the text-field
    this.baseElement.addEventListener('click', (e) => {
      e.preventDefault();
      e.stopPropagation();
    });
  }

  handleSelectAll() {
    if (this.isSelectAll) {
      this.selectFirstCounter();
    }
  }

  #popoverPosStylesheet;

  #popoverRenderer(root) {
    // popoverRenderer should run only once, when the popover is first rendering.
    if (!root.firstChild) {
      this.overlay.positionTarget = this.shadowRoot.querySelector('.toggle-calendar');

      root.appendChild(this.#getPopoverContent());

      // override vaadin's constructed stylesheet which hides the host element
      overrideConstructedStylesheet(this.baseElement);

      this.backdrop.addEventListener('click', this.closePopover.bind(this));
    }

    // Hide overlay before adjusting position to prevent flickering
    root.style.setProperty('visibility', 'hidden');

    // Wait until overlay is ready
    setTimeout(() => {
      this.#adjustPopoverPosition(root);

      // Show adjusted overlay
      root.style.setProperty('visibility', 'visible');
      this.updateCalendarView(root);
    }, 100);
  }

  #adjustPopoverPosition() {
    const popover = this.shadowRoot.querySelector('vaadin-popover').shadowRoot;

    // Remove previously added stylesheets
    this.#popoverPosStylesheet?.remove();

    const windowRect = document.body.getBoundingClientRect();
    const inputRect = this.getBoundingClientRect();
    const calendarRect = this.calendar.getBoundingClientRect();

    const side = this.isRTL ? 'right' : 'left';
    const offset = inputRect[side] - calendarRect[side];
    const availableLeft = calendarRect.left;
    const availableRight = windowRect.width - calendarRect.right;

    let newOffset;
    if (offset > 0) {
      newOffset = Math.min(offset, availableRight);
    } else {
      newOffset = Math.min(Math.abs(offset), availableLeft) * -1;
    }

    injectStyle(
      `
      vaadin-popover-overlay::part(overlay) {
        transform: translateX(${newOffset}px);
      }
    `,
      popover
    );
  }

  #getPopoverContent() {
    const ele = document.createElement('span');

    ele.innerHTML = `<descope-calendar></descope-calendar>`;

    const calendar = ele.querySelector('descope-calendar');

    calendar.addEventListener('date-submit', this.onCalendarSubmit.bind(this));
    calendar.addEventListener('cancel', this.closePopover.bind(this));

    return ele;
  }

  // the default vaadin behavior is to attach the overlay to the body when opened
  // we do not want that because it's difficult to style the overlay in this way
  // so we override it to open inside the shadow DOM
  #overrideOverlaySettings() {
    this.overlay._attachOverlay = () => this.overlay.bringToFront();
    this.overlay._detachOverlay = () => {};
    this.overlay._enterModalState = () => {};
  }

  onPopoverToggle() {
    this.opened ? this.closePopover() : this.openPopover();
  }

  openPopover() {
    if (this.disableCalendar) return;
    this.setAttribute('opened', 'true');
  }

  closePopover() {
    this.removeAttribute('opened');
    this.inputElement.focus();
  }

  getCounterById(id) {
    return this.dateCounters.find((dc) => dc.id === id);
  }

  onCalendarSubmit() {
    if (!this.isReadOnly) {
      if (!this.calendar.value) return;

      const calendarDate = newDate(this.calendar.value);

      this.value = dateToEpoch(calendarDate, this.isUtcTime);

      this.getCounterById('year').replaceValue(calendarDate.getFullYear());
      this.getCounterById('month').replaceValue(calendarDate.getMonth() + 1);
      this.getCounterById('day').replaceValue(calendarDate.getDate());

      this.#dispatchInput();
    }

    this.closePopover();
  }

  updateCalendarView() {
    const validInputVal =
      isValidEpoch(newDate(this.inputElement.value || '').getTime()) &&
      formats[this.format].validate(this.inputElement.value);

    if (this.displayValueEpoch || validInputVal) {
      this.calendar.setAttribute(
        'initial-value',
        formatEpoch(this.displayValueEpoch || this.epoch, NATIVE_FORMAT)
      );
    } else {
      this.calendar.clearValue();
      this.calendar.setAttribute('preview', formatEpoch(getCurrentTime(), NATIVE_FORMAT));
    }

    forwardAttrs(this, this.calendar, {
      includeAttrs: [
        'st-host-direction',
        'readonly',
        'initial-month',
        'initial-year',
        'years-range',
        'calendar-label-submit',
        'calendar-label-cancel',
        'calendar-months',
        'calendar-weekdays',
        'calendar-weekdays-short',
      ],
    });
  }

  // In mobile devices, there are cases were `pointerdown` is triggered
  // instead of `click`.
  onPointerDown(e) {
    setTimeout(() => this.handleMouseCaretPositionChange(e), MOBILE_DEVICE_INTERACTION_TIMEOUT_MS);
  }

  onFocus() {
    if (this.isReadOnly) {
      return;
    }

    // We need to wait for focus to end before we set selection
    setTimeout(() => this.resetDisplay());
  }

  resetDisplay() {
    if (!this.inputElement.value) {
      this.inputElement.value = this.format;
    }

    // On focus select the first counter
    this.selectFirstCounter();
    // set selection on first counter
    this.inputElement.setSelectionRange(0, this.sortedCounters[0].length);
  }

  onBlur() {
    if (this.opened) {
      return;
    }

    if (this.inputElement.value === this.format) {
      this.clearInputEle();
    }
  }

  clearInputEle() {
    this.inputElement.value = '';
  }

  onFormatUpdate(format) {
    if (Object.keys(formats).includes(format)) {
      this.format = format;
      this.updateFormatPattern();
    }
  }

  updateFormatPattern() {
    const format = this.getAttribute('format') || this.format || DEFAULT_FORMAT;
    this.setAttribute('type', 'date');
    this.setAttribute('pattern', formats[format].pattern);
  }

  parseDigits(value) {
    this.activeCounter.add(value);

    if (this.activeCounter.isFull) {
      this.selectNextCounter();
    }

    // We wait for the digit to be parsed, and only then set the selection.
    // Failing to do so results in unexpected "jump" of the screen in mobile devices.
    this.setInputSelectionRange();
  }

  getCounterIdx(caretPos) {
    const [counter1ln, counter2ln] = this.sortedCounters.map((dc) => dc.length);

    const c1 = caretPos <= counter1ln;
    const c2 = caretPos >= counter1ln && caretPos <= counter1ln + counter2ln + 1;
    const c3 = caretPos >= counter1ln + counter2ln + 2;

    return [c1, c2, c3].indexOf(true);
  }

  setSelectedCounterByCaretPosition(e) {
    this.selectedCounterIdx = this.getCounterIdx(
      // if triggered by touch event, target might not include `selectionStart`
      // in that case we fall back to the inputElement's `selectionStart` value.
      // Therefore, it is recommended to run this function with setTimeout,
      // at least for mobile events.
      e.target?.selectionStart || this.inputElement.selectionStart
    );
  }

  selectFirstCounter() {
    this.selectedCounterIdx = 0;
  }

  selectNextCounter() {
    if (this.selectedCounterIdx < this.dateCounters.length) {
      this.selectedCounterIdx = Math.min(this.selectedCounterIdx + 1, 2);
    }
  }

  selectPrevCounter() {
    if (this.selectedCounterIdx > 0) {
      this.selectedCounterIdx = Math.min(this.selectedCounterIdx - 1, 1);
    }
  }

  // Sets the text selection range in the input element based on the selected counter.
  // The function calculates the starting position of the selection by summing the lengths
  // of all counters before the currently selected counter (`selectedCounterIdx`).
  // It then selects the entire length of the current counter in the input element.
  setInputSelectionRange() {
    // For preview/readonly state we don't have a caret or a caretIdx, so we skip setting input selection range
    if (this.selectedCounterIdx < 0) {
      return;
    }

    // We wait for before setting the selection, otherwise there's an
    // unexpected "jump" of the screen in mobile devices.
    setTimeout(() => {
      const caretStart = this.sortedCounters
        .slice(0, this.selectedCounterIdx)
        .reduce((acc, counter) => acc + counter.length, this.selectedCounterIdx);

      this.inputElement.setSelectionRange(
        caretStart,
        caretStart + this.sortedCounters[this.selectedCounterIdx].length
      );
    });
  }

  resetDateCounters() {
    this.dateCounters.forEach((dc) => dc.clear());
  }

  // in case value is set from external source we need to update date counters
  updateDateCounters(date) {
    this.dateCounters.forEach((dc) => {
      switch (dc.id) {
        case counterConfig.MONTH.id:
          dc.set(this.isUtcTime ? date.getUTCMonth() + 1 : date.getMonth() + 1);
          break;
        case counterConfig.YEAR.id:
          dc.set(this.isUtcTime ? date.getUTCFullYear() : date.getFullYear());
          break;
        case counterConfig.DAY.id:
          dc.set(this.isUtcTime ? date.getUTCDate() : date.getDate());
          break;
        default:
          break;
      }
    });
  }

  handleKeyboard(e) {
    if (e.metaKey || e.ctrlKey) {
      if (e.key.toLowerCase() === 'x') {
        this.onCut(e);
      }

      return;
    }

    this.handleSelectAll();

    if (e.key === 'ArrowUp') {
      this.activeCounter.inc();
    } else if (e.key === 'ArrowDown') {
      this.activeCounter.dec();
    } else if (e.key === 'ArrowRight') {
      this.selectNextCounter();
    } else if (e.key === 'ArrowLeft') {
      this.selectPrevCounter();
    }

    this.setInputSelectionRange();
  }

  handleNavKeys(e) {
    if (this.isReadOnly) {
      return;
    }

    if (this.opened) {
      this.closePopover();
    }

    if (!this.activeCounter) return;

    if (e.inputType === 'deleteContentBackward') {
      this.handleBackspace();
    }

    this.setInputSelectionRange();
  }

  handleBackspace() {
    if (this.isSelectAll) {
      this.resetToInitialState();
      return;
    }

    const counter = this.activeCounter;

    if (counter.isEmpty) {
      this.selectPrevCounter();
      this.setInputSelectionRange();
    } else {
      counter.set('');
    }

    // To support keyboards like SwiftKey, we need to re-render the counters display and selection,
    // otherwise we get an unexpected behavior, where the format is deleted.
    setTimeout(() => {
      this.updateCountersDisplay();
      this.setInputSelectionRange();
    });
  }

  handleMouseCaretPositionChange(e) {
    if (this.opened) {
      return;
    }

    e.preventDefault();

    this.setSelectedCounterByCaretPosition(e);
    this.setInputSelectionRange();
  }

  onInitialValueChange(val) {
    // if component already has a value don't re-set value
    if (this.value) return;

    // we need to wait for the text-field to init
    setTimeout(() => {
      if (Number.isNaN(parseInt(val, 10))) {
        return;
      }
      this.value = Number(val);
    });
  }

  togglePopoverAccess(visibility) {
    if (visibility) {
      this.popoverToggleButton.classList.remove('hidden');
    } else {
      this.popoverToggleButton.classList.add('hidden');
    }
  }

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

    if (oldValue !== newValue) {
      if (attrName === 'disable-calendar') {
        this.togglePopoverAccess(newValue !== 'true');
      }
      if (dateFieldAttrs.includes(attrName)) {
        if (newValue && attrName === 'format') {
          this.onFormatUpdate(newValue);
        }
        if (attrName === 'initial-value') {
          this.onInitialValueChange(newValue);
        }
      } else if (calendarAttrs.includes(attrName)) {
        if (newValue) {
          this.calendar?.setAttribute(attrName, newValue);
        } else {
          this.calendar?.removeAttribute(attrName);
        }
      }
    }
  }

  // we want to validate the date supplied actually exists. For example: Feb 31 is not an actual date,
  // but in JS when create a `new Date('1999-02-31') we get March 2nd 1999.
  // To prevent this error from being submitted, we evaluate the
  // date parts against their generated Date value.
  isInvalidDate() {
    const yearCounter = this.getCounterById('year');
    const isYearOutOfRange =
      yearCounter.numberValue > valRange.year.max || yearCounter.numberValue < valRange.year.min;

    const isDateDisplayMismatch = Object.entries(this.getDateVals()).some(([key, val]) => {
      const counter = this.getCounterById(key);
      return !val || counter.numberValue !== val;
    });

    return isYearOutOfRange || isDateDisplayMismatch;
  }

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

    if (this.isCountersOutOfRange || this.isInvalidDate()) {
      return { patternMismatch: true };
    }

    return {};
  }

  getDateVals() {
    const ret = {
      day: '',
      month: '',
      year: '',
    };

    if (Number.isNaN(this.epoch)) {
      return ret;
    }

    try {
      const date = new Date(this.epoch);

      if (this.isUtcTime) {
        ret.month = date.getUTCMonth() + 1;
        ret.day = date.getUTCDate();
        ret.year = date.getUTCFullYear();
      } else {
        ret.month = date.getMonth() + 1;
        ret.day = date.getDate();
        ret.year = date.getFullYear();
      }
    } catch (e) {}

    return ret;
  }

  resetToInitialState() {
    this.resetDateCounters();
    this.selectFirstCounter();
    this.resetDisplay();
  }

  onCut(e) {
    e.preventDefault();

    if (this.isSelectAll) {
      this.#copyToClipboard(this.countersValue);
      this.resetToInitialState();
    } else {
      this.#copyToClipboard(this.activeCounter.stringValue);
      this.activeCounter.set('');
    }

    this.setInputSelectionRange();
  }

  #copyToClipboard(value) {
    try {
      navigator.clipboard.writeText(value);
    } catch (err) {
      console.error('Failed to copy date value:', err);
    }
  }

  onPaste(e) {
    e.preventDefault();

    const clipboardData = e.clipboardData || window.clipboardData;
    const pastedData = clipboardData.getData('Text');

    // try paste entire date if valid
    const validDate = parseDateString(pastedData, this.format);

    if (validDate) {
      this.value = dateToEpoch(validDate, this.isUtcTime);
      this.onDateCounterChange();

      // select all
      setTimeout(() => this.inputElement.setSelectionRange(0, this.inputElement.value.length));
    } else {
      const value = Number(pastedData);

      // try paste in counter if possible
      if (value && this.activeCounter.min <= value && this.activeCounter.max >= value) {
        // use String to get rid of any zero padding
        this.activeCounter.set(String(value));

        setTimeout(() => this.setInputSelectionRange());
      }
    }
  }
}

const textVars = TextFieldClass.cssVarList;
const { host, input, inputEleRTL, toggleButton, overlay, backdrop, errorMessage } = {
  host: { selector: () => ':host' },
  input: { selector: () => 'descope-text-field' },
  inputEleRTL: { selector: () => ':host([st-host-direction="rtl"]) descope-text-field' },
  toggleButton: { selector: () => '.toggle-calendar' },
  overlay: { selector: 'vaadin-popover-overlay::part(overlay)' },
  backdrop: { selector: 'vaadin-popover-overlay::part(backdrop)' },
};

export const DateFieldClass = compose(
  createStyleMixin({ componentNameOverride: getComponentName('input-wrapper') }),
  createStyleMixin({
    mappings: {
      iconMargin: { ...toggleButton, property: 'margin-inline-end' },
      hostWidth: { ...host, property: 'width' },
      hostDirection: { ...host, property: 'direction' },
      textAlign: { ...input, property: 'text-align' },
      rtlInputDirection: { ...inputEleRTL, property: textVars.inputDirection },
      rtlInputAlignment: { ...inputEleRTL, property: textVars.inputTextAlign },
      overlayGap: {
        property: () => DateFieldClass.cssVarList.overlayGap,
      },
      overlayBackgroundColor: {
        property: () => DateFieldClass.cssVarList.overlayBackgroundColor,
      },
      overlayPadding: {
        property: () => DateFieldClass.cssVarList.overlayPadding,
      },
      overlayBoxShadow: { property: () => DateFieldClass.overlayBoxShadow },
      overlayOutlineWidth: {
        property: () => DateFieldClass.cssVarList.overlayOutlineWidth,
      },
      overlayOutlineColor: {
        property: () => DateFieldClass.cssVarList.overlayOutlineColor,
      },
      overlayOutlineStyle: {
        property: () => DateFieldClass.cssVarList.overlayOutlineStyle,
      },
      errorMessageIcon: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageIcon,
      },
      errorMessageIconSize: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageIconSize,
      },
      errorMessageIconPadding: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageIconPadding,
      },
      errorMessageIconRepeat: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageIconRepeat,
      },
      errorMessageIconPosition: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageIconPosition,
      },
      errorMessageFontSize: {
        selector: TextFieldClass.componentName,
        property: TextFieldClass.cssVarList.errorMessageFontSize,
      },
    },
  }),
  portalMixin({
    name: 'overlay',
    selector: '',
    mappings: {
      marginTop: { ...overlay, property: 'margin-top' },
      backgroundColor: { ...overlay },
      backdropBackgroundColor: { ...backdrop, property: 'background-color' },
      backdropPointerEvents: { ...backdrop, property: 'pointer-events' },
      padding: { ...overlay },
      boxShadow: { ...overlay },
      outlineWidth: { ...overlay },
      outlineColor: { ...overlay },
      outlineStyle: { ...overlay },
    },
  }),
  draggableMixin,
  componentNameValidationMixin
)(RawDateFieldClass);
