import { Colors } from '../constants/tokens/colors';
import { Spacing } from '../constants/tokens/spacing';
import { Shadows } from '../constants/tokens/shadows';
import { Breakpoints } from '../constants/tokens/breakpoints';
import { BorderRadius } from '../constants/tokens/border-radius';
import { BorderStyle, BoxDisplay, CssStates, FlexAlignItem, FlexDirections, FlexJustifyContent, GridJustifyItems, FlexWrap, TextAlign, } from '../constants/ui';
import { GridColumnsRegex, GridRowsRegex } from '../constants/regex';
import { TypographyKeys } from '../constants/public-api';
import { Borders } from '../constants/tokens/borders';
import { CalendarDate } from '../helpers/calendar';
/**
 * Returns true if the input is a string that is one of the four directions.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link SpacingDirection}
 */
export const isSpacingDirection = (input) => {
  return typeof input === 'string' && ['top', 'bottom', 'left', 'right', 'x', 'y'].includes(input);
};
/**
 * If the input is a string and the string is a key in the Spacing enum, then the input is a SpacingTokenType.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link SpacingTokenType}
 */
export const isSpacingTokenType = (input) => {
  return typeof input === 'string' && typeof Spacing[input] === 'number';
};
/**
 * If every key in the input is a valid spacing direction and every value in the input is a valid spacing value, then the
 * input is a valid directional spacing type.
 *
 * @param {unknown} input - unknown - This is the input value that we want to check.
 * @returns {boolean} indicating if the input is type of {@link DirectionalSpacingType}
 */
export const isDirectionalSpacingType = (input) => {
  return (typeof input === 'object' &&
    input !== null &&
    Object.keys(input).every(isSpacingDirection) &&
    Object.values(input).every((paddingValue) => paddingValue in Spacing));
};
/**
 * `isResponsiveType` returns true if the input is an object with a `default` property and all other properties are valid
 * breakpoints
 *
 * @param {unknown} input - unknown - this is the input that we want to check if it's a ResponsiveType
 * @returns {boolean} indicating if the input is type of {@link ResponsiveType<ResponsiveScope>}
 */
export const isResponsiveType = (input) => {
  return typeof input === 'object' && input !== null && Object.keys(input).every(isBreakpointsTokenType);
};
/**
 * If the input is a string and it's either in the Breakpoints enum or it's the string 'default', then it's a
 * BreakpointsTokenType.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link BreakpointsTokenType}
 */
export const isBreakpointsTokenType = (input) => {
  return typeof input === 'string' && (input in Breakpoints || input === 'default');
};
/**
 * This function returns true if the input is a string that is a key in the BorderRadius object.
 *
 * @param {unknown} input - unknown - This is the input that will be checked.
 * @returns {boolean} indicating if the input is type of {@link BorderRadiusTokenType}
 */
export const isBorderRadiusTokenType = (input) => {
  return typeof input === 'string' && input in BorderRadius;
};
/**
 * If the input is a string and it's in the Colors array, then it's a ColorsTokenType.
 *
 * @param {unknown} input - unknown - This is the input that we're checking.
 * @returns {boolean} indicating if the input is type of {@link ColorsTokenType}
 */
export const isColorTokenType = (input) => {
  return typeof input === 'string' && Colors.includes(input);
};
/**
 * It returns true if the input is a string that starts with 'border-color-' and is a valid color token type.
 *
 * @param {unknown} input - The value to be tested.
 * @returns {boolean} indicating if the input is {@link BorderColorsTokenType}
 */
export const isBorderColorTokenType = (input) => {
  return isColorTokenType(input) && /^border-color-.+$/.test(input);
};
/**
 * If the input is a string and it's in the Borders array, then it's a BordersTokenType.
 *
 * @param {unknown} input - unknown - This is the input that we're checking.
 * @returns {boolean} indicating if the input is type of {@link BordersTokenType}
 */
