import type { ReactElement } from 'react';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { action } from 'storybook/actions';
import { Clock, Id, Taxi, Travel } from '@transferwise/icons';
import { lorem5 } from '../../test-utils';
import Link from '../../link';
import { InlinePrompt, InlinePromptProps } from './InlinePrompt';

const withComponentGrid =
  ({ maxWidth = 'auto', gap = '1rem' } = {}) =>
  (Story: () => ReactElement) => (
    <div
      style={{
        width: '100%',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'flex-start',
        alignItems: 'flex-start',
        alignContent: 'flex-start',
        gap,
        maxWidth,
      }}
    >
      <Story />
    </div>
  );

export default {
  title: 'Prompts/InlinePrompt',
  component: InlinePrompt,
  tags: ['new'],
  decorators: [withComponentGrid()],
  args: {
    loading: false,
    muted: false,
    children: lorem5,
  },
  argTypes: {
    sentiment: {
      control: 'select',
      options: ['positive', 'negative', 'neutral', 'warning', 'proposition'],
    },
    loading: {
      control: 'boolean',
    },
    muted: {
      control: 'boolean',
    },
    children: {
      control: 'text',
      table: {
        type: { summary: 'ReactNode' },
      },
    },
  },
  parameters: {
    docs: {
      toc: true,
    },
  },
} satisfies Meta<InlinePromptProps>;

/**
 * Convenience controls for previewing rich markup,
 * not otherwise possible via Storybook
 */
type PreviewStoryArgs = InlinePromptProps & {
  previewMedia: InlinePromptProps['media'];
};

const previewArgGroup = {
  category: 'Storybook Preview options',
  type: {
    summary: undefined,
  },
};

const previewArgTypes = {
  previewMedia: {
    name: 'Preview with `media`',
    control: 'boolean',
    mapping: {
      false: undefined,
      true: <Taxi />,
    },
    table: previewArgGroup,
  },
} as const;

const getPropsForPreview = (
  args: PreviewStoryArgs,
): [InlinePromptProps, Partial<InlinePromptProps>] => {
  const { previewMedia, ...props } = args;

  return [
    props,
    {
      media: previewMedia,
    },
  ];
};

