import type { FC } from 'react';

import { Map as ImmutableMap } from 'immutable';

import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';

import type { StatusVisibility } from '@/mastodon/api_types/statuses';
import { useAppSelector } from '@/mastodon/store';
import {
  accountFactoryImmutable,
  pollFactoryImmutable,
  statusFactoryAPI,
  statusFactoryImmutable,
} from '@/testing/factories';

import type { AttachmentArgs } from './testing';
import { attachmentArgTypes, attachmentFactory } from './testing';
import type { StatusContextType } from './types';
import { TypedStatus } from './types';

interface StatusStoryProps extends AttachmentArgs {
  // Contents
  text: string;
  visibility: StatusVisibility;
  isReblog?: boolean;
  isReply?: boolean;
  isPoll?: boolean;
  isQuote?: boolean;
  contentWarning?: string;

  // Interactions
  hasFavourited?: boolean;
  hasReblogged?: boolean;
  hasBookmarked?: boolean;
  hasReplied?: boolean;
  hasFilter?: boolean;
  hasVoted?: boolean;
  disableActions?: boolean;
  showTranslate?: boolean;

  // Display
  showThread?: boolean;
  contextType?: StatusContextType;
  showCounters?: boolean;
  favouriteCount?: number;
  reblogCount?: number;
  replyCount?: number;
  hidden?: boolean;
  muted?: boolean;
  showPrepend?: boolean;
}

const otherAccount = accountFactoryImmutable({
  id: '2',
  display_name: 'Another user',
});

const StatusStoryComponent: FC<StatusStoryProps> = (props) => {
  const {
    isReblog,
    isReply,
    isPoll,
    isQuote,
    contentWarning,

    hasFilter,
    hasVoted,
    disableActions = false,

    contextType,
    showThread,
    showCounters,
    hidden,
    muted,
    showPrepend = true,
  } = props;
  const account = useAppSelector((state) => state.accounts.get('1'));
  const status = useAppSelector((state) =>
    state.statuses.get('1')?.withMutations((status) => {
      status.set('account', account);
      status.set('matched_filters', hasFilter ? ['test'] : false);
      status.set('matched_media_filters', hasFilter ? ['test'] : false);
      status.set('hidden', hidden);

      // StatusActionBar checks specifically for null so undefined doesn't work.
      if (!status.get('in_reply_to_id')) {
        status.set('in_reply_to_id', null);
      }

      if (isReblog) {
        status.set(
          'reblog',
          statusFactoryImmutable({ id: '2' }).set('account', otherAccount),
        );
      }
      if (isPoll) {
        status.set('poll', hasVoted ? '2' : '1');
      }
    }),
  );

  return (
    <div style={{ width: 'min(600px, 80vw)' }}>
      <TypedStatus
        {...staticProps}
        key={JSON.stringify(props)} // Update on any props change. Required because Status has updateOnProps set.
        status={status}
        account={isReblog ? account : undefined}
        isQuotedPost={isQuote}
        showActions={!disableActions}
        contextType={contextType}
        withCounters={showCounters}
        // Either we are showing a thread (in a timeline) or it's a full reply chain view.
        showThread={isReply && showThread}
        previousId={isReply && !showThread ? '2' : undefined}
        rootId={isReply && !showThread ? '2' : undefined}
        nextInReplyToId={isReply && !showThread ? '1' : undefined}
        muted={muted}
        hidden={hidden && !contentWarning && !hasFilter}
        skipPrepend={!showPrepend}
        withDismiss={contextType === 'notifications'}
      />
    </div>
  );
};

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const staticProps = Object.fromEntries(
  // As Storybook auto-names from args only,
  // we need to manually name these for proper action tracking.
  Object.entries({
    onReply: fn(),
    onFavourite: fn(),
    onMention: fn(),
    onOpenMedia: fn(),
    onOpenVideo: fn(),
    onQuote: fn(),
    onReblog: fn(),
    onToggleCollapsed: fn(),
    onToggleHidden: fn(),
    onTranslate: fn(),
    onAddFilter: fn(),
    onBlock: fn(),
    onClick: fn(),
    onDelete: fn(),
    onDirect: fn(),
    onEmbed: fn(),
    onHeightChange: fn(),
    onInteractionModal: fn(),
    onPin: fn(),
    deployPictureInPicture: fn(),
  } as const)
    .map(([key, value]) => [key, value.mockName(key)])
    .concat([
      [
        'pictureInPicture',
        ImmutableMap<'inUse' | 'available', boolean>({
          inUse: false,
          available: true,
          // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Casting to solves infinite recursion errors.
        }) as any,
      ],
    ]),
);

const categoryContents = {
  table: {
    category: 'contents',
  },
} as const;
const categoryInteraction = {
  table: {
    category: 'interactions',
  },
} as const;
const categoryDisplay = {
  table: {
    category: 'display',
  },
} as const;