export const isBorderTokenType = (input) => {
  return typeof input === 'string' && Borders.includes(input);
};
/**
 * It returns true if the input is a string that starts with `bg-` and is a valid color token type.
 *
 * @param {unknown} input - The value to be tested.
 * @returns {boolean} indicating if the input is {@link BackgroundColorsTokenType}
 */
export const isBackgroundColorTokenType = (input) => {
  return isColorTokenType(input) && /^bg-.+$/.test(input);
};
/**
 * This function returns true if the input is a string that starts with 'text-' and is a valid color token type.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is {@link TextColorsTokenType}
 */
export const isTextColorTokenType = (input) => {
  return isColorTokenType(input) && /^text-.+$/.test(input);
};
/**
 * If the input is a string and it's in the Shadows array, then it's a ShadowsTokenType.
 *
 * @param {unknown} input - unknown - This is the input that will be checked.
 * @returns {boolean} indicating if the input is {@link ShadowsTokenType}
 */
export const isShadowsTokenType = (input) => {
  return typeof input === 'string' && Shadows.includes(input);
};
/**
 * If the input is a number, it must be between 1 and 12. If the input is a string, it must be in the
 * format of '1-auto' to '12-auto'.
 *
 * @param {unknown} input - The input to be tested.
 * @returns {boolean} indicating if the input is {@link GridColumnsType}
 */
export const isGridColumnsType = (input) => {
  return ((typeof input === 'string' || typeof input === 'number') && GridColumnsRegex.test(input.toString()));
};
/**
 * If the input is a number, it must be between 1 and 12. If the input is a string, it must be in the
 * format of '1-auto' to '12-auto'.
 *
 * @param {unknown} input - The input to be tested.
 * @returns {boolean} indicating if the input is {@link GridRowsType}
 */
export const isGridRowsType = (input) => {
  return (typeof input === 'string' || typeof input === 'number') && GridRowsRegex.test(input.toString());
};
/**
 * Checks if the input is a valid GridJustifyItemsType value.
 *
 * @param {unknown} input - The input to test.
 * @returns {boolean} indicating if the input is {@link GridJustifyItemsType}
 */
export const isGridJustifyItemsType = (input) => {
  return typeof input === 'string' && GridJustifyItems.includes(input);
};
/**
 * If the input is a string, and that string is a key in the Breakpoints object, then the input is a GridRowType.
 *
 * @param {unknown} input - The value that is being checked.
 * @returns {boolean} indicating if the input is {@link BreakpointsTokenType}
 */
export const isBreakpointsForPrefixType = (input) => {
  return Object.keys(Breakpoints).includes(input);
};
/**
 * If the input is an object with a number property named X and a number property named Y, then it's a TranslocationType.
 *
 * @param {unknown} input - The input to check.
 * @returns {boolean} indicating if the input is {@link TranslocationType}
 */
export const isTranslocationType = (input) => {
  return (typeof input === 'object' &&
    input !== null &&
    (typeof input['X'] === 'number' ||
      ['start', 'center', 'end'].includes(input['X'])) &&
    (typeof input['Y'] === 'number' ||
      ['start', 'center', 'end'].includes(input['Y'])));
};
/**
 * "If the input is an object with a `groupId` property that is a string, and an `element` property that is an object with
 * a `tagName` property that is `'VEGA-ACCORDION'`, then return true, otherwise return false."
 *
 * The above function is a type guard. It's a function that takes an input of type `unknown` and returns a boolean. If the
 * input is of the type we're looking for, then the function returns `true`, otherwise it returns `false`
 *
 * @param {unknown} input - The input to check.
 * @returns {boolean} indicating if the input is {@link AccordionObserverPayload}
 */
