# Autocomplete

Suggest option values from your own data while someone types a slash command, and read the options they already filled in.

`addChoices` covers a list you know when you deploy, up to Discord's limit of 25. A catalogue you query, or anything that changes while the bot runs, needs the suggestions built per keystroke.

An option offers those once you call `setAutocomplete(true)` on it.

```ts title="src/commands/Search.ts"
import {
    BuilderComponent,
    RegisterCommand
} from '@seedcord/gateway';

@RegisterCommand('global')
export class Search extends BuilderComponent<'command'> {
    constructor() {
        super('command');

        this.instance
            .setName('search')
            .setDescription('Search the catalogue')
            .addStringOption((option) =>
                option
                    .setName('query')
                    .setDescription('What to search')
                    .setRequired(true)
                    .setAutocomplete(true)
            )
            .addIntegerOption((option) =>
                option
                    .setName('limit')
                    .setDescription('How many results')
                    .setAutocomplete(true)
            )
            .addStringOption((option) =>
                option
                    .setName('category')
                    .setDescription('Scope the query')
                    .addChoices(
                        { name: 'Books', value: 'books' },
                        { name: 'Films', value: 'films' }
                    )
            );
    }
}
```

Suggestions arrive as their own interaction, so you write them their own class and decorator. You give it the command's own name, so a mismatch is a compile error.

```ts title="src/handlers/SearchAutocomplete.ts"
@AutocompleteRoute('search')
export class SearchAutocomplete extends AutocompleteHandler<'search'> {
    public async execute(): Promise<void> {
        await this.match({
            query: (value, respond) => respond(this.titles(value)),
            limit: (value, respond) => respond(this.counts(value))
        });
    }

    private titles(
        value: string
    ): { name: string; value: string }[] {
        return TITLES.filter((title) =>
            title.toLowerCase().startsWith(value.toLowerCase())
        ).map((title) => ({ name: title, value: title }));
    }

    private counts(
        value: string
    ): { name: string; value: number }[] {
        const parsed = Number.parseInt(value, 10);

        return Number.isNaN(parsed)
            ? []
            : [{ name: `${parsed}`, value: parsed }];
    }
}
```

Discord sends one of these every time the person edits the field, so `value` is whatever they've typed so far.

## One arm per field

An autocomplete interaction says which field is focused, so every handler branches on it. `this.match` takes one arm per autocompletable field, and an arm you leave out fails to compile.

Each arm receives the partial value and a `respond` typed to that field's choice values.

Typing the opening quote shows the fields that command declared.

```ts
await this.match({ '
```

## One handler for several commands

Name every command in the decorator, and repeat those names in the generic, so the arms reach every autocompletable field across all of them.

`search` completes `query` and `limit`. A second command, `probe`, completes `query`, `count`, and `ratio`. One handler for both needs four arms.

```ts
@AutocompleteRoute('search', 'probe')
export class Catalog extends AutocompleteHandler<
    'search' | 'probe'
> {
    public async execute(): Promise<void> {
        await this.match({
            query: (value, respond) => respond([]),
            limit: (value, respond) => respond([]),
            count: (value, respond) => respond([]),
            ratio: (value, respond) => respond([])
        });
    }
}
```

Both commands complete `query`, so its arm reads `this.route` to tell them apart.

```ts
const command = this.route;
```

## The value is always a string

`limit` is an integer option. Its partial still arrives as a string.

```ts
await this.match({
    query: (value, respond) => respond([]),
    limit: (value, respond) => respond([])
});
```

Discord sends the raw text, so someone typing `120` sends `'1'`, then `'12'`, then `'120'`. Parse it yourself and guard the result, since an empty field sends an empty string.

What you send back carries the real type. `limit` takes `{ value: number }`.

## Reading the other options

`this.options` holds what the person has already filled in, which is how a suggestion narrows to their earlier answers.

```ts
const category = this.options.getString('category');
```

`category` comes back nullable, along with every other read here, since the person might still be on an earlier option.

Discord resolves these option kinds mid-type, so `getString`, `getInteger`, `getNumber`, and `getBoolean` are the only getters you get here.
