import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { fn, type Mock } from 'storybook/test';
import { Calendar, ChevronDown } from '@transferwise/icons';
import { Flag } from '@wise/art';
import { clsx } from 'clsx';
import { useState } from 'react';

import {
  createSandboxStory,
  globalScope,
} from '../../../../.storybook/components/sandbox/SandboxEditor';
import Button from '../../../button';
import { getMonthNames } from '../../../common/dateUtils';
import Drawer from '../../../drawer';
import { Field } from '../../../field/Field';
import Modal from '../../../modal';
import {
  SelectInput,
  type SelectInputItem,
  SelectInputOptionContent,
  type SelectInputProps,
  SelectInputTriggerButton,
} from '..';

/**
 * A searchable, accessible combobox for selecting a single or multiple values.
 * Supports flat and grouped items, optional filtering, custom triggers, a clear button,
 * and a custom footer. Renders a popover on desktop and a bottom sheet on mobile.
 * Lists with 50+ items are automatically virtualised.
 */
const meta = {
  title: 'Forms/SelectInput',
  component: SelectInput,
  args: {
    onFilterChange: fn() satisfies Mock,
    onChange: fn() satisfies Mock,
    onClose: fn() satisfies Mock,
    onOpen: fn() satisfies Mock,
  },
  argTypes: {
    parentId: {
      table: { category: 'WDS internal' },
    },
    renderTrigger: {
      table: {
        defaultValue: { summary: 'Default trigger (InputGroup + ButtonInput)' },
      },
    },
    // Events
    onClear: {
      table: { category: 'Events' },
    },
    onFilterChange: {
      table: { category: 'Events' },
    },
    onChange: {
      table: { category: 'Events' },
    },
    onClose: {
      table: { category: 'Events' },
    },
    onOpen: {
      table: { category: 'Events' },
    },
  },
  parameters: {
    actions: { argTypesRegex: '' },
  },
} satisfies Meta<typeof SelectInput>;
export default meta;

type Story<T, M extends boolean = false> = StoryObj<SelectInputProps<T, M>>;

interface Month {
  id: number;
  name: string;
}

const months: Month[] = getMonthNames('en-US').map((name, index) => ({
  id: index + 1,
  name,
}));

interface Currency {
  code: string;
  name: string;
  countries?: string[];
}

const popularCurrencies: Currency[] = [
  {
    code: 'USD',
    name: 'United States Dollar',
    countries: ['Hong Kong', 'Saudi Arabia'],
  },
  {
    code: 'EUR',
    name: 'Euro',
    countries: ['Spain', 'Germany', 'France', 'Austria', 'Estonia'],
  },
  {
    code: 'GBP',
    name: 'British pound',
    countries: ['England', 'Scotland', 'Wales'],
  },
];

const otherCurrencies: Currency[] = [
  {
    code: 'CAD',
    name: 'Canadian dollar',
    countries: ['Canada'],
  },
  {
    code: 'AUD',
    name: 'Australian dollar',
  },
  {
    code: 'JPY',
    name: 'Japanese yen',
    countries: ['Japan'],
  },
];

const allCurrencies: Currency[] = [...popularCurrencies, ...otherCurrencies].sort((a, b) =>
  a.code.localeCompare(b.code),
);

function currencyOption(currency: Currency) {
  return {
    type: 'option',
    value: currency,
    filterMatchers: [currency.code, currency.name, ...(currency.countries ?? [])],
  } satisfies SelectInputItem<Currency>;
}

const CurrenciesArgs = {
  items: [
    {
      type: 'group',
      label: 'Popular currencies',
      options: popularCurrencies.map((currency) => currencyOption(currency)),
    },
    {
      type: 'group',
      label: 'All currencies',
      options: allCurrencies.map((currency) => currencyOption(currency)),
    },
  ],
  defaultValue: popularCurrencies[0],
  renderValue: (currency, withinTrigger) => (
    <SelectInputOptionContent
      title={currency.code}
      note={withinTrigger ? undefined : currency.name}
      icon={<Flag code={currency.code} intrinsicSize={24} />}
    />
  ),
  renderFooter: ({ resultsEmpty, queryNormalized }) =>
    resultsEmpty && queryNormalized != null && /^[a-z]{3}$/u.test(queryNormalized) ? (
      <>
        It is not possible to use {queryNormalized.toUpperCase()} yet.{' '}
        <a href="#_" className="np-text-link-default" onClick={(event) => event.preventDefault()}>
          Email me when it is available.
        </a>
      </>
    ) : (
      <>
        Cannot find it?{' '}
        <a href="#_" className="np-text-link-default" onClick={(event) => event.preventDefault()}>
          Request the currency you need,
        </a>{' '}
        and we will notify you once it is available.
      </>
    ),
  filterable: true,
  filterPlaceholder: 'Type a currency / country',
  size: 'lg',
} satisfies Story<Currency>['args'];