export const isAccordionObserverPayload = (input) => {
  return (typeof input['groupId'] === 'string' &&
    input['element'] &&
    input['element']['tagName'] === 'VEGA-ACCORDION');
};
/**
 * If the input is a string and it's in the list of FlexDirections, then it's a FlexDirectionType.
 *
 * @param {unknown} input - unknown - This is the input that we want to check.
 * @returns {boolean} A function that takes an unknown input and returns a boolean.
 */
export const isFlexDirectionType = (input) => {
  return typeof input === 'string' && FlexDirections.includes(input);
};
/**
 * This TypeScript function checks if an input is a valid FlexAlignItemType.
 *
 * @param {unknown} input - The input parameter is of type unknown, which means it can be any type of
 * value. The function is checking if the input is a string and if it is included in the FlexAlignItem
 * array. If both conditions are true, it returns true, indicating that the input is of type
 * FlexAlignItemType
 * @returns {boolean} The function `isFlexAlignItemType` is returning a boolean value. It checks if the `input`
 * parameter is a string and if it is included in the `FlexAlignItem` array. If both conditions are
 * true, it returns `true`, indicating that the `input` is of type `FlexAlignItemType`. Otherwise, it
 * returns `false`.
 */
export const isFlexAlignItemType = (input) => {
  return typeof input === 'string' && FlexAlignItem.includes(input);
};
/**
 * This TypeScript function checks if an input is a valid FlexJustifyContentType.
 *
 * @param {unknown} input - The input parameter is of type unknown, which means it can be any type of
 * value. The function is checking if the input is a string and if it is one of the values in the
 * FlexJustifyContent array.
 * @returns {boolean} A boolean value. The function checks if the input is a string and if it is included in the
 * `FlexJustifyContent` array, and returns `true` if both conditions are met, indicating that the input
 * is of type `FlexJustifyContentType`. If either condition is not met, the function returns `false`.
 */
export const isFlexJustifyContentType = (input) => {
  return typeof input === 'string' && FlexJustifyContent.includes(input);
};
/**
 * The function `isFlexWrapType` checks if the input is a valid `FlexWrapType` string.
 *
 * @param {unknown} input - The `input` parameter in the `isFlexWrapType` function is of type
 * `unknown`, which means it can be any type of value. The function checks if the input is a string and
 * if it is included in the `FlexWrap` array.
 * @returns {boolean} The `isFlexWrapType` function is returning a boolean value. It checks if the `input` is a
 * string and if it is included in the `FlexWrap` array, which contains valid `FlexWrapType` values. If
 * both conditions are met, the function returns `true`, indicating that the `input` is of type
 * `FlexWrapType`. Otherwise, it returns `false`.
 */
export const isFlexWrapType = (input) => {
  return typeof input === 'string' && FlexWrap.includes(input);
};
/**
 * The function `isTextAlignType` checks if the input is a valid `TextAlignType` string.
 *
 * @param {unknown} input - The `input` parameter is of type `unknown`, which means it can be any type
 * of value.
 * @returns {boolean} The `isTextAlignType` function is returning a boolean value. It checks if the `input` is a
 * string and if it is included in the `TextAlign` array, which presumably contains valid
 * `TextAlignType` values. If both conditions are met, the function returns `true`, indicating that the
 * `input` is of type `TextAlignType`. Otherwise, it returns `false
 */
export const isTextAlignType = (input) => {
  return typeof input === 'string' && TextAlign.includes(input);
};
/**
 * It takes an array of unknowns and a type guard function, and returns a type guard function that
 * returns true if the input is an array of the type that the type guard function guards
 *
 * @param {unknown} input - unknown - the input to check
 * @param {Function} itemTypeGuard - (item: unknown) => item is T
 * @returns {boolean} A function that takes two arguments, input and itemTypeGuard.
 */
export const isArrayOfType = (input, itemTypeGuard) => {
  return Array.isArray(input) && input.every(itemTypeGuard);
};
/**
 * If the input is an object with a label and key property, then it's a DropdownSourceItem.
 *
 * @param {unknown} input - The input to check.
 * @returns {boolean} A function that takes in an unknown input and returns a boolean.
 */
