import { observeAttributes } from '../helpers/componentHelpers';

const defaultValidateSchema = () => true;
const defaultItemRenderer = (item) => `<pre>${JSON.stringify(item, null, 4)}</pre>`;

const createTemplate = (templateString) => {
  const template = document.createElement('template');
  template.innerHTML = templateString;

  return template;
};

const getTemplateContent = (templateOrString) => {
  if (typeof templateOrString === 'string') {
    return createTemplate(templateOrString).content;
  }

  if (templateOrString instanceof HTMLTemplateElement) {
    return templateOrString.content;
  }

  // eslint-disable-next-line no-console
  console.error('Invalid template', templateOrString);
  return null;
};

export const createDynamicDataMixin =
  ({
    itemRenderer = defaultItemRenderer,
    validateSchema = defaultValidateSchema,
    slotName,
    rerenderAttrsList = [],
  }) =>
  (superclass) =>
    class DynamicDataMixinClass extends superclass {
      #data = [];

      #validateSchema(data) {
        if (!validateSchema) return true;

        const validation = validateSchema(data);
        if (validation === true) return true;

        // eslint-disable-next-line no-console
        console.error('Data schema validation failed', validation || '');

        return false;
      }

      #removeOldItems() {
        const selector = slotName ? `*[slot="${slotName}"]` : ':not([slot])';
        this.baseElement.querySelectorAll(selector).forEach((item) => item.remove());
      }

      #renderItems() {
        this.#removeOldItems();
        this.data.forEach((item, index) => {
          const content = getTemplateContent(itemRenderer(item, index, this));
          this.baseElement.appendChild(content?.cloneNode(true));
        });
      }

      set data(value) {
        if (this.#validateSchema(value)) {
          this.#data = value;
          this.#renderItems();
        }
      }

      get data() {
        return this.#data;
      }

      init() {
        super.init?.();

        observeAttributes(
          this,
          (attrs) => {
            if (attrs.includes('data')) this.#handleDataAttr();
            else this.#renderItems();
          },
          { includeAttrs: [...rerenderAttrsList, 'data'] }
        );
      }

      #handleDataAttr() {
        const dataAttr = this.getAttribute('data');

        if (!dataAttr) return;

        try {
          this.data = JSON.parse(dataAttr);
        } catch (e) {
          // eslint-disable-next-line no-console
          console.warn('Invalid JSON data', dataAttr);
        }
      }
    };
