import React from 'react';

/**
 * Adds the properties from the defined parameters object to 'storyFunction.story.parameters._plugin_.*'
 * @param story the story function
 * @param plugin the name of the plugin/configuration property that should be set
 * @param parameters an object holding the settings that should be added to the configuration
 */
export const addParameters = (
  storyFunction: React.FC,
  plugin: string,
  params: Record<string, unknown>,
): void => {
  const s = storyFunction as unknown as {
    story: { parameters: { [key: string]: Record<string, unknown> } };
  };

  s.story = s.story ?? {};
  s.story.parameters = s.story.parameters ?? {};
  s.story.parameters[plugin] = s.story.parameters[plugin] ?? {};

  s.story.parameters[plugin] = {
    ...s.story.parameters[plugin],
    ...params,
  };
};

/**
 * Adds the properties from the defined parameters object to 'storyFunction.story.parameters.info.*'
 * @param story the story function
 * @param parameters an object holding the settings that should be added to the configuration
 */
export const addInfo = (
  story: React.FC,
  parameters: Record<string, unknown>,
): void => {
  addParameters(story, 'info', parameters);
};

/**
 * Generates an array with the amount of items defined.
 * @param amount number of items
 * @param generator generator function that can generate an item
 */
export function generateItemArray<T>(
  amount: number,
  generator: (index: number) => T,
): T[] {
  return [...Array(amount)].map((_i, idx) => generator(idx));
}

/**
 * Returns a random number between min (inclusive) and max (exclusive)
 */
export function getRandomArbitrary(min: number, max: number): number {
  return Math.random() * (max - min) + min;
}

/**
 * Returns a random integer between min (inclusive) and max (inclusive).
 * The value is no lower than min (or the next integer greater than min
 * if min isn't an integer) and no greater than max (or the next integer
 * lower than max if max isn't an integer).
 * Using Math.round() will give you a non-uniform distribution!
 */
export function getRandomInt(min: number, max: number): number {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
 * @deprecated Use Storybook's 'ComponentStoryObj' and CSF3 instead
 */
export type Story = React.FC & {
  story?: {
    [key: string]: any;
    name?: string;
    parameters?: {
      [key: string]: any;
      docs: {
        storyDescription: string;
      };
    };
  };
};

export function randomDate(
  start: Date = new Date(0),
  end: Date = new Date(),
): Date {
  return new Date(
    start.getTime() + Math.random() * (end.getTime() - start.getTime()),
  );
}

export function wait(ms: number): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

/**
 * Generates a groups object for storybook controls. Used groups:
 * @example
 * createGroups({
 *   Storybook: [],
 *   Content: [],
 *   Behavior: [],
 *   Styling: [],
 * })
 * @param definition
 * @returns
 */
export const createGroups = (definition: {
  [key: string]: string[];
}): { [key: string]: { table: { category: string } } } => {
  const categories = Object.keys(definition).reduce<{
    [key: string]: { table: { category: string } };
  }>(
    (acc, category) => ({
      ...acc,
      ...definition[category].reduce(
        (acc2, property) => ({
          ...acc2,
          [property]: { table: { category: category } },
        }),
        {},
      ),
    }),
    {},
  );
  return categories;
};

/**
 * Maps an enum to an object with the same keys and values.
 * @example
 * enum MyEnum {
 *  A = 1,
 *  B = 2,
 * }
 * const obj = enumToObj(MyEnum);
 * // obj = { A: 1, B: 2 }
 * @param en The enum to map
 * @returns An object with the same keys and values as the enum
 */
export const enumToObj = <T extends Record<string, string | number>>(
  en: T,
): Record<keyof T, T[keyof T]> => {
  return Object.keys(en)
    .filter((key) => isNaN(Number(key)))
    .reduce((obj, key) => {
      obj[key as keyof T] = en[key as keyof T];
      return obj;
    }, {} as Record<keyof T, T[keyof T]>);
};
