import stylesConfig from '../../styleNames';
import { createElement } from '../../utils';
import { createConfigPageRecursion } from './createConfigPage';
import { createConfigControlButton } from './helpers/helpers';

type Props = {
  targetNode: HTMLElement;
  oldValues?: OldValues;
};

type OldValues = {
  name: string;
  value: string | boolean | number | DynamicObject | HTMLOptionElement[];
  key: string;
  type?: string;
  step?: string;
};

type ValueInputTypes = 'select' | 'details' | 'text' | 'integer' | 'float' | 'checkbox';

const optionValues = [
  { name: 'текст', value: 'text', selected: true },
  { name: 'целое число', value: 'integer' },
  { name: 'дробное число', value: 'float' },
  { name: 'логическое значение', value: 'checkbox' },
  { name: 'список вариантов', value: 'select' },
  { name: 'раздел', value: 'details' },
];

let isCreatedSection = false;
let newSection: HTMLElement | null; // хранит в себе самую первую созданную секцию
let oldValues: OldValues | undefined;
let targetNode: HTMLElement;

// функция создаёт форму создания/изменения поля
const createForm = (props: Props): HTMLFormElement => {
  oldValues = props.oldValues;
  targetNode = props.targetNode;

  // по наличию oldValues определяется, создание 
  if (!oldValues) {
    (targetNode as HTMLDetailsElement).open = true;
  }

  const form = createElement('form', { className: stylesConfig.form });
  window.setTimeout(() => document.addEventListener('click', removeForm));

  const nameInput = createInput(
    'Имя поля',
    oldValues?.name || '',
    stylesConfig.formNameInput,
    form,
  );
  window.setTimeout(() => nameInput.focus());

  const keyInput = createInput(
    'Ключ поля',
    oldValues?.key || '',
    stylesConfig.formKeyInput,
    form,
  );
  keyInput.pattern = '[0-9a-zA-Z-_]*';

  const valueTypeInputWrapper = createElement('div', null, form);
  createElement('span', { innerHTML: 'Тип значения:' }, valueTypeInputWrapper);

  const options = optionValues.map((opt) =>
    createElement('option', {
      innerHTML: opt.name,
      id: `c_opt_${opt.value}`,
      value: opt.value,
      selected: opt.selected,
    }),
  );

  const valueInputTypeSelect = createElement(
    'select',
    { className: stylesConfig.formValueTypeInput },
    valueTypeInputWrapper,
    options,
  );

  let valueInput: HTMLElement = createInput(
    'Значение поля',
    '',
    stylesConfig.formValueInput,
    form,
  );

  valueInputTypeSelect.onchange = (e) => {
    valueInput = handleValueInputTypeSelectChange(e, form, valueInput);
  };

  if (oldValues) {
    fillForm(oldValues, form, valueInputTypeSelect, valueInput);
  }

  createElement(
    'button',
    {
      className: stylesConfig.formSubmitButton,
      textContent: 'Сохранить',
      type: 'button',
      onclick: () => handleSaveButtonClick(form, oldValues),
    },
    form,
  );

  return form;
};

// заполняет форму значениями из oldValues
const fillForm = (
  oldValues: OldValues,
  form: HTMLElement,
  valueInputTypeSelect: HTMLSelectElement,
  valueInput: HTMLElement,
) => {
  let valueInputType: ValueInputTypes;
  switch (typeof oldValues.value) {
    case 'object':
      if (Array.isArray(oldValues.value)) {
        valueInputType = 'select';
        const newValueInput = createOptionsForm(oldValues.value);
        valueInput.replaceWith(newValueInput);
        valueInput = newValueInput;
      } else {
        valueInputType = 'details';
        (valueInput as HTMLInputElement).disabled = true;
        (valueInput as HTMLInputElement).value = 'объект';
        (valueInput as HTMLInputElement).type = 'text';
      }

      break;
    case 'boolean':
    case 'number':
    case 'string':
      const input = targetNode.querySelector('input') as HTMLInputElement;
      (valueInput as HTMLInputElement).type = input.type;
      (valueInput as HTMLInputElement).value = `${oldValues.value}`;
      valueInputType = input.type as ValueInputTypes;

      if (input.type === 'number') {
        valueInputType = oldValues.type as ValueInputTypes;
        (valueInput as HTMLInputElement).step = input.step;

        const stepInput = createInput(
          'Шаг для значения',
          input.step,
          stylesConfig.formStepInput,
        );
        stepInput.onchange = () =>
          ((valueInput as HTMLInputElement).step = stepInput.value.replace(',', '.'));
        stepInput.pattern =
          valueInputType === 'float' ? '([0]*[,.]{0,1}[0]*)?1([0]*)?' : '1([0]*)?';
        form.insertBefore(
          stepInput.parentElement as HTMLElement,
          valueInput.parentElement,
        );
      } else if (input.type === 'checkbox') {
        (valueInput as HTMLInputElement).checked = !!oldValues.value;
      }

      break;
  }

  const selectedOption = valueInputTypeSelect.options.namedItem(
    `c_opt_${valueInputType}`,
  );
  if (selectedOption) {
    valueInputTypeSelect.options[0].selected = false;
    selectedOption.selected = true;
  }
};

