import { CSSProperties } from 'glamor';
import { Palette } from '../Palette';
import { Theme } from '../Theme';
import { Token } from '../Token';
import ColorUtils from './color.utils';
import { ColorDefinition } from './color.interface';
import { CssPropertyStyle, StyleTokenReference } from '../style.interface';
import { tokenTypes } from '../token.interface';

interface ReferencePaletteTokenInfo {
  paletteId: string;
  paletteMode: string;
  colorId: string;
}
/**
 * Get palette info from reference
 * @param reference Token reference
 * @param selectedTheme light | dark
 * @returns {
      paletteId: string;
      paletteMode: string;
      colorId: string;
    }
 */
export const getPaletteInfoFromReferenceKey = (
  reference: StyleTokenReference,
  selectedTheme?: 'dark' | 'light'
): ReferencePaletteTokenInfo | undefined => {
  let paletteId;
  let paletteMode;
  let colorId;
  if (reference.type === 'color') {
    // key can be in 2 different formats
    // base.primary.color or theme.color
    const referenceKey = reference.key.split('.');
    if (referenceKey.length === 2) {
      // reference is a themed color
      const mode = referenceKey[0];
      if (mode === 'theme') {
        paletteId = selectedTheme;
        paletteMode = selectedTheme;
        colorId = referenceKey[1];
      }
    } else if (referenceKey.length === 3) {
      paletteId = referenceKey[1];
      paletteMode = referenceKey[0];
      colorId = referenceKey[2];
    }
  }

  if (!paletteId || !paletteMode || !colorId) {
    return undefined;
  }
  return {
    paletteId,
    paletteMode,
    colorId
  };
};

export const isValidTokenKey = (tokenStringKey: string): boolean => {
  if (typeof tokenStringKey !== 'string') {
    // console.warn("key must be of type string");
    return false;
  }

  const info = tokenStringKey.split('.');
  if (info[0] === '0') {
    return false;
  }
  if (info.length < 2) {
    // console.warn(
    //   `key does not have the minimum length for a reference key (${tokenStringKey})`
    // );
    return false;
  }

  if (info[0] === 'base') {
    if (info.length !== 3) {
      // console.warn("base references must have 3 tokens");
      return false;
    }
  }

  if (info[0] === 'theme') {
    if (info.length !== 2) {
      // console.warn("theme references must have 2 tokens");
      return false;
    }
  }

  return true;
};

export const getPaletteInfoFromStringKey = (
  stringKey: string,
  selectedTheme?: 'dark' | 'light'
): ReferencePaletteTokenInfo | undefined => {
  if (!stringKey) {
    return undefined;
  }
  if (!isValidTokenKey(stringKey)) {
    return undefined;
  }
  const referenceInfo = stringKey.split('.');
  if (referenceInfo.length === 0) {
    return undefined;
  }

  if (referenceInfo.length === 2) {
    if (referenceInfo[0] === 'theme') {
      if (!selectedTheme) {
        console.warn(
          'attempted to get a themed token but no theme was provided'
        );
        return undefined;
      }

      return {
        paletteId: selectedTheme,
        paletteMode: selectedTheme,
        colorId: referenceInfo[1]
      };
    }
  }

  if (referenceInfo.length === 3) {
    const paletteMode = referenceInfo[0];
    if (paletteMode === 'base') {
      return {
        paletteId: referenceInfo[1],
        paletteMode: referenceInfo[0],
        colorId: referenceInfo[2]
      };
    }
  }
  return undefined;
};

export const getTokenInfoFromReferenceKey = (
  reference: StyleTokenReference
): TokenReferenceInfo | undefined => {
  let tokenType;
  let tokenKey;

  if (reference.type === 'token') {
    const referenceKey = reference.key.split('.');
    if (referenceKey.length === 2) {
      tokenType = referenceKey[0];
      if (!isValidTokenType(tokenType)) {
        console.log('getTokenInfoFromReferenceKey', reference);
        return undefined;
      }
      tokenKey = referenceKey[1];
    }
  }
  if (!tokenType || !tokenKey) {
    return undefined;
  }

  const tokenId = getIdForTokenFromValue(tokenType, tokenKey);
  if (!tokenId) {
    return undefined;
  }
  return {
    tokenType,
    tokenKey,
    tokenId
  };
};

interface TokenReferenceInfo {
  tokenType: string;
  tokenKey: string;
  tokenId: string;
}
export const getTokenInfoFromStringKey = (
  tokenReferenceString: string
): TokenReferenceInfo | undefined => {
  let tokenType;
  let tokenKey;

  if (tokenReferenceString.startsWith('rgba')) {
    // this is a string color
    return undefined;
  }

  if (!isValidTokenKey(tokenReferenceString)) {
    return undefined;
  }

  const referenceKey = tokenReferenceString.split('.');

  if (referenceKey.length === 2) {
    tokenType = referenceKey[0];
    if (!tokenTypes.includes(tokenType as any)) {
      return undefined;
    }

    if (!isValidTokenType(tokenType)) {
      return undefined;
    }
    tokenKey = referenceKey[1];
  }

  if (!tokenType || !tokenKey) {
    return undefined;
  }

  const tokenId = getIdForTokenFromValue(tokenType, tokenKey);
  if (!tokenId) {
    return undefined;
  }
  return {
    tokenType,
    tokenKey,
    tokenId
  };
};

export const getIdForTokenFromValue = (type: string, key: string) => {
  if (!tokenTypes.includes(type as any)) {
    console.warn(
      `INVALID TOKEN VALUE(getIdForTokenFromValue): Attempted to get a token reference with invalid type ${type}`
    );
    return undefined;
  }
  return `${type}.${key}`;
};