const previewArgGroup = {
  table: { category: 'Storybook Preview', type: { summary: undefined } },
};

type PlaygroundArgs = {
  size?: 'sm' | 'md' | 'lg';
  filterable?: boolean;
  filterPlaceholder?: string;
  disabled?: boolean;
  placeholder?: string;
  multiple?: boolean;
  previewWithGroups: boolean;
  previewClearable: boolean;
  previewWithFooter: boolean;
};

export const Playground: StoryObj<PlaygroundArgs> = {
  argTypes: {
    previewWithGroups: {
      name: 'Preview with groups',
      control: 'boolean',
      ...previewArgGroup,
    },
    previewClearable: {
      name: 'Preview with clear button',
      control: 'boolean',
      ...previewArgGroup,
    },
    previewWithFooter: {
      name: 'Preview with footer',
      control: 'boolean',
      ...previewArgGroup,
    },
  },
  args: {
    disabled: false,
    multiple: false,
    filterable: true,
    filterPlaceholder: 'Type a currency / country',
    placeholder: 'Select a currency',
    size: 'lg',
    previewWithGroups: true,
    previewClearable: true,
    previewWithFooter: true,
  },
  render: function Render({
    previewWithGroups,
    previewClearable,
    previewWithFooter,
    multiple,
    filterPlaceholder,
    ...args
  }) {
    const [singleValue, setSingleValue] = useState<Currency | undefined>(undefined);
    const [multiValue, setMultiValue] = useState<Currency[]>([]);

    const items: SelectInputItem<Currency>[] = previewWithGroups
      ? [
          {
            type: 'group',
            label: 'Popular currencies',
            options: popularCurrencies.map(currencyOption),
          },
          { type: 'group', label: 'All currencies', options: allCurrencies.map(currencyOption) },
        ]
      : allCurrencies.map(currencyOption);

    const renderValue = (currency: Currency, withinTrigger: boolean) => {
      if (multiple && withinTrigger) return currency.code;
      return (
        <SelectInputOptionContent
          title={currency.code}
          note={withinTrigger ? undefined : currency.name}
          icon={<Flag code={currency.code} intrinsicSize={24} />}
        />
      );
    };

    const renderFooter = previewWithFooter
      ? ({
          resultsEmpty,
          queryNormalized,
        }: {
          resultsEmpty: boolean;
          queryNormalized: string | null | undefined;
        }) =>
          resultsEmpty && queryNormalized != null && /^[a-z]{3}$/u.test(queryNormalized) ? (
            <>It is not possible to use {queryNormalized.toUpperCase()} yet.</>
          ) : (
            <>
              Cannot find it?{' '}
              <a href="#_" className="np-text-link-default" onClick={(e) => e.preventDefault()}>
                Request the currency
              </a>
            </>
          )
      : undefined;

    if (multiple) {
      return (
        <SelectInput<Currency, true>
          {...(args as SelectInputProps<Currency, true>)}
          multiple
          items={items}
          value={multiValue}
          renderValue={renderValue}
          renderFooter={renderFooter}
          filterPlaceholder={filterPlaceholder}
          onClear={previewClearable ? () => setMultiValue([]) : undefined}
          onChange={setMultiValue}
        />
      );
    }

    return (
      <SelectInput<Currency>
        {...(args as SelectInputProps<Currency>)}
        items={items}
        value={singleValue}
        renderValue={renderValue}
        renderFooter={renderFooter}
        filterPlaceholder={filterPlaceholder}
        onClear={previewClearable ? () => setSingleValue(undefined) : undefined}
        onChange={setSingleValue}
      />
    );
  },
  parameters: {
    docs: {
      canvas: { sourceState: 'hidden' },
    },
  },
};

