// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../test/src/custom_typings/chai.d.ts" />
/* eslint-disable no-undef */
import { ZuiInputElement } from '@zywave/zui-input';
import { assert } from '@esm-bundle/chai';
import { executeServerCommand } from '@web/test-runner-commands';
import { awaitEvent, randString } from '../../../../test/src/util/helpers.js';
import { conditionalTest } from '../../../../test/src/util/mocha-helpers.js';
import { faceTests } from '../../../../test/src/util/face-tests.js';

suite('zui-input', () => {
  let element: ZuiInputElement;

  setup(() => {
    element = document.createElement('zui-input') as ZuiInputElement;
    document.body.appendChild(element);
  });

  teardown(() => {
    element?.remove();
  });

  test('initializes as a ZuiInputElement', () => {
    assert.instanceOf(element, ZuiInputElement);
  });

  test('setting a prefix value, renders prefix related html', async () => {
    const prefix = '$';
    element.prefix = prefix;
    element.value = '5.23';
    await element.updateComplete;
    assert.equal(element.shadowRoot.querySelector('div.prefix').textContent, prefix);

    element.prefix = null;
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('div.prefix'));
  });

  test('setting a suffix value, renders suffix related html', async () => {
    const suffix = '.00';
    element.suffix = suffix;
    element.value = '6';
    await element.updateComplete;
    assert.equal(element.shadowRoot.querySelector('div.suffix').textContent, suffix);

    element.suffix = null;
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('div.suffix'));
  });

  test('Typing keys into input updates value', async () => {
    await element.updateComplete;

    const text = randString();
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });

    await element.updateComplete;
    element.focus();

    assert.equal(element.shadowRoot?.querySelector('input')?.value, text, 'input');
    assert.equal(element.value, text, 'zui-input');
  });

  test('setting invalid color resets value to default', async () => {
    element.type = 'color';

    const defaultValue = '#000000';
    const invalidValue = randString();
    element.value = invalidValue;

    assert.notEqual(element.value, invalidValue);
    assert.equal(element.value, defaultValue);
  });

  test('setting type to date renders date input', async () => {
    const type = 'date';
    element.type = type;
    await element.updateComplete;
    const typeVal = element?.shadowRoot?.querySelector('input')?.getAttribute('type');

    assert.equal(typeVal, type);
  });

  test('clicking input type date, focuses on input', async () => {
    element.type = 'date';
    await element.updateComplete;

    element?.shadowRoot?.querySelector('zui-icon')?.click();
    assert.equal(document.activeElement, element);
  });

  test('providing recommended values renders datalist', async () => {
    const recommendedValues = [randString(), randString(), randString()];

    element.recommendedValues = recommendedValues;
    await element.updateComplete;

    const datalist = element.shadowRoot?.querySelector('datalist');

    assert.exists(datalist);
    assert.equal(datalist.children.length, recommendedValues.length);
  });

  test('reflects updates to optionally slotted input', async () => {
    const nativeInp = document.createElement('input');
    nativeInp.type = 'text';
    element.appendChild(nativeInp);

    await element.updateComplete;

    element.value = randString();
    await Promise.all([element.updateComplete, awaitEvent(nativeInp, 'input')]);

    assert.equal(nativeInp.value, element.value);
  });

  conditionalTest(
    navigator.userAgent === 'chromium',
    'dispatches change event when value changes by user interaction',
    async () => {
      let eventCount = 0;
      element.addEventListener('change', () => eventCount++);
      await element.updateComplete;

      const text = randString();
      await executeServerCommand('fill-text', { selector: `zui-input input`, text });
      element.blur();

      await element.updateComplete;

      assert.equal(eventCount, 1);
    }
  );

  test('dispatches input event when value changes by user interaction', async () => {
    let eventCount = 0;
    element.addEventListener('input', () => eventCount++);
    await element.updateComplete;

    const text = randString();
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });

    await element.updateComplete;

    assert.equal(eventCount, text.length);
  });

  test('does not dispatch change event when value changes programmatically', async () => {
    let eventCount = 0;
    element.addEventListener('change', () => eventCount++);
    await element.updateComplete;

    element.value = randString();
    await element.updateComplete;

    assert.equal(eventCount, 0);
  });

  test('required is validated', async () => {
    const errorMessage = randString();
    element.setAttribute('required', '');
    element.setAttribute('validation-message-required', errorMessage);

    await element.updateComplete;

    element.value = '';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.valueMissing, 'validityState.valueMissing');
  });

  test('customError is preserved', async () => {
    const errorMessage = randString();
    element.setAttribute('required', '');
    element.setAttribute('validation-message-required', errorMessage);

    await element.updateComplete;
    assert.equal(element.validationMessage, errorMessage);
    assert.equal(element.validity.customError, false, 'validity.customError');
    assert.equal(element.validity.valueMissing, true, 'validity.valueMissing');

    element.setCustomValidity('Custom Error');
    assert.equal(element.validationMessage, 'Custom Error');
    assert.equal(element.validity.customError, true, 'validity.customError');
    assert.equal(element.validity.valueMissing, true, 'validity.valueMissing');

    element.value = randString();
    element.blur();
    await element.updateComplete;

    assert.equal(element.validationMessage, 'Custom Error');
    assert.equal(element.validity.customError, true, 'validity.customError');
    assert.equal(element.validity.valueMissing, false, 'validity.valueMissing');
  });

  test('clearing customError restores original validity state', async () => {
    const errorMessage = randString();
    element.setAttribute('required', '');
    element.setAttribute('validation-message-required', errorMessage);

    await element.updateComplete;
    assert.equal(element.validationMessage, errorMessage);
    assert.equal(element.validity.customError, false, 'validity.customError');
    assert.equal(element.validity.valueMissing, true, 'validity.valueMissing');

    element.setCustomValidity('Custom Error');
    assert.equal(element.validationMessage, 'Custom Error');
    assert.equal(element.validity.customError, true, 'validity.customError');
    assert.equal(element.validity.valueMissing, true, 'validity.valueMissing');

    element.setCustomValidity('');
    assert.equal(element.validationMessage, errorMessage);
    assert.equal(element.validity.customError, false, 'validity.customError');
    assert.equal(element.validity.valueMissing, true, 'validity.valueMissing');
  });

  // maxlength testing isn't possible, as browsers typically prevent typing past the maxlength
  test('[type=text] minlength is validated', async () => {
    const errorMessage = randString();
    const minlength = 5;
    element.setAttribute('minlength', `${minlength}`);
    element.setAttribute('type', 'text');
    element.setAttribute('validation-message-minlength', errorMessage);

    await element.updateComplete;

    const text = randString(undefined, minlength - 1);
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.tooShort, 'validityState.tooShort');
  });

  test('[type=text] pattern is validated', async () => {
    const errorMessage = randString();
    const pattern = '[A-Z]{3}';
    element.setAttribute('pattern', pattern);
    element.setAttribute('type', 'text');
    element.setAttribute('validation-message-pattern', errorMessage);

    await element.updateComplete;

    const text = randString();
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.patternMismatch, 'validityState.patternMismatch');
  });

  test('[type=email] is validated', async () => {
    const errorMessage = randString();
    element.setAttribute('type', 'email');
    element.setAttribute('validation-message-type', errorMessage);

    await element.updateComplete;

    let text = `${randString()}@${randString()}.com`;
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isTrue(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.isEmpty(message, 'validationMessage');

    const validityState = element.validity;

    assert.isTrue(validityState.valid, 'validityState.valid');

    text = randString();
    element.value = '';
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const invalid = element.checkValidity();
    assert.isFalse(invalid, 'checkValidity');

    const invalidMessage = element.validationMessage;
    assert.equal(invalidMessage, errorMessage, 'validationMessage');

    const invalidvalidityState = element.validity;
    assert.isFalse(invalidvalidityState.valid, 'validityState.valid');
    assert.isTrue(invalidvalidityState.typeMismatch, 'validityState.typeMismatch');
  });

  test('[type=url] is validated', async () => {
    const errorMessage = randString();
    element.setAttribute('type', 'url');
    element.setAttribute('validation-message-type', errorMessage);

    await element.updateComplete;

    let text = `https://${randString()}.com`;
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isTrue(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.isEmpty(message, 'validationMessage');

    const validityState = element.validity;

    assert.isTrue(validityState.valid, 'validityState.valid');

    text = randString();
    element.value = '';
    await executeServerCommand('fill-text', { selector: `zui-input input`, text });
    element.blur();
    await element.updateComplete;

    const invalid = element.checkValidity();
    assert.isFalse(invalid, 'checkValidity');

    const invalidMessage = element.validationMessage;
    assert.equal(invalidMessage, errorMessage, 'validationMessage');

    const invalidvalidityState = element.validity;
    assert.isFalse(invalidvalidityState.valid, 'validityState.valid');
    assert.isTrue(invalidvalidityState.typeMismatch, 'validityState.typeMismatch');
  });

  test('[type=number] min is validated', async () => {
    const errorMessage = randString();
    const min = 5;
    element.setAttribute('min', `${min}`);
    element.setAttribute('type', 'number');
    element.setAttribute('validation-message-min', errorMessage);

    await element.updateComplete;

    element.value = '4';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.rangeUnderflow, 'validityState.rangeUnderflow');
  });

  test('[type=number] max is validated', async () => {
    const errorMessage = randString();
    const max = 5;
    element.setAttribute('max', `${max}`);
    element.setAttribute('type', 'number');
    element.setAttribute('validation-message-max', errorMessage);

    await element.updateComplete;

    element.value = '6';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.rangeOverflow, 'validityState.rangeOverflow');
  });

  test('[type=number] step is validated', async () => {
    const errorMessage = randString();
    const step = 5;
    element.setAttribute('step', `${step}`);
    element.setAttribute('type', 'number');
    element.setAttribute('validation-message-step', errorMessage);

    await element.updateComplete;

    element.value = '4';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.stepMismatch, 'validityState.stepMismatch');
  });

  test('[type=date] min is validated', async () => {
    const errorMessage = randString();
    const min = '2022-02-02';
    element.setAttribute('min', `${min}`);
    element.setAttribute('type', 'date');
    element.setAttribute('validation-message-min', errorMessage);

    await element.updateComplete;

    element.value = '2022-02-01';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.rangeUnderflow, 'validityState.rangeUnderflow');
  });

  test('[type=date] max is validated', async () => {
    const errorMessage = randString();
    const max = '2022-02-02';
    element.setAttribute('max', `${max}`);
    element.setAttribute('type', 'date');
    element.setAttribute('validation-message-max', errorMessage);

    await element.updateComplete;

    element.value = '2022-02-03';
    element.blur();
    await element.updateComplete;

    const valid = element.checkValidity();
    assert.isFalse(valid, 'checkValidity');

    const message = element.validationMessage;
    assert.equal(message, errorMessage, 'validationMessage');

    const validityState = element.validity;

    assert.isFalse(validityState.valid, 'validityState.valid');
    assert.isTrue(validityState.rangeOverflow, 'validityState.rangeOverflow');
  });
});

faceTests<ZuiInputElement>('zui-input', {
  defaultValue: '',
  valueAccessor(element) {
    return element.value;
  },
});
faceTests<ZuiInputElement>('zui-input', {
  suiteName: 'zui-input[type="color"]',
  value: '#000000',
  defaultValue: '#000000',
  preConfigure(element: ZuiInputElement) {
    element.setAttribute('type', 'color');
  },
  valueAccessor(element) {
    return element.value;
  },
});
faceTests<ZuiInputElement>('zui-input', {
  suiteName: 'zui-input[type="number"]',
  value: '1234',
  defaultValue: '',
  preConfigure(element: ZuiInputElement) {
    element.setAttribute('type', 'number');
  },
  valueAccessor(element) {
    return element.value;
  },
});
faceTests<ZuiInputElement>('zui-input', {
  suiteName: 'zui-input[type="date"]',
  value: '2022-02-13',
  defaultValue: '',
  preConfigure(element: ZuiInputElement) {
    element.setAttribute('type', 'date');
  },
  valueAccessor(element) {
    return element.value;
  },
});
