import saveConfig from '../saveConfigPage/saveConfigPage';
import createForm from './createForm';
import stylesConfig from '../../styleNames';
import { createElement, emit } from '../../utils';
import { close as closeConfig } from '../../../index';
import {
  createConfigControlButton,
  showInvalidInputs,
  createSubmitDeleteModal,
  sortComparator,
} from './helpers/helpers';

type ConfigPageProps = {
  config: Config;
  newFields: string[];
  isConfigChanged: boolean;
  pathToSaveLocalConfig: string;
  pathToSaveDefaultConfig: string;
};

let newFields: string[];
let mainConfig: Config;
const paths = {} as { default: string; local: string };

// функция создаёт HTML страницу конфига
const createConfigPageEntry = (props: ConfigPageProps): HTMLFormElement => {
  newFields = props.newFields;
  mainConfig = props.config;
  paths.local = props.pathToSaveLocalConfig;
  paths.default = props.pathToSaveDefaultConfig;

  const wrapper = createElement('form', {
    className: stylesConfig.wrapper,
    onsubmit: (e) => e.preventDefault(),
  });
  wrapper.dataset.isChanged = `${props.isConfigChanged}`;

  const heading = createElement(
    'div',
    { className: stylesConfig.heading },
    wrapper,
    createElement('p', { innerHTML: 'Конфиг' }),
  );

  const configEntryLayer = createElement(
    'div',
    {
      className: `${stylesConfig.node} ${stylesConfig.highestNode}`,
    },
    wrapper,
  );

  createConfigControlButton({
    type: 'collapseAll',
    parent: heading,
    onClick: () =>
      configEntryLayer
        .querySelectorAll('details[open]')
        .forEach((details) => ((details as HTMLDetailsElement).open = false)),
  });
  createConfigControlButton({
    type: 'add',
    parent: heading,
    onClick: () =>
      configEntryLayer.appendChild(createForm({ targetNode: configEntryLayer })),
  });

  mainConfig.HTML = wrapper;

  createConfigPageRecursion(mainConfig.united, configEntryLayer);

  createBottomButtons(wrapper);

  emit('info', 'страница с конфигом создана');

  return wrapper;
};

// рекурсивное создание HTML-страницы
const createConfigPageRecursion = (config: DynamicObject, parentNode: HTMLElement) => {
  for (const key in config) {
    const isSection =
      typeof config[key].value === 'object' && !Array.isArray(config[key].value);

    if (isSection) {
      const container = createDetailsField(key, config[key]);

      parentNode.appendChild(container);
      createConfigPageRecursion(config[key].value, container);
    } else {
      createInputField(key, config[key], parentNode);
    }
  }

  // группировка по типу элемента(раздел, инпут) и сортировка по алфавиту
  const children = Array.from(parentNode.children);
  parentNode.innerHTML = '';

  const details: Element[] = [],
    divs: Element[] = [];

  // группировка
  children.forEach((child) => {
    if (child.classList.contains(stylesConfig.nodeDescription)) {
      parentNode.appendChild(child);
      return;
    }

    if (child.classList.contains(stylesConfig.inputNode)) {
      divs.push(child);
    } else {
      details.push(child);
    }
  });

  // сортировка
  divs.sort((a, b) => sortComparator(a.textContent, b.textContent));
  details.sort((a, b) =>
    sortComparator(a.children[0].textContent, b.children[0].textContent),
  );

  parentNode.append(...divs);
  parentNode.append(...details);
};

