import { camelCaseJoin, isFunction, kebabCase, kebabCaseJoin } from '../../helpers';
import { getCssVarName } from '../../helpers/componentHelpers';

const getOverrideCssVarName = (origVarName) => `${origVarName}__override`;

const createCssVar = (varName, fallback, createOverride = false) => {
  const ret = `var(${varName}${fallback ? `, ${fallback}` : ''})`;

  if (!createOverride) return ret;

  // we are generating an override css var to allow override with fallback to the default var
  const overrideVarName = getOverrideCssVarName(varName);
  return `var(${overrideVarName}, ${ret})`;
};

const createCssSelector = (baseSelector = '', relativeSelectorOrSelectorFn = '') =>
  isFunction(relativeSelectorOrSelectorFn)
    ? relativeSelectorOrSelectorFn(baseSelector)
    : `${baseSelector}${
        /^[A-Za-z]/.test(relativeSelectorOrSelectorFn)
          ? ` ${relativeSelectorOrSelectorFn}`
          : relativeSelectorOrSelectorFn
      }`;

class StyleBuilder {
  constructor() {
    this.styleMap = new Map();
  }

  add(selector, property, value) {
    if (!this.styleMap.has(selector)) {
      this.styleMap.set(selector, []);
    }

    this.styleMap.set(selector, [...this.styleMap.get(selector), { property, value }]);
  }

  toString() {
    return Array.from(this.styleMap.entries()).reduce((acc, [selector, propValArr]) => {
      const properties = propValArr
        .map(({ property, value }) => `${property}: ${value}`)
        .join(';\n');

      return `${acc}${selector} { \n${properties} \n}\n\n`;
    }, '');
  }
}

const normalizeConfig = (attr, config) => {
  const defaultMapping = { selector: '', property: kebabCase(attr) };

  if (!config || !Object.keys(config).length) return [defaultMapping];

  if (Array.isArray(config)) return config.map((entry) => ({ ...defaultMapping, ...entry }));

  return [{ ...defaultMapping, ...config }];
};

const getFallbackVarName = (origVarName, suffix = 'fallback') => kebabCaseJoin(origVarName, suffix);

export const createStyle = (componentName, baseSelector, mappings) => {
  const style = new StyleBuilder();
  // we generate all the fallback vars recursively
  const createFallbackVar = (fallback, origVarName) => {
    if (!fallback) return '';
    if (typeof fallback === 'string') return fallback;

    const fallbackVarName = getFallbackVarName(origVarName, fallback?.suffix);
    return createCssVar(fallbackVarName, createFallbackVar(fallback.fallback, fallbackVarName));
  };

  Object.keys(mappings).forEach((attr) => {
    const attrConfig = normalizeConfig(attr, mappings[attr]);

    const cssVarName = getCssVarName(componentName, attr);

    attrConfig.forEach(
      ({ selector: relativeSelectorOrSelectorFn, property, important, fallback }) => {
        const fallbackValue = createFallbackVar(fallback, cssVarName);
        style.add(
          createCssSelector(baseSelector, relativeSelectorOrSelectorFn),
          isFunction(property) ? property() : property,
          createCssVar(cssVarName, fallbackValue, true) + (important ? '!important' : '')
        );
      }
    );
  });

  return style.toString();
};

const getFallbackVarsNames = (attr, origVarName, { fallback }) => {
  if (!fallback) return {};

  const fallbackVarName = getFallbackVarName(origVarName, fallback.suffix);
  const fallbackAttrName = camelCaseJoin(attr, fallback.suffix || 'fallback');
  return {
    [fallbackAttrName]: fallbackVarName,
    ...getFallbackVarsNames(fallbackAttrName, fallbackVarName, fallback),
  };
};

export const createCssVarsList = (componentName, mappings) =>
  Object.keys(mappings).reduce((acc, attr) => {
    const varName = getCssVarName(componentName, attr);

    return Object.assign(
      acc,
      { [attr]: varName },
      // we are exposing the override css var names, the convention is to add 'Override' suffix to the attribute name
      { [`${attr}Override`]: getOverrideCssVarName(varName) },
      getFallbackVarsNames(attr, varName, mappings[attr])
    );
  }, {});

// on some cases we need a selector to be more specific than another
// for this we have this fn that generate a class selector multiple times
export const createClassSelectorSpecifier = (className, numOfRepeats) =>
  `.${className}`.repeat(numOfRepeats);
