// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../test/src/custom_typings/chai.d.ts" />
/* eslint-disable no-undef, @typescript-eslint/no-explicit-any */
import { ZuiTabsElement, ZuiTabElement } from '@zywave/zui-tabs';
import { assert } from '@esm-bundle/chai';
import { sendKeys, setViewport } from '@web/test-runner-commands';
import { awaitEvent, sleep } from '@zywave/../../test/src/util/helpers';

suite('zui-tabs', () => {
  let element: ZuiTabsElement;
  let tab1: ZuiTabElement;
  let tab2: ZuiTabElement;

  const viewport = () => {
    const containerEl = element.shadowRoot?.querySelector('#slotted-tabs-container') as HTMLElement;
    return {
      get scroll() {
        return Math.round(containerEl.scrollLeft);
      },
      set scroll(value: number) {
        containerEl.scrollLeft = value;
      },
      left: containerEl.scrollLeft,
      right: containerEl.scrollLeft + containerEl.offsetWidth,
      width: containerEl.offsetWidth,
    };
  };

  const getTabData = (tab: ZuiTabElement) => {
    const xOffset = tab1.offsetLeft;
    return {
      left: tab.offsetLeft - xOffset,
      right: tab.offsetLeft + tab.offsetWidth - xOffset,
      width: tab.offsetWidth,
    };
  };

  setup(() => {
    element = document.createElement('zui-tabs') as ZuiTabsElement;
    tab1 = document.createElement('zui-tab') as ZuiTabElement;
    tab2 = document.createElement('zui-tab') as ZuiTabElement;
    element.appendChild(tab1);
    element.appendChild(tab2);
    document.body.appendChild(element);
  });

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

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

  test('defaults to first tab being selected', async () => {
    await element.updateComplete;
    assert.equal(element.selected, 0);
    assert.isTrue((tab1 as any)._selected);
  });

  test('clicking a tab selects it', async () => {
    await element.updateComplete;
    (tab2?.shadowRoot?.querySelector('.tab') as HTMLElement)?.click();
    await element.updateComplete;
    assert.equal(element.selected, 1);
    assert.isFalse((tab1 as any)._selected);
    assert.isTrue((tab2 as any)._selected);
  });

  test('setting the selected attribute selects a tab', async () => {
    await element.updateComplete;
    element.selected = 1;
    await element.updateComplete;
    await tab1.updateComplete;
    assert.equal(element.selected, 1);
    assert.isFalse((tab1 as any)._selected);
    assert.isTrue((tab2 as any)._selected);
  });

  test('tabs can be selected with space and enter keys', async () => {
    await element.updateComplete;
    await sendKeys({ press: 'Tab' });
    await sendKeys({ press: 'Tab' });
    await tab2.updateComplete;
    await sendKeys({ press: 'Enter' });
    await tab2.updateComplete;
    assert.isTrue((tab2 as any)._selected);
    assert.isFalse((tab1 as any)._selected);
    await sendKeys({ down: 'Shift' });
    await sendKeys({ press: 'Tab' });
    await tab1.updateComplete;
    await sendKeys({ press: 'Space' });
    await tab1.updateComplete;
    assert.isTrue((tab1 as any)._selected);
    assert.isFalse((tab2 as any)._selected);
  });

  // TODO(pat) - tab6 is off by 1px, we need to understand why (and/or decide we don't care about 1px)
  test.skip('selected tab is in view to user', async () => {
    await element.updateComplete;
    const tab3 = document.createElement('zui-tab') as ZuiTabElement;
    const tab4 = document.createElement('zui-tab') as ZuiTabElement;
    const tab5 = document.createElement('zui-tab') as ZuiTabElement;
    const tab6 = document.createElement('zui-tab') as ZuiTabElement;
    tab3.innerText = '3 reallly really long name';
    tab4.innerText = '4 reallly really long name, really really really longggg long name';
    tab5.innerText = '5 shorty';
    tab6.innerText = '6 reallly really long name';
    element.appendChild(tab3);
    element.appendChild(tab4);
    element.appendChild(tab5);
    element.appendChild(tab6);
    await element.updateComplete;

    // on zui-tabs.selected make sure tab is in viewport
    await setViewport({ width: 900, height: 640 });
    element.selected = 5;
    await tab6.updateComplete;
    await element.updateComplete;
    await sleep(2);
    assert.isTrue(
      getTabData(tab6).right <= viewport().right,
      `tab6 right ${getTabData(tab6).right} <= viewport right ${viewport().right}`
    );
    assert.isTrue(
      getTabData(tab6).left > viewport().left,
      `tab6 left ${getTabData(tab6).left} > viewport left ${viewport().left}`
    );

    // on window resize, ensure underline is correctly placed, buttons are shown/hidden
    await setViewport({ width: 300, height: 640 });
    await element.updateComplete;
    await tab6.updateComplete;
    await sleep(2);
    assert.isTrue(
      getTabData(tab6).left >= viewport().left,
      `tab6 left ${getTabData(tab6).left} >= viewport left ${viewport().left}`
    );
    await setViewport({ width: 900, height: 640 });
    await element.updateComplete;
    await tab6.updateComplete;
    await sleep(2);
    assert.isTrue(
      getTabData(tab6).left >= viewport().left,
      `tab6 left ${getTabData(tab6).left} >= viewport left ${viewport().left}`
    );
    assert.isTrue(
      getTabData(tab6).right <= viewport().right,
      `tab6 right ${getTabData(tab6).right} <= viewport right ${viewport().right}`
    );

    // on tab selection, shifts scroll so tab is in view and adjacent left tab is partially visible, if tab is closer to left edge of view versus right edge
    element.selected = 2;
    await tab3.updateComplete;
    await element.updateComplete;
    await sleep(2);
    assert.isTrue(
      getTabData(tab3).right <= viewport().right,
      `tab3 right ${getTabData(tab3).right} <= viewport right ${viewport().right}`
    );
    assert.isTrue(
      getTabData(tab3).left > viewport().left,
      `tab3 left ${getTabData(tab3).left} > viewport left ${viewport().left}`
    );
    assert.equal(
      getTabData(tab3).left - 35,
      viewport().left,
      `tab3 left ${getTabData(tab3).left} - 35 = viewport left ${viewport().left}`
    );

    // test same as above but if selected tab is closer to right edge
    await setViewport({ width: 400, height: 640 });
    element.selected = 4;
    await tab5.updateComplete;
    await element.updateComplete;
    await sleep(2);
    assert.isTrue(
      getTabData(tab5).right <= viewport().right,
      `tab5 right ${getTabData(tab5).right} <= viewport right ${viewport().right}`
    );
    assert.isTrue(
      getTabData(tab5).left > viewport().left,
      `tab5 left ${getTabData(tab5).left} > viewport left ${viewport().left}`
    );
    assert.equal(
      getTabData(tab5).right + 35,
      viewport().right,
      `tab5 right ${getTabData(tab5).right} + 35 = viewport right ${viewport().right}`
    );

    // test that modifying a non selected tab will correctly adjust so currently selected tab is in view
    tab2.innerText = '';
    await tab2.updateComplete;
    await element.updateComplete;
    assert.isTrue(
      getTabData(tab5).right <= viewport().right,
      `tab5 right ${getTabData(tab5).right} <= viewport right ${viewport().right}`
    );
    assert.isTrue(
      getTabData(tab5).left > viewport().left,
      `tab5 left ${getTabData(tab5).left} > viewport left ${viewport().left}`
    );

    // if tab is too wide (wider than viewable screen area) selecting a tab places tab.left value to left edge of viewport scroll
    element.selected = 3;
    await tab4.updateComplete;
    await element.updateComplete;
    await sleep(2);
    assert.equal(
      getTabData(tab4).left,
      viewport().scroll,
      `tab4 left ${getTabData(tab4).left} = viewport scroll ${viewport().scroll}`
    );
  });

  test('scroll buttons conditionally appear when appropriate', async () => {
    await setViewport({ width: 300, height: 640 });
    const buttonLeft = () => element.shadowRoot?.querySelector('#scroll-left') as HTMLButtonElement;
    const buttonRight = () => element.shadowRoot?.querySelector('#scroll-right') as HTMLButtonElement;
    const tab3 = document.createElement('zui-tab') as ZuiTabElement;
    element.appendChild(tab3);

    await element.updateComplete;
    await sleep(20);
    // issue with Safari, it hides buttons but renders them whereas FF and Chrome will not render them if condition is met
    const isNullOrDisabled = (button: HTMLButtonElement) =>
      button === null || button?.classList.contains('disabled') ? true : false;
    assert.isTrue(isNullOrDisabled(buttonLeft()));
    assert.isTrue(isNullOrDisabled(buttonRight()));

    // scroll buttons, if tab content is wider than viewport, show scroll buttons
    tab1.innerText = '1 reallly really really really really long name';
    tab2.innerText = '2 short';
    tab3.innerText = '3 reallly really really really really long name';
    await awaitEvent(element, 'tabscalibrated');
    await sleep(2);

    assert.exists(buttonLeft());
    assert.exists(buttonRight());

    // scroll buttons, if scrolled to left edge, hide left scroll button
    element.selected = 0;
    assert.isTrue(buttonLeft().classList.contains('disabled'));
    assert.isFalse(buttonRight().classList.contains('disabled'));

    // scroll buttons, both appear if not scrolled to left edge AND tab is wider than viewport
    element.selected = 2;
    await awaitEvent(element, 'tabscalibrated');
    await sleep(20);
    assert.isFalse(buttonLeft().classList.contains('disabled'));
    assert.isFalse(buttonRight().classList.contains('disabled'));
  });

  // test('selected tab underline is correctly positioned', async () => {
  //   await setViewport({ width: 800, height: 640 });
  //   await element.updateComplete;
  //   await tab1.updateComplete;
  //   await sleep(10);

  //   const getUnderlineData = () => {
  //     const underline = element.shadowRoot?.querySelector('#selected-underline') as HTMLDivElement;
  //     return {
  //       width: underline.offsetWidth,
  //       left: Number(underline.style.transform.replace('translateX(', '').replace('px)', '')),
  //     };
  //   };
  //   assert.equal(Math.round(getUnderlineData().left), Math.round(getTabData(tab1).left));
  //   assert.equal(Math.round(getUnderlineData().width), Math.round(getTabData(tab1).width));

  //   tab1.innerText = 'now it has text';
  //   await tab1.updateComplete;
  //   await sleep(2);
  //   await awaitEvent(element, 'tabscalibrated');
  //   assert.equal(Math.round(getUnderlineData().left), Math.round(getTabData(tab1).left));
  //   assert.equal(Math.round(getUnderlineData().width), Math.round(getTabData(tab1).width));

  //   element.selected = 1;
  //   await tab2.updateComplete;
  //   await awaitEvent(element, 'tabscalibrated');
  //   await sleep(2);
  //   assert.equal(Math.round(getUnderlineData().left), Math.round(getTabData(tab2).left));
  // });
});