// обработчик изменения селекта
const handleValueInputTypeSelectChange = (
  e: Event,
  form: HTMLElement,
  valueInput: HTMLElement,
) => {
  const valueInputTypeSelect = e.currentTarget as HTMLSelectElement;
  const valueInputType = valueInputTypeSelect.value as ValueInputTypes;
  form.querySelector(`.${stylesConfig.formStepInput}`)?.parentElement?.remove();

  if (!newSection) {
    isCreatedSection = valueInputType === 'details';
  } else if (valueInputType !== 'details') {
    newSection = null;
    isCreatedSection = false;
  }

  // создаём новый инпут
  let newValueInput: HTMLElement;
  switch (valueInputType) {
    case 'select':
      newValueInput = createOptionsForm();
      break;
    case 'details':
      newValueInput = createElement('div', {
        innerHTML:
          'Вы перейдёте к созданию дочернего поля,<br /> как только завершите создание текущего.',
      });
      break;
    case 'float':
    case 'integer':
      newValueInput = createElement('input', {
        type: 'number',
        className: stylesConfig.formValueInput,
        required: true,
        step: valueInputType === 'float' ? '0.1' : '1',
      });

      if (
        valueInput.nodeName === 'INPUT' &&
        (valueInput as HTMLInputElement).type === 'number'
      ) {
        const oldInputValue = (valueInput as HTMLInputElement).value;
        (newValueInput as HTMLInputElement).value =
          valueInputType === 'integer'
            ? Math.round(+oldInputValue).toString()
            : oldInputValue;
      }

      const stepInput = createInput(
        'Шаг для значения',
        valueInputType === 'float' ? '0.1' : '1',
        stylesConfig.formStepInput,
      );
      stepInput.onchange = () =>
        ((newValueInput as HTMLInputElement).step = stepInput.value.replace(',', '.'));
      stepInput.pattern =
        valueInputType === 'float' ? '([0]*[,.]{0,1}[0]*)?1([0]*)?' : '1([0]*)?';
      form.insertBefore(stepInput.parentElement as HTMLElement, valueInput.parentElement);
      break;
    case 'text':
      newValueInput = createElement('input', {
        type: 'text',
        className: stylesConfig.formValueInput,
        required: true,
      });

      if (
        valueInput.nodeName === 'INPUT' &&
        (valueInput as HTMLInputElement).type === 'number'
      ) {
        (newValueInput as HTMLInputElement).value = (
          valueInput as HTMLInputElement
        ).value;
      }
      break;
    case 'checkbox':
      newValueInput = createElement('input', {
        type: 'checkbox',
        className: stylesConfig.formValueInput,
      });
      break;
  }

  valueInput.replaceWith(newValueInput);
  window.setTimeout(() => newValueInput.focus());

  return newValueInput;
};

