import { ApproxStructure, Assertions, Chain, Step, type StructAssert } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { Result } from '@ephox/katamari';

import type { AlloyComponent } from 'ephox/alloy/api/component/ComponentApi';
import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory';
import { Button } from 'ephox/alloy/api/ui/Button';
import { Input } from 'ephox/alloy/api/ui/Input';
import { SlotContainer } from 'ephox/alloy/api/ui/SlotContainer';
import * as GuiSetup from 'ephox/alloy/test/GuiSetup';

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

  GuiSetup.setup((store, _doc, _body) => GuiFactory.build(
    SlotContainer.sketch((parts) => ({
      dom: {
        tag: 'div',
        classes: [ 'test-slot-container' ]
      },
      components: [
        parts.slot('inputA', Input.sketch({ inputClasses: [ 'slot-input' ] })),
        {
          dom: {
            tag: 'p',
            classes: [ 'something-else' ],
            innerHtml: 'Hello'
          }
        },
        {
          dom: {
            tag: 'div',
            classes: [ 'slot-wrapper' ]
          },
          components: [
            parts.slot('buttonB', Button.sketch({
              dom: {
                tag: 'button',
                classes: [ 'slot-button' ],
                innerHtml: 'Patchy button'
              },
              action: store.adder('slot-button')
            }))
          ]
        }
      ]
    }))
  ), (_doc, _body, _gui, component, _store) => {

    const cGetSlot = (slot: string) => Chain.binder(() => SlotContainer.getSlot(component, slot).fold(
      () => Result.error<AlloyComponent, string>('Could not find slot: ' + slot),
      Result.value
    ));

    const sAssertSlotStructure = (label: string, slot: string, expectedStructure: ApproxStructure.Builder<StructAssert>) => Chain.asStep({ }, [
      cGetSlot(slot),
      Chain.op((c) => {
        Assertions.assertStructure(
          label,
          ApproxStructure.build(expectedStructure),
          c.element
        );
      })
    ]);

    const sAssertButtonShowing = (label: string) => sAssertSlotStructure(label, 'buttonB', (s, str, _arr) => s.element('button', {
      attrs: {
        'aria-hidden': str.none()
      },
      styles: {
        display: str.none()
      }
    }));

    const sAssertButtonHidden = (label: string) => sAssertSlotStructure(label, 'buttonB', (s, str, _arr) => s.element('button', {
      attrs: {
        'aria-hidden': str.is('true')
      },
      styles: {
        display: str.is('none')
      }
    }));

    const sAssertInputShowing = (label: string) => sAssertSlotStructure(label, 'inputA', (s, str, _arr) => s.element('input', {
      attrs: {
        'aria-hidden': str.none()
      },
      styles: {
        display: str.none()
      }
    }));

    const sAssertInputHidden = (label: string) => sAssertSlotStructure(label, 'inputA', (s, str, _arr) => s.element('input', {
      attrs: {
        'aria-hidden': str.is('true')
      },
      styles: {
        display: str.is('none')
      }
    }));

    const cSlotShowing = (slot: string) => Chain.mapper(() => SlotContainer.isShowing(component, slot));

    const sAssertSlotShowing = (slot: string, expectedShowing: boolean) => (label: string) => Chain.asStep({}, [
      cSlotShowing(slot),
      Assertions.cAssertEq('SlotContainer.isShowing(_,"' + slot + '") !== ' + expectedShowing + ' - ' + label, expectedShowing)
    ]);

    const sAssertButtonSlotShowing = sAssertSlotShowing('buttonB', true);
    const sAssertButtonSlotHidden = sAssertSlotShowing('buttonB', false);
    const sAssertInputSlotShowing = sAssertSlotShowing('inputA', true);
    const sAssertInputSlotHidden = sAssertSlotShowing('inputA', false);

    const cGetSlotNames = () => Chain.mapper(() => SlotContainer.getSlotNames(component));

    const sAssertGetSlotNames = (label: string, expectedSlots: string[]) => Chain.asStep({}, [
      cGetSlotNames(),
      Assertions.cAssertEq(label, expectedSlots)
    ]);

    const sShowSlot = (slot: string) => Step.sync(() => {
      SlotContainer.showSlot(component, slot);
    });

    const sHideSlot = (slot: string) => Step.sync(() => {
      SlotContainer.hideSlot(component, slot);
    });

    const sHideAllSlots = () => Step.sync(() => {
      SlotContainer.hideAllSlots(component);
    });

    return [
      Assertions.sAssertStructure(
        'Checking initial structure',
        ApproxStructure.build((s, _str, arr) => s.element('div', {
          classes: [ arr.has('test-slot-container') ],
          children: [
            s.element('input', { classes: [ arr.has('slot-input') ] }),
            s.element('p', { classes: [ arr.has('something-else') ] }),
            s.element('div', {
              classes: [ arr.has('slot-wrapper') ],
              children: [
                s.element('button', {
                  classes: [ arr.has('slot-button') ]
                })
              ]
            })
          ]
        })),
        component.element
      ),

      sAssertButtonShowing('button: Before any APIs are called'),
      sAssertInputShowing('input: Before any APIs are called'),
      sAssertInputSlotShowing('before any APIs are called'),
      sAssertButtonSlotShowing('before any APIs are called'),
      sHideSlot('buttonB'),
      sAssertButtonHidden('button: After SlotContainer.hideSlot(_, button)'),
      sAssertButtonSlotHidden('After SlotContainer.hideSlot(_, button)'),
      sAssertInputShowing('input: After SlotContainer.hideSlot(_, button)'),
      sAssertInputSlotShowing('After SlotContainer.hideSlot(_, button)'),
      sShowSlot('buttonB'),
      sAssertButtonShowing('button: After SlotContainer.showSlot(_, button)'),
      sAssertButtonSlotShowing('After SlotContainer.showSlot(_, button)'),
      sAssertInputShowing('input: After SlotContainer.showSlot(_, button)'),
      sAssertInputSlotShowing('After SlotContainer.showSlot(_, button)'),
      sHideSlot('inputA'),
      sAssertButtonShowing('button: After SlotContainer.hideSlot(_, input)'),
      sAssertButtonSlotShowing('After SlotContainer.hideSlot(_, input)'),
      sAssertInputHidden('input: After SlotContainer.hideSlot(_, input)'),
      sAssertInputSlotHidden('After SlotContainer.hideSlot(_, input)'),
      sShowSlot('inputA'),
      sAssertButtonShowing('button: After SlotContainer.showSlot(_, input)'),
      sAssertInputShowing('input: After SlotContainer.showSlot(_, input)'),
      sAssertInputSlotShowing('After SlotContainer.showSlot(_, input)'),
      sAssertButtonSlotShowing('After SlotContainer.showSlot(_, input)'),
      sHideAllSlots(),
      sAssertButtonHidden('button: After SlotContainer.hideAllSlots(_)'),
      sAssertInputHidden('input: After SlotContainer.hideAllSlots(_)'),
      sAssertInputSlotHidden('After SlotContainer.hideAllSlots(_)'),
      sAssertButtonSlotHidden('After SlotContainer.hideAllSlots(_)'),
      sAssertGetSlotNames('checking the list slots', [ 'inputA', 'buttonB' ])
    ];
  }, success, failure);
});
