import { faker } from '@faker-js/faker';
import { Meta, StoryObj } from '@storybook/react';
import React, { useMemo, useState } from 'react';
import { MemoryRouter, useLocation } from 'react-router-dom';
import {
  createGroups,
  enumToObj,
  generateItemArray,
} from '../../helpers/storybook';
import { Message } from '../Message';
import { List, ListProps } from './List';
import { Column, ColumnMap, ListSelectMode, SortData } from './List.model';
import { sortStoryData, useLocalSort } from './List.stories.helper';
import { ListRowProps } from './ListRow/ListRow';
import { TagsRenderer, createStateRenderer } from './ListRow/Renderers';

interface ListStoryData {
  id: number;
  desc: string;
  title: string;
  tags: string[];
}

// Extracted the type into a new file, as Typescript has issues with the syntax
// and formatting stops working in the IDE.
type StoryListType = ListProps<ListStoryData> & {
  rowCount: number;
};

interface StateStoryData {
  id: number;
  state: string;
  title: string;
  desc: string;
  tags: string[];
}

type StateStoryType = ListProps<StateStoryData>;

const defaultColumns: Column<ListStoryData>[] = [
  {
    propertyName: 'id',
    label: 'Id',
    size: '50px',
  },
  {
    propertyName: 'title',
    label: 'Title',
  },
  {
    propertyName: 'desc',
    label: 'Description',
  },
  {
    propertyName: 'tags',
    label: 'Tags',
    sortable: false,
    render: TagsRenderer,
  },
];

const defaultTags = faker.lorem.words(10).split(' ');

const generateData = (amount: number): ListStoryData[] =>
  generateItemArray(amount, (index) => ({
    id: index + 1,
    desc: `Description ${index + 1}: ${faker.lorem.words(
      faker.datatype.number({ min: 10, max: 50 }),
    )}`,
    title: `Item ${index + 1}: ${faker.random.words(
      faker.datatype.number({ min: 1, max: 3 }),
    )}`,
    tags: faker.helpers.arrayElements(
      defaultTags,
      faker.datatype.number({ min: 1, max: 10 }),
    ),
  }));

const selectionOptions = enumToObj(ListSelectMode);

const groups = createGroups({
  Storybook: ['rowCount'],
  Content: [
    'data',
    'columns',
    'errorMsg',
    'isError',
    'isLoading',
    'generateItemLink',
    'handleRetry',
    'keyProperty',
    'onRequestMoreData',
    'inlineMenuActions',
  ],
  Behavior: [
    'defaultSortOrder',
    'selectionMode',
    'onRetry',
    'onItemClicked',
    'onItemSelected',
    'onSortChanged',
    'onSortCleared',
    'enableSelectAll',
    'showActionButton',
    'loadingTriggerOffset',
    'isRowDisabled',
    'customRowRenderer',
  ],
  Styling: [
    'horizontalTextAlign',
    'verticalTextAlign',
    'textWrap',
    'minimumWidth',
    'columnGap',
    'rowGap',
    'headerRowHeight',
    'listRowHeight',
    'headerRowActionSize',
    'listRowActionSize',
    'className',
  ],
});

const meta: Meta<StoryListType> = {
  title: 'Other Components/List',
  component: List,
  argTypes: {
    ...groups,
    data: {
      ...groups.data,
      control: false,
    },
    selectionMode: {
      ...groups.selectionMode,
      control: {
        type: 'radio',
      },
      options: selectionOptions,
    },
    rowCount: {
      ...groups.rowCount,
      description: `<b>[Storybook only]</b> The number of rows to display.
      The story will generate the requests amount of items and assign it to the <code>data</code> prop.`,
    },
    showActionButton: {
      ...groups.showActionButton,
      description: `Whether to show the action button in the header row.`,
      options: [true, false, 'random'],
      mapping: {
        random: (): boolean => (Math.random() > 0.5 ? true : false),
      },
    },
    isRowDisabled: {
      ...groups.isRowDisabled,
      description:
        'Function to determine if a row should be disabled. Choose from predefined options or disable control for custom logic.',
      control: {
        type: 'select',
      },
      options: ['none', 'evenRows', 'oddRows', 'specificIds', 'randomRows'],
      mapping: {
        none: undefined,
        evenRows: (_data: ListStoryData, index: number) => index % 2 === 0,
        oddRows: (_data: ListStoryData, index: number) => index % 2 !== 0,
        specificIds: (data: ListStoryData) => [1, 3, 5].includes(data.id),
        randomRows: () => Math.random() > 0.7,
      },
    },
    customRowRenderer: {
      ...groups.customRowRenderer,
      description:
        'Custom renderer for rows. Choose from predefined options or disable control for custom logic.',
      control: {
        type: 'select',
      },
      options: ['none', 'customRenderer'],
      mapping: {
        none: undefined,
        customRenderer: (props: ListRowProps<ListStoryData>) => {
          const renderCustom = Math.random() < 0.3;

          return renderCustom ? (
            <div
              style={{
                display: 'grid',
                gridTemplateColumns: props.columnSizes,
                columnGap: props.columnGap,
                height: '50px',
                placeItems: 'center left',
                paddingLeft: '5px',
                borderBottom: '1px solid #ddd',
                backgroundColor: '#f5f5f5',
                opacity: props.isRowDisabled ? 0.4 : 0.7,
                cursor: props.isRowDisabled ? 'not-allowed' : 'pointer',
              }}
              onClick={() =>
                !props.isRowDisabled &&
                typeof props.onItemClicked === 'function' &&
                props.onItemClicked(props.data)
              }
            >
              <span>{props.data.id}</span>
              <span>{props.data.title}</span>
              <div
                style={{
                  display: 'grid',
                  width: '100%',
                  overflow: 'hidden',
                }}
              >
                <span
                  style={{
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    whiteSpace: 'nowrap',
                  }}
                >
                  {props.data.desc}
                </span>
              </div>
            </div>
          ) : null;
        },
      },
    },
  },
  args: {
    columns: defaultColumns,
    rowCount: 10,
  },
  decorators: [
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    (Story) => (
      <MemoryRouter>
        <Story />
      </MemoryRouter>
    ),
  ],
};
export default meta;