// обработчик клика на кнопку сохранения формы
const handleSaveButtonClick = (form: HTMLFormElement, oldValues?: OldValues) => {
  const keyInput = form.querySelector(`.${stylesConfig.formKeyInput}`,) as HTMLInputElement;
  const nameInput = form.querySelector(`.${stylesConfig.formNameInput}`,) as HTMLInputElement;
  const valueInput = form.querySelector(`.${stylesConfig.formValueInput}`) as HTMLElement;
  const valueInputTypeSelect = form.querySelector(`.${stylesConfig.formValueTypeInput}`,) as HTMLSelectElement;

  if (!form.checkValidity()) {
    form.reportValidity();
    return;
  }

  const key = keyInput.value.replace(/[ ]+/g, '_'),
    name = nameInput.value,
    target = oldValues && form.parentElement ? form.parentElement : targetNode,
    isKeyUsed = Array.from(target.children).find((node) => {
      return node.nodeName !== 'SUMMARY' && node.querySelector(`[data-key="${key}"]`);
    });

  if (isKeyUsed) {
    setError('в этом разделе уже есть поле с таким ключём');
    return;
  }

  let value: any,
    step = '';
  const valueInputType = valueInputTypeSelect.value as ValueInputTypes;

  switch (valueInputType) {
    case 'select': {
      let options = Array.from(
        form.getElementsByClassName(stylesConfig.optionsFormOptionInputs),
      ).map(
        (div): Option => ({
          value: (
            div.querySelector(`.${stylesConfig.formValueInput}`) as HTMLInputElement
          ).value,
          name: (div.querySelector(`.${stylesConfig.formNameInput}`) as HTMLInputElement)
            .value,
          chosen: (div.querySelector('[type=radio]') as HTMLInputElement).checked,
        }),
      );

      let isValuesUnique = true;
      for (let i = 0; i < options.length - 1 && isValuesUnique; i++) {
        isValuesUnique = options
          .slice(i + 1)
          .some(
            (option) =>
              options[i].value !== option.value && options[i].name !== option.name,
          );
      }

      if (!isValuesUnique) {
        setError('Ключи и названия опций должны быть уникальными');
        return;
      }

      value = options;
      break;
    }
    case 'text': {
      value = (valueInput as HTMLInputElement).value;
      break;
    }
    case 'float':
    case 'integer': {
      value = Number((valueInput as HTMLInputElement).value.replace(',', '.'));
      step = (valueInput as HTMLInputElement).step;
      break;
    }
    case 'checkbox': {
      value = (valueInput as HTMLInputElement).checked;
      break;
    }
    case 'details': {
      let isOldValuesValueObject = typeof oldValues?.value === 'object' && !Array.isArray(oldValues.value);
      value = isOldValuesValueObject ? oldValues?.value : ({} as DynamicObject);
      break;
    }
  }

  (document.querySelector(`.${stylesConfig.wrapper}`) as HTMLElement).dataset.isChanged =
    'true';

  createConfigPageRecursion(
    {
      [key]: { name, value, type: valueInputType, step },
    },
    target,
  );

  form.remove();

  // определяем, что нужно изменить в дефолтном конфиге
  let saveInDefault = '';
  if (!oldValues) {
    saveInDefault = 'key name value';
  } else {
    saveInDefault =
      (targetNode.querySelector(`[data-key="${oldValues.key}"]`) as HTMLElement)?.dataset
        .saveInDefault || '';
    saveInDefault +=
      key !== oldValues.key && !saveInDefault.includes('key') ? ' key' : '';
    saveInDefault +=
      name !== oldValues.name && !saveInDefault.includes('name') ? ' name' : '';
    if (
      (JSON.stringify(value) !== JSON.stringify(oldValues.value) &&
        !saveInDefault.includes('value')) ||
      (oldValues.type && oldValues.type !== valueInputType)
    ) {
      saveInDefault += ' value';
    }
    saveInDefault = saveInDefault.trimStart();
  }

  // создаём вложенную форму, если был создан раздел
  if (valueInputType === 'details') {
    const newTarget = Array.from(target.children).find((node) =>
      node.querySelector(`[data-key="${key}"]`),
    ) as HTMLElement | null;
    if (!newSection) {
      newSection = newTarget;
    }

    newTarget?.appendChild(createForm({ targetNode: newTarget }));
  }

  (target.querySelector(`[data-key="${key}"]`) as HTMLElement).dataset.saveInDefault =
    saveInDefault;

  document.removeEventListener('click', removeForm);
};