// создание раздела
const createDetailsField = (key: string, config: DynamicObject): HTMLDetailsElement => {
  const container = createElement('details', { className: stylesConfig.node });

  const description = createElement(
    'summary',
    { className: stylesConfig.nodeDescription, innerHTML: `<span>${config.name}</span>` },
    container,
  );
  description.dataset.key = key;

  // выделение, если поле новое(index.ts: 29)
  if (newFields.includes(key)) {
    description.classList.add(stylesConfig.newNode);

    const removeNew = () => {
      description.classList.remove(stylesConfig.newNode);

      description.removeEventListener('click', removeNew);
    };

    description.addEventListener('click', removeNew);
  }

  createConfigControlButton({
    type: 'add',
    parent: description,
    onClick: () => container.appendChild(createForm({ targetNode: container })),
  });
  createConfigControlButton({
    type: 'edit',
    parent: description,
    onClick: () => handleDetailsEditButtonClick(container, { [key]: { ...config } }),
  });
  createConfigControlButton({
    type: 'delete',
    parent: description,
    onClick: () =>
      createSubmitDeleteModal(mainConfig.HTML, description.parentElement as HTMLElement),
  });

  return container;
};

// обработчик клика на кнопку редактирования раздела
const handleDetailsEditButtonClick = (container: HTMLElement, config: DynamicObject) => {
  const key = Object.keys(config)[0];

  const form = createForm({
    targetNode: container,
    oldValues: {
      key,
      name: config[key].name,
      value: config[key].value,
      type: config[key].type,
      step: config[key].step,
    },
  });

  container.replaceWith(form);
};

// создание инпута
const createInputField = (
  key: string,
  config: DynamicObject,
  parentSection: HTMLElement,
) => {
  const container = createElement('div', { className: stylesConfig.node }, parentSection);
  const description = createElement(
    'p',
    { className: stylesConfig.nodeDescription },
    container,
  );
  description.dataset.key = key;

  createElement(
    'span',
    { innerHTML: config.name, className: stylesConfig.nodeName },
    description,
  );

  // выделение поля
  if (newFields.includes(key)) {
    description.classList.add(stylesConfig.newNode);
    const removeNew = () => {
      description.classList.remove(stylesConfig.newNode);
      description.removeEventListener('click', removeNew);
    };
    description.addEventListener('click', removeNew);
  }

  let valueInput = createValueInput(
    config,
    key,
    container,
  );

  createConfigControlButton({
    type: 'edit',
    parent: description,
    onClick: () => handleInputEditButtonClick(valueInput, container, key, config),
  });
  createConfigControlButton({
    type: 'delete',
    parent: description,
    onClick: () =>
      createSubmitDeleteModal(mainConfig.HTML, description.parentElement as HTMLElement),
  });

  container.classList.add(stylesConfig.inputNode);
};

// создание инпута
const createValueInput = (
  config: DynamicObject,
  key: string,
  parentContainer: HTMLDivElement,
): HTMLInputElement | HTMLSelectElement => {
  let valueInput: HTMLInputElement | HTMLSelectElement;
  const description = parentContainer.querySelector(
    `.${stylesConfig.nodeDescription}`,
  ) as HTMLElement;
  const parentSection = parentContainer.parentElement;

  switch (typeof config.value) {
    case 'boolean':
      valueInput = createElement(
        'input',
        {
          type: 'checkbox',
          checked: config.value,
          onchange: () => (mainConfig.HTML.dataset.isChanged = 'true'),
        },
        parentContainer,
      );
      (description.querySelector(`.${stylesConfig.nodeName}`) as HTMLElement).onclick =
        () => valueInput.click();
      break;
    case 'number':
      valueInput = createElement(
        'input',
        {
          type: 'number',
          value: config.value.toString(),
          step: config.step,
          onchange: () => (mainConfig.HTML.dataset.isChanged = 'true'),
        },
        parentContainer,
      );
      createElement(
        'span',
        {
          innerHTML: ` (${config.type === 'float' ? 'дробное' : 'целое'}  число)`,
          className: stylesConfig.nodeInputType,
        },
        description,
      );
      (description.querySelector(`.${stylesConfig.nodeName}`) as HTMLElement).onclick =
        () => valueInput.focus();
      break;
    // в данном случае это массив, т.к. все поля со значением value типа "объект/не массив" были отсеяны в функции createConfigPageRecursion
    case 'object':
      valueInput = createElement('select', null, parentContainer);

      // для камер нужно ставить уникальный наблюдатель за значением другого инпута камер
      if (key === 'selected_camera') {
        valueInput.dataset.key =
          parentSection?.getElementsByTagName('summary')[0].dataset.key;
        valueInput.onchange = () =>
          handleCameraSelectChange(
            mainConfig.HTML,
            valueInput.value,
            valueInput.dataset.key,
          );
      } else {
        valueInput.onchange = () => (mainConfig.HTML.dataset.isChanged = 'true');
      }

      config.value.forEach((opt: Option) =>
        createElement(
          'option',
          {
            textContent: opt.name,
            value: opt.value,
            selected: opt.chosen,
            disabled: opt?.disabled,
          },
          valueInput,
        ),
      );
      break;
    // default также отвечает за текстовый инпут
    default: {
      valueInput = createElement(
        'input',
        {
          type: 'text',
          value: config.value,
          onchange: () => (mainConfig.HTML.dataset.isChanged = 'true'),
        },
        parentContainer,
      );
      createElement(
        'span',
        { innerHTML: ' (текст)', className: stylesConfig.nodeInputType },
        description,
      );
      (description.querySelector(`.${stylesConfig.nodeName}`) as HTMLElement).onclick =
        () => valueInput.focus();
    }
  }

  return valueInput;
};

