# Autocomplete

Suggest option values while someone types, so they pick from your data rather than guessing. Covers one arm per field, serving several commands from one handler, the string value you always receive, and reading 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.