export const isValidTokenType = (tokenType: any): boolean => {
  if (!tokenTypes.includes(tokenType)) {
    console.warn(
      `INVALID TOKEN TYPE(isValidTokenType): Attempted to get a token reference with invalid type ${tokenType}`
    );
    return false;
  }
  return true;
};

export const getCssWithThemedTokens = ({
  value,
  lookup,
  withColorsFromPaletteId,
  withRawValue
}: {
  value: CssPropertyStyle[];
  lookup: { [id: string]: string };
  withColorsFromPaletteId?: string; // this is actually the paletted id ('dark' or 'light' for themes things defaults)
  withRawValue?: boolean;
}): CSSProperties => {
  const css: CSSProperties = {};
  if (!value) {
    return css;
  }
  value.map((v) => {
    const property = v.property;
    if (property) {
      const camelCaseProperty = Theme.toCamelCase(property);

      if (typeof v.value === 'string' || typeof v.value === 'number') {
        // start default value
        css[camelCaseProperty] = v.value;
        if (withRawValue === true) {
          const key =
            withColorsFromPaletteId !== undefined
              ? `${v.value}`.replace(
                  'theme.',
                  `theme.${withColorsFromPaletteId}.`
                )
              : v.value;

          const referenceValue = lookup[key] || v.value;
          css[camelCaseProperty] = referenceValue;
        }
      } else {
        css[camelCaseProperty] = getCssWithThemedTokens({
          value: v.value,
          lookup,
          withColorsFromPaletteId,
          withRawValue
        });
      }
    }
  });

  return css;
};

export const getValueFromReference = ({
  reference,
  withRaw,
  lookup,
  theme
}: {
  reference?: any;
  theme: 'dark' | 'light' | undefined;
  withRaw?: boolean | undefined;
  lookup: { [id: string]: string };
}): string | number | undefined => {
  if (!reference) {
    return undefined;
  }
  const referenceValue = reference.key;
  if (!referenceValue) {
    return undefined;
  }

  if (reference?.type === 'palette') {
    let colorValue = reference.value;
    // here we need to update the reference value since it might be stale
    // if we are asking for the raw value we actually want the rgba string
    if (withRaw) {
      const paletteInfo = getPaletteInfoFromReferenceKey(reference, theme);

      // let's get the rgba string from the color palette
      if (paletteInfo) {
        const key = `${paletteInfo.paletteMode}.${paletteInfo.paletteId}.${paletteInfo.colorId}`;
        const color = lookup[key];
        // defaulting to reference, missing rgb string
        colorValue = color;
      } else {
        // defaulting to reference
        colorValue = reference;
      }
    } else {
      const rgbValue = reference.value as ColorDefinition;
      const potentialColorValue = ColorUtils.toRgbaStringFromRgbaObject(
        rgbValue?.rgb
      );
      if (potentialColorValue) {
        colorValue = potentialColorValue;
      }
    }
    return colorValue;
  }

  if (reference?.type === 'token') {
    let value: string | number | ColorDefinition = reference.value;
    if (withRaw) {
      value = reference.value;
      const tokenInfo = getTokenInfoFromReferenceKey(reference);
      if (tokenInfo) {
        const token = lookup[tokenInfo.tokenId];
        value = token || reference.value;
      }
    }

    if (typeof value !== 'string' && typeof value !== 'number') {
      return JSON.stringify(value);
    }
    return value;
  }

  return referenceValue;
};

export const getValueFromReferenceString = ({
  referenceString,
  withRaw,
  tokens,
  colors,
  theme
}: {
  referenceString?: any;
  withRaw?: boolean | undefined;
  tokens: Record<string, Token>;
  colors: Record<string, Palette>;
  theme?: 'dark' | 'light' | undefined;
}): string | number | undefined => {
  if (typeof referenceString !== 'string') {
    return undefined;
  }

  if (referenceString === '0') {
    return referenceString;
  }

  if (isColorReferenceString(referenceString)) {
    const paletteInfo = getPaletteInfoFromStringKey(referenceString, theme);
    if (!withRaw) {
      return referenceString;
    }
    if (paletteInfo && withRaw) {
      let colorValue;
      // attempt to get value from reference string
      const palette = colors[paletteInfo.paletteId];
      const paletteColors = palette?.colors;
      if (paletteColors) {
        const color = paletteColors[paletteInfo.colorId];
        console.log('defaulting to reference, missing rgb string');
        colorValue = color?.rgbString;
      }

      if (colorValue) {
        return colorValue;
      } else {
        console.log('defaulting to property value', referenceString);
        return referenceString;
      }
    }
  }

  const tokenInfo = getTokenInfoFromStringKey(referenceString);
  if (tokenInfo && withRaw) {
    // attempt to get value from token string
    const token = tokens[tokenInfo.tokenId];
    if (token) {
      const tokenValue = tokens[tokenInfo.tokenId].value;
      if (tokenValue) {
        return tokenValue;
      } else {
        return referenceString;
      }
    } else {
      return referenceString;
    }
  }

  return referenceString;
};

export const isColorReferenceString = (referenceString: string): boolean => {
  if (typeof referenceString !== 'string') {
    return false;
  }
  const ref = referenceString.split('.');

  if (ref[0].startsWith('rgba')) {
    return false;
  }
  if (ref.length > 0) {
    return (
      ref[0] === 'theme' ||
      ref[0] === 'light' ||
      ref[0] === 'dark' ||
      ref[0] === 'base'
    );
  }
  return false;
};