export const isDropdownSourceItemType = (input) => {
  return (typeof input === 'object' &&
    Object.prototype.hasOwnProperty.call(input, 'label') &&
    Object.prototype.hasOwnProperty.call(input, 'key'));
};
/**
 * If the input is an object with an id and displayName property, then it's a SelectSourceItem.
 *
 * @param {unknown} input - The input to check.
 * @returns {boolean} A function that takes an input and returns a boolean.
 */
export const isSelectSourceItemType = (input) => {
  return (typeof input === 'object' &&
    Object.prototype.hasOwnProperty.call(input, 'id') &&
    Object.prototype.hasOwnProperty.call(input, 'displayName'));
};
/**
 * The function `isBoxDisplayType` checks if the input is a valid BoxDisplayType.
 *
 * @param {unknown} input - The input parameter is of type unknown, which means it can be any type.
 * @returns {boolean} a boolean value.
 */
export const isBoxDisplayType = (input) => {
  return typeof input === 'string' && BoxDisplay.includes(input);
};
/**
 * Check if the input is not null and undefined
 *
 * @param {Nullable} input - the value
 * @returns {boolean} return true if the input is not null and undefined
 */
export const isNonNullable = (input) => {
  return input !== null && input !== undefined;
};
/**
 * The function `isTypographyTokenType` checks if the input is a string and if it is one of the predefined TypographyTokenType values.
 *
 * @param {unknown} input - The input parameter is of type unknown, which means it can be any type.
 * @returns {boolean} a boolean value.
 */
export const isTypographyTokenType = (input) => {
  return typeof input === 'string' && TypographyKeys.includes(input);
};
/**
 * The function `isBorderStyleType` checks if the input is a valid BorderStyleType string.
 *
 * @param {unknown} input - The `input` parameter is of type `unknown`, which means it can be any type
 * of value. The `isBorderStyleType` function is a type guard function that checks if the `input` is of
 * type `BorderStyleType`.
 * @returns {boolean} The `isBorderStyleType` function is returning a boolean value. It checks if the `input` is
 * of type `BorderStyleType` by verifying if it is a string and if it is included in the `BorderStyle`
 * array. If the condition is met, it returns `true`, indicating that the `input` is of type
 * `BorderStyleType`. Otherwise, it returns `false`.
 */
export const isBorderStyleType = (input) => {
  return typeof input === 'string' && BorderStyle.includes(input);
};
/**
 * The function `isBorderRadiusDirectionType` checks if the input is a valid `BorderRadiusDirection` type.
 *
 * @param {unknown} input - The `input` parameter in the `isBorderRadiusDirectionType` function is of
 * type `unknown`, which means it can be any type of value. The function checks if the input is a
 * string and if it matches one of the specified values in the array ['top', 'left', 'right
 * @returns {boolean} The function `isBorderRadiusDirectionType` is returning a boolean value indicating whether
 * the `input` is of type `BorderRadiusDirection` or not.
 */
export const isBorderRadiusDirectionType = (input) => {
  return (typeof input === 'string' &&
    ['top', 'right', 'bottom', 'left', 'top-left', 'top-right', 'bottom-left', 'bottom-right'].includes(input));
};
/**
 * The function `isDirectionalBorderRadiusType` checks if the input object conforms to the
 * `DirectionalBorderRadiusType` type.
 *
 * @param {unknown} input - The `input` parameter in the `isDirectionalBorderRadiusType` function is of
 * type `unknown`, which means it can be any type of value. The function is checking if the input is a
 * valid `DirectionalBorderRadiusType` by verifying that it is an object, not null,
 * @returns {boolean} The function `isDirectionalBorderRadiusType` is returning a boolean value.
 */
