// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../test/src/custom_typings/chai.d.ts" />
/* eslint-disable no-undef */
import { ZuiInputFile } 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';
import { faceTests } from '../../../../test/src/util/face-tests';
import { conditionalTest } from '../../../../test/src/util/mocha-helpers';
import type { FileInputChangeEventDetail } from '@zywave/zui-input';

const IS_WEBKIT = navigator.userAgent.indexOf('WebKit') !== -1;

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

  setup(() => {
    element = document.createElement('zui-input-file') as ZuiInputFile;
    element.setAttribute('foo', 'World');
    document.body.appendChild(element);
  });

  teardown(() => {
    document.body.removeChild(element);
  });

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

  test('placeholder attribute matches input or lack there of', async () => {
    await element.updateComplete;
    let placeholder = element.shadowRoot!.querySelector('.faux-input span') as HTMLElement;
    assert.isNull(placeholder);

    const placeholderInput = 'I know it is fun to wrestle. A nice pile-drive to the face; or a punch to the face';
    element.placeholder = placeholderInput;
    await element.updateComplete;
    placeholder = element.shadowRoot!.querySelector('.faux-input span') as HTMLElement;
    assert.equal(placeholder.innerText, placeholderInput);
  });

  test('button text attribute matches input or lack there of', async () => {
    await element.updateComplete;
    const buttonText = element.shadowRoot!.querySelector('.button') as HTMLElement;
    assert.equal(buttonText.innerText, '');

    const buttonTextInput = 'Finding Nemo';
    element.buttonText = buttonTextInput;
    await element.updateComplete;
    assert.equal(buttonText.innerText?.trim(), buttonTextInput);
  });

  test('accept attribute value binds to input correctly', async () => {
    const imageFileTypes = 'image/png, image/jpeg';
    element.accept = imageFileTypes;
    await element.updateComplete;

    const inputElement = element.shadowRoot!.querySelector('input') as HTMLElement;
    assert.equal(inputElement.getAttribute('accept'), imageFileTypes);
  });

  test('required attribute value binds to input correctly', async () => {
    await element.updateComplete;
    const inputElement = element.shadowRoot!.querySelector('input') as HTMLElement;
    assert.isNull(inputElement.getAttribute('required'));

    element.setAttribute('required', '');
    await element.updateComplete;
    assert.exists(inputElement.getAttribute('required'));
  });

  test('choosing a file correctly sets the files property', async () => {
    const filename = `${randString()}.txt`;
    await element.updateComplete;
    await executeServerCommand('choose-file', {
      selector: 'zui-input-file input',
      files: [{ name: filename, mimeType: 'text/plain', buffer: ['foo'] }],
    });

    await element.updateComplete;
    assert.isDefined(element.files);
    assert.isNotNull(element.files);
    assert.equal(element.files.length, 1);

    const file = element.files[0];
    assert.equal(file.name, filename);
  });

  test('choosing a file correctly dispatches change event', async () => {
    const filename = `${randString()}.txt`;
    await element.updateComplete;
    const serverCommandPromise = executeServerCommand('choose-file', {
      selector: 'zui-input-file input',
      files: [{ name: filename, mimeType: 'text/plain', buffer: ['foo'] }],
    });

    let changeEvent: CustomEvent<FileInputChangeEventDetail>;
    const changeEventPromise = (async () =>
      (changeEvent = (await awaitEvent(element, 'change')) as CustomEvent<FileInputChangeEventDetail>))();

    await Promise.all([serverCommandPromise, changeEventPromise]);

    assert.exists(changeEvent);
    // this is a legacy test assertion that can safely be removed if and when detail as File is removed
    assert.instanceOf(changeEvent.detail, File);

    assert.equal(changeEvent.detail.files.length, 1);

    const file = changeEvent.detail.files[0];

    assert.equal(file.name, filename);
    assert.equal(changeEvent.detail.name, filename);
  });

  test('disabled attribute value binds to input correctly', async () => {
    await element.updateComplete;
    const inputElement = element.shadowRoot!.querySelector('input') as HTMLElement;
    assert.isNull(inputElement.getAttribute('disabled'));

    element.setAttribute('disabled', '');
    await element.updateComplete;
    assert.exists(inputElement.getAttribute('disabled'));
  });

  conditionalTest(!IS_WEBKIT, 'file drag and drop is functional', async () => {
    await element.updateComplete;
    const content: string = await executeServerCommand('read-file', { path: 'test-file-clippy.webp' });
    const file = new File([content], 'test-file-clippy.webp', { type: 'image/webp' });
    const dataTransfer = new DataTransfer();
    dataTransfer?.items?.add(file);
    assert.isTrue(element.files.length === 0);
    element.dispatchEvent(new DragEvent('drop', { dataTransfer }));
    await element.updateComplete;
    assert.isTrue(element.files.length === 1);
  });

  conditionalTest(!IS_WEBKIT, 'drag and drop two or more files does not add file', async () => {
    await element.updateComplete;
    const content: string = await executeServerCommand('read-file', { path: 'test-file-clippy.webp' });
    const file = new File([content], 'test-file-clippy.webp', { type: 'image/webp' });
    const fileCopy = new File([content], 'test-file-clippy.webp', { type: 'image/webp' });
    const dataTransfer = new DataTransfer();
    dataTransfer?.items?.add(file);
    dataTransfer?.items?.add(fileCopy);
    assert.isTrue(element.files.length === 0);
    element.dispatchEvent(new DragEvent('drop', { dataTransfer }));
    await element.updateComplete;
    assert.isTrue(element.files.length === 0);
  });

  test('dragover adds `.has-file-dragover` class for border highlighting, dragleave removes class', async () => {
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('.has-file-dragover'));
    element.dispatchEvent(new DragEvent('dragover'));
    await element.updateComplete;
    assert.exists(element.shadowRoot.querySelector('.has-file-dragover'));
    element.dispatchEvent(new DragEvent('dragleave'));
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('.has-file-dragover'));
    element.dispatchEvent(new DragEvent('dragenter'));
    await element.updateComplete;
    assert.exists(element.shadowRoot.querySelector('.has-file-dragover'));
  });

  conditionalTest(!IS_WEBKIT, 'file drop if successful updates input text value', async () => {
    const filePath = 'test-file-clippy.webp';
    const content: string = await executeServerCommand('read-file', { path: filePath });
    const file = new File([content], filePath, { type: 'image/webp' });
    const dataTransfer = new DataTransfer();
    dataTransfer?.items?.add(file);
    await element.updateComplete;
    element.dispatchEvent(new DragEvent('drop', { dataTransfer }));
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('.has-file-dragover'));
    assert.equal(element.shadowRoot.querySelector('.value-text-color')?.textContent, filePath);
  });

  test('disabled state syncs with how drag and drop events should be handled ', async () => {
    await element.updateComplete;
    assert.isNull(element.shadowRoot.querySelector('.has-file-dragover'));
    element.dispatchEvent(new DragEvent('dragover'));
    await element.updateComplete;
    assert.exists(element.shadowRoot.querySelector('.has-file-dragover'));
    element.dispatchEvent(new DragEvent('dragleave'));
    element.disabled = true;
    await element.updateComplete;
    element.dispatchEvent(new DragEvent('dragover'));
    assert.isNull(element.shadowRoot.querySelector('.has-file-dragover'));
    element.disabled = false;
    await element.updateComplete;
    element.dispatchEvent(new DragEvent('dragover'));
    assert.exists(element.shadowRoot.querySelector('.has-file-dragover'));
  });

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

    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');
  });
});

faceTests<ZuiInputFile>('zui-input-file', {
  async postConnectedConfigure(fileInput: ZuiInputFile) {
    const filename = `${randString()}.txt`;
    await fileInput.updateComplete;
    await executeServerCommand('choose-file', {
      selector: `zui-input-file#${fileInput.id} input`,
      files: [{ name: filename, mimeType: 'text/plain', buffer: ['foo'] }],
    });
  },
  valueAccessor(fileInput: ZuiInputFile) {
    return fileInput.files?.[0];
  },
});