export const Sandbox = createSandboxStory({
  code: `const App = () => {
  const months = [
    { id: 1, name: 'January' },
    { id: 2, name: 'February' },
    { id: 3, name: 'March' },
    { id: 4, name: 'April' },
    { id: 5, name: 'May' },
    { id: 6, name: 'June' },
  ];

  const [selected, setSelected] = React.useState(undefined);

  return (
    <Field label="Select a month">
      <SelectInput
        size="lg"
        filterable
        filterPlaceholder="Type a month name"
        placeholder="Choose a month"
        items={months.map((month) => ({
          type: 'option',
          value: month,
          filterMatchers: [month.name],
        }))}
        renderValue={(month) => (
          <SelectInputOptionContent title={month.name} />
        )}
        value={selected}
        onChange={(month) => setSelected(month)}
        onClear={() => setSelected(undefined)}
      />
    </Field>
  );
};`,
  scope: globalScope,
});

/**
 * The simplest usage: a flat list of options with no icons or grouping.
 * `renderValue` controls what is shown in both the trigger and each dropdown row.
 */
export const Basic: Story<Month | null> = {
  args: {
    placeholder: 'Month',
    items: months.map((month) => ({
      type: 'option',
      value: month,
    })),
    renderValue: (month) => <SelectInputOptionContent title={month.name} />,
  },
  render: function Render({ onChange, onClear, ...args }) {
    const [selectedMonth, setSelectedMonth] = useState<Month | null>(null);

    return (
      <SelectInput
        {...args}
        value={selectedMonth}
        onChange={(month) => {
          setSelectedMonth(month);
          onChange?.(month);
        }}
        onClear={() => {
          setSelectedMonth(null);
          onClear?.();
        }}
      />
    );
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  placeholder="Month"
  items={[
    { type: 'option', value: { id: 1, name: 'January' } },
    { type: 'option', value: { id: 2, name: 'February' } },
    // ... 10 more months
  ]}
  renderValue={(month) => <SelectInputOptionContent title={month.name} />}
  value={selectedMonth}
  onChange={setSelectedMonth}
/>`,
      },
    },
  },
};

/**
 * A real-world currency selector with grouped items, flag icons, filterable search,
 * and a custom footer. `renderValue` receives a `withinTrigger` boolean to render
 * a compact version inside the trigger/selected state (code only, no full currency name).
 */
export const Grouping: Story<Currency> = {
  args: CurrenciesArgs,
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  filterable
  filterPlaceholder="Type a currency / country"
  size="lg"
  items={[
    {
      type: 'group',
      label: 'Popular currencies',
      options: popularCurrencies.map((c) => ({
        type: 'option',
        value: c,
        filterMatchers: [c.code, c.name, ...(c.countries ?? [])],
      })),
    },
    // ... more groups
  ]}
  defaultValue={popularCurrencies[0]}
  renderValue={(currency, withinTrigger) => (
    <SelectInputOptionContent
      title={currency.code}
      note={withinTrigger ? undefined : currency.name}
      icon={<Flag code={currency.code} intrinsicSize={24} />}
    />
  )}
  renderFooter={({ resultsEmpty }) =>
    resultsEmpty ? (
      <>Cannot find it? <a>Request the currency you need.</a></>
    ) : null
  }
/>`,
      },
    },
  },
};

/**
 * When `multiple` is `true`, `value` and `onChange` operate on arrays. Use the
 * `withinTrigger` argument in `renderValue` to show a compact summary inside the
 * trigger (e.g. currency codes joined by commas).
 */