export const Default: StoryObj<StoryListType> = {
  render: (args) =>
    React.createElement(() => {
      const data = useMemo(() => generateData(args.rowCount), []);
      const { listData, sortChangedHandler } =
        useLocalSort<ListStoryData>(data);

      return (
        <List<ListStoryData>
          {...args}
          data={listData}
          onSortChanged={sortChangedHandler}
        />
      );
    }),
};

export const WithUnboundColumn: StoryObj<StoryListType> = {
  name: 'With unbound column',
  args: {
    columns: [
      ...defaultColumns,
      {
        // Columns don't have to be bound to a property.
        // They can also just define a renderer and infer the cell data from the row data.
        label: 'Unbound',
        render: (_, data) => <span>{data.desc.substring(15, 20)}</span>,
        key: 'unbound',
        size: '100px',
      },
    ],
  },
  render: Default.render,
};

export const Navigating: StoryObj<StoryListType> = {
  name: 'Navigating using DOM links',
  args: {
    generateItemLink: (i) => ({ hash: `#${i.id}` }),
  },
  render: (args) =>
    React.createElement(() => {
      const data = useMemo(() => generateData(args.rowCount), []);
      const { listData, sortChangedHandler } =
        useLocalSort<ListStoryData>(data);

      const location = useLocation();
      return (
        <>
          <div style={{ marginBottom: '20px', padding: '0 50px' }}>
            <Message
              title="About this story"
              isBodyOpen={true}
              isBodyPositionAbsolute={false}
              type="info"
            >
              <p style={{ padding: '5px' }}>
                This list uses the <code>generateItemLink</code> property to
                render an actual DOM link for each row. <br />
                <br />
                This story is using a <code>&lt;MemoryRouter /&gt;</code> to
                prevent actual navigation, but you can see the generated link in
                the lower right corner of your Browser when hovering over a row
                and after clicking an item the current location will also be
                shown in the next line:
                <br />
                <br />
                You&apos;re currently at item{' '}
                <strong>{location.hash || 'none'}</strong>.
              </p>
            </Message>
          </div>

          <List<ListStoryData>
            {...args}
            data={listData}
            onSortChanged={sortChangedHandler}
          />
        </>
      );
    }),
};

export const SingleSelectionMode: StoryObj<StoryListType> = {
  name: 'Selection Mode: Single',
  args: {
    selectionMode: ListSelectMode.Single,
  },
  render: Default.render,
};

export const MultiSelectionMode: StoryObj<StoryListType> = {
  name: 'Selection Mode: Multi',
  args: {
    selectionMode: ListSelectMode.Multi,
  },
  render: Default.render,
};

export const NoneSelectionMode: StoryObj<StoryListType> = {
  name: 'Selection Mode: None',
  args: {
    selectionMode: ListSelectMode.None,
  },
  render: Default.render,
};

export const WithoutActionButton: StoryObj<StoryListType> = {
  args: {
    showActionButton: false,
  },
  render: Default.render,
};

export const NotSortableColumn: StoryObj<StoryListType> = {
  args: {
    columns: [
      defaultColumns[0],
      { ...defaultColumns[1], sortable: false, disableResizing: true },
      ...defaultColumns.slice(2),
    ],
  },
  render: Default.render,
};

