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

import Typeahead, { type TypeaheadOption } from './Typeahead';
import { Size } from '../common';
import { useState } from 'react';
import { Input } from '../inputs/Input';
import { Field } from '../field/Field';

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,
  );
};

export default {
  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,
    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],
    },
  },
} satisfies Meta<typeof Typeahead>;

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}
      </>
    );
  },
};