export const MultiSelect: Story<Currency, true> = {
  argTypes: {
    multiple: { table: { disable: true } },
  },
  args: {
    ...CurrenciesArgs,
    multiple: true,
    placeholder: 'Choose currencies…',
    defaultValue: [popularCurrencies[0]],
    renderValue: (currency, withinTrigger) =>
      withinTrigger ? (
        currency.code
      ) : (
        <SelectInputOptionContent
          title={currency.code}
          note={currency.name}
          icon={<Flag code={currency.code} intrinsicSize={24} />}
        />
      ),
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  multiple
  filterable
  placeholder="Choose currencies…"
  items={[
    { type: 'option', value: { code: 'USD', name: 'United States Dollar' }, filterMatchers: ['USD', 'United States Dollar'] },
    // ... more options
  ]}
  defaultValue={[currencies[0]]}
  renderValue={(currency, withinTrigger) =>
    withinTrigger
      ? currency.code
      : (
        <SelectInputOptionContent
          title={currency.code}
          note={currency.name}
          icon={<Flag code={currency.code} intrinsicSize={24} />}
        />
      )
  }
/>`,
      },
    },
  },
};

/**
 * Groups can have an `action` button (e.g. "Select all" / "Deselect all") rendered
 * next to the group heading. The label should update dynamically based on whether
 * all items in that group are selected.
 */
export const WithGroupActions: Story<Currency, true> = {
  argTypes: {
    multiple: { table: { disable: true } },
  },
  args: {
    ...MultiSelect.args,
  },
  render: function Render(args) {
    const [selectedItems, setSelectedItems] = useState<Currency[]>([]);

    const allSelected = (items: Currency[]) => {
      const selectedSet = new Set(selectedItems);
      return items.every((item) => selectedSet.has(item));
    };

    const toggleItems = (items: Currency[]) => {
      setSelectedItems((currentItems) =>
        allSelected(items)
          ? currentItems.filter((item) => !items.includes(item))
          : [...new Set([...currentItems, ...items])],
      );
    };

    return (
      <SelectInput
        {...args}
        items={[
          {
            type: 'group',
            label: 'Popular currencies',
            options: popularCurrencies.map((currency) => currencyOption(currency)),
            action: {
              label: allSelected(popularCurrencies) ? 'Deselect all' : 'Select all',
              onClick: () => {
                toggleItems(popularCurrencies);
              },
            },
          },
          {
            type: 'group',
            label: 'Other currencies',
            options: otherCurrencies.map((currency) => currencyOption(currency)),
            action: {
              label: allSelected(otherCurrencies) ? 'Deselect all' : 'Select all',
              onClick: () => {
                toggleItems(otherCurrencies);
              },
            },
          },
        ]}
        value={selectedItems}
        onChange={(currencies) => {
          setSelectedItems(currencies);
        }}
        onClear={() => {
          setSelectedItems([]);
        }}
      />
    );
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  multiple
  items={[
    {
      type: 'group',
      label: 'Popular currencies',
      options: popularCurrencies.map(currencyOption),
      action: {
        label: allSelected(popularCurrencies) ? 'Deselect all' : 'Select all',
        onClick: () => toggleItems(popularCurrencies),
      },
    },
    // ... more groups
  ]}
  value={selectedItems}
  onChange={setSelectedItems}
  onClear={() => setSelectedItems([])}
/>`,
      },
    },
  },
};

/** Pass `onClear` to show a clear button when a value is selected. */
export const WithClear: Story<Currency> = {
  argTypes: {
    onClear: { table: { disable: true } },
  },
  args: {
    ...CurrenciesArgs,
    onClear: fn() satisfies Mock,
  },
  render: function Render({ onChange, onClear, defaultValue: _dv, ...args }) {
    const [value, setValue] = useState<Currency | undefined>(popularCurrencies[0]);
    return (
      <SelectInput
        {...args}
        value={value}
        onChange={(currency) => {
          setValue(currency);
          onChange?.(currency);
        }}
        onClear={() => {
          setValue(undefined);
          onClear?.();
        }}
      />
    );
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  filterable
  size="lg"
  items={[
    { type: 'option', value: { code: 'USD', name: 'United States Dollar' }, filterMatchers: ['USD', 'United States Dollar'] },
    // ... more options
  ]}
  defaultValue={currencies[0]}
  renderValue={(currency) => (
    <SelectInputOptionContent
      title={currency.code}
      icon={<Flag code={currency.code} intrinsicSize={24} />}
    />
  )}
  onClear={() => setValue(undefined)}
/>`,
      },
    },
  },
};

/**
 * Use `renderTrigger` with `SelectInputTriggerButton` to fully customise the trigger
 * appearance. The interactive element **must** be `SelectInputTriggerButton` to receive
 * the correct ARIA attributes (`aria-expanded`, `aria-haspopup`, `aria-controls`).
 */
export const CustomTrigger: Story<Month> = {
  args: {
    placeholder: 'Month',
    items: months.map((month) => ({
      type: 'option',
      value: month,
    })),
    renderValue: (month, withinTrigger) =>
      withinTrigger ? month.name : <SelectInputOptionContent title={month.name} />,
    renderTrigger: ({ content, className }) => (
      <SelectInputTriggerButton
        className={clsx(className, 'btn-unstyled np-text-link-large align-items-center')}
        style={{ display: 'inline-flex', columnGap: '0.25rem' }}
      >
        {content}
        <ChevronDown size={16} />
      </SelectInputTriggerButton>
    ),
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  placeholder="Month"
  items={monthItems}
  renderValue={(month, withinTrigger) =>
    withinTrigger ? month.name : <SelectInputOptionContent title={month.name} />
  }
  renderTrigger={({ content, className }) => (
    <SelectInputTriggerButton
      className={clsx(className, 'btn-unstyled np-text-link-large align-items-center')}
      style={{ display: 'inline-flex', columnGap: '0.25rem' }}
    >
      {content}
      <ChevronDown size={16} />
    </SelectInputTriggerButton>
  )}
/>`,
      },
    },
  },
};

