import { toCSS, toJSON } from 'cssjson';
import { camelcaseify } from '../themes/utils/string.utils';

import { CSSProperties } from 'glamor';
import { camelcaseifyCssProperty } from '../themes/utils/styles.utils';

// eslint-disable-next-line @typescript-eslint/no-var-requires
// const beautify_css = require("js-beautify").css;
import { css } from 'js-beautify';
const beautify_css = css;
/**
 * Helper function to determine if a property key is a selector
 * @param styleKey
 * @returns
 */
export const isSelector = (property: string): boolean => {
  const selectorPrefixes = [':', '&:', '>', '.', '::', '@'];
  const found = selectorPrefixes.find((prefix) => property.startsWith(prefix));

  return found !== undefined;
};

export default class CSS {
  static parseCss = (css: string): object => {
    const cssObj: any = toJSON(css);
    const allStyles: { [selector: string]: CSSProperties } = {};
    const parseChildren = (children: any) => {
      Object.keys(children).forEach((cssSelectorString) => {
        const styles = children[cssSelectorString].attributes;
        if (!styles) {
          parseChildren(children.children);
        } else {
          allStyles[cssSelectorString] = styles;
        }
      });
    };

    parseChildren(cssObj.children);
    return allStyles;
  };

  static toJSONAttributes(cssString: string): object {
    return toJSON(cssString);
  }

  static getCssObjectWithCamelcaseProperties(values: any): object {
    const _values: any = {};
    Object.keys(values).forEach((k) => {
      const _nKey = camelcaseify(k);
      _values[_nKey] = values[k];
    });
    return _values;
  }

  static getCssObjectWithDashedProperties(values: {
    [id: string]: any;
  }): object {
    const _values: any = {};
    Object.keys(values).forEach((k) => {
      const _nKey = CSS.camelcaseToDashed(k);
      _values[_nKey] = values[k];
    });
    return _values;
  }

  static isMediaQuery(value: string): boolean {
    return value.startsWith('@media');
  }

  static getSelectorValues(cssString: string): object {
    const json: any = CSS.parseCss(cssString);
    const _values: any = {};
    Object.keys(json).forEach((key) => {
      const values = json[key];

      const appentToRoot = () => {
        if (_values['root']) {
          _values['root'] = {
            ..._values['root'],
            ...CSS.getCssObjectWithCamelcaseProperties(values)
          };
        } else {
          _values['root'] = CSS.getCssObjectWithCamelcaseProperties(values);
        }
      };
      if (isSelector(key)) {
        let k = key;
        if (!CSS.isMediaQuery(key)) {
          const marker = '********';
          const selectorKey = key.replace(':', marker);
          k = selectorKey.split(marker)[1];
        }
        if (k !== undefined && !CSS.isMediaQuery(key)) {
          _values[`:${k}`] = CSS.getCssObjectWithCamelcaseProperties(values);
        } else if (CSS.isMediaQuery(key)) {
          _values[k] = CSS.getCssObjectWithCamelcaseProperties(values);
        } else {
          if (_values['root']) {
            _values['root'] = {
              ..._values['root'],
              ...CSS.getCssObjectWithCamelcaseProperties(values)
            };
          } else {
            appentToRoot();
          }
        }
      } else {
        appentToRoot();
      }
    });

    return _values;
  }

  static toCssInJs(cssString: string): object {
    const json: any = CSS.getSelectorValues(cssString);
    let cssInJs = {};
    if (json.root) {
      cssInJs = json.root;
    }
    Object.keys(json).forEach((key) => {
      if (key !== 'root') {
        //@ts-ignore
        cssInJs[key] = json[key];
      }
    });

    return cssInJs;
  }

  static toCssInJsPrettified(value: string): string {
    const css = CSS.toCssInJs(value);
    const result2 = CSS.prettify(JSON.stringify(css));
    return result2;
  }

