import { Token, TokenGroup, TokenType } from '@supernovaio/sdk-exporters';
import { FileData } from '../config/fileConfig';
import { indentAndFormat } from '../formatters/stylesFormatter';
import { convertToJs, convertToScss, deepMergeObjects } from '../helpers/objectHelper';
import { generateStylesFromTokens } from './stylesGenerator';
import { StylesObjectType, generateStylesObjectFromTokens } from './stylesObjectGenerator';
import { findTokenPrefix } from '../helpers/findTokenPrefix';
import { generateMixinFromTokens } from './mixinGenerator';

// Add disclaimer to the top of the content
export const addDisclaimer = (content: string): string => {
  return `/* This file was generated by Supernova, don't change manually */\n${content}`;
};

export const filterTokensByTypeAndGroup = (tokens: Token[], type: TokenType, group: string) => {
  return tokens.filter((token) => {
    const hasMatchingType = token.tokenType === type;
    const isInGroup = token.origin?.name?.includes(group);
    const hasValidTypography = !(token.tokenType === TokenType.typography && token.name.includes('-Underline'));

    return hasMatchingType && isInGroup && hasValidTypography;
  });
};

const addEmptyLineBetweenTokenGroups = (index: number, length: number): string => {
  return index !== length - 1 ? '\n\n' : '\n';
};

type ExportTemplateCallback = (entriesLength: number) => (entry: [string, unknown], index: number) => string;

const jsExportTemplate: ExportTemplateCallback = (entriesLength) => {
  return ([key, obj], index) => {
    return `export const ${key} = {\n${convertToJs(obj as StylesObjectType)}\n};${addEmptyLineBetweenTokenGroups(index, entriesLength)}`;
  };
};

const scssExportTemplate: ExportTemplateCallback = (entriesLength) => {
  return ([key, obj], index) => {
    return `${key}: (\n${convertToScss(obj as StylesObjectType)}\n) !default;${addEmptyLineBetweenTokenGroups(index, entriesLength)}`;
  };
};

const generateObjectOutput = (stylesObject: StylesObjectType, callback: ExportTemplateCallback) => {
  const entries = Object.entries(stylesObject);

  return entries.map(callback(entries.length)).join('');
};

export const generateJsObjectOutput = (stylesObject: StylesObjectType): string => {
  return generateObjectOutput(stylesObject, jsExportTemplate);
};

export const generateScssObjectOutput = (stylesObject: StylesObjectType): string => {
  return generateObjectOutput(stylesObject, scssExportTemplate);
};

export const getGroups = (tokens: Token[], excludeGroupNames: string[] | null, groupNames: string[]): string[] => {
  let groups;

  if (excludeGroupNames && excludeGroupNames.length > 0) {
    const filteredTokens = tokens.filter((token) => {
      return !excludeGroupNames.some((excludedGroup) => token.origin?.name?.includes(excludedGroup));
    });

    const restOfGroupNames = filteredTokens.reduce((acc: string[], token) => {
      const groupName = token.origin?.name?.split('/')[0];
      if (groupName && !acc.includes(groupName)) {
        acc.push(groupName);
      }

      return acc;
    }, []);

    groups = [...new Set(restOfGroupNames)];
  } else {
    groups = groupNames;
  }

  return groups;
};

export const generateFileContent = (
  tokens: Token[],
  mappedTokens: Map<string, Token>,
  tokenGroups: Array<TokenGroup>,
  fileData: FileData,
  hasJsOutput: boolean,
) => {
  let styledTokens = '';
  let stylesObject: StylesObjectType = {};
  let styledMixin = '';
  const {
    excludeGroupNames = null,
    groupNames = [''],
    hasMixin = false,
    hasParentPrefix = true,
    hasStylesObject = true,
    sortByNumValue = false,
    tokenTypes,
  } = fileData;

  // Iterate over token types and groups to filter tokens
  tokenTypes.forEach((tokenType) => {
    const groups = getGroups(tokens, excludeGroupNames, groupNames);

    groups.forEach((group) => {
      const filteredTokens = filterTokensByTypeAndGroup(tokens, tokenType, group);
      const tokenPrefix = findTokenPrefix(tokens);

      // Generate css tokens
      if (tokenType !== TokenType.typography) {
        styledTokens += generateStylesFromTokens(
          filteredTokens,
          mappedTokens,
          tokenGroups,
          tokenPrefix,
          hasMixin,
          hasParentPrefix,
          sortByNumValue,
          hasJsOutput,
        );
        styledTokens += '\n\n';
      }

      // Generate mixin
      if (!hasJsOutput && hasMixin) {
        styledMixin += generateMixinFromTokens(
          filteredTokens,
          tokenGroups,
          tokenPrefix,
          hasParentPrefix,
          sortByNumValue,
        );
      }

      // Generate css object and merge it with the existing one
      const groupStylesObject = generateStylesObjectFromTokens(
        filteredTokens,
        tokenGroups,
        hasParentPrefix,
        hasJsOutput,
        sortByNumValue,
      );
      stylesObject = deepMergeObjects(stylesObject, groupStylesObject);
    });
  });

  let content = styledTokens;

  // convert css object to scss or js structure based on file extension
  if (hasStylesObject) {
    content += hasJsOutput ? generateJsObjectOutput(stylesObject) : generateScssObjectOutput(stylesObject);
  }

  if (!hasJsOutput && hasMixin) {
    content += styledMixin;
  }

  return {
    content: addDisclaimer(indentAndFormat(content, hasJsOutput)),
  };
};