/**
 * Combines grouped items, `separator` items, disabled options, and filterable search.
 * `SelectInputOptionContent` supports `title`, `note` (inline), and
 * `description` (below-title) for rich two-line option layouts.
 */
export const Advanced: Story<Month> = {
  args: {
    placeholder: 'Month',
    items: [months.slice(0, 3), months.slice(3, 6), months.slice(6, 9), months.slice(9, 12)]
      .flatMap<SelectInputItem<Month>>((quarterMonths, quarterIndex) => [
        {
          type: 'group',
          label: `Quarter #${quarterIndex + 1}`,
          options: quarterMonths.map((month, monthIndex) => ({
            type: 'option',
            value: month,
            filterMatchers: [month.name],
            disabled: monthIndex % 6 === 2,
          })),
        },
        { type: 'separator' },
      ])
      .slice(0, -1),
    renderValue: (month) => (
      <SelectInputOptionContent
        title={month.name}
        note="Note"
        description={`Month #${month.id}`}
        icon={<Calendar size={24} />}
      />
    ),
    filterable: true,
    filterPlaceholder: "Type a month's name",
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  filterable
  filterPlaceholder="Type a month name"
  items={[
    {
      type: 'group',
      label: 'Quarter #1',
      options: [
        { type: 'option', value: jan, filterMatchers: ['January'] },
        { type: 'option', value: feb, filterMatchers: ['February'] },
        { type: 'option', value: mar, filterMatchers: ['March'], disabled: true },
      ],
    },
    { type: 'separator' },
    // ... more groups
  ]}
  renderValue={(month) => (
    <SelectInputOptionContent
      title={month.name}
      note="Note"
      description={\`Month #\${month.id}\`}
      icon={<Calendar size={24} />}
    />
  )}
/>`,
      },
    },
  },
};

/**
 * Lists with 50+ items are automatically virtualised using a windowed renderer.
 * This example renders 1,000 numbered options with multi-select enabled.
 */
export const Virtualization: Story<string, true> = {
  args: {
    multiple: true,
    items: Array.from({ length: 1000 }, (_, index) => ({
      type: 'option',
      value: String(index + 1),
    })),
    renderValue: (value, withinTrigger) =>
      withinTrigger ? (
        value
      ) : (
        <SelectInputOptionContent
          title={value}
          description={Number(value) % 10 === 0 ? 'Divisible by 10' : undefined}
        />
      ),
    filterable: true,
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  multiple
  filterable
  items={Array.from({ length: 1000 }, (_, i) => ({
    type: 'option',
    value: String(i + 1),
  }))}
  renderValue={(value, withinTrigger) =>
    withinTrigger ? value : <SelectInputOptionContent title={value} />
  }
/>`,
      },
    },
  },
};

/** The `disabled` prop prevents all interaction. */
export const Disabled: Story<Currency> = {
  argTypes: {
    disabled: { table: { disable: true } },
    id: { table: { disable: true } },
    name: { table: { disable: true } },
    multiple: { table: { disable: true } },
    placeholder: { table: { disable: true } },
    items: { table: { disable: true } },
    autocomplete: { table: { disable: true } },
    defaultValue: { table: { disable: true } },
    value: { table: { disable: true } },
    compareValues: { table: { disable: true } },
    renderValue: { table: { disable: true } },
    renderFooter: { table: { disable: true } },
    renderTrigger: { table: { disable: true } },
    filterable: { table: { disable: true } },
    filterPlaceholder: { table: { disable: true } },
    sortFilteredOptions: { table: { disable: true } },
    className: { table: { disable: true } },
    UNSAFE_triggerButtonProps: { table: { disable: true } },
    triggerRef: { table: { disable: true } },
    parentId: { table: { disable: true } },
    onFilterChange: { table: { disable: true } },
    onChange: { table: { disable: true } },
    onClose: { table: { disable: true } },
    onOpen: { table: { disable: true } },
    onClear: { table: { disable: true } },
  },
  args: {
    ...CurrenciesArgs,
    disabled: true,
  },
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  disabled
  filterable
  size="lg"
  items={currencyItems}
  defaultValue={currencies[0]}
  renderValue={(currency) => (
    <SelectInputOptionContent
      title={currency.code}
      icon={<Flag code={currency.code} intrinsicSize={24} />}
    />
  )}
/>`,
      },
    },
  },
};