// проверяет значение другого инпута камер и обнуляет его, если оно совпадает с введённым
const handleCameraSelectChange = (
  configHTML: HTMLElement,
  currentInputValue: string,
  currentInputKey: string | undefined,
) => {
  const anotherCameraSelect = Array.from(document.getElementsByTagName('select')).find(
    (s) => s.dataset.key && s.dataset.key !== currentInputKey,
  );
  if (anotherCameraSelect?.value === currentInputValue && currentInputValue !== 'none') {
    anotherCameraSelect.value = 'none';
  }
  configHTML.dataset.isChanged = 'true';
};

// обработчик клика на кнопку изменения инпута
const handleInputEditButtonClick = (
  valueInput: HTMLInputElement | HTMLSelectElement,
  parent: HTMLElement,
  key: string,
  config: DynamicObject,
) => {
  let value: string | boolean | HTMLOptionElement[] | number;

  // находим значение инпута
  if (valueInput.nodeName === 'SELECT') {
    value = Array.from((valueInput as HTMLSelectElement).options);
  } else {
    switch (valueInput.type) {
      case 'number': {
        value = +valueInput.value.replace(',', '.');
        break;
      }
      case 'checkbox': {
        value = (valueInput as HTMLInputElement).checked;
        break;
      }
      default:
        value = valueInput.value;
    }
  }

  // форма создания/изменения поля
  const form = createForm({
    targetNode: parent,
    oldValues: {
      key,
      name: config.name,
      value,
      step: config?.step,
      type: config?.type,
    },
  });

  parent.replaceWith(form);
};

// создаёт кнопки "сохранить" и "сбросить изменения" внизу экрана
const createBottomButtons = (parentNode: HTMLFormElement) => {
  const buttonContainer = createElement(
    'div',
    { className: stylesConfig.bottomButtonsContainer },
    parentNode,
  );

  createElement(
    'button',
    {
      className: stylesConfig.bottomButton,
      textContent: 'Сохранить изменения',
      onclick: () => {
        if (parentNode.checkValidity()) {
          const newConfig = saveConfig({
            config: mainConfig,
            pathToSaveLocalConfig: paths.local,
            pathToSaveDefaultConfig: paths.default,
          });
          if (newConfig) {
            mainConfig.united = newConfig;
          }
        } else {
          showInvalidInputs(parentNode);
        }
      },
    },
    buttonContainer,
  );

  createElement(
    'button',
    {
      className: stylesConfig.bottomButton,
      textContent: 'Сбросить изменения',
      onclick: closeConfig,
    },
    buttonContainer,
  );
};

export { createConfigPageRecursion };
export default createConfigPageEntry;