export const loadingData: StoryObj<StoryListType> = {
  args: {
    isLoading: true,
    rowCount: 0,
  },
  render: Default.render,
};

export const noData: StoryObj<StoryListType> = {
  args: {
    rowCount: 0,
  },
  render: Default.render,
};

export const WithError: StoryObj<StoryListType> = {
  args: {
    rowCount: 0,
    isError: true,
    errorMsg: 'There was an error.',
  },
  render: Default.render,
};

export const WithStateRenderer = ((): StoryObj<StateStoryType> => {
  const stateMap: ColumnMap = {
    success: '#68a357',
    failure: 'red',
    warning: 'rgba(255, 165, 0, 1)',
  };

  const stateValues = Object.keys(stateMap);
  const data = generateData(10).map((data): StateStoryData => {
    return {
      ...data,
      state: stateValues[Math.floor(Math.random() * stateValues.length)],
    };
  });

  return {
    args: {
      columns: [
        {
          propertyName: 'id',
          label: 'Id',
          size: '50px',
        },
        {
          propertyName: 'state',
          label: '',
          render: createStateRenderer(stateMap),
          size: '30px',
        },
        {
          propertyName: 'title',
          label: 'Title',
        },
        {
          propertyName: 'desc',
          label: 'Description',
        },
      ],
    },
    render: (args) =>
      React.createElement(() => {
        const { listData, sortChangedHandler } =
          useLocalSort<StateStoryData>(data);

        return (
          <List<StateStoryData>
            {...args}
            data={listData}
            onSortChanged={sortChangedHandler}
          />
        );
      }),
  };
})();

export const LocaleFormattingAndCustomRenderer: StoryObj<StoryListType> = {
  parameters: {
    controls: {
      hideNoControlsWarning: true,
      exclude: /.*/g,
    },
  },
  render: (_args) => {
    return (
      <List<{ date: Date; number: number; text: string }>
        columns={[
          {
            propertyName: 'date',
            size: '1fr',
            label: 'Date',
          },
          {
            propertyName: 'number',
            size: '1fr',
            label: 'Number',
          },
          {
            propertyName: 'text',
            size: '1fr',
            label: 'Bold text (custom renderer)',
            render: function Bold(data: unknown) {
              return <b>{data}</b>;
            },
          },
        ]}
        keyProperty="number"
        data={[{ text: 'some text', date: new Date(), number: 55324.33 }]}
        showActionButton={false}
      />
    );
  },
};

export const PagedData: StoryObj<StoryListType> = {
  parameters: {
    controls: {
      hideNoControlsWarning: true,
      exclude: /.*/g,
    },
  },
  decorators: [
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    (Story) => (
      <div style={{ height: '500px', overflow: 'auto' }}>
        <Story />
      </div>
    ),
  ],
  render: (_args) =>
    React.createElement(() => {
      const [data, setData] = useState<ListStoryData[]>(generateData(30));
      const [dataIndexes, setDataIndexes] = useState(30);
      const [mockIsLoading, setMockIsLoading] = useState(false);
      const [sort, setSort] = useState<SortData<ListStoryData>>({
        column: 'id',
        direction: 'asc',
      });
      const sortChangedHandler = (sort: SortData<ListStoryData>): void => {
        const defaultSortOrder = sortStoryData(sort, data);
        setSort(sort);
        setData([...defaultSortOrder]);
      };

      const loadingTime = 1000;
      const pageSize = 30;

      const onRequestMoreDataHandler = (): void => {
        setMockIsLoading(true);
        setTimeout(() => {
          const newData = generateItemArray(pageSize, (index) => ({
            id: index + dataIndexes,
            desc: `Description ${index + dataIndexes}: ${faker.lorem.words(
              faker.datatype.number({ min: 10, max: 20 }),
            )}`,
            title: `Item ${index + dataIndexes}: ${faker.random.words(
              faker.datatype.number({ min: 1, max: 3 }),
            )}`,
            tags: faker.helpers.arrayElements(
              ['Tag1', 'Tag2', 'Tag3', 'Tag4', 'Tag5'],
              faker.datatype.number({ min: 1, max: 3 }),
            ),
          }));
          setMockIsLoading(false);
          setData(sortStoryData(sort, [...data, ...newData]));
          setDataIndexes(dataIndexes + pageSize);
        }, loadingTime);
      };

      return (
        <List<ListStoryData>
          columns={defaultColumns}
          data={data}
          isLoading={mockIsLoading}
          onRequestMoreData={() => {
            onRequestMoreDataHandler();
          }}
          onSortChanged={sortChangedHandler}
        />
      );
    }),
};
