import { kebabCaseJoin } from '.';
import { DESCOPE_PREFIX } from '../constants';

export const observeAttributes = (ele, callback, { excludeAttrs = [], includeAttrs = [] }) => {
  // sync all attrs on init
  const filteredAttrs = Array.from(ele.attributes)
    .filter(
      (attr) =>
        !excludeAttrs.includes(attr.name) &&
        (!includeAttrs.length || includeAttrs.includes(attr.name))
    )
    .map((attr) => attr.name);

  callback(filteredAttrs);

  const observer = new MutationObserver((mutationsList) => {
    mutationsList.forEach((mutation) => {
      if (
        mutation.type === 'attributes' &&
        !excludeAttrs.includes(mutation.attributeName) &&
        (!includeAttrs.length || includeAttrs.includes(mutation.attributeName))
      ) {
        callback([mutation.attributeName]);
      }
    });
  });

  observer.observe(ele, { attributes: true });
};

// calling the callback with this object: { addedNodes, removedNodes }
export const observeChildren = (ele, callback) => {
  callback({ addedNodes: Array.from(ele.children), removedNodes: [] });

  const observer = new MutationObserver((mutationsList) => {
    mutationsList.forEach((mutation) => {
      if (mutation.type === 'childList' || mutation.type === 'characterData') {
        callback(mutation);
      }
    });
  });

  observer.observe(ele, { childList: true, characterData: true, subtree: true });
};

const createSyncAttrsCb =
  (srcEle, targetEle, mapAttrs = {}) =>
  (attrNames) => {
    attrNames.forEach((attrName) => {
      const targetAttrName = mapAttrs[attrName] || attrName;
      const srcAttrVal = srcEle.getAttribute(attrName);

      if (!targetEle) return;

      if (srcAttrVal !== null) {
        if (targetEle.getAttribute(targetAttrName) !== srcAttrVal) {
          targetEle.setAttribute(targetAttrName, srcAttrVal);
        }
      } else {
        targetEle.removeAttribute(targetAttrName);
      }
    });
  };

export const syncAttrs = (ele1, ele2, options) => {
  observeAttributes(ele1, createSyncAttrsCb(ele1, ele2), options);
  observeAttributes(ele2, createSyncAttrsCb(ele2, ele1), options);
};

export const getComponentName = (name) => kebabCaseJoin(DESCOPE_PREFIX, name);

export const getCssVarName = (...args) => `--${kebabCaseJoin(...args)}`;

export const forwardAttrs = (source, dest, options = {}) => {
  observeAttributes(source, createSyncAttrsCb(source, dest, options.mapAttrs), options);
};

export const forwardProps = (src, target, props = []) => {
  if (!props.length) return;

  const config = props.reduce(
    (acc, prop) =>
      Object.assign(acc, {
        [prop]: {
          get() {
            return src[prop];
          },
          set(v) {
            src[prop] = v;
          },
        },
      }),
    {}
  );

  Object.defineProperties(target, config);
};
