import { Meta, StoryObj } from '@storybook/react-webpack5';
import { Search as SearchIcon } from '@transferwise/icons';
import { userEvent, within, fn } from 'storybook/test';
import { useState } from 'react';

import { createSandboxStory, globalScope } from '../../.storybook/components/sandbox/SandboxEditor';
import { Sentiment, Size } from '../common';
import { Input } from '../inputs/Input';
import { Field } from '../field/Field';
import Button from '../button';
import Modal from '../modal';
import { InlinePromptProps } from '../prompt';
import Typeahead, { type TypeaheadOption } from './Typeahead';

// needed for SB to display correct name in the docs
(Typeahead as React.FC).displayName = 'Typeahead';
type Story = StoryObj<typeof Typeahead>;

/**
 * Checks if provided TypeaheadOption contains an HTML5-compliant email address
 * @see https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
 */
const validateOptionAsEmail = (option: TypeaheadOption) => {
  return /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(
    option.label,
  );
};

const meta: Meta<typeof Typeahead> = {
  component: Typeahead,
  title: 'Forms/Typeahead',
  args: {
    allowNew: false,
    autoFillOnBlur: true,
    autoFocus: false,
    chipSeparators: [',', ' '],
    clearable: true,
    inputAutoComplete: 'new-password',
    minQueryLength: 3,
    multiple: false,
    searchDelay: 200,
    showSuggestions: true,
    showNewEntry: true,
    alert: undefined,
    size: Size.MEDIUM,
    initialValue: [],
    id: 'myTypeahead',
    name: 'typeahead-input-name',
    placeholder: 'placeholder',
    onChange: fn(),
    onBlur: fn(),
    onFocus: fn(),
    onInputChange: fn(),
    onSearch: fn(),
  },

  argTypes: {
    size: {
      control: 'inline-radio',
      options: [Size.MEDIUM, Size.LARGE],
    },
    alert: {
      description:
        'Displays an [InlinePrompt](?path=/docs/prompts-inlineprompt--docs) below the input when provided. <blockquote class="m-0">**⚠️ DEPRECATED:** Please use [&lt;Field />](?path=/docs/forms-field--docs) component and its `message` and `sentiment` props instead. `error`, `info` and `success` alert types are no longer supported and will be soon removed.</blockquote>',
      control: 'object',
      table: {
        type: {
          summary:
            '{ message: ReactNode; type?: "negative" | "neutral" | "positive" | "warning" | "proposition" }',
        },
      },
    },
  },
};
export default meta;

export const Sandbox = createSandboxStory({
  code: `const App = () => {
  const [options, setOptions] = React.useState([
    { label: 'British pound', note: 'GBP' },
    { label: 'Euro', note: 'EUR' },
    { label: 'US Dollar', note: 'USD' },
    { label: 'Japanese yen', note: 'JPY' },
    { label: 'Australian dollar', note: 'AUD' },
  ]);

  return (
    <Field label="Select a currency" id="typeahead-sandbox">
      <Typeahead
        id="typeahead-sandbox"
        name="currency"
        placeholder="Search currencies..."
        clearable
        addon={<Search size={24} />}
        options={options}
        onSearch={() => {
          setTimeout(() => setOptions(options), 1000);
        }}
      />
    </Field>
  );
};`,
  scope: globalScope,
});

export const Basic: Story = {
  render: function Render(args) {
    const [options, setOptions] = useState([
      {
        label: 'A thing',
        note: 'with a note',
      },
      {
        label: 'Another thing',
        secondary: 'with secondary text this time',
      },
      {
        label: 'Profile',
      },
      {
        label: 'Globe',
      },
      {
        label: 'British pound',
      },
      {
        label: 'Euro',
      },
      {
        label: 'Something else',
      },
    ]);

    const validateChipWhenMultiple = () =>
      args.multiple && args.allowNew
        ? (option: TypeaheadOption) => validateOptionAsEmail(option)
        : undefined;

    return (
      <Typeahead
        {...args}
        initialValue={[]}
        validateChip={validateChipWhenMultiple()}
        addon={<SearchIcon size={24} />}
        options={options}
        onSearch={() => {
          setTimeout(() => setOptions(options), 1500);
        }}
      />
    );
  },
};

export const Creatable: Story = {
  render: (args) => (
    <Field id={`input-${args.id}`} label="Typeahead with values">
      <Typeahead
        {...args}
        allowNew
        multiple
        initialValue={[]}
        validateChip={validateOptionAsEmail}
        addon={<SearchIcon size={24} />}
        options={[]}
      />
    </Field>
  ),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByRole('combobox'), 'chip{Enter}hello@wise.com{Enter}');
  },
};

type Result =
  | {
      type: 'action';
      value: string;
    }
  | {
      type: 'search';
      value: string;
    };

type SearchState = 'success' | 'idle' | 'error' | 'loading';

/**
 * @FIXME This story feels incomplete. It ignores bunch of props
 *        and seems very opinionated. Surely we can do better?
 */