/** Three size variants: `sm`, `md`, and `lg`. */
export const Sizes: Story<Currency> = {
  argTypes: {
    size: { table: { disable: true } },
  },
  render: (args) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', maxWidth: '400px' }}>
      <SelectInput<Currency>
        {...args}
        size="sm"
        placeholder="Small"
        items={popularCurrencies.map(currencyOption)}
        renderValue={(c) => <SelectInputOptionContent title={c.code} note={c.name} />}
      />
      <SelectInput<Currency>
        {...args}
        size="md"
        placeholder="Medium"
        items={popularCurrencies.map(currencyOption)}
        renderValue={(c) => <SelectInputOptionContent title={c.code} note={c.name} />}
      />
      <SelectInput<Currency>
        {...args}
        size="lg"
        placeholder="Large (default)"
        items={popularCurrencies.map(currencyOption)}
        renderValue={(c) => <SelectInputOptionContent title={c.code} note={c.name} />}
      />
    </div>
  ),
  parameters: {
    docs: {
      source: {
        code: `<SelectInput size="sm" placeholder="Small" items={currencyItems} renderValue={renderCurrency} />
<SelectInput size="md" placeholder="Medium" items={currencyItems} renderValue={renderCurrency} />
<SelectInput size="lg" placeholder="Large" items={currencyItems} renderValue={renderCurrency} />`,
      },
    },
  },
};

/**
 * "Commit on close" pattern: stage selections locally and apply them only when the
 * dropdown closes. Useful when each selection change is expensive (e.g. an API call)
 * or when the UX requires confirmation before updating external state.
 */
export const WithCommitOnClose: Story<Currency, true> = {
  argTypes: {
    onClose: { table: { disable: true } },
  },
  render: function Render(args) {
    const [committedValues, setCommittedValues] = useState<Currency[]>([popularCurrencies[0]]);
    const [stagedValues, setStagedValues] = useState<Currency[]>([popularCurrencies[0]]);

    return (
      <div>
        <p className="m-t-2 np-text-body-small">
          Applied: {committedValues.map((c) => c.code).join(', ') || 'none'}
        </p>
        <SelectInput<Currency, true>
          {...args}
          multiple
          filterable
          filterPlaceholder="Type a currency / country"
          size="lg"
          placeholder="Choose currencies…"
          items={[
            {
              type: 'group',
              label: 'Popular currencies',
              options: popularCurrencies.map(currencyOption),
            },
            {
              type: 'group',
              label: 'All currencies',
              options: allCurrencies.map(currencyOption),
            },
          ]}
          renderValue={(currency, withinTrigger) =>
            withinTrigger ? (
              currency.code
            ) : (
              <SelectInputOptionContent
                title={currency.code}
                note={currency.name}
                icon={<Flag code={currency.code} intrinsicSize={24} />}
              />
            )
          }
          value={stagedValues}
          onChange={setStagedValues}
          onClose={() => setCommittedValues(stagedValues)}
          onClear={() => {
            setStagedValues([]);
            setCommittedValues([]);
          }}
        />
      </div>
    );
  },
  parameters: {
    docs: {
      source: {
        code: `const [committedValues, setCommittedValues] = useState([]);
const [stagedValues, setStagedValues] = useState([]);

<SelectInput
  multiple
  value={stagedValues}
  onChange={setStagedValues}
  onClose={() => setCommittedValues(stagedValues)}
  onClear={() => { setStagedValues([]); setCommittedValues([]); }}
  // ...items, renderValue
/>`,
      },
    },
  },
};

/**
 * Wrap with `Field` to inherit error state and label association automatically.
 * `Field` injects `aria-describedby` on the trigger pointing to the validation
 * message — no manual wiring required.
 */
export const WithinField: Story<Month> = {
  args: {
    placeholder: "Today's Month",
    items: months.map((month) => ({
      type: 'option',
      value: month,
    })),
    renderValue: (month) => <SelectInputOptionContent title={month.name} />,
  },
  render: function Render({ onChange, ...args }) {
    const currentMonthIndex = new Date().getMonth();
    const defaultMonth = months[currentMonthIndex === 0 ? 2 : 0];
    const [selectedMonth, setSelectedMonth] = useState<Month>(defaultMonth);
    const isCorrect = selectedMonth.id === currentMonthIndex + 1;

    return (
      <Field
        label="Today's Month"
        message={isCorrect ? undefined : 'Please select the current month'}
        sentiment={isCorrect ? undefined : 'negative'}
      >
        <SelectInput
          {...args}
          value={selectedMonth}
          onChange={(month) => {
            setSelectedMonth(month);
            onChange?.(month);
          }}
        />
      </Field>
    );
  },
  parameters: {
    docs: {
      source: {
        code: `const isCorrect = selectedMonth.id === currentMonthIndex + 1;

<Field
  label="Today's Month"
  message={isCorrect ? undefined : 'Please select the current month'}
  sentiment={isCorrect ? undefined : 'negative'}
>
  <SelectInput
    placeholder="Today's Month"
    items={monthItems}
    renderValue={(month) => <SelectInputOptionContent title={month.name} />}
    value={selectedMonth}
    onChange={setSelectedMonth}
  />
</Field>`,
      },
    },
  },
};

