const helpers = require('./support/helpers');

function setFixtures (hierarchy, newTemplateDataModules = "") {

  const foldersCheckboxesHTML = function (filter) {
    let count = 0;

    // use closure to give all calls access to count
    return function (nodes) {
      let result = '';

      nodes
        .filter(node => node.type === 'folder')
        .forEach(node => {
          result += `<li class="multiple-choice">
                      <input id="node-${count}" name="move_to" type="radio" value="node-${count}">
                      <label class="usa-label" for="node-${count}">
                        ${node.label}
                      </label>
                      ${node.children ? foldersCheckboxesHTML(node.children) : ''}
                    </li>`;
          count++;
        });

      return `<ul>${result}</ul>`;
    };

  }();

  function controlsHTML (newTemplateDataModules) {

    return `<div id="sticky_template_forms">
              <button type="submit" name="operation" value="unknown" hidden=""></button>
              <div id="move_to_folder_radios" class="sticky-template-form" role="region" aria-label="Choose the folder to move selected items to">
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="form-group ">
                    <fieldset id="move_to">
                      <legend class="form-label">
                        Choose a folder
                      </legend>
                      <div class="radios-nested">
                        ${foldersCheckboxesHTML(hierarchy)}
                      </div>
                    </fieldset>
                  </div>
                </div>
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="page-footer">
                    <button type="submit" class="usa-button" name="operation" value="move-to-existing-folder">Move</button>
                  </div>
                </div>
              </div>
              <div id="move_to_new_folder_form" class="sticky-template-form" role="region" aria-label="Enter name of the new folder to move selected items to">
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="usa-form-group">
                    <label class="usa-label" for="move_to_new_folder_name">
                      Folder name
                    </label>
                    <input class="usa-input " id="move_to_new_folder_name" name="move_to_new_folder_name" type="text">
                  </div>
                  <div class="page-footer">
                    <button type="submit" class="usa-button" name="operation" value="move-to-new-folder">Add to new folder</button>
                  </div>
                </div>
              </div>
              <div id="add_new_folder_form" class="sticky-template-form" role="region" aria-label="Enter name of the new folder">
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="usa-form-group">
                    <label class="usa-label" for="add_new_folder_name">
                      Folder name
                    </label>
                    <input class="usa-input " id="add_new_folder_name" name="add_new_folder_name" type="text">
                  </div>
                  <div class="page-footer">
                    <button type="submit" class="usa-button page-footer__button" name="operation" value="add-new-folder">Add new folder</button>
                  </div>
                </div>
              </div>
              <div id="add_new_template_form" class="sticky-template-form" role="region" aria-label="Choose template type" ${newTemplateDataModules}>
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="form-group ">
                    <fieldset id="add_template_by_template_type">
                      <legend class="form-label">
                        New template
                      </legend>
                      <div class="multiple-choice">
                        <input id="add_template_by_template_type-0" name="add_template_by_template_type" type="radio" value="email">
                        <label class="usa-label" for="add_template_by_template_type-0">
                          Email
                        </label>
                      </div>
                      <div class="multiple-choice">
                        <input id="add_template_by_template_type-1" name="add_template_by_template_type" type="radio" value="sms">
                        <label class="usa-label" for="add_template_by_template_type-1">
                          Text message
                        </label>
                      </div>
                      <div class="multiple-choice">
                        <input id="add_template_by_template_type-2" name="add_template_by_template_type" type="radio" value="letter">
                        <label class="usa-label" for="add_template_by_template_type-2">
                          Letter
                        </label>
                      </div>
                      <div class="multiple-choice">
                        <input id="add_template_by_template_type-3" name="add_template_by_template_type" type="radio" value="copy-existing">
                        <label class="usa-label" for="add_template_by_template_type-3">
                          Copy an existing template
                        </label>
                      </div>
                    </fieldset>
                  </div>
                </div>
                <div class="js-will-stick-at-bottom-when-scrolling">
                  <div class="page-footer">
                    <button type="submit" class="usa-button page-footer__button" name="operation" value="add-new-template">Continue</button>
                  </div>
                </div>
              </div>
              <div class="selection-counter visuallyhidden" role="status" aria-live="polite">
                Nothing selected
              </div>
            </div>`
  };

  document.body.innerHTML = `
    <form method="post" data-module="template-folder-form">
      ${helpers.templatesAndFoldersCheckboxes(hierarchy)}
      ${controlsHTML(newTemplateDataModules)}
    </form>`;

};