const createInput = (
  descriptionText: string,
  value: string,
  style: string,
  parent?: HTMLElement,
) => {
  const wrapper = createElement(
    'div',
    null,
    parent,
    createElement('span', { innerHTML: `${descriptionText}:` }),
  );

  return createElement(
    'input',
    { type: 'text', required: true, className: style, value: value },
    wrapper,
  );
};

// удаляет форму по клику на любое место на экране, кроме самой формы
const removeForm = (e: Event) => {
  const form = document.querySelector(`.${stylesConfig.form}`);

  if (form && !e.composedPath().includes(form)) {
    if (oldValues) {
      form.replaceWith(targetNode);
    } else {
      form.remove();
      if (
        isCreatedSection &&
        !Array.from(newSection?.children || ([] as HTMLElement[])).some(
          (elem) => elem.nodeName !== 'SUMMARY',
        )
      ) {
        newSection?.remove();
      }
      isCreatedSection = false;
      newSection = null;
    }
    document.removeEventListener('click', removeForm);
  }
};

// добавляет текст ошибки валидации в форме
const setError = (text: string) => {
  const form = document.querySelector(`.${stylesConfig.form}`) as HTMLElement;
  if (!form) {
    return;
  }

  const errorMessage = createElement(
    'p',
    {
      className: stylesConfig.formValidationError,
      innerHTML: text,
    },
    form,
  );

  const removeError = () => {
    errorMessage.remove();
    form?.removeEventListener('click', removeError);
  };

  setTimeout(() => form?.addEventListener('click', removeError));
};

// форма ввода опций для селекта
const createOptionsForm = (oldValues: HTMLOptionElement[] = []) => {
  const form = createElement('div', { className: stylesConfig.optionsForm });

  createElement(
    'h3',
    { innerHTML: 'Создайте опции', },
    form,
  );

  createElement(
    'div',
    { className: stylesConfig.optionsFormTableHead, },
    form,
    [
      createElement('p', { innerHTML: 'Имя' }),
      createElement('p', { innerHTML: 'Значение' }),
    ],
  );

  createElement(
    'button',
    {
      textContent: 'Добавить опцию',
      className: stylesConfig.optionsFormAddOptionButton,
      type: 'button',
      onclick: () => createOptionInputs(form),
    },
    form,
  );

  // если это форма редактирования, то добавляем уже существующие опции, если нет, то добавляем дефолтную
  if (oldValues.length > 0) {
    oldValues.forEach((option: HTMLOptionElement) => createOptionInputs(form, option));
  } else {
    createOptionInputs(
      form,
      createElement('option', { value: 'none', innerHTML: 'не выбрано', selected: true }),
    );
    createOptionInputs(form);
  }

  return form;
};

// инпуты для опций
const createOptionInputs = (parent: HTMLElement, option?: HTMLOptionElement) => {
  const addNewOptionButton = parent.querySelector(
    `.${stylesConfig.optionsFormAddOptionButton}`,
  ) as HTMLElement;

  const optionNameInput = createElement('input', {
    type: 'text',
    className: stylesConfig.formNameInput,
    required: true,
    value: option?.textContent || '',
  });

  const optionValueInput = createElement('input', {
    type: 'text',
    className: stylesConfig.formValueInput,
    required: true,
    value: option?.value || '',
  });

  const optionSelectedInput = createElement('input', {
    type: 'radio',
    checked: option?.selected,
    name: 'chosen',
  });

  const optionInputsContainer = createElement(
    'div',
    { className: stylesConfig.optionsFormOptionInputs },
    null,
    [optionNameInput, optionValueInput, optionSelectedInput],
  );

  createConfigControlButton({
    type: 'delete',
    parent: optionInputsContainer,
    onClick: () => {
      optionInputsContainer.remove();
      if (
        parent.querySelectorAll(`.${stylesConfig.optionsFormOptionInputs}`).length === 0
      ) {
        createOptionInputs(parent);
      }
    },
  });

  parent.insertBefore(optionInputsContainer, addNewOptionButton);
  window.setTimeout(() => optionNameInput.focus());
};

export default createForm;