const meta = {
  title: 'Components/Status/Status',
  component: StatusStoryComponent,
  argTypes: {
    // Contents
    visibility: {
      ...categoryContents,
      control: 'inline-radio',
      options: [
        'direct',
        'private',
        'public',
        'unlisted',
      ] satisfies StatusVisibility[],
    },
    isReblog: categoryContents,
    isReply: categoryContents,
    isPoll: categoryContents,
    isQuote: categoryContents,
    text: categoryContents,
    attachment1: {
      ...categoryContents,
      ...attachmentArgTypes.attachment1,
    },
    attachment2: {
      ...categoryContents,
      ...attachmentArgTypes.attachment2,
    },
    attachment3: {
      ...categoryContents,
      ...attachmentArgTypes.attachment3,
    },
    contentWarning: categoryContents,

    // Interactions
    hasFavourited: categoryInteraction,
    hasReblogged: categoryInteraction,
    hasBookmarked: categoryInteraction,
    hasFilter: categoryInteraction,
    hasVoted: {
      ...categoryInteraction,
      if: {
        arg: 'isPoll',
        truthy: true,
      },
    },
    disableActions: categoryInteraction,
    showTranslate: categoryInteraction,

    // Display
    showCounters: categoryDisplay,
    favouriteCount: categoryDisplay,
    reblogCount: categoryDisplay,
    replyCount: categoryDisplay,
    showPrepend: categoryDisplay,
    showThread: {
      ...categoryDisplay,
      if: {
        arg: 'showPrepend',
        truthy: true,
      },
    },
    contextType: {
      ...categoryDisplay,
      control: 'select',
      options: [
        'account',
        'bookmarks',
        'composer',
        'detailed',
        'favourites',
        'home',
        'notifications',
        'public',
        'search',
        'thread',
      ] satisfies StatusContextType[],
    },
    hidden: categoryDisplay,
    muted: categoryDisplay,
  },
  args: {
    text: 'This is a status',
    visibility: 'public',
    isReblog: false,
    isReply: false,
    isPoll: false,
    isQuote: false,
    contentWarning: '',
    attachment1: undefined,
    attachment2: undefined,
    attachment3: undefined,

    hasFavourited: false,
    hasReblogged: false,
    hasBookmarked: false,
    hasFilter: false,
    hasVoted: false,
    disableActions: false,
    showTranslate: false,

    favouriteCount: 0,
    reblogCount: 0,
    replyCount: 0,
    showCounters: true,
    contextType: 'home',
    showPrepend: true,
    showThread: false,
    hidden: false,
    muted: false,
  } satisfies StatusStoryProps,
  parameters: {
    state: {
      accounts: {
        '2': otherAccount,
      },
      polls: {
        '1': pollFactoryImmutable(),
        '2': pollFactoryImmutable({
          voted: true,
          voters_count: 1,
          votes_count: 1,
          own_votes: [0],
        }),
      },
      server: {
        translationLanguages: {
          item: {
            xx: ['en', 'de', 'fr'],
          },
        },
      },
    },
    stateFn({
      text,
      contentWarning,
      visibility,
      attachment1,
      attachment2,
      attachment3,
      hasBookmarked,
      hasFavourited,
      hasReblogged,
      isQuote,
      isReply,
      favouriteCount,
      reblogCount,
      replyCount,
      showTranslate,
    }: StatusStoryProps) {
      const account = accountFactoryImmutable();

      const status = statusFactoryImmutable({
        text,
        spoiler_text: contentWarning,
        visibility,
        media_attachments: attachmentFactory(
          attachment1,
          attachment2,
          attachment3,
        ),
        reblogged: hasReblogged,
        favourited: hasFavourited,
        bookmarked: hasBookmarked,
        in_reply_to_account_id: isReply ? '2' : undefined,
        in_reply_to_id: isReply ? '2' : undefined,
        quote: isQuote
          ? {
              state: 'accepted',
              quoted_status: { ...statusFactoryAPI(), quote: undefined },
            }
          : undefined,
        favourites_count: favouriteCount,
        reblogs_count: reblogCount,
        replies_count: replyCount,
        language: showTranslate ? 'xx' : undefined,
      });

      return {
        statuses: {
          '1': status,
        },
        accounts: {
          '1': account,
        },
      };
    },
    controls: {
      disableSaveFromUI: true,
    },
  },
} satisfies Meta<typeof StatusStoryComponent>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Reply: Story = {
  args: {
    isReply: true,
  },
};

export const LongText: Story = {
  args: {
    text: [
      'This is a long-form piece of text that wraps multiple lines.',
      'It is here to test what a longer status looks like.',
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
      'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
    ].join('\n'),
  },
};

export const Images: Story = {
  args: {
    attachment1: 'image',
    attachment2: 'image',
    attachment3: 'image',
  },
};

export const Video: Story = {
  args: {
    attachment1: 'video',
  },
};

export const Audio: Story = {
  args: {
    attachment1: 'audio',
  },
};

export const Poll: Story = {
  args: {
    isPoll: true,
    hasVoted: true,
  },
};