export const WithinDrawer: Story<Currency> = {
  args: CurrenciesArgs,
  decorators: [
    (Story, context) => {
      const [open, setOpen] = useState(context.viewMode === 'story');
      return (
        <>
          <Button v2 onClick={() => setOpen(true)}>
            Open drawer
          </Button>
          <Drawer
            open={open}
            headerTitle="This is the title of the drawer"
            onClose={() => setOpen(false)}
          >
            <Story />
          </Drawer>
        </>
      );
    },
  ],
  parameters: {
    docs: {
      source: {
        code: `<Drawer open={open} onClose={() => setOpen(false)}>
  <SelectInput
    filterable
    filterPlaceholder="Type a currency / country"
    size="lg"
    items={currencyItems}
    renderValue={(currency, withinTrigger) => (
      <SelectInputOptionContent
        title={currency.code}
        note={withinTrigger ? undefined : currency.name}
        icon={<Flag code={currency.code} intrinsicSize={24} />}
      />
    )}
    onChange={setSelectedCurrency}
  />
</Drawer>`,
      },
    },
  },
};

export const WithinModal: Story<Currency> = {
  args: CurrenciesArgs,
  decorators: [
    (Story, context) => {
      const [open, setOpen] = useState(context.viewMode === 'story');
      return (
        <>
          <Button v2 onClick={() => setOpen(true)}>
            Open modal
          </Button>
          <Modal
            title="Select a currency"
            open={open}
            body={<Story />}
            onClose={() => setOpen(false)}
          />
        </>
      );
    },
  ],
  parameters: {
    docs: {
      source: {
        code: `<Modal
  open={open}
  onClose={() => setOpen(false)}
  body={
    <SelectInput
      filterable
      filterPlaceholder="Type a currency / country"
      size="lg"
      items={currencyItems}
      renderValue={(currency, withinTrigger) => (
        <SelectInputOptionContent
          title={currency.code}
          note={withinTrigger ? undefined : currency.name}
          icon={<Flag code={currency.code} intrinsicSize={24} />}
        />
      )}
      onChange={setSelectedCurrency}
    />
  }
/>`,
      },
    },
  },
};

interface Country {
  code: string;
  name: string;
}

const countries: Country[] = [
  { code: 'US', name: 'United States' },
  { code: 'GB', name: 'United Kingdom' },
  { code: 'CA', name: 'Canada' },
  { code: 'AU', name: 'Australia' },
  { code: 'DE', name: 'Germany' },
  { code: 'FR', name: 'France' },
  { code: 'JP', name: 'Japan' },
  { code: 'BR', name: 'Brazil' },
  { code: 'IN', name: 'India' },
  { code: 'CN', name: 'China' },
  { code: 'IT', name: 'Italy' },
  { code: 'ES', name: 'Spain' },
  { code: 'NL', name: 'Netherlands' },
  { code: 'CH', name: 'Switzerland' },
  { code: 'SE', name: 'Sweden' },
  { code: 'MX', name: 'Mexico' },
];

function countryOption(country: Country) {
  return {
    type: 'option',
    value: country.code,
    filterMatchers: [country.code, country.name],
  } satisfies SelectInputItem;
}

/**
 * Use `name` and `autocomplete` to integrate with the browser's native autofill.
 * The component renders a hidden `<input>` that carries the selected value,
 * making it submittable inside a `<form>` and detectable by password managers.
 */
