// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../test/src/custom_typings/chai.d.ts" />
/* eslint-disable no-undef */
import { ZuiFormField } from '@zywave/zui-formfield';
import { ZuiFormAssociatedElement } from '@zywave/zui-base';
import { html } from 'lit';
import { assert } from '@esm-bundle/chai';
import { buildForm } from '../../../../test/src/util/form-helpers.js';
import { awaitEvent } from '../../../../test/src/util/helpers.js';

suite('zui-formfield', () => {
  let form: HTMLFormElement;
  let formField: ZuiFormField;
  let formFieldInput: HTMLInputElement;

  setup(async () => {
    formField = document.createElement('zui-formfield') as ZuiFormField;
    formField.label = 'Label 1';
    formFieldInput = document.createElement('input');
    formFieldInput.setAttribute('name', 'input1');
    formField.appendChild(formFieldInput);

    form = buildForm({
      appendChildren: [formField],
      numInputs: 0,
    });

    await formField.updateComplete;
  });

  teardown(() => {
    formField.remove();
    formFieldInput.remove();
    form.remove();

    formFieldInput = null;
    formField = null;
    form = null;
  });

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

  test('clicking on a label focuses the associated label control', async () => {
    await formField.updateComplete;

    const formFieldLabel = formField.shadowRoot.querySelector('label') as HTMLElement;
    formFieldLabel.click();

    assert.equal(
      document.activeElement,
      formFieldInput,
      'clicking on a label did not set the active element to the associated label control'
    );
  });

  test('shows validation message when control is invalid', async () => {
    formFieldInput.setAttribute('required', '');
    formFieldInput.value = '';

    form.requestSubmit();
    await formField.updateComplete;

    const validationMessage = formField.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isNotEmpty(validationMessage?.textContent, 'validation message text is missing');
  });

  test("doesn't show validation message when control is valid", async () => {
    formFieldInput.setAttribute('required', '');
    formFieldInput.value = 'value';

    form.requestSubmit();
    await formField.updateComplete;

    const validationMessage = formField.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isEmpty(validationMessage?.textContent, 'validation message text should be empty for valid control');
  });

  test('validation message updates when control validity changes', async () => {
    formFieldInput.setAttribute('required', '');
    formFieldInput.value = '';

    form.requestSubmit();
    await formField.updateComplete;

    let validationMessage = formField.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isNotEmpty(validationMessage?.textContent, 'validation message should not be empty for invalid control');

    formFieldInput.value = 'new value';

    formFieldInput.dispatchEvent(new Event('blur'));

    form.requestSubmit();
    await formField.updateComplete;

    validationMessage = formField.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isEmpty(validationMessage?.textContent, 'validation message should be empty for valid control');
  });

  test('clears validation message when setCustomValidity is called from a ZuiFormAssociatedElement', async () => {
    const tagName = 'test-face-setcustomvalidity';
    class SetCustomValidityElement extends ZuiFormAssociatedElement {
      get _focusControlSelector() {
        return 'input';
      }

      render() {
        return html`<input />`;
      }
    }
    window.customElements.define(tagName, SetCustomValidityElement);

    const newFormfield = document.createElement('zui-formfield');
    newFormfield.label = 'Custom Validity Test';
    const newInput = document.createElement(tagName) as SetCustomValidityElement;
    newInput.setAttribute('name', 'input2');
    newFormfield.appendChild(newInput);

    const newForm = buildForm({
      appendChildren: [newFormfield],
      numInputs: 0,
    });

    await newInput.updateComplete;

    await Promise.allSettled([
      awaitEvent(newInput, 'validitystatechange'),
      newInput.dispatchEvent(new Event('blur')), // simulate user interaction
      newInput.setCustomValidity('Custom error message'),
      newFormfield.updateComplete,
    ]);
    newForm.requestSubmit();

    await newFormfield.updateComplete;

    let validationMessage = newFormfield.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isNotEmpty(validationMessage?.textContent, 'validation message should not be empty after setCustomValidity');

    await Promise.allSettled([
      awaitEvent(newInput, 'validitystatechange'),
      newInput.setCustomValidity(''),
      newFormfield.updateComplete,
    ]);

    newForm.requestSubmit();
    await newFormfield.updateComplete;

    validationMessage = newFormfield.shadowRoot.querySelector('.validation-message') as HTMLElement;
    assert.isEmpty(validationMessage?.textContent, 'validation message should be empty after clearing custom validity');

    newForm.remove();
  });

  suite('required indicator', () => {
    test('adds required class to label when control has required attribute', async () => {
      formFieldInput.setAttribute('required', '');

      // Manually trigger the slot change handler
      const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
      slot.dispatchEvent(new Event('slotchange'));

      await formField.updateComplete;

      const label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isTrue(
        label?.classList.contains('required'),
        'label should have required class when control has required attribute'
      );
    });

    test('does not add required class to label when control does not have required attribute', async () => {
      await formField.updateComplete;

      const label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isFalse(
        label?.classList.contains('required'),
        'label should not have required class when control does not have required attribute'
      );
    });

    test('updates required class when required attribute is added dynamically', async () => {
      await formField.updateComplete;

      let label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isFalse(label?.classList.contains('required'), 'label should not have required class initially');

      formFieldInput.setAttribute('required', '');

      // Manually trigger the slot change handler
      const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
      slot.dispatchEvent(new Event('slotchange'));

      await formField.updateComplete;

      label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isTrue(
        label?.classList.contains('required'),
        'label should have required class after required attribute is added'
      );
    });

    test('updates required class when required attribute is removed dynamically', async () => {
      formFieldInput.setAttribute('required', '');

      // Manually trigger the slot change handler to apply required class
      const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
      slot.dispatchEvent(new Event('slotchange'));

      await formField.updateComplete;

      let label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isTrue(label?.classList.contains('required'), 'label should have required class initially');

      formFieldInput.removeAttribute('required');

      // Manually trigger the slot change handler again to remove required class
      slot.dispatchEvent(new Event('slotchange'));

      await formField.updateComplete;

      label = formField.shadowRoot.querySelector('label') as HTMLElement;
      assert.isFalse(
        label?.classList.contains('required'),
        'label should not have required class after required attribute is removed'
      );
    });
  });

  suite('tooltip functionality', () => {
    test('renders zui-tooltip when tooltip attribute is provided', async () => {
      const tooltipText = 'This is help text';
      formField.tooltip = tooltipText;

      await formField.updateComplete;

      const tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
      assert.exists(tooltip, 'zui-tooltip should exist');
      assert.equal(tooltip.textContent.trim(), tooltipText);
    });

    test('does not render zui-tooltip when tooltip attribute is not provided', async () => {
      await formField.updateComplete;

      const tooltip = formField.shadowRoot.querySelector('zui-tooltip');
      assert.notExists(tooltip, 'zui-tooltip should not exist');
    });

    test('tooltip position is set to right', async () => {
      formField.tooltip = 'Help text';

      await formField.updateComplete;

      const tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
      assert.equal(tooltip.getAttribute('position'), 'right');
    });

    test('does not render tooltip when label is not provided', async () => {
      const formFieldNoLabel = document.createElement('zui-formfield') as ZuiFormField;
      formFieldNoLabel.tooltip = 'Help text';
      const inputNoLabel = document.createElement('input');
      formFieldNoLabel.appendChild(inputNoLabel);
      document.body.appendChild(formFieldNoLabel);

      await formFieldNoLabel.updateComplete;

      const tooltip = formFieldNoLabel.shadowRoot.querySelector('zui-tooltip');
      assert.notExists(tooltip, 'tooltip should not render without a label');

      formFieldNoLabel.remove();
      inputNoLabel.remove();
    });

    test('tooltip updates when tooltip property changes', async () => {
      const initialText = 'Initial help text';
      const updatedText = 'Updated help text';

      formField.tooltip = initialText;
      await formField.updateComplete;

      let tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
      assert.equal(tooltip.textContent.trim(), initialText);

      formField.tooltip = updatedText;
      await formField.updateComplete;

      tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
      assert.equal(tooltip.textContent.trim(), updatedText);
    });

    test('tooltip is removed when tooltip property is set to null', async () => {
      formField.tooltip = 'Help text';
      await formField.updateComplete;

      let tooltip = formField.shadowRoot.querySelector('zui-tooltip');
      assert.exists(tooltip, 'tooltip should exist initially');

      formField.tooltip = null;
      await formField.updateComplete;

      tooltip = formField.shadowRoot.querySelector('zui-tooltip');
      assert.notExists(tooltip, 'tooltip should not exist after being set to null');
    });
  });
});
