/*
 * Copyright 2023 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
export default class HtmlFormToEDForm {
  form = null;

  fields = [];

  labelIncludedMap = new Map();

  inputField = ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'];

  labelTags = ['H4', 'LEGEND', 'P'];

  #isFormField(element) {
    const tagName = element?.tagName;
    return this.inputField.includes(tagName);
  }

  #isLabel(element) {
    const tagName = element?.tagName;
    return this.labelTags.includes(tagName);
  }

  #initField(name, type, label, element) {
    const field = {
      Name: name,
      Type: type,
      Description: element?.title?.trim(),
      Placeholder: element?.placeholder?.trim() || '',
      Label: label,

      'Read Only': element?.readOnly || '',

      Mandatory: element?.required || element?.getAttribute('aria-required') === 'true' || '',
      Pattern: element?.pattern ?? '',
      Step: element?.step || '',
      Min: element?.minLength || element?.min || '',
      Max: element?.max || element?.maxLength || '',
      Value: element?.value,
      Options: '',
      OptionNames: '',
      Fieldset: '',
    };

    // In case of JSDOM it will return max value as 524288
    if (field.Max === 524288) {
      field.Max = '';
    }
    return field;
  }

  #removeEmptyProperties(field) {
    Object.keys(field).forEach((key) => {
      if (!field[key]) {
        delete field[key];
      }
    });
    return field;
  }

  #isValidSelector(selector) {
    try {
      if (selector) {
        this.form.querySelector(selector);
        return true;
      }
    } catch (e) {
      console.log(`Invalid selector ${selector}`);
    }
    return false;
  }

  /**
   * HTML Form to Edge Delivery Form ( Franklin Form )
   * @param {*} form
   * @param {includePlainText : false}
   * @returns
   */
  getFormDefFromHTMLForm(form, { includePlainText = false, includeHidden = true, groupBySelector = '' } = {}) {
    this.form = form;
    this.fields = [];
    this.labelIncludedMap = new Map();
    let counter = 1;
    if (form && form.elements) {
      const query = [...this.inputField, ...this.labelTags];
      if (this.#isValidSelector(groupBySelector)) query.push(groupBySelector);
      const elements = form.querySelectorAll(query.join(','));
      elements?.forEach((element) => {
        let field;
        if (this.#isFormField(element)) {
          // include hidden fields based on the includeHidden flag
          if (!includeHidden && element.type === 'hidden') {
            return;
          }
          const name = element?.name?.trim() || element?.id?.trim();
          field = this.#initField(name, element.type, this.#getLabel(element), element);
          HtmlFormToEDForm.handleHiddenValue(element, field);
          HtmlFormToEDForm.handleSelectElement(element, field);
          this.#handleCheckBox(element);
          this.#handleSubmit(element, field);
          this.#handleFieldSet(element, field, groupBySelector);
        } else if (this.#isLabel(element) && (includePlainText && !groupBySelector)) {
          field = {
            Type: 'plaintext',
            Label: element.textContent,
          };
        } else if (!this.#isLabel(element)) {
          const groupByLabel = element?.querySelector(this.labelTags.join(','));
          const label = groupByLabel?.textContent?.trim();
          element.dataset.name = element.dataset.name ?? `panel-${counter}`;
          field = this.#initField(element.dataset.name, 'fieldset', label);
          counter += 1;
        }
        if (field) {
          field = this.#removeEmptyProperties(field);
          this.fields.push(field);
        }
      });
    }
    return this.fields;
  }

  /**
     *  Find the associated label element for the input field
     * @param {*} element
     * @returns
     */
  #getLabel(element) {
    let value;
    if (element?.tagName === 'BUTTON') {
      value = element.textContent?.trim();
    } else if (element.id) {
      try {
        const label = this.form.querySelector(`label[for=${element.id}]`);
        value = label?.textContent?.trim();
      } catch (e) {
        console.log(`Error using selector ${element.id}`);
      }
    }

    if (!value) {
      // input field with the <label> element
      const label = element.closest('label');
      if (label) {
        value = label.textContent.trim();
      }

      // sibling label
      if (!value) {
        let siblingElement = element.previousElementSibling;
        if (siblingElement && siblingElement.tagName === 'LABEL') {
          value = siblingElement.textContent.trim();
        }

        if (!value) {
          siblingElement = element.nextElementSibling;
          if (siblingElement && siblingElement.tagName === 'LABEL') {
            value = siblingElement.textContent.trim();
          }
        }
      }
    }
    return value || element?.name;
  }

  #handleFieldSet(element, field, groupBySelector = '') {
    if (this.#isValidSelector(groupBySelector)) {
      const fieldset = element.closest(groupBySelector);
      field.Fieldset = fieldset?.dataset?.name || '';
    }
  }

  #handleSubmit(element, field) {
    if (element.type === 'submit' && element.tagName === 'INPUT') {
      field.Label = element.value;
    }
  }

  #handleCheckBox(element) {
    if (element.type === 'checkbox' || element.type === 'radio') {
      if (!this.labelIncludedMap.has(element.name)) {
        const label = this.form.querySelector(`label[for="${element.name}"]`);
        if (label) {
          const field = this.#initField(null, 'fieldset', label?.textContent?.trim());
          this.labelIncludedMap.set(element.name, true);
          this.fields.push(field);
        }
      }
    }
  }

  static handleHiddenValue(element, field) {
    if (element?.type === 'hidden') {
      field.Value = element?.value;
    }
  }

  static handleSelectElement(element, field) {
    if (element?.tagName === 'SELECT') {
      field.Type = 'select';
      field.Options = [];
      field.OptionNames = [];
      [...element.options].forEach((option) => {
        if (option.disabled) {
          field.Placeholder = option.value;
          field.Value = option.value;
        } else {
          field.Options.push(option.value);
          field.OptionNames.push(option.text?.trim());
        }
      });
      field.Options = field.Options.join(',');
    }
  }
}