export const Search: Story = {
  render: function Render(args) {
    const [results, setResults] = useState<Result[]>([]);
    const [state, setState] = useState<SearchState>('idle');
    const [filledValue, setFilledValue] = useState<string | null>(null);

    const handleInputChange = (query: string) => {
      args?.onInputChange?.(query);

      if (query === 'loading' || query === 'error' || query === 'nothing') {
        setState(query === 'nothing' ? 'success' : query);
        setResults([]);
        return;
      }

      setState('success');
      setResults(getResults(query));
    };

    const handleResultSelected = (option: Result) => {
      if (option.type === 'search') {
        setResults([
          { type: 'action', value: `${option.value} Result #1` },
          { type: 'action', value: `${option.value} Result #2` },
          { type: 'action', value: `${option.value} Result #3` },
        ]);
      }
      if (option.type === 'action') {
        setFilledValue(option.value);
      }
    };

    const handleChange = (values: TypeaheadOption<Result>[]) => {
      args?.onChange?.(values);

      if (values.length > 0) {
        const [updatedValue] = values;

        if (updatedValue.value) {
          handleResultSelected(updatedValue.value);
        }
      }
    };

    const getResults = (query: string): Result[] => {
      return [
        { type: 'action', value: `${query} Result #1` },
        { type: 'action', value: `${query} Result #2` },
        { type: 'action', value: `${query} Result #3` },
        { type: 'search', value: `Search for more: '${query}'` },
      ];
    };

    const renderFooter = (options: Result[]) => {
      let output = null;

      if (state === 'loading') {
        output = 'Loading…';
      }

      if (state === 'success' && options.length === 0) {
        output = 'No results found';
      }

      if (state === 'error' && options.length === 0) {
        output = 'Something went wrong';
      }

      return <p className="m-y-2 m-x-2">{output}</p>;
    };

    return (
      <>
        <Typeahead<Result>
          {...args}
          initialValue={undefined}
          footer={renderFooter(results)}
          addon={<SearchIcon />}
          options={results.map((option) => ({
            value: option,
            label: option.value,
            keepFocusOnSelect: option.type === 'search',
            clearQueryOnSelect: option.type === 'action',
          }))}
          onChange={handleChange}
          onInputChange={handleInputChange}
        />

        {filledValue != null ? <Input value={filledValue} /> : null}
      </>
    );
  },
};

export const WithField: Story = {
  args: {
    autoFillOnBlur: undefined,
    chipSeparators: undefined,
    clearable: undefined,
    initialValue: undefined,
    inputAutoComplete: undefined,
    minQueryLength: undefined,
    onBlur: undefined,
    onFocus: undefined,
    onInputChange: undefined,
    onSearch: undefined,
    placeholder: undefined,
    searchDelay: undefined,
    showSuggestions: undefined,
    showNewEntry: undefined,
    size: undefined,
  },
  render: function Render(args) {
    const options = [{ label: 'Option 1' }];

    const alertTypes: InlinePromptProps['sentiment'][] = [
      Sentiment.NEGATIVE,
      Sentiment.WARNING,
      Sentiment.NEUTRAL,
      Sentiment.POSITIVE,
      'proposition',
    ];

    return (
      <>
        <Field
          id="typeahead"
          label="Field label"
          description="Additional information about the field"
          required
        >
          <Typeahead {...args} name="typeahead" options={options} />
        </Field>
        {alertTypes.map((sentiment) => (
          <Field
            key={sentiment}
            label="Field label"
            id={`typeahead-${sentiment}`}
            message={`This is a ${sentiment} message`}
            sentiment={sentiment}
          >
            <Typeahead {...args} name={`typeahead-${sentiment}`} options={options} />
          </Field>
        ))}
      </>
    );
  },
  decorators: (Story) => (
    <div className="d-flex flex-column">
      <Story />
    </div>
  ),
};

/**
 * > **⚠️ DEPRECATED:** Please use [&lt;Field />](?path=/docs/forms-field--docs) component and its `message` and `sentiment` props instead. <br/>`error`, `info` and `success` alert types are no longer supported and will be soon removed.
 */
export const WithAlert: Story = {
  name: 'With Alert (deprecated)',
  args: {
    autoFillOnBlur: undefined,
    chipSeparators: undefined,
    clearable: undefined,
    initialValue: undefined,
    inputAutoComplete: undefined,
    minQueryLength: undefined,
    onBlur: undefined,
    onFocus: undefined,
    onInputChange: undefined,
    onSearch: undefined,
    placeholder: undefined,
    searchDelay: undefined,
    showSuggestions: undefined,
    showNewEntry: undefined,
    size: undefined,
  },
  parameters: {
    docs: {
      canvas: {
        sourceState: 'hidden',
      },
    },
  },
  render: function Render(args) {
    const options = [{ label: 'Option 1' }];

    const alertTypes: InlinePromptProps['sentiment'][] = [
      Sentiment.NEGATIVE,
      Sentiment.WARNING,
      Sentiment.NEUTRAL,
      Sentiment.POSITIVE,
      'proposition',
    ];

    return (
      <>
        {alertTypes.map((sentiment) => (
          <Typeahead
            key={sentiment}
            {...args}
            id={`typeahead-${sentiment}`}
            name={`typeahead-${sentiment}`}
            options={options}
            alert={{
              message: `This is a ${sentiment} message`,
              type: sentiment,
            }}
          />
        ))}
      </>
    );
  },
  decorators: (Story) => (
    <div className="d-flex flex-column">
      <Story />
    </div>
  ),
};
