import React, { useState } from 'react';
import {
  storybookArgTypes,
  storybookExcludedControlParams,
  type StoryMetaType,
  StoryType,
} from '@lg-tools/storybook-utils';
import { StoryContext, StoryFn } from '@storybook/react';
import { userEvent, within } from '@storybook/test';

import { Badge } from '@leafygreen-ui/badge';
import { Button } from '@leafygreen-ui/button';
import { css } from '@leafygreen-ui/emotion';

import { getComboboxOptions } from './test-utils/getTestOptions.testutils';
import {
  ComboboxSize,
  DropdownWidthBasis,
  Overflow,
  SearchState,
  State,
  TruncationLocation,
} from './types';
import { Combobox, ComboboxOption, ComboboxProps } from '.';

const wrapperStyle = css`
  width: 256px;
  padding-block: 64px;
  display: flex;
`;

const multiValue = ['apple', 'banana'];

const meta: StoryMetaType<typeof Combobox> = {
  title: 'Components/Inputs/Combobox',
  component: Combobox,
  decorators: [
    StoryFn => (
      <div className={wrapperStyle}>
        <StoryFn />
      </div>
    ),
  ],
  parameters: {
    default: 'LiveExample',
    controls: {
      exclude: [
        ...storybookExcludedControlParams,
        'as',
        'filteredOptions',
        'initialValue',
        'setError',
        'value',
        'children',
      ],
    },
    generate: {
      storyNames: ['SingleSelect', 'MultiSelect', 'DisabledInput'],
      combineArgs: {
        darkMode: [false, true],
        clearable: [true, false],
        description: [undefined, 'Please pick fruit(s)'],
        label: [undefined, 'Choose a fruit'],
        state: Object.values(State),
        size: Object.values(ComboboxSize),
      },
      excludeCombinations: [
        ['description', { label: undefined }],
        {
          clearable: false,
          value: undefined,
        },
        {
          multiselect: true,
          value: 'apple',
        },
        {
          multiselect: false,
          value: multiValue,
        },
      ],
    },
  },

  args: {
    label: 'Choose a fruit',
    description: 'Please pick fruit(s)',
    placeholder: 'Select fruit',
    multiselect: false,
    darkMode: false,
    disabled: false,
    clearable: true,
    dropdownWidthBasis: DropdownWidthBasis.Trigger,
    errorMessage: 'No Pomegranates!',
    children: getComboboxOptions(),
  },

  argTypes: {
    darkMode: storybookArgTypes.darkMode,
    multiselect: { control: 'boolean' },
    disabled: { control: 'boolean' },
    clearable: { control: 'boolean' },
    label: { control: 'text' },
    description: { control: 'text' },
    placeholder: { control: 'text' },
    inputValue: { control: 'text' },
    size: {
      options: Object.values(ComboboxSize),
      control: 'select',
    },
    state: {
      options: Object.values(State),
      control: 'select',
    },
    errorMessage: {
      control: 'text',
    },
    searchEmptyMessage: { control: 'text' },
    searchState: {
      options: Object.values(SearchState),
      control: 'select',
    },
    searchErrorMessage: {
      control: 'text',
      if: { arg: 'searchState', eq: SearchState.Error },
    },
    searchLoadingMessage: {
      control: 'text',
      if: { arg: 'searchState', eq: SearchState.Loading },
    },
    chipTruncationLocation: {
      options: Object.values(TruncationLocation),
      control: 'select',
      if: { arg: 'multiselect' },
    },
    chipCharacterLimit: {
      control: 'number',
      if: { arg: 'multiselect' },
    },
    overflow: {
      options: Object.values(Overflow),
      control: 'select',
      if: { arg: 'multiselect' },
    },
    dropdownWidthBasis: {
      options: Object.values(DropdownWidthBasis),
      control: 'select',
    },
  },
};

export default meta;

export const LiveExample: StoryFn<ComboboxProps<boolean>> = args => {
  return (
    <>
      {/* Since Combobox doesn't fully refresh when `multiselect` changes, we need to explicitly render a different instance */}
      {args.multiselect ? (
        // @ts-ignore - multiselect check ensures props match ComboboxProps<true>
        <Combobox key="multi" {...args} multiselect={true} />
      ) : (
        // @ts-ignore - multiselect check ensures props match ComboboxProps<false>
        <Combobox key="single" {...args} multiselect={false} />
      )}
    </>
  );
};
LiveExample.parameters = {
  chromatic: { disableSnapshot: true },
};