export const Playground: StoryObj<PreviewStoryArgs> = {
  tags: ['!autodocs'],
  argTypes: previewArgTypes,
  args: {
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => {
    const [props, previewProps] = getPropsForPreview(args);
    return <InlinePrompt {...props} {...previewProps} />;
  },
};

/**
 * Aside from the known `neutral`, `negative`, `warning` and `positive` sentiments, `InlinePrompt` is the first of the prompts to introduce a new `proposition` sentiment, which can be used to encourage the user to take an action that might benefit them.
 *
 * By default, its associated icon for the prompt is `GiftBox`.
 */
export const Sentiments: StoryObj<PreviewStoryArgs> = {
  argTypes: previewArgTypes,
  args: {
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => {
    const [props, previewProps] = getPropsForPreview(args);
    return (
      <>
        <InlinePrompt {...props} {...previewProps} sentiment="neutral">
          This is a neutral prompt.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="negative">
          This is a negative prompt.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="warning">
          This is a warning prompt.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="positive">
          This is a positive prompt.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="proposition">
          This is a proposition prompt.
        </InlinePrompt>
      </>
    );
  },
  decorators: [withComponentGrid()],
};

/**
 * Inline prompts are also used when forms need to check something in the background, like
 * verifying an account number is real. Turning on the loading state will replace the icon with
 * a spinner while waiting for the check to finish.
 */
export const Loading: StoryObj<PreviewStoryArgs> = {
  argTypes: previewArgTypes,
  args: {
    loading: true,
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => {
    const [props, previewProps] = getPropsForPreview(args);
    return <InlinePrompt {...props} {...previewProps} />;
  },
};

/**
 * `InlinePrompt` can include a single instance of the `Link` component, which can be rendered as
 * either HTML anchor or button. That element will spread across the whole surface of the Prompt
 * so it's more accessible for motor–impaired and touch devices users.
 *
 * > ⚠️ For that reason it's crucial to only include **one** interactive element inside a prompt.
 */
export const Interactivity: StoryObj<PreviewStoryArgs> = {
  argTypes: previewArgTypes,
  args: {
    muted: true,
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => (
    <>
      <InlinePrompt sentiment={args.sentiment}>
        This prompt includes a{' '}
        <Link href="https://wise.com" target="_blank" rel="noreferrer">
          link to some resource
        </Link>{' '}
        to help the user in their journey.
      </InlinePrompt>

      <InlinePrompt sentiment={args.sentiment}>
        {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
        This prompt includes an <Link onClick={action('inline button')}>inline button</Link> than
        can e.g. trigger a modal.
      </InlinePrompt>
    </>
  ),
  decorators: [withComponentGrid()],
};

/**
 * Inline prompt is usually associated with a different component, such as `Input` or `ListItem`.
 * When those components are disabled, the prompt is often used to communicate to the user why they
 * cannot interact with them. For that reason, the prompt cannot inherit the usual disabled visual
 * style, and it must retain its interactivity. To bring these 2 components visually closer to each
 * other, `muted` prop introduces slightly reduced opacity and luminosity as well as a special
 * backslash circle icon.
 */
export const Muted: StoryObj<PreviewStoryArgs> = {
  argTypes: previewArgTypes,
  args: {
    muted: true,
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => {
    const [props, previewProps] = getPropsForPreview(args);
    return (
      <InlinePrompt {...props} {...previewProps}>
        Please <Link href="#">confirm your residential address</Link> to activate this feature.
      </InlinePrompt>
    );
  },
};

/**
 * While all main sentiments (`warning`, `negative`, `positive` and`neutral`) are associated with a
 * default `StatusIcon`s, we also allow for Icon overrides to bring the prompt's visual language
 * closer to the prompt's content. <br /><br />
 * It's also possible to override the default icon's accessible name announced by screen readers
 * via `mediaLabel` prop, which is especially useful for the `proposition` sentiment.
 */
export const IconOverrides: StoryObj<PreviewStoryArgs> = {
  render: (args: PreviewStoryArgs) => {
    return (
      <>
        <InlinePrompt {...args} media={<Travel />} sentiment="positive" mediaLabel="Success: ">
          Your travel account is set up and ready to use.
        </InlinePrompt>
        <InlinePrompt {...args} media={<Taxi />} sentiment="proposition" mediaLabel="Taxi addon: ">
          Connect Wise with your taxi app to get exclusive discounts.
        </InlinePrompt>
        <InlinePrompt {...args} media={<Clock />} sentiment="warning" mediaLabel="Processing: ">
          The account verification is taking longer than usual.
        </InlinePrompt>
        <InlinePrompt {...args} media={<Id />} sentiment="negative" mediaLabel="Error: ">
          The identity document you provided has expired.
        </InlinePrompt>
      </>
    );
  },
  decorators: [withComponentGrid()],
};

/**
 * `InlinePrompt` can either hug its content or take the full width of its container,
 * depending on the `width` prop.
 * Components like `ListItem`, `ExpressiveMoneyInput` or similar will often set it to
 * `auto`, while plain inputs will prefer `full` to visually match their boundaries.
 *
 * **NB**: `InlinePrompt` in its default – `auto` – width will expand to the full width of its container when the content spans across multiple lines.
 */
export const SizingStrategies: StoryObj<PreviewStoryArgs> = {
  render: (args: PreviewStoryArgs) => {
    return (
      <>
        <InlinePrompt {...args} media={<Travel />} sentiment="positive" width="full">
          This prompt will take the full width of its container.
        </InlinePrompt>
        <InlinePrompt {...args} media={<Travel />} sentiment="positive">
          This prompt will hug its content.
        </InlinePrompt>
        <InlinePrompt {...args} media={<Travel />} sentiment="positive">
          This prompt is configured to hug its content, but since the content is long enough to span
          across multiple lines, it will expand to the full width of its container. And no, you
          should not put pages of text here in the first place, but we recognise that some
          translations are longer than others, and people also use accessibility features that
          increase font size.
        </InlinePrompt>
      </>
    );
  },
  decorators: [withComponentGrid()],
};

/**
 * When configured with any of the supported sentiments, the colour scheme of the component will propagate to all of its supported descendants, such as instances of a `Link`, `Icon`, and `StatusIcon`.
 */
export const SentimentAwareness: StoryObj<PreviewStoryArgs> = {
  argTypes: previewArgTypes,
  args: {
    previewMedia: false,
  },
  render: (args: PreviewStoryArgs) => {
    const [props, previewProps] = getPropsForPreview(args);

    return (
      <>
        <InlinePrompt {...props} {...previewProps} sentiment="negative">
          This is a negative prompt with an <Link href="#">inline link</Link>.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="positive">
          This is a positive prompt with an <Link href="#">inline link</Link>.
        </InlinePrompt>
        <InlinePrompt {...props} {...previewProps} sentiment="proposition">
          This is a proposition prompt with an <Link href="#">inline link</Link>.
        </InlinePrompt>
      </>
    );
  },
  decorators: [withComponentGrid()],
};
