import { ApproxStructure, Assertions, Mouse, Waiter } from '@ephox/agar';
import { type AlloyComponent, GuiFactory } from '@ephox/alloy';
import { afterEach, describe, it } from '@ephox/bedrock-client';
import type { Menu, Toolbar } from '@ephox/bridge';
import { Cell, Fun, Optional } from '@ephox/katamari';
import { Attribute, Class, SelectorFind } from '@ephox/sugar';
import { assert } from 'chai';

import { renderMenuButton } from 'tinymce/themes/silver/ui/button/MenuButton';
import { renderSplitButton, renderToolbarButton, renderToolbarToggleButton } from 'tinymce/themes/silver/ui/toolbar/button/ToolbarButtons';

import * as GuiSetup from '../../module/GuiSetup';
import TestBackstage from '../../module/TestBackstage';
import * as TestExtras from '../../module/TestExtras';

describe('headless.tinymce.themes.silver.toolbar.ToolbarButtonsTest', () => {
  const shouldDisable = Cell(false);
  const shouldActivate = Cell(false);
  const extrasHook = TestExtras.bddSetup();

  const hook = GuiSetup.bddSetup((store, _doc, _body) => GuiFactory.build({
    dom: {
      tag: 'div'
    },
    components: [
      {
        dom: {
          tag: 'div',
          classes: [ 'button1-container' ]
        },
        components: [
          renderToolbarButton({
            context: 'any',
            type: 'button',
            shortcut: Optional.none(),
            enabled: true,
            tooltip: Optional.some('tooltip'),
            icon: Optional.none(),
            text: Optional.some('button1'),
            onSetup: (_api: Toolbar.ToolbarButtonInstanceApi) => {
              store.adder('onSetup.1')();
              return Fun.noop;
            },
            onAction: (api: Toolbar.ToolbarButtonInstanceApi) => {
              store.adder('onAction.1')();
              api.setEnabled(!shouldDisable.get());
            }
          }, TestBackstage().shared.providers)
        ]
      },

      {
        dom: {
          tag: 'div',
          classes: [ 'button2-container' ]
        },
        components: [
          renderToolbarToggleButton({
            context: 'any',
            type: 'togglebutton',
            shortcut: Optional.none(),
            enabled: true,
            active: false,
            tooltip: Optional.some('tooltip'),
            icon: Optional.none(),
            text: Optional.some('button2'),
            onSetup: (_api: Toolbar.ToolbarToggleButtonInstanceApi) => {
              store.adder('onSetup.2')();
              return Fun.noop;
            },
            onAction: (api: Toolbar.ToolbarToggleButtonInstanceApi) => {
              store.adder('onToggleAction.2')();
              api.setEnabled(!shouldDisable.get());
              api.setActive(shouldActivate.get());
            }
          }, TestBackstage().shared.providers)
        ]
      },

      {
        dom: {
          tag: 'div',
          classes: [ 'button3-container' ]
        },
        components: [
          ...renderSplitButton({
            context: 'any',
            type: 'splitbutton',
            tooltip: Optional.some('tooltip'),
            chevronTooltip: Optional.none(),
            icon: Optional.none(),
            text: Optional.some('button3'),
            columns: 1,
            presets: 'normal',
            select: Optional.none(),
            fetch: (callback) => {
              callback([
                {
                  type: 'choiceitem',
                  text: 'Item 1',
                  value: 'item1'
                }
              ]);
            },
            onSetup: (_api: Toolbar.ToolbarSplitButtonInstanceApi) => {
              store.adder('onSetup.3')();
              return Fun.noop;
            },
            onAction: (api: Toolbar.ToolbarSplitButtonInstanceApi) => {
              store.adder('onToggleAction.3')();
              api.setEnabled(!shouldDisable.get());
              api.setActive(shouldActivate.get());
            },
            onItemAction: (api: Toolbar.ToolbarSplitButtonInstanceApi, _value: string) => {
              store.adder('onItemAction.3')();
              api.setActive(true);
            }
          }, extrasHook.access().extras.backstages.popup.shared)
        ]
      },

      {
        dom: {
          tag: 'div',
          classes: [ 'button4-container' ]
        },
        components: [
          renderMenuButton({
            context: 'any',
            buttonType: 'default',
            tooltip: Optional.some('tooltip'),
            icon: Optional.none(),
            text: Optional.some('button4'),
            search: Optional.none(),
            fetch: (callback) => {
              callback([
                {
                  type: 'menuitem',
                  text: 'Item 1',
                  onAction: (_api: Menu.MenuItemInstanceApi) => {
                    store.adder('onAction.4')();
                  }
                }
              ]);
            },
            onSetup: (_api: Toolbar.ToolbarMenuButtonInstanceApi) => {
              store.adder('onSetup.4')();
              return Fun.noop;
            }
          }, 'tox-mbtn', extrasHook.access().extras.backstages.popup, Optional.none())
        ]
      }
    ]
  }));

  const getButton = (selector: string) => {
    const component = hook.component();
    return component.getSystem().getByDom(
      SelectorFind.descendant(component.element, selector).getOrDie(
        `Could not find button defined by: ${selector}`
      )
    ).getOrDie();
  };

  const assertButtonDisabledState = (label: string, expected: boolean, button: AlloyComponent) => {
    assert.equal(Class.has(button.element, 'tox-tbtn--disabled'), expected, 'Checking if disabled class is present: ' + label);
  };

  const assertButtonActiveState = (label: string, expected: boolean, button: AlloyComponent) => {
    assert.equal(Class.has(button.element, 'tox-tbtn--enabled'), expected, label);
  };

  const assertSplitButtonDisabledState = (label: string, expected: boolean, button: AlloyComponent) => {
    assert.equal(Attribute.get(button.element, 'aria-disabled') === 'true', expected, 'Checking if aria-disabled attr is present: ' + label);
    assert.equal(Class.has(button.element, 'tox-tbtn--disabled'), expected, 'Checking if disabled class is present: ' + label);
  };

  const assertSplitButtonActiveState = (label: string, expected: boolean, button: AlloyComponent) => {
    assert.equal(Attribute.get(button.element, 'aria-pressed') === 'true', expected, label);
  };

  afterEach(() => {
    shouldDisable.set(false);
    shouldActivate.set(false);
  });

  it('Check initial event state', () => {
    const store = hook.store();
    store.assertEq('Store should have setups only', [ 'onSetup.1', 'onSetup.2', 'onSetup.3', 'onSetup.4' ]);
  });

  it('First button (button1): normal button', () => {
    const component = hook.component();
    const store = hook.store();
    store.clear();

    const button1 = getButton('.button1-container .tox-tbtn');
    Assertions.assertStructure(
      'Checking initial structure',
      ApproxStructure.build((s, str, arr) => s.element('button', {
        classes: [ arr.has('tox-tbtn') ],
        attrs: {
          'type': str.is('button'),
          'aria-label': str.is('tooltip')
        },
        children: [
          s.element('span', {
            classes: [ arr.has('tox-tbtn__select-label') ]
          })
        ]
      })),
      button1.element
    );
    Mouse.clickOn(component.element, '.button1-container .tox-tbtn');
    store.assertEq('Store should now have action1', [ 'onAction.1' ]);
    assertButtonDisabledState('Enabled', false, button1);
    store.clear();

    shouldDisable.set(true);
    Mouse.clickOn(component.element, '.button1-container .tox-tbtn');
    store.assertEq('Store have action', [ 'onAction.1' ]);
    assertButtonDisabledState('Disabled', true, button1);
    store.clear();
    Mouse.clickOn(component.element, '.button1-container .tox-tbtn');
    store.assertEq('No actions should come through a disabled button', [ ]);
  });

  it('Second button (button2): toggle button', () => {
    const component = hook.component();
    const store = hook.store();
    store.clear();

    const button2 = getButton('.button2-container .tox-tbtn');
    Mouse.clickOn(component.element, '.button2-container .tox-tbtn');
    store.assertEq('Store should have action2', [ 'onToggleAction.2' ]);
    store.clear();
    assertButtonDisabledState('Enabled', false, button2);
    assertButtonActiveState('Off', false, button2);

    shouldActivate.set(true);
    Mouse.clickOn(component.element, '.button2-container .tox-tbtn');
    store.assertEq('Store should have action2', [ 'onToggleAction.2' ]);
    store.clear();
    assertButtonDisabledState('Disabled', false, button2);
    assertButtonActiveState('Off', true, button2);

    shouldActivate.set(false);
    Mouse.clickOn(component.element, '.button2-container .tox-tbtn');
    store.assertEq('Store should have action2', [ 'onToggleAction.2' ]);
    store.clear();
    assertButtonDisabledState('Disabled', false, button2);
    assertButtonActiveState('Off', false, button2);

    shouldDisable.set(true);
    Mouse.clickOn(component.element, '.button2-container .tox-tbtn');
    store.assertEq('Store should now have action2', [ 'onToggleAction.2' ]);
    store.clear();
    assertButtonDisabledState('Disabled', true, button2);
    assertButtonActiveState('Off still', false, button2);
  });

  it('Third button (button3): split button', async () => {
    const body = hook.body();
    const component = hook.component();
    const store = hook.store();
    store.clear();

    const button3 = getButton('.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    Assertions.assertStructure(
      'Checking initial structure',
      ApproxStructure.build((s, str, arr) => s.element('button', {
        classes: [ arr.has('tox-tbtn') ],
        attrs: {
          'type': str.is('button'),
          'aria-label': str.is('tooltip')
        }
      })),
      button3.element
    );

    // Toggle button
    Mouse.clickOn(component.element, '.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    store.assertEq('Store should have action3', [ 'onToggleAction.3' ]);
    store.clear();
    assertSplitButtonDisabledState('Enabled', false, button3);
    assertSplitButtonActiveState('Off', false, button3);

    // Menu item selected
    Mouse.clickOn(component.element, '.button3-container .tox-split-button__chevron');
    await Waiter.pTryUntil('Wait for split button menu item to show',
      () => Mouse.clickOn(body, '.tox-collection .tox-collection__item')
    );
    store.assertEq('Store should have item action3', [ 'onItemAction.3' ]);
    store.clear();
    assertSplitButtonDisabledState('Enabled', false, button3);
    assertSplitButtonActiveState('Off', true, button3);

    shouldActivate.set(true);
    Mouse.clickOn(component.element, '.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    store.assertEq('Store should have action3', [ 'onToggleAction.3' ]);
    store.clear();
    assertSplitButtonDisabledState('Disabled', false, button3);
    assertSplitButtonActiveState('Off', true, button3);

    shouldActivate.set(false);
    Mouse.clickOn(component.element, '.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    store.assertEq('Store should have action3', [ 'onToggleAction.3' ]);
    store.clear();
    assertSplitButtonDisabledState('Disabled', false, button3);
    assertSplitButtonActiveState('Off', false, button3);

    shouldDisable.set(true);
    Mouse.clickOn(component.element, '.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    store.assertEq('Store should now have action3', [ 'onToggleAction.3' ]);
    store.clear();
    assertSplitButtonDisabledState('Disabled', true, button3);
    assertSplitButtonActiveState('Off still', false, button3);

    // TINY-9504: The button is disabled now. Clicking on it should not call onAction callback.
    Mouse.clickOn(component.element, '.button3-container .tox-tbtn:not(.tox-split-button__chevron)');
    store.assertEq('Store should not have action3', [ ]);
    assertSplitButtonDisabledState('Disabled', true, button3);
    assertSplitButtonActiveState('Off still', false, button3);
  });

  it('Fourth button (button4): menu button', async () => {
    const body = hook.body();
    const component = hook.component();
    const store = hook.store();
    store.clear();

    const button4 = getButton('.button4-container .tox-mbtn');
    Assertions.assertStructure(
      'Checking initial structure',
      ApproxStructure.build((s, str, arr) => s.element('button', {
        classes: [
          arr.has('tox-mbtn'),
          arr.has('tox-mbtn--select')
        ],
        attrs: {
          'type': str.is('button'),
          'aria-label': str.is('tooltip'),
          'aria-expanded': str.is('false'),
          'aria-haspopup': str.is('true')
        },
        children: [
          s.element('span', {
            classes: [ arr.has('tox-mbtn__select-label') ]
          }),
          s.element('div', {
            classes: [ arr.has('tox-mbtn__select-chevron') ]
          })
        ]
      })),
      button4.element
    );

    // Select menu item
    Mouse.clickOn(component.element, '.button4-container .tox-mbtn');
    await Waiter.pTryUntil('Wait for button menu to show',
      () => Mouse.clickOn(body, '.tox-collection .tox-collection__item')
    );
    store.assertEq('Store should have item action4', [ 'onAction.4' ]);
  });
});