export const ControlledSingleSelect = () => {
  const [selection, setSelection] = useState<string | null>(null);

  const handleChange = (value: string | null) => {
    setSelection(value);
  };

  return (
    <div>
      <Combobox
        multiselect={false}
        label="Choose a fruit"
        description="Please pick one"
        placeholder="Select fruit"
        onChange={handleChange}
        value={selection}
      >
        <ComboboxOption value="apple" />
        <ComboboxOption value="banana" />
        <ComboboxOption value="carrot" />
      </Combobox>
      <Button onClick={() => setSelection('carrot')}>Select Carrot</Button>
    </div>
  );
};
ControlledSingleSelect.parameters = {
  chromatic: { disableSnapshot: true },
};

export const ExternalFilter = () => {
  const allOptions = [
    'apple',
    'banana',
    'carrot',
    'dragonfruit',
    'eggplant',
    'fig',
    'grape',
    'honeydew',
    'iceberg-lettuce',
    'jalapeño',
  ];

  const [filteredOptions, setOptions] = useState(['carrot', 'grape']);

  const handleFilter = (input: string) => {
    setOptions(allOptions.filter(option => option.includes(input)));
  };

  return (
    <Combobox
      label="Choose some fruit"
      placeholder="Select fruit"
      initialValue={['apple', 'fig', 'raspberry']}
      multiselect={true}
      overflow={'expand-y'}
      onFilter={handleFilter}
      filteredOptions={filteredOptions}
    >
      {allOptions.map(option => (
        <ComboboxOption key={option} value={option} />
      ))}
    </Combobox>
  );
};
ExternalFilter.parameters = {
  chromatic: { disableSnapshot: true },
};

/**
 * Example showing the `customContent` prop for rendering custom components in dropdown options.
 * The `customContent` prop accepts any ReactNode, allowing you to add badges, icons, or other
 * custom components to your options. The `displayName` is still used for filtering and chips.
 */
export const WithCustomContent = () => {
  return (
    <Combobox
      label="Choose a feature"
      description="Some features are new!"
      placeholder="Select a feature"
      multiselect={false}
    >
      <ComboboxOption
        value="feature-a"
        displayName="Feature A"
        customContent={
          <>
            <span>Feature A</span>
            <Badge variant="blue">New</Badge>
          </>
        }
      />
      <ComboboxOption
        value="feature-b"
        displayName="Feature B"
        customContent={
          <>
            <span>Feature B</span>
            <Badge variant="green">Beta</Badge>
          </>
        }
      />
      <ComboboxOption value="feature-c" displayName="Feature C" />
      <ComboboxOption
        value="feature-d"
        displayName="Feature D"
        customContent={
          <>
            <span>Feature D</span>
            <Badge variant="red">Deprecated</Badge>
          </>
        }
      />
    </Combobox>
  );
};
WithCustomContent.parameters = {
  chromatic: { disableSnapshot: true },
};

export const SingleSelect: StoryType<typeof Combobox> = () => <></>;
SingleSelect.args = {
  multiselect: false,
};
SingleSelect.parameters = {
  generate: {
    combineArgs: {
      value: [undefined, 'apple'],
    },
  },
};

export const MultiSelect = () => <></>;
MultiSelect.args = {
  multiselect: true,
};
MultiSelect.parameters = {
  generate: {
    combineArgs: {
      value: [undefined, multiValue],
    },
  },
};

export const MultiSelectNoIcons: StoryFn<ComboboxProps<boolean>> = (
  args: ComboboxProps<boolean>,
) => {
  return (
    // @ts-expect-error - args will have multiselect=true from storybook controls
    <Combobox {...args} multiselect={true}>
      {getComboboxOptions(false)}
    </Combobox>
  );
};
MultiSelectNoIcons.parameters = {
  chromatic: { disableSnapshot: true },
};

export const DisabledInput = () => <></>;
DisabledInput.args = {
  disabled: true,
};
DisabledInput.parameters = {
  generate: {
    combineArgs: {
      darkMode: [true, false],
    },
  },
};

export const InitialLongComboboxOpen = {
  render: () => {
    return (
      <Combobox
        multiselect={false}
        label="Choose a fruit"
        description="Please pick one"
        placeholder="Select fruit"
      >
        {getComboboxOptions()}
      </Combobox>
    );
  },
  play: async (ctx: StoryContext) => {
    const { findByRole } = within(ctx.canvasElement.parentElement!);
    const trigger = await findByRole('combobox');
    userEvent.click(trigger);
  },
  decorators: [
    (Story: StoryFn, _ctx: StoryContext) => (
      <div
        className={css`
          height: 100vh;
          padding: 0;
        `}
      >
        <Story />
      </div>
    ),
  ],
};
