import { ApproxStructure, Assertions, FocusTools, Keyboard, Keys, Mouse, Touch, UiFinder, Waiter } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { Arr, Fun, Future, Optional, Result } from '@ephox/katamari';
import { Attribute } from '@ephox/sugar';

import * as Behaviour from 'ephox/alloy/api/behaviour/Behaviour';
import { Positioning } from 'ephox/alloy/api/behaviour/Positioning';
import type { AlloyComponent } from 'ephox/alloy/api/component/ComponentApi';
import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory';
import * as Memento from 'ephox/alloy/api/component/Memento';
import * as AlloyTriggers from 'ephox/alloy/api/events/AlloyTriggers';
import * as SystemEvents from 'ephox/alloy/api/events/SystemEvents';
import { Container } from 'ephox/alloy/api/ui/Container';
import { SplitDropdown } from 'ephox/alloy/api/ui/SplitDropdown';
import { tieredMenu as TieredMenu } from 'ephox/alloy/api/ui/TieredMenu';
import type { TestItem } from 'ephox/alloy/test/dropdown/TestDropdownMenu';
import * as TestDropdownMenu from 'ephox/alloy/test/dropdown/TestDropdownMenu';
import * as GuiSetup from 'ephox/alloy/test/GuiSetup';

UnitTest.asynctest('SplitDropdown List', (success, failure) => {

  const sink = Memento.record(
    Container.sketch({
      containerBehaviours: Behaviour.derive([
        Positioning.config({
          useFixed: Fun.always
        })
      ])
    })
  );

  GuiSetup.setup((store, _doc, _body) => {
    const c = GuiFactory.build(
      SplitDropdown.sketch({
        dom: {
          tag: 'span',
          classes: [ 'test-split-dropdown' ],
          attributes: {
            'data-test-id': 'split-dropdown'
          }
        },

        toggleClass: 'test-selected-dropdown',
        onExecute: (dropdown, button) => {
          const arg0Name = Attribute.get(dropdown.element, 'data-test-id');
          const arg1Name = Attribute.get(button.element, 'data-test-id');
          store.adderH('dropdown.execute(' + arg0Name + ', ' + arg1Name + ')')();
        },
        onItemExecute: (dropdown, tieredMenu, item) => {
          const arg0Name = Attribute.get(dropdown.element, 'data-test-id');
          const arg1Name = Attribute.get(tieredMenu.element, 'data-test-id');
          const arg2Name = Attribute.get(item.element, 'data-test-id');
          AlloyTriggers.emit(item, SystemEvents.sandboxClose());
          store.adderH('dropdown.item.execute(' + [ arg0Name, arg1Name, arg2Name ].join(', ') + ')')();
        },

        components: [
          SplitDropdown.parts.button({
            dom: {
              tag: 'button',
              classes: [ 'test-split-button-action' ],
              attributes: {
                'data-test-id': 'split-dropdown-button'
              }
            },
            components: [
              {
                dom: {
                  tag: 'div',
                  innerHtml: 'hi'
                }
              }
            ]
          }),
          SplitDropdown.parts.arrow({
            dom: {
              tag: 'button',
              innerHtml: 'v',
              classes: [ 'test-split-button-arrow' ],
              attributes: {
                'data-test-id': 'split-dropdown-arrow'
              }
            }
          })
        ],

        lazySink: (c: AlloyComponent) => {
          TestDropdownMenu.assertLazySinkArgs('span', 'test-split-dropdown', c);
          // Only gets here is the above assertion passes.
          return Result.value(sink.get(c));
        },

        parts: {
          menu: {
            dom: {
              tag: 'div',
              attributes: {
                'data-test-id': 'split-tiered-menu'
              }
            },
            markers: TestDropdownMenu.markers()
          }
        },

        fetch: () => {
          const future = Future.pure<TestItem[]>([
            { type: 'item', data: { value: 'alpha', meta: { text: 'Alpha' }}},
            { type: 'item', data: { value: 'beta', meta: { text: 'Beta' }}}
          ]);

          return future.map((f) => {
            const menu = TestDropdownMenu.renderMenu({
              value: 'split-dropdown-test',
              items: Arr.map(f, TestDropdownMenu.renderItem)
            });
            return Optional.some(TieredMenu.singleData('test', menu));
          });
        }
      })
    );

    return c;

  }, (doc, _body, gui, component, store) => {
    gui.add(
      GuiFactory.build(sink.asSpec())
    );

    return [
      Assertions.sAssertStructure(
        'Check basic initial structure',
        ApproxStructure.build((s, str, _arr) => s.element('span', {
          attrs: {
            'role': str.is('button'),
            'aria-expanded': str.is('false'),
            'aria-haspopup': str.is('true'),
            'tabindex': str.is('-1')
          },

          children: [
            s.element('span', {
              attrs: {
                role: str.is('presentation'),
                tabindex: str.none()
              }
            }),
            s.element('span', {
              attrs: {
                role: str.is('presentation'),
                tabindex: str.none()
              }
            })
          ]
        })),
        component.element
      ),

      store.sClear,
      store.sAssertEq('Should be empty', [ ]),
      Mouse.sClickOn(gui.element, '.test-split-button-action'),
      store.sAssertEq('After clicking on action', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]),
      UiFinder.sNotExists(gui.element, '[role="menu"]'),
      store.sClear,

      Mouse.sClickOn(gui.element, '.test-split-button-arrow'),
      store.sAssertEq('After clicking on action', [ ]),
      Waiter.sTryUntil(
        'Waiting until menu appears',
        UiFinder.sExists(gui.element, '[role="menu"]')
      ),
      FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'),
      Assertions.sAssertStructure(
        'Check menu opened structure',
        ApproxStructure.build((s, str, arr) => s.element('span', {
          attrs: {
            'role': str.is('button'),
            'aria-expanded': str.is('true'),
            'aria-haspopup': str.is('true'),
            'aria-controls': str.contains('aria-controls')
          },

          children: [
            s.element('span', {
              attrs: {
                role: str.is('presentation')
              }
            }),
            s.element('span', {
              attrs: {
                role: str.is('presentation')
              },
              classes: [ arr.has('test-selected-dropdown') ]
            })
          ]
        })),
        component.element
      ),
      Keyboard.sKeyup(doc, Keys.escape(), { }),
      UiFinder.sNotExists(gui.element, '[role="menu"]'),

      // Now, let's do some keyboard testing. Pressing space and enter should trigger execute
      store.sAssertEq('Before keyboard testing: should be clear', [ ]),
      Keyboard.sKeydown(doc, Keys.space(), { }),
      store.sAssertEq('After space on button', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]),
      // NOTE: this sNotExists isn't 100% fool-proof. We should probably wait first because
      // it is async ... however, how long to wait?
      UiFinder.sNotExists(gui.element, '[role="menu"]'),
      store.sClear,
      Keyboard.sKeydown(doc, Keys.enter(), { }),
      store.sAssertEq('After enter on button', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]),
      UiFinder.sNotExists(gui.element, '[role="menu"]'),
      store.sClear,

      Keyboard.sKeydown(doc, Keys.down(), { }),
      store.sAssertEq('After down on button', [ ]),
      Waiter.sTryUntil(
        'Waiting until menu appears',
        UiFinder.sExists(gui.element, '[role="menu"]')
      ),
      FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'),

      // Now press enter on one of the items
      Keyboard.sKeydown(doc, Keys.enter(), { }),
      store.sAssertEq('After enter on item', [ 'dropdown.item.execute(split-dropdown, split-tiered-menu, item-alpha)' ]),
      // NOTE: This is due to the itemExecute handler here.
      UiFinder.sNotExists(gui.element, '[role="menu"]'),
      store.sClear,

      // Test to make sure tapping on the button/arrow also works
      store.sAssertEq('Should be empty', [ ]),
      Touch.sTapOn(gui.element, '.test-split-button-action'),
      store.sAssertEq('After tapping on action', [ 'dropdown.execute(split-dropdown, split-dropdown-button)' ]),
      UiFinder.sNotExists(gui.element, '[role="menu"]'),
      store.sClear,

      Touch.sTapOn(gui.element, '.test-split-button-arrow'),
      store.sAssertEq('After tapping on action', [ ]),
      Waiter.sTryUntil(
        'Waiting until menu appears',
        UiFinder.sExists(gui.element, '[role="menu"]')
      ),
      FocusTools.sTryOnSelector('Focus should be on alpha', doc, 'li:contains("Alpha")'),
      store.sClear
    ];
  }, success, failure);
});