function resetStickyMocks () {

  GOVUK.stickAtBottomWhenScrolling.recalculate.mockClear();
  GOVUK.stickAtBottomWhenScrolling.setMode.mockClear();

};

beforeAll(() => {
  require('../../app/assets/javascripts/templateFolderForm.js');

  // plug JSDOM's lack of support for window.scrollTo
  window.scrollTo = () => {};
});

afterAll(() => {
  require('./support/teardown.js');

  // tidy up
  delete window.scrollTo;
});

describe('TemplateFolderForm', () => {

  const hierarchy = [
    {
      'label': 'Folder 1',
      'type': 'folder',
      'meta': '1 template, 1 folder',
      'children': [
        {
          'label': 'Template 3',
          'type': 'template',
          'meta': 'Email template'
        },
        {
          'label': 'Folder 2',
          'type': 'folder',
          'meta': 'Empty',
          'children': []
        }
      ]
    },
    {
      'label': 'Template 1',
      'type': 'Email template',
      'meta': 'Email template'
    },
    {
      'label': 'Template 2',
      'type': 'template',
      'meta': 'Email template'
    }
  ];

  let templateFolderForm;
  let formControls;
  let visibleCounter;
  let hiddenCounter;

  beforeAll(() => {

    // stub out calls to sticky JS
    GOVUK.stickAtBottomWhenScrolling = {
      setMode: jest.fn(),
      recalculate: jest.fn()
    };

  });

  afterAll(() => {

    GOVUK.stickAtBottomWhenScrolling = undefined;

  });

  beforeEach(() => {

    setFixtures(hierarchy);

    templateFolderForm = document.querySelector('form[data-module=template-folder-form]');

  });

  afterEach(() => {

    document.body.innerHTML = '';

  });

  function getTemplateFolderCheckboxes () {
    return templateFolderForm.querySelectorAll('.usa-checkbox__label');
  };

  function getVisibleCounter () {
    return formControls.querySelector('.template-list-selected-counter__count');
  };

  function getHiddenCounter () {
    return formControls.querySelector('[role=status]');
  };

  describe("Before the module starts", () => {

    // We need parts of the module to be made sticky, but by the module code,
    // not the sticky JS code that operates on the HTML at page load.
    // Because of this, they will need to be marked with classes
    test("the HTML for the module should contain placeholder classes on each part that needs to be sticky", () => {

      expect(templateFolderForm.querySelectorAll('#move_to_folder_radios > .js-will-stick-at-bottom-when-scrolling').length).toEqual(2);
      expect(templateFolderForm.querySelector('#move_to_new_folder_form > .js-will-stick-at-bottom-when-scrolling')).not.toBeNull();
      expect(templateFolderForm.querySelector('#add_new_folder_form > .js-will-stick-at-bottom-when-scrolling')).not.toBeNull();
      expect(templateFolderForm.querySelectorAll('#add_new_template_form > .js-will-stick-at-bottom-when-scrolling').length).toEqual(2);

    });

  });

  describe("When the module starts", () => {

    beforeEach(() => {

      // start module
      window.GOVUK.modules.start();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');
      visibleCounter = getVisibleCounter();

    });

    afterEach(() => resetStickyMocks());

    test("the default controls and the counter should be showing", () => {

      expect(document.querySelector('button[value=add-new-template]')).not.toBeNull();
      expect(document.querySelector('button[value=add-new-folder]')).not.toBeNull();
      expect(document.querySelector('button[value=add-new-template]').getAttribute('aria-expanded')).toEqual('false');
      expect(document.querySelector('button[value=add-new-folder]').getAttribute('aria-expanded')).toEqual('false');
      expect(visibleCounter).not.toBeNull();

    });

    // Our counter needs to be wrapped in an ARIA live region so changes to its content are
    // communicated to assistive tech'.
    // ARIA live regions need to be in the HTML before JS loads.
    // Because of this, we have a counter, in a live region, in the page when it loads, and
    // a duplicate, visible, one in the HTML the module adds to the page.
    // We hide the one in the live region to avoid duplication of it's content.
    describe("Selection counter", () => {

      beforeEach(() => {

        hiddenCounter = getHiddenCounter();

      })

      test("the visible counter should be hidden from assistive tech", () => {

        expect(visibleCounter.getAttribute('aria-hidden')).toEqual("true");

      });

      test("the content of both visible and hidden counters should match", () => {

        expect(visibleCounter.textContent.trim()).toEqual(hiddenCounter.textContent.trim());

      });

    });

  });

  describe("Click 'New template' for single channel service", () => {
    test("should redirect to new template page", () => {
      setFixtures(hierarchy, "data-channel='sms' data-service='123'")
      templateFolderForm = document.querySelector('form[data-module=template-folder-form]');

      // start module
      window.GOVUK.modules.start();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');

      // reset sticky JS mocks called when the module starts
      resetStickyMocks();
      // add listener for url change
      const descriptor1 = Object.getOwnPropertyDescriptor(window, 'location');
      delete window.location

      const mockCallback = jest.fn(x => {});

      Object.defineProperty(window, 'location', {
        set: mockCallback
      });
      // click
      helpers.triggerEvent(formControls.querySelector('[value=add-new-template]'), 'click');
      // expect url to change
      expect(mockCallback).toHaveBeenCalledWith("/services/123/templates/add-sms")

      setFixtures(hierarchy)
      resetStickyMocks()
    });
  })

  describe("Clicking 'New template'", () => {

    beforeEach(() => {

      // start module
      window.GOVUK.modules.start();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');

      // reset sticky JS mocks called when the module starts
      resetStickyMocks();

      helpers.triggerEvent(formControls.querySelector('[value=add-new-template]'), 'click');

    });

    afterEach(() => resetStickyMocks());

    describe("Should show a region", () => {

      test("with options for all the types of template", () => {

        const options = [
          'Email', 'Text message', 'Letter', 'Copy an existing template'
        ];

        const labels = Array.from(formControls.querySelectorAll('label'));
        const radios = Array.from(formControls.querySelectorAll('input[type=radio]'));

        options.forEach(option => {
          let matchingLabels = labels.filter(label => label.textContent.trim() === option);

          expect(matchingLabels.length > 0).toBe(true);

          let matchingRadio = formControls.querySelector(`#${matchingLabels[0].getAttribute('for')}`)

          expect(matchingRadio).not.toBeNull();
        });

      });

      test("with a 'Cancel' link", () => {

        const cancelLink = formControls.querySelector('.js-cancel');

        expect(cancelLink).not.toBeNull();
        expect(cancelLink.querySelector('.usa-sr-only')).not.toBeNull();
        expect(cancelLink.querySelector('.usa-sr-only').textContent.trim()).toEqual('new template');

      });

      test("with an accessible role, name and description", () => {

        let id, description;
        const region = document.querySelector('#add_new_template_form');
        expect(region.hasAttribute('role')).toBe(true);
        expect(region.getAttribute('role')).toEqual('region');
        expect(region.hasAttribute('aria-label')).toBe(true);
        expect(region.getAttribute('aria-label')).toEqual('Choose template type');
        expect(region.hasAttribute('aria-describedby')).toBe(true);
        id = region.getAttribute('aria-describedby');
        description = document.getElementById(id);
        expect(description).not.toBeNull();
        expect(description.textContent.trim()).toEqual('Press continue to confirm selection or cancel to close');

      });

      test("and focus it", () => {

        expect(document.activeElement).toBe(formControls.querySelector('#add_new_template_form'));

      });

    });

    describe("When the 'Cancel' link is clicked after choosing to add a new template", () => {

      let addNewTemplateButton;

      beforeEach(() => {

        // reset sticky JS mocks called when the new template state loaded
        resetStickyMocks();

        helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click');

        addNewTemplateButton = formControls.querySelector('[value=add-new-template]');

      });

      test("the controls should reset", () => {

        expect(addNewTemplateButton).not.toBeNull();

      });

      test("the add new template control should be focused", () => {

        expect(document.activeElement).toBe(addNewTemplateButton);

      });

    });

  });

  describe("Clicking 'New folder'", () => {

    let textbox;

    afterEach(() => resetStickyMocks());

    beforeEach(() => {

      // start module
      window.GOVUK.modules.start();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');

      // reset sticky JS mocks called when the module starts
      resetStickyMocks();

      helpers.triggerEvent(formControls.querySelector('[value=add-new-folder]'), 'click');

      textbox = formControls.querySelector('input[type=text]');

    });

    describe("should show a parent region", () => {

      test("with an accessible role, name and description", () => {

        let id, description;
        const region = document.querySelector('#add_new_folder_form');
        expect(region.hasAttribute('role')).toBe(true);
        expect(region.getAttribute('role')).toEqual('region');
        expect(region.hasAttribute('aria-label')).toBe(true);
        expect(region.getAttribute('aria-label')).toEqual('Enter name of the new folder');
        expect(region.hasAttribute('aria-describedby')).toBe(true);
        id = region.getAttribute('aria-describedby');
        description = document.getElementById(id);
        expect(description).not.toBeNull();
        expect(description.textContent.trim()).toEqual('Press add new folder to confirm name or cancel to close');

      });

      test("with a textbox for the folder name", () => {

        expect(textbox).not.toBeNull();

        // check textbox has a label
        expect(formControls.querySelector(`label[for=${textbox.getAttribute('id')}]`)).not.toBeNull();

      });

      test("with a 'Cancel' link", () => {

        const cancelLink = formControls.querySelector('.js-cancel');

        expect(cancelLink).not.toBeNull();
        expect(cancelLink.querySelector('.usa-sr-only')).not.toBeNull();
        expect(cancelLink.querySelector('.usa-sr-only').textContent.trim()).toEqual('new folder');

      });

      test("and focus it", () => {

        expect(document.activeElement).toBe(formControls.querySelector('#add_new_folder_form'));

      });

    });

    test("", () => {

      // the class the sticky JS hooks into should be present
      expect(formControls.querySelector('#add_new_folder_form .js-stick-at-bottom-when-scrolling')).not.toBeNull();

      // .recalculate should have been called so the sticky JS picks up the controls
      expect(GOVUK.stickAtBottomWhenScrolling.recalculate.mock.calls.length).toEqual(0);

    });

    describe("When the 'Cancel' link is clicked after choosing to add a new folder", () => {

      let addNewFolderButton;

      beforeEach(() => {

        helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click');

        addNewFolderButton = formControls.querySelector('button[value=add-new-folder]');

      });

      test("the controls should reset", () => {

        expect(addNewFolderButton).not.toBeNull();

      });

      test("the control for adding a new folder should be focused", () => {

        expect(document.activeElement).toBe(addNewFolderButton);

      });

    });

  });

  describe("When some templates/folders are selected", () => {

    let templateFolderCheckboxes;

    beforeEach(() => {

      // start module
      window.GOVUK.modules.start();

      templateFolderCheckboxes = getTemplateFolderCheckboxes();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');

      // reset sticky JS mocks called when the module starts
      resetStickyMocks();

      helpers.triggerEvent(templateFolderCheckboxes[0], 'click');
      helpers.triggerEvent(templateFolderCheckboxes[2], 'click');

    });

    afterEach(() => resetStickyMocks());

    test("the buttons for moving to a new or existing folder are showing", () => {

      expect(formControls.querySelector('button[value=move-to-new-folder]')).not.toBeNull();
      expect(formControls.querySelector('button[value=move-to-existing-folder]')).not.toBeNull();
      expect(formControls.querySelector('button[value=move-to-new-folder]').getAttribute('aria-expanded')).toEqual('false');
      expect(formControls.querySelector('button[value=move-to-existing-folder]').getAttribute('aria-expanded')).toEqual('false');

    });

    describe("'Clear selection' link", () => {

      let clearLink;

      beforeEach(() => {

        clearLink = formControls.querySelector('.js-cancel');

      });

      test("the link has been added with the right text", () => {

        expect(clearLink).not.toBeNull();
        expect(clearLink.textContent.trim()).toEqual('Clear selection');

      });

      test("clicking the link clears the selection", () => {

        helpers.triggerEvent(clearLink, 'click');

        const checkedCheckboxes = Array.from(templateFolderCheckboxes).filter(checkbox => checkbox.checked);

        expect(checkedCheckboxes.length === 0).toBe(true);

      });

    });

    describe("Selection counter", () => {

      let visibleCounterText;
      let hiddenCounterText;

      beforeEach(() => {

        visibleCounterText = getVisibleCounter().textContent.trim();
        hiddenCounterText = getHiddenCounter().textContent.trim();

      });

      test("the content of both visible and hidden counters should match", () => {

        expect(visibleCounterText).toEqual(hiddenCounterText);

      });

      test("the content of the counter should reflect the selection", () => {

        expect(visibleCounterText).toEqual('1 template, 1 folder selected');

      });

    });

    describe("Clicking the 'Move' button", () => {

      beforeEach(() => {

      // reset sticky JS mocks called when a selection was made
      resetStickyMocks();

        helpers.triggerEvent(formControls.querySelector('[value=move-to-existing-folder]'), 'click');

      });

      describe("Should show a region", () => {

        test("with an accessible role, name and description", () => {

          let id, description;
          const region = document.querySelector('#move_to_folder_radios');
          expect(region.hasAttribute('role')).toBe(true);
          expect(region.getAttribute('role')).toEqual('region');
          expect(region.hasAttribute('aria-label')).toBe(true);
          expect(region.getAttribute('aria-label')).toEqual('Choose the folder to move selected items to');
          expect(region.hasAttribute('aria-describedby')).toBe(true);
          id = region.getAttribute('aria-describedby');
          description = document.getElementById(id);
          expect(description).not.toBeNull();
          expect(description.textContent.trim()).toEqual('Press move to confirm or cancel to close');

        });

        test("with radios for all the folders in the hierarchy", () => {

          const foldersInHierarchy = [];

          function getFolders (nodes) {

            nodes.forEach(node => {
              if (node.type === 'folder') {

                foldersInHierarchy.push(node.label);
                if (node.children.length) { getFolders(node.children) }

              }
            });

          };

          getFolders(hierarchy);

          const folderLabels = Array.from(formControls.querySelectorAll('#move_to label'))
                                    .filter(label => label.textContent.trim() !== 'Templates');

          expect(folderLabels.map(label => label.textContent.trim())).toEqual(foldersInHierarchy);

          const radiosForLabels = folderLabels
                                    .map(label => formControls.querySelector(`#${label.getAttribute('for')}`))
                                    .filter(radio => radio !== null);

          expect(radiosForLabels.length).toEqual(foldersInHierarchy.length);

        });

        test("with a 'Cancel' link", () => {

          const cancelLink = formControls.querySelector('.js-cancel');

          expect(cancelLink).not.toBeNull();
          expect(cancelLink.querySelector('.usa-sr-only')).not.toBeNull();
          expect(cancelLink.querySelector('.usa-sr-only').textContent.trim()).toEqual('move to folder');

        });

        test("and focus it", () => {

          expect(document.activeElement).toBe(formControls.querySelector('#move_to_folder_radios'));

        });

      });

      describe("When the 'Cancel' link is clicked after choosing to move a template or folder", () => {

        let moveToFolderButton;

        beforeEach(() => {

          helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click');

          moveToFolderButton = formControls.querySelector('button[value=move-to-existing-folder]');

        });

        test("the controls should reset", () => {

          expect(moveToFolderButton).not.toBeNull();

        });

        test("the control for moving to an existing folder should be focused", () => {

          expect(document.activeElement).toBe(moveToFolderButton);

        });

      });

    });

    describe("Clicking the 'Add to new folder' button", () => {

      let textbox;

      beforeEach(() => {

        // reset sticky JS mocks called when a selection was made
        resetStickyMocks();

        helpers.triggerEvent(formControls.querySelector('[value=move-to-new-folder]'), 'click');

        textbox = formControls.querySelector('input[type=text]');

      });

      describe("Should show a region", () => {

        test("with an accessible role, name and description", () => {

          let id, description;
          const region = document.querySelector('#move_to_new_folder_form');
          expect(region.hasAttribute('role')).toBe(true);
          expect(region.getAttribute('role')).toEqual('region');
          expect(region.hasAttribute('aria-label')).toBe(true);
          expect(region.getAttribute('aria-label')).toEqual('Enter name of the new folder to move selected items to');
          expect(region.hasAttribute('aria-describedby')).toBe(true);
          id = region.getAttribute('aria-describedby');
          description = document.getElementById(id);
          expect(description).not.toBeNull();
          expect(description.textContent.trim()).toEqual('Press add to new folder to confirm name or cancel to close');

        });

        test("with a textbox for the folder name", () => {

          expect(textbox).not.toBeNull();

          // check textbox has a label
          expect(formControls.querySelector(`label[for=${textbox.getAttribute('id')}]`)).not.toBeNull();

        });

        test("with a 'Cancel' link", () => {

          const cancelLink = formControls.querySelector('.js-cancel');

          expect(cancelLink).not.toBeNull();
          expect(cancelLink.querySelector('.usa-sr-only')).not.toBeNull();
          expect(cancelLink.querySelector('.usa-sr-only').textContent.trim()).toEqual('move to new folder');

        });

        test("and focus it", () => {

          expect(document.activeElement).toBe(formControls.querySelector('#move_to_new_folder_form'));

        });

      });

      describe("When the 'Cancel' link is clicked after choosing to add a template or folder to a new folder", () => {

        let moveToNewFolderButton;

        beforeEach(() => {

          helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click');

          moveToNewFolderButton = formControls.querySelector('button[value=move-to-new-folder]');

        });

        test("the controls should reset", () => {

          expect(moveToNewFolderButton).not.toBeNull();

        });

        test("the control for adding a new folder should be focused", () => {

          expect(document.activeElement).toBe(moveToNewFolderButton);

        });

      });

    });

  });

  describe("Additional selection counter scenarios", () => {

    let templateFolderCheckboxes;

    beforeEach(() => {

      // start module
      window.GOVUK.modules.start();

      templateFolderCheckboxes = getTemplateFolderCheckboxes();
      visibleCounterText = getVisibleCounter().textContent.trim();
      hiddenCounterText = getHiddenCounter().textContent.trim();

      formControls = templateFolderForm.querySelector('#sticky_template_forms');

      // reset sticky JS mocks called when the module starts
      resetStickyMocks();

    });

    afterEach(() => resetStickyMocks());

    describe("When just templates are selected", () => {

      test("the content of both visible and hidden counters should match", () => {

        helpers.triggerEvent(templateFolderCheckboxes[1], 'click');
        helpers.triggerEvent(templateFolderCheckboxes[2], 'click');

        expect(visibleCounterText).toEqual(hiddenCounterText);

      });

      test("the content of the counter should reflect the selection", () => {

        helpers.triggerEvent(templateFolderCheckboxes[1], 'click');
        helpers.triggerEvent(templateFolderCheckboxes[2], 'click');

        expect(visibleCounterText).toEqual('2 templates selected');

      });

    });

    describe("When just folders are selected", () => {

      test("the content of both visible and hidden counters should match", () => {

        helpers.triggerEvent(templateFolderCheckboxes[0], 'click');

        expect(visibleCounterText).toEqual(hiddenCounterText);

      });

      test("the content of the counter should reflect the selection", () => {

        helpers.triggerEvent(templateFolderCheckboxes[0], 'click');

        expect(visibleCounterText).toEqual('1 folder selected');

      });

    });

  });

});
