import { ApproxStructure, Assertions, Step } from '@ephox/agar';
import { UnitTest } from '@ephox/bedrock-client';
import { Arr, Fun } from '@ephox/katamari';
import { Attribute, SelectorFind } from '@ephox/sugar';

import * as Behaviour from 'ephox/alloy/api/behaviour/Behaviour';
import { Replacing } from 'ephox/alloy/api/behaviour/Replacing';
import { Representing } from 'ephox/alloy/api/behaviour/Representing';
import * as GuiFactory from 'ephox/alloy/api/component/GuiFactory';
import type { AlloySpec } from 'ephox/alloy/api/component/SpecTypes';
import { Container } from 'ephox/alloy/api/ui/Container';
import { DataField } from 'ephox/alloy/api/ui/DataField';
import { FormChooser } from 'ephox/alloy/api/ui/FormChooser';
import { FormCoupledInputs } from 'ephox/alloy/api/ui/FormCoupledInputs';
import { FormField } from 'ephox/alloy/api/ui/FormField';
import { HtmlSelect } from 'ephox/alloy/api/ui/HtmlSelect';
import { Input } from 'ephox/alloy/api/ui/Input';
import * as Tagger from 'ephox/alloy/registry/Tagger';
import * as RepresentPipes from 'ephox/alloy/test/behaviour/RepresentPipes';
import * as GuiSetup from 'ephox/alloy/test/GuiSetup';

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

  const renderChoice = (choiceSpec: { value: string; text: string }): AlloySpec & { value: string } => ({
    value: choiceSpec.value,
    dom: {
      tag: 'span',
      innerHtml: choiceSpec.text,
      attributes: {
        'data-value': choiceSpec.value
      }
    },
    components: [ ]
  });

  const labelSpec: AlloySpec = {
    dom: {
      tag: 'label',
      innerHtml: 'Label'
    },
    components: [ ]
  };

  GuiSetup.setup((_store, _doc, _body) => {
    const inputA = FormField.sketch({
      uid: 'input-a',
      dom: {
        tag: 'div'
      },
      components: [
        FormField.parts.field({
          factory: Input,
          data: 'init'
        }),
        FormField.parts.label(labelSpec),
        FormField.parts['aria-descriptor']({
          text: 'help'
        })
      ]
    });

    const selectB = FormField.sketch({
      uid: 'select-b',
      dom: {
        tag: 'div'
      },
      components: [
        // TODO: Do not recalculate
        FormField.parts.label(labelSpec),
        FormField.parts.field({
          factory: HtmlSelect,
          options: [
            { value: 'select-b-init', text: 'Select-b-init' }
          ]
        })
      ]
    });

    const chooserC = FormChooser.sketch({
      uid: 'chooser-c',
      dom: {
        tag: 'div'
      },
      components: [
        FormChooser.parts.legend({ }),
        FormChooser.parts.choices({ })
      ],

      markers: {
        choiceClass: 'test-choice',
        selectedClass: 'test-selected-choice'
      },
      choices: Arr.map([
        { value: 'choice1', text: 'Choice1' },
        { value: 'choice2', text: 'Choice2' },
        { value: 'choice3', text: 'Choice3' }
      ], renderChoice)
    });

    const coupledDText = {
      dom: {
        tag: 'div'
      },
      components: [
        FormField.parts.label(labelSpec),
        FormField.parts.field({
          factory: Input
        })
      ]
    };

    const coupledD = FormCoupledInputs.sketch({
      dom: {
        tag: 'div',
        classes: [ 'coupled-group' ]
      },
      components: [
        FormCoupledInputs.parts.field1(coupledDText),
        FormCoupledInputs.parts.field2(coupledDText),
        FormCoupledInputs.parts.lock({
          dom: {
            tag: 'button',
            innerHtml: '+'
          }
        })
      ],

      onLockedChange: (current, other) => {
        Representing.setValueFrom(other, current);
      },
      markers: {
        lockClass: 'coupled-lock'
      }
    });

    const dataE = DataField.sketch({
      uid: 'data-e',
      dom: {
        tag: 'span'
      },
      getInitialValue: Fun.constant('data-e-init')
    });

    return GuiFactory.build(
      Container.sketch({
        components: [
          inputA,
          selectB,
          chooserC,
          coupledD,
          dataE
        ],

        containerBehaviours: Behaviour.derive([
          Replacing.config({ })
        ])
      })
    );

  }, (doc, _body, _gui, component, _store) => {

    const inputA = component.getSystem().getByUid('input-a').getOrDie();
    const selectB = component.getSystem().getByUid('select-b').getOrDie();
    const chooserC = component.getSystem().getByUid('chooser-c').getOrDie();
    const dataE = component.getSystem().getByUid('data-e').getOrDie();

    return [
      GuiSetup.mAddStyles(doc, [
        '.test-selected-choice, .coupled-lock { background: #cadbee }'
      ]),

      RepresentPipes.sAssertValue('Checking input-a value', 'init', inputA),

      Assertions.sAssertStructure('Check the input-a DOM', ApproxStructure.build((s, str, _arr) => {
        const input = SelectorFind.descendant(inputA.element, 'input').getOrDie('input element child was not found');
        const span = SelectorFind.descendant(inputA.element, 'span').getOrDie('span element child was not found');

        const inputID = Attribute.getOpt(input, 'id').getOrDie('Expected value for input.id');
        const spanID = Attribute.getOpt(span, 'id').getOrDie('Expected value for span.id');
        return s.element('div', {
          children: [
            s.element('input', {
              attrs: {
                'aria-describedby': str.is(spanID)
              }
            }),
            s.element('label', {
              attrs: {
                for: str.is(inputID)
              }
            }),
            s.element('span', { })
          ]
        });
      }), inputA.element),

      Assertions.sAssertStructure('Check the select-b dom', ApproxStructure.build((s, _str, _arr) => s.element('div', {
        children: [
          s.element('label', { }),
          s.element('select', { })
        ]
      })), selectB.element),

      Assertions.sAssertStructure('Check the chooser-c dom', ApproxStructure.build((s, str, _arr) => s.element('div', {
        children: [
          s.element('legend', { }),
          s.element('span', { attrs: { role: str.is('radio') }}),
          s.element('span', { attrs: { role: str.is('radio') }}),
          s.element('span', { attrs: { role: str.is('radio') }})
        ]
      })), chooserC.element),

      RepresentPipes.sAssertValue('Checking select-b value', 'select-b-init', selectB),

      Step.sync(() => {
        const val = Representing.getValue(chooserC).getOrDie();
        Assertions.assertEq('Checking chooser-c value', 'choice1', val);

        Representing.setValue(chooserC, 'choice3');
        const val2 = Representing.getValue(chooserC).getOrDie();
        Assertions.assertEq('Checking chooser-c value after set', 'choice3', val2);
      }),

      Assertions.sAssertStructure('Checking the data field (E)', ApproxStructure.build((s, _str, _arr) => s.element('span', { children: [ ] })), dataE.element),

      Step.sync(() => {
        const val = Representing.getValue(dataE);
        Assertions.assertEq('Checking data-e value', 'data-e-init', val);

        Representing.setValue(dataE, 'data-e-new');
        Assertions.assertEq('Checking data-e value after set', 'data-e-new', Representing.getValue(dataE));

        // Remove it from the container
        Replacing.remove(component, dataE);

        // Add it back the the container
        Replacing.append(component, GuiFactory.premade(dataE));
        Assertions.assertEq('Checking data-e value was reset when added back to DOM', 'data-e-init', Representing.getValue(dataE));
      }),

      Step.sync(() => {
        FormField.getField(inputA).fold(() => {
          throw new Error('The input Field could not be found');
        }, (comp) => {
          const alloyId = Tagger.readOrDie(comp.element);
          Assertions.assertEq('FormField should have an api that returns the input field', 'input-a-field', alloyId);
        });

        FormField.getLabel(inputA).fold(() => {
          throw new Error('The input Label could not be found');
        }, (comp) => {
          const alloyId = Tagger.readOrDie(comp.element);
          Assertions.assertEq('FormField should have an api that returns the input Label', 'input-a-label', alloyId);
        });
      }),

      GuiSetup.mRemoveStyles
    ];
  }, success, failure);
});
