import { useCallback, useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

import Button from '@design-system/Buttons/Button';
import { Stack, Typography } from '@mui/material';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { IconMapPin, IconSearch, IconTrash } from '@tabler/icons-react';
import { maxBy, sampleSize } from 'lodash';

import List from '../../List';
import ListItem from '../../List/components/ListItem';

import FormAutocomplete from './form';
import { type AutocompleteOption, type AutocompleteProps } from './types';
import Autocomplete from '.';

type MockOption = {
  value: number;
  label: string;
};

const mockOptions: MockOption[] = [
  { label: 'Option 1', value: 1 },
  { label: 'Option 2', value: 2 },
  { label: 'Option 3', value: 3 },
];

const mockColors: MockOption[][] = [
  [
    { value: 1, label: 'Yellow' },
    { value: 2, label: 'Blue' },
    { value: 3, label: 'Red' },
    { value: 4, label: 'Black' },
    { value: 5, label: 'White' },
    { value: 6, label: 'Gray' },
    { value: 7, label: 'Pink' },
    { value: 8, label: 'Purple' },
    { value: 9, label: 'Gold' },
    { value: 0, label: 'Silver' },
  ],
  [
    { value: 10, label: 'Green' },
    { value: 11, label: 'Orange' },
    { value: 12, label: 'Brown' },
    { value: 13, label: 'Cyan' },
    { value: 14, label: 'Magenta' },
    { value: 15, label: 'Teal' },
    { value: 16, label: 'Lavender' },
    { value: 17, label: 'Maroon' },
    { value: 18, label: 'Navy' },
    { value: 19, label: 'Olive' },
  ],
  [
    { value: 20, label: 'Coral' },
    { value: 21, label: 'Indigo' },
    { value: 22, label: 'Turquoise' },
    { value: 23, label: 'Beige' },
    { value: 24, label: 'Mint' },
    { value: 25, label: 'Salmon' },
    { value: 26, label: 'Khaki' },
    { value: 27, label: 'Plum' },
    { value: 28, label: 'Azure' },
    { value: 29, label: 'Lime' },
  ],
];

const meta: Meta<AutocompleteProps<MockOption>> = {
  component: Autocomplete,
  title: 'Design System/Inputs/Autocomplete',
  tags: ['autodocs'],
  argTypes: {
    label: { control: 'text' },
    placeholder: { control: 'text' },
    helperText: { control: 'text' },
    hasError: { control: 'boolean' },
    disabled: { control: 'boolean' },
    loading: { control: 'boolean' },
    isServerFiltered: { control: 'boolean' },
    hidePopupIcon: { control: 'boolean' },
    startIcon: { control: false },
    virtualized: { control: 'boolean' },
    filterLimit: { control: 'number' },
    hasMoreOptions: { control: 'boolean' },
    options: { control: false },
    value: { control: false },
    onChange: { control: false },
    onCreate: { control: false },
    onLoadMore: { control: false },
    onInputChange: { control: false },
    renderTags: { control: false },
    noOptionsMessage: { control: false },
    fieldRef: { control: false },
  },
  args: {
    options: mockOptions,
    label: 'Label',
    placeholder: 'Select an option',
    isServerFiltered: false,
  },
  render: props => {
    const [state, setState] = useState<MockOption | null>(null);
    return (
      <Autocomplete
        {...props}
        value={state}
        onChange={nextValue => setState(nextValue)}
      />
    );
  },
};

export default meta;

type Story = StoryObj<AutocompleteProps<MockOption>>;

export const Default: Story = {
  args: {},
};

export const Error: Story = {
  args: {
    hasError: true,
    helperText: 'Field is required',
  },
};

export const NoOptions: Story = {
  args: {
    options: [],
  },
};

export const HelperText: Story = {
  args: {
    helperText: 'Select or search an option',
  },
};

export const WithInputType: Story = {
  args: {
    type: 'number',
    label: 'Numeric input type',
    placeholder: 'Underlying input uses type="number"',
    helperText:
      'HTML input type is passed to the TextField (e.g. number, search)',
    options: [
      { label: '1', value: 1 },
      { label: '2', value: 2 },
      { label: '3', value: 3 },
    ],
  },
};

export const SearchBox: Story = {
  args: {
    label: 'Search',
    placeholder: 'Search places',
    startIcon: <IconSearch />,
    hidePopupIcon: true,
    helperText:
      'Search-style field: start icon + no chevron (e.g. a maps searcher)',
  },
};

export const WithStartIcon: Story = {
  args: {
    label: 'Location',
    placeholder: 'Select a location',
    startIcon: <IconMapPin />,
  },
};

export const HiddenPopupIcon: Story = {
  args: {
    label: 'No chevron',
    placeholder: 'Select an option',
    hidePopupIcon: true,
    helperText:
      'hidePopupIcon without a startIcon: chevron hidden, default left padding kept',
  },
};

export const Disabled: Story = {
  args: {
    disabled: true,
  },
};

export const Loading: Story = {
  args: {
    loading: true,
    options: [],
  },
};

export const multiple: StoryObj<AutocompleteProps<MockOption, true>> = {
  render: props => {
    const [state, setState] = useState<MockOption[]>([]);
    return (
      <Autocomplete
        {...props}
        value={state}
        onChange={nextValue => setState(nextValue)}
        multiple
      />
    );
  },
};

export const NoTags: StoryObj<AutocompleteProps<MockOption, true>> = {
  render: props => {
    const [state, setState] = useState<MockOption[]>([]);
    const handleRemove = (option: MockOption) => {
      setState(s => s.filter(item => item.value !== option.value));
    };
    return (
      <Stack sx={{ gap: 2 }}>
        <Autocomplete
          {...props}
          value={state}
          onChange={nextValue => setState(nextValue)}
          multiple
          renderTags={() => null}
        />
        <List
          sx={{
            borderRadius: 2,
            backgroundColor: 'oklch(97% 0.009 254.604)',
            gap: 1,
          }}
        >
          {state?.map((option, index) => (
            <ListItem
              key={option.value}
              text={{
                title: option.label,
              }}
              divider={index !== state.length - 1}
              action={{
                Icon: IconTrash,
                onClick: () => handleRemove(option),
              }}
            />
          ))}
        </List>
      </Stack>
    );
  },
};

export const ServerFiltered: Story = {
  render: () => {
    const [options, setOptions] = useState(mockOptions);
    const [loading, setLoading] = useState(false);
    const [state, setState] = useState<MockOption | null>(null);

    const handleFilterOptions = (searchText: string) => {
      setLoading(true);

      // Simulate a server request
      setTimeout(() => {
        setLoading(false);
        setOptions(
          mockOptions.filter(option => option.label.startsWith(searchText)),
        );
      }, 1000);
    };

    return (
      <Autocomplete
        placeholder="Search an option"
        helperText="Filtered on the server"
        options={options}
        onInputChange={(_event, searchText) => handleFilterOptions(searchText)}
        loading={loading}
        value={state}
        onChange={value => setState(value)}
        isServerFiltered
      />
    );
  },
};

export const Creatable: Story = {
  render: props => {
    const baseColors = mockColors[0];
    const sampleColors = sampleSize(baseColors, 5);
    const [options, setOptions] = useState<MockOption[]>(sampleColors);
    const [color, setColor] = useState<MockOption | null>(sampleColors[0]);

    return (
      <Stack>
        <Autocomplete
          {...props}
          options={options}
          value={color}
          onChange={nextValue => {
            setColor(nextValue);
          }}
          onCreate={inputValue => {
            const nextId = maxBy(sampleColors, item => item.value)!.value + 1;
            const nextColor = {
              label: inputValue,
              value: nextId,
            };
            setOptions(state => [...state, nextColor]);
            setColor(nextColor);
          }}
        />
        <Typography>Selected:</Typography>
        <Typography>
          <em>{JSON.stringify(color, null, 2)}</em>
        </Typography>
      </Stack>
    );
  },
};

export const CreatableWithMaxLength: Story = {
  render: props => {
    const baseColors = mockColors[0];
    const sampleColors = sampleSize(baseColors, 5);
    const [options, setOptions] = useState<MockOption[]>(sampleColors);
    const [color, setColor] = useState<MockOption | null>(sampleColors[0]);

    return (
      <Stack>
        <Autocomplete
          {...props}
          options={options}
          value={color}
          onChange={nextValue => {
            setColor(nextValue);
          }}
          onCreate={inputValue => {
            const nextId = maxBy(sampleColors, item => item.value)!.value + 1;
            const nextColor = {
              label: inputValue,
              value: nextId,
            };
            setOptions(state => [...state, nextColor]);
            setColor(nextColor);
          }}
          maxLength={200}
        />
        <Typography>Selected:</Typography>
        <Typography>
          <em>{JSON.stringify(color, null, 2)}</em>
        </Typography>
      </Stack>
    );
  },
};

export const MultipleCreatable: StoryObj<AutocompleteProps<MockOption, true>> =
  {
    render: props => {
      const baseColors = mockColors[0];
      const sampleColors = sampleSize(baseColors, 5);
      const [options, setOptions] = useState<MockOption[]>(sampleColors);
      const [colors, setColors] = useState<MockOption[]>([]);

      return (
        <Stack>
          <Autocomplete
            {...props}
            options={options}
            value={colors}
            onChange={nextValue => setColors(nextValue ?? [])}
            multiple
            onCreate={inputValue => {
              const nextId =
                (maxBy(options, item => item.value)?.value ?? 0) + 1;
              const nextColor = { label: inputValue, value: nextId };
              setOptions(state => [...state, nextColor]);
              setColors(prev => [...prev, nextColor]);
            }}
            placeholder="Select or create options"
            helperText="Multiple selection with create option; new items are appended to selection"
          />
          <Typography>Selected:</Typography>
          <Typography>
            <em>{JSON.stringify(colors, null, 2)}</em>
          </Typography>
        </Stack>
      );
    },
  };

export const LoadMore: Story = {
  render: () => {
    const [currentPage, setCurrentPage] = useState(1);
    const [options, setOptions] = useState<MockOption[]>(mockColors[0]);
    const [color, setColor] = useState<MockOption | null>(mockColors[0][0]);

    const hasMorePages = currentPage < mockColors.length - 1;

    const handleLoad = () => {
      if (hasMorePages) {
        setTimeout(() => {
          setCurrentPage(currentPage + 1);
        }, 1500);
      }
    };

    useEffect(() => {
      setOptions(state => [...state, ...mockColors[currentPage]]);
    }, [currentPage]);

    return (
      <Stack>
        <Autocomplete
          options={options}
          value={color}
          onChange={value => setColor(value)}
          onLoadMore={handleLoad}
          hasMoreOptions={hasMorePages}
        />
        <Typography>Pagination:</Typography>
        <Typography>
          <em>
            {JSON.stringify(
              {
                lastPageLoaded: currentPage,
                loadedOptionsCount: options.length,
              },
              null,
              2,
            )}
          </em>
        </Typography>
      </Stack>
    );
  },
};

const pinkOptions: MockOption[] = [
  { value: 1, label: 'Pink' },
  { value: 2, label: 'Light Pink' },
  { value: 3, label: 'Dark Pink' },
  { value: 4, label: 'Pale Pink' },
  { value: 5, label: 'Hot Pink' },
  { value: 6, label: 'Intense Pink' },
  { value: 7, label: 'Magenta' },
  { value: 8, label: 'Rose' },
  { value: 9, label: 'Light Intense Pink' },
  { value: 10, label: 'Dark Intense Pink' },
  { value: 11, label: 'Pale Intense Pink' },
  { value: 12, label: 'Soft Pink' },
];

const usePaginatedOptions = (options: MockOption[], initialInput = '') => {
  const [input, setInput] = useState(initialInput);
  const [currentPage, setCurrentPage] = useState(1);
  const [filteredOptions, setFilteredOptions] = useState<MockOption[]>([]);
  const [paginatedOptions, setPaginatedOptions] = useState<MockOption[]>([]);
  const [hasMorePages, setHasMorePages] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const PAGE_SIZE = 4;

  const filterOptions = useCallback(() => {
    setIsLoading(true);
    setTimeout(() => {
      const filtered = options.filter(option =>
        option.label.toLowerCase().includes(input.toLowerCase()),
      );
      setFilteredOptions(filtered);
      setCurrentPage(1);
      setIsLoading(false);
    }, 1500);
  }, [input, options]);

  const paginateOptions = useCallback(() => {
    setIsLoading(true);
    setTimeout(() => {
      const startIndex = (currentPage - 1) * PAGE_SIZE;
      const nextPageOptions = filteredOptions.slice(
        startIndex,
        startIndex + PAGE_SIZE,
      );
      setPaginatedOptions(prev => [...prev, ...nextPageOptions]);
      setHasMorePages(startIndex + PAGE_SIZE < filteredOptions.length);
      setIsLoading(false);
    }, 1500);
  }, [currentPage, filteredOptions]);

  const loadMore = useCallback(() => {
    if (hasMorePages) {
      setCurrentPage(prev => prev + 1);
    }
  }, [hasMorePages]);

  const addOption = useCallback(
    (label: string) => {
      const highestValue = Math.max(...options.map(option => option.value));
      const newOption = { label, value: highestValue + 1 };
      options.push(newOption);
      setFilteredOptions(prev => [...prev, newOption]);
      setPaginatedOptions(prev => [...prev, newOption]);
    },
    [options],
  );

  useEffect(() => {
    filterOptions();
  }, [filterOptions]);

  useEffect(() => {
    paginateOptions();
  }, [filteredOptions, paginateOptions]);

  useEffect(() => {
    paginateOptions();
  }, [currentPage, paginateOptions]);

  return {
    paginatedOptions,
    hasMorePages,
    loadMore,
    setInput,
    isLoading,
    addOption,
  };
};

export const CreatableLoadMore: Story = {
  render: props => {
    const [input, setInput] = useState('');
    const [selectedOption, setSelectedOption] = useState<MockOption | null>(
      null,
    );

    const {
      hasMorePages,
      loadMore: handleLoadMore,
      paginatedOptions: data,
      addOption: handleCreate,
      isLoading,
    } = usePaginatedOptions(pinkOptions, input);

    return (
      <Stack>
        <Autocomplete
          {...props}
          options={data}
          value={selectedOption}
          onChange={setSelectedOption}
          onCreate={handleCreate}
          onLoadMore={handleLoadMore}
          hasMoreOptions={hasMorePages}
          placeholder="Select pink color"
          onInputChange={(_event, nextInput) => setInput(nextInput)}
          loading={isLoading}
          isServerFiltered
        />
        <Typography>Selected Option:</Typography>
        <Typography>
          <em>{JSON.stringify(selectedOption, null, 2)}</em>
        </Typography>
      </Stack>
    );
  },
};

export const Form: Story = {
  render: () => {
    const form = useForm({
      defaultValues: {
        option: null,
      },
    });

    const { watch } = form;

    return (
      <Stack sx={{ gap: 2 }}>
        <FormProvider {...form}>
          <FormAutocomplete
            name="option"
            options={mockOptions}
            autocompleteProps={{
              placeholder: 'Controlled by React Hook Form',
            }}
          />
        </FormProvider>
        <pre>{JSON.stringify(watch(), null, 2)}</pre>
      </Stack>
    );
  },
};

export const FormDisabled: Story = {
  render: () => {
    const form = useForm({
      defaultValues: {
        color: mockColors[0][0],
      },
    });

    const submit = () => {
      form.handleSubmit(data => {
        alert(JSON.stringify(data, null, 2));
      })();
    };

    return (
      <Stack sx={{ gap: 2 }}>
        <FormProvider {...form}>
          <FormAutocomplete
            name="color"
            options={[
              { value: 1, label: 'Yellow' },
              { value: 2, label: 'Blue' },
              { value: 3, label: 'Red' },
            ]}
            autocompleteProps={{
              disabled: true,
            }}
          />
          <Button onClick={submit}>Submit</Button>
        </FormProvider>
      </Stack>
    );
  },
};

export const FormLimitedTagsDisabled: Story = {
  render: () => {
    const form = useForm({
      defaultValues: {
        colors: mockColors[0],
      },
    });
    const { watch } = form;

    return (
      <Stack sx={{ gap: 2 }}>
        <FormProvider {...form}>
          <FormAutocomplete
            name="colors"
            options={mockColors[0]}
            autocompleteProps={{
              multiple: true,
              disabled: true,
              limitTags: 2,
            }}
          />
        </FormProvider>
        <pre>{JSON.stringify(watch(), null, 2)}</pre>
      </Stack>
    );
  },
};

const generateOptions = (count: number): AutocompleteOption[] =>
  Array.from({ length: count }, (_, i) => ({
    label: `Option ${i + 1} — ${crypto.randomUUID().slice(0, 8)}`,
    value: i + 1,
    description: i % 5 === 0 ? `Description for option ${i + 1}` : undefined,
  }));

export const StressTest15K: Story = {
  render: () => {
    const options = useMemo(() => generateOptions(15_000), []);
    const [value, setValue] = useState<AutocompleteOption | null>(null);

    const handleChange = useCallback(
      (next: AutocompleteOption | null) => setValue(next),
      [],
    );

    return (
      <Stack sx={{ maxWidth: 400, gap: 2 }}>
        <Typography
          variant="globalS"
          color="text.secondary"
        >
          Virtualized + filterLimit: {options.length.toLocaleString()} options
        </Typography>
        <Autocomplete
          label="15K Options (Virtualized)"
          placeholder="Search options…"
          options={options}
          value={value}
          onChange={handleChange}
          isServerFiltered={false}
          virtualized
          filterLimit={200}
        />
        {value && (
          <Typography variant="globalXS">
            Selected: {value.label} (value: {value.value})
          </Typography>
        )}
      </Stack>
    );
  },
};

export const StressTest15KMultiple: StoryObj<
  AutocompleteProps<AutocompleteOption, true>
> = {
  render: () => {
    const options = useMemo(() => generateOptions(15_000), []);
    const [value, setValue] = useState<AutocompleteOption[]>([]);

    const handleChange = useCallback(
      (next: AutocompleteOption[]) => setValue(next ?? []),
      [],
    );

    return (
      <Stack sx={{ maxWidth: 500, gap: 2 }}>
        <Typography
          variant="globalS"
          color="text.secondary"
        >
          Multi-select virtualized: {options.length.toLocaleString()} options
        </Typography>
        <Autocomplete<AutocompleteOption, true>
          multiple
          label="15K Options Multi (Virtualized)"
          placeholder="Search options…"
          options={options}
          value={value}
          onChange={handleChange}
          isServerFiltered={false}
          virtualized
          filterLimit={200}
          limitTags={5}
        />
        {value.length > 0 && (
          <Typography variant="globalXS">
            Selected {value.length.toLocaleString()} option(s)
          </Typography>
        )}
      </Stack>
    );
  },
};

export const StressTest15KNoVirtualization: Story = {
  render: () => {
    const options = useMemo(() => generateOptions(15_000), []);
    const [value, setValue] = useState<AutocompleteOption | null>(null);

    const handleChange = useCallback(
      (next: AutocompleteOption | null) => setValue(next),
      [],
    );

    return (
      <Stack sx={{ maxWidth: 400, gap: 2 }}>
        <Typography
          variant="globalS"
          color="text.secondary"
        >
          No virtualization (baseline): {options.length.toLocaleString()}{' '}
          options — WARNING: expect freezing!
        </Typography>
        <Autocomplete
          label="15K Options (No Virtualization)"
          placeholder="Search options…"
          options={options}
          value={value}
          onChange={handleChange}
          isServerFiltered={false}
        />
        {value && (
          <Typography variant="globalXS">
            Selected: {value.label} (value: {value.value})
          </Typography>
        )}
      </Stack>
    );
  },
};