export const WithAutocomplete: Story<string> = {
  args: {
    name: 'country',
    autocomplete: 'country-name',
    placeholder: 'Select your country',
    items: countries.map(countryOption),
    renderValue: (countryCode, withinTrigger) => {
      const country = countries.find((c) => c.code === countryCode);
      return (
        <SelectInputOptionContent
          title={withinTrigger ? countryCode : country?.name || countryCode}
          note={withinTrigger ? undefined : countryCode}
          icon={<Flag code={countryCode} intrinsicSize={24} />}
        />
      );
    },
    filterable: true,
    filterPlaceholder: 'Type a country name',
    size: 'lg',
  },
  render: function Render({ onChange, onClear, ...args }) {
    const [selectedCountry, setSelectedCountry] = useState<string | undefined>(undefined);

    return (
      <form method="post" onSubmit={(e) => e.preventDefault()}>
        <Field label="Country">
          <SelectInput
            {...args}
            id="country-select"
            value={selectedCountry}
            onChange={(country) => {
              setSelectedCountry(country);
              onChange?.(country);
            }}
            onClear={() => {
              setSelectedCountry(undefined);
              onClear?.();
            }}
          />
        </Field>
        <Button type="submit" v2 className="m-t-2" data-testid="submit-btn">
          Submit Form
        </Button>
      </form>
    );
  },
  parameters: {
    docs: {
      source: {
        code: `<form method="post">
  <Field label="Country">
    <SelectInput
      name="country"
      autocomplete="country-name"
      filterable
      filterPlaceholder="Type a country name"
      size="lg"
      items={[
        { type: 'option', value: 'US', filterMatchers: ['US', 'United States'] },
        { type: 'option', value: 'GB', filterMatchers: ['GB', 'United Kingdom'] },
        // ... more countries
      ]}
      renderValue={(code, withinTrigger) => (
        <SelectInputOptionContent
          title={withinTrigger ? code : countryName(code)}
          note={withinTrigger ? undefined : code}
          icon={<Flag code={code} intrinsicSize={24} />}
        />
      )}
      value={selectedCountry}
      onChange={setSelectedCountry}
    />
  </Field>
</form>`,
      },
    },
  },
};

interface CountryWithCurrency extends Country {
  keywords: string[];
}

const countriesWithCurrency: CountryWithCurrency[] = [
  { code: 'AD', name: 'Andorra', keywords: ['united states dollar'] },
  { code: 'DE', name: 'Germany', keywords: ['EUR'] },
  { code: 'NR', name: 'New United Republic', keywords: ['NUR'] },
  { code: 'US', name: 'United States', keywords: ['USD'] },
  { code: 'ZM', name: 'Zambia', keywords: ['USD', 'united states dollar'] },
];

function countryWithCurrencyOption(country: CountryWithCurrency) {
  return {
    type: 'option',
    value: country,
    filterMatchers: [country.name, country.code, ...country.keywords],
  } satisfies SelectInputItem<CountryWithCurrency>;
}

export const WithBuiltInSearchResultSorting: Story<CountryWithCurrency> = {
  args: {
    items: countriesWithCurrency.map(countryWithCurrencyOption),
    compareValues: 'code',
    renderValue: (country) => (
      <SelectInputOptionContent
        title={country.name}
        icon={<Flag code={country.code} intrinsicSize={24} />}
      />
    ),
    value: countriesWithCurrency[0],
    filterable: true,
    filterPlaceholder: 'Type a currency / country',
    sortFilteredOptions: SelectInput.sortByRelevance((value) => value.name),
    size: 'lg',
  } satisfies Story<CountryWithCurrency>['args'],
  decorators: [
    (Story) => (
      <div>
        <p className="m-b-3 np-text-body-default" style={{ maxWidth: '600px' }}>
          This example uses the built-in <code>SelectInput.sortByRelevance</code> helper to sort
          filtered results by relevance (You can implement your own). This one prioritises: exact
          matches → starts with → contains → alphabetical.
          <br />
          <br />
          Try searching for &quot;united&quot; to see the sorting tiers in action:
          <br />
          1. <strong>United States</strong> — name starts with &quot;United&quot;
          <br />
          2. <strong>New United Republic</strong> — name contains &quot;United&quot;
          <br />
          3. <strong>Andorra, Zambia</strong> — match only via keywords
        </p>
        <Story />
      </div>
    ),
  ],
  parameters: {
    docs: {
      source: {
        code: `<SelectInput
  filterable
  filterPlaceholder="Type a currency / country"
  sortFilteredOptions={SelectInput.sortByRelevance((value) => value.name)}
  items={countries.map((country) => ({
    type: 'option',
    value: country,
    filterMatchers: [country.name, country.code, ...country.keywords],
  }))}
  renderValue={(country) => (
    <SelectInputOptionContent
      title={country.name}
      icon={<Flag code={country.code} />}
    />
  )}
/>`,
      },
    },
  },
};