export const isDirectionalBorderRadiusType = (input) => {
  return (typeof input === 'object' &&
    input !== null &&
    Object.keys(input).every(isBorderRadiusDirectionType) &&
    Object.values(input).every((value) => value in BorderRadius));
};
/**
 * The function `isStateVariantTokenType` checks if the input is an object with keys that are all of
 * type `CssStatesType`.
 *
 * @param {unknown} input - The `input` parameter is a value of type `unknown`, which means it can be
 * any type of value in TypeScript.
 * @returns {boolean} The `isStateVariantTokenType` function is returning a boolean value. It checks if the
 * `input` is an object that is not null and if all the keys of the object pass the `isCssStateType`
 * check. If these conditions are met, it returns `true`, indicating that the `input` is of type
 * `StateVariantType<T, CssStatesType>`. Otherwise, it
 */
export const isStateVariantTokenType = (input) => {
  return typeof input === 'object' && input !== null && Object.keys(input).every(isCssStateType);
};
/**
 * The function `isCssStateType` checks if the input is a valid CSS state type by verifying if it is a
 * string and included in a predefined list of CSS states.
 *
 * @param {CssStatesType} input - The `input` parameter in the `isCssStateType` function is of type
 * `CssStatesType`.
 * @returns {boolean} The function `isCssStateType` is returning a boolean value.
 */
export const isCssStateType = (input) => {
  return typeof input === 'string' && CssStates.includes(input);
};
/**
 * Checks that the provided value is a non-null object with at least the
 * required grouping-model properties (`key` and `label`) and that `label`
 * is a string.
 *
 * @param {unknown} input - Value to test.
 * @returns {input is VegaTableColumnGroupingModel} True when input matches the grouping model shape.
 */
export const isVegaTableColumnGroupingModelType = (input) => {
  return (typeof input === 'object' &&
    input !== null &&
    'key' in input &&
    'label' in input &&
    typeof input.label === 'string');
};
/**
 * Check is calendar date type
 *
 * @param {unknown} input - Value to test.
 * @returns {input is CalendarDate} True when input matches the calendar date type.
 */
export const isCalendarDateType = (input) => {
  return input instanceof CalendarDate;
};
/**
 * Type guard to check if the provided instance implements the VegaEATriggerStrategy interface
 *
 * @param {unknown} instance - The instance to check.
 * @returns {instance is VegaEATriggerStrategy} True if the instance implements VegaEATriggerStrategy.
 */
export const isEATriggerStrategy = (instance) => {
  return (!!instance &&
    typeof instance.evaluate === 'function' &&
    typeof instance.canEvaluate === 'function');
};
/**
 * Validates if the input is of type MarginValueType, which can be either a SpacingTokenType or the string 'auto'.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link MarginValueType}
 */
export const isMarginValueType = (input) => {
  return isSpacingTokenType(input) || input === 'auto';
};
/**
 * Validates if the input is of type DirectionalMarginType.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link DirectionalMarginType}
 */
export const isDirectionalMarginType = (input) => {
  return (typeof input === 'object' &&
    input !== null &&
    Object.keys(input).every(isSpacingDirection) &&
    Object.values(input).every(isMarginValueType));
};
/**
 * Validates if the input is of type SizeType, which can be either a number or a string in the format of `${number}%`.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link SizeType}
 */
export const isSizeType = (input) => {
  if (typeof input === 'number')
    return true;
  if (typeof input !== 'string' || !input)
    return false;
  return !isNaN(Number(input)) || /^(\d+(?:\.\d+)?)%$/.test(input);
};
/**
 * Validates if the input is of type SizeType or an object with breakpoint keys and SizeType values.
 *
 * @param {unknown} input - The value to check.
 * @returns {boolean} indicating if the input is type of {@link ResponsiveType<SizeType>}
 */
export const isResponsiveSizeType = (input) => {
  return (!!input &&
    (isSizeType(input) ||
      (typeof input === 'object' &&
        Object.keys(input).every(isBreakpointsTokenType) &&
        Object.values(input).every(isSizeType))));
};