  static toCssInJs2Prettified(value: string): string {
    const css = CSS.toCssInJs(value);
    const result = CSS.prettify(toCSS(css));
    return result;
  }

  static fromCssInJsToJSONAttributes(value: {
    [id: string]: object | string;
  }): object {
    const tempSelector = '.container';
    const jsonInAttributes: any = {
      children: {},
      attributes: {}
    };
    let json: any = {
      children: {},
      attributes: {}
    };
    Object.keys(value).forEach((key) => {
      let id = tempSelector;
      if (isSelector(key) && !CSS.isMediaQuery(key)) {
        id = `${tempSelector}${key}`;
      } else if (CSS.isMediaQuery(key)) {
        id = key;
      }

      const styles = value[key];
      if (typeof styles === 'object') {
        // ONLY ACCEPTS ONE LEVEL
        if (CSS.isMediaQuery(key)) {
          jsonInAttributes.children[id] = {
            attributes: {},
            children: {
              [tempSelector]: {
                //@ts-ignore
                attributes: CSS.getCssObjectWithDashedProperties(value[key]),
                children: {}
              }
            }
          };
        } else {
          jsonInAttributes.children[id] = {
            children: {},
            //@ts-ignore
            attributes: CSS.getCssObjectWithDashedProperties(value[key])
          };
        }
      } else {
        const id = CSS.camelcaseToDashed(key);
        if (!isSelector(id)) {
          json = {
            //@ts-ignore
            attributes: {
              ...json.attributes,
              [id]: value[key]
            }
          };
        }
      }
    });

    const jsonA = {
      children: {
        ...{ [tempSelector]: { attributes: json.attributes, children: {} } },
        ...jsonInAttributes.children
      },
      attributes: {}
    };
    return jsonA;
  }

  static fromflatJSONToJSONAttributes(value: { [id: string]: object }): object {
    const tempSelector = '.container';
    const jsonInAttributes: any = {
      children: {},
      attributes: {}
    };
    Object.keys(value).forEach((key) => {
      let id = tempSelector;
      if (isSelector(key) && !CSS.isMediaQuery(key)) {
        id = `${tempSelector}${key}`;
      } else if (CSS.isMediaQuery(key)) {
        id = key;
      }

      // ONLY ACCEPTS ONE LEVEL
      if (CSS.isMediaQuery(key)) {
        jsonInAttributes.children[id] = {
          attributes: {},
          children: {
            [tempSelector]: {
              attributes: CSS.getCssObjectWithDashedProperties(value[key]),
              children: {}
            }
          }
        };
      } else {
        jsonInAttributes.children[id] = {
          children: {},
          attributes: CSS.getCssObjectWithDashedProperties(value[key])
        };
      }
    });
    return jsonInAttributes;
  }
  /**
   * Convert JSON to CSS
   * @param value JSON in string or object format to be converted to CSS
   * @returns
   */
  static fromCssInJstoCssString(
    value: { [id: string]: object | string },
    prettify?: boolean
  ): string {
    try {
      const jsonAttributes = CSS.fromCssInJsToJSONAttributes(value);
      const css = toCSS(jsonAttributes);
      if (prettify) {
        return CSS.prettify(css);
      }
      return css;
    } catch (e) {
      //   return `UNABLE TO CONVERT JSON TO CSS ${e}`;
      return `${value}`;
    }
  }

  static prettify(value: string): string {
    return beautify_css(value);
  }

  /**
   * Finds all uppercase letters, converts them to lowercase and the sufix - before them
   * @param str string in camelcase format
   */
  static camelcaseToDashed(str: string): string {
    return str.replace(/[A-Z]/g, (match) => {
      return '-' + match.toLowerCase();
    });
  }

  static toCamelcase(css: CSSProperties): CSSProperties {
    const camel: CSSProperties = {};
    Object.keys(css).forEach((key) => {
      camel[camelcaseifyCssProperty(key)] = css[key];
    });
    return camel;
  }
}
