# Creating a Provider

Providers live in `external/<name>/` (e.g. `external/jellyfin/`). A provider is
a Go package that implements the base `playlist.Provider` interface and
optionally implements capability interfaces from the `provider/` package. The UI
discovers capabilities at runtime via type assertions and enables features
accordingly.

See the existing providers for reference:
- `external/navidrome/`: Subsonic API, browsing, scrobbling
- `external/plex/`: Plex Media Server, search, album tracks
- `external/spotify/`: Spotify, search, playlist management, custom streaming
- `external/radio/`: internet radio, favorites
- `external/local/`: local TOML playlist files

## Base Interface (required)

Every provider must implement `playlist.Provider`:

```go
type Provider interface {
    Name() string
    Playlists() ([]playlist.PlaylistInfo, error)
    Tracks(playlistID string) ([]Track, error)
}
```

This gives the provider a name, a list of playlists, and the ability to return
tracks for a playlist. That's enough for basic playback.

## Capability Interfaces (optional)

Implement any combination of these to unlock additional UI features. All
interfaces are defined in `provider/interfaces.go`.

| Interface | What it enables | Methods |
|---|---|---|
| `Searcher` | Track search overlay | `SearchTracks(ctx, query, limit)` |
| `ArtistBrowser` | Hierarchical artist browsing | `Artists()`, `ArtistAlbums(id)` |
| `AlbumBrowser` | Paginated album browsing with sort | `AlbumList(sort, offset, size)`, `AlbumSortTypes()` |
| `AlbumTrackLoader` | Album track listing | `AlbumTracks(albumID)` |
| `Scrobbler` | Playback reporting | `Scrobble(track, submission)` |
| `PlaylistWriter` | Add track to playlist | `AddTrackToPlaylist(ctx, playlistID, track)` |
| `PlaylistCreator` | Create new playlist | `CreatePlaylist(ctx, name)` |
| `PlaylistDeleter` | Remove playlists/tracks | `DeletePlaylist(name)`, `RemoveTrack(name, index)` |
| `CustomStreamer` | Custom URI decode pipeline | `URISchemes()`, `NewStreamer(uri)` |
| `FavoriteToggler` | Favorite toggling | `ToggleFavorite(id)` |
| `Closer` | Cleanup on shutdown | `Close()` |
| `Authenticator` | Interactive sign-in flow | `Authenticate() error` (in `playlist` package) |

## Steps

### 1. Create the package

Create `external/<name>/provider.go`:

```go
package jellyfin

import (
    "context"

    "github.com/bjarneo/cliamp/playlist"
    "github.com/bjarneo/cliamp/provider"
)

// Compile-time interface checks.
var (
    _ provider.Searcher         = (*Provider)(nil)
    _ provider.AlbumTrackLoader = (*Provider)(nil)
)

type Provider struct {
    baseURL string
    token   string
}

func New(baseURL, token string) *Provider {
    return &Provider{baseURL: baseURL, token: token}
}

func (p *Provider) Name() string { return "Jellyfin" }

func (p *Provider) Playlists() ([]playlist.PlaylistInfo, error) {
    // Fetch playlists from your server's API.
    return nil, nil
}

func (p *Provider) Tracks(playlistID string) ([]playlist.Track, error) {
    // Fetch tracks for a playlist.
    return nil, nil
}

func (p *Provider) SearchTracks(ctx context.Context, query string, limit int) ([]playlist.Track, error) {
    // Search the server's catalog.
    return nil, nil
}

func (p *Provider) AlbumTracks(albumID string) ([]playlist.Track, error) {
    // Fetch tracks for an album.
    return nil, nil
}
```

### 2. Return tracks

When building `playlist.Track` values:

- **`Path`**: the playable URL or file path. For HTTP streams, use a full URL.
  For custom URI schemes (e.g. `spotify:track:xxx`), implement `CustomStreamer`.
- **`Stream: true`**: set this for HTTP URLs so the player uses the streaming
  pipeline.
- **`ProviderMeta`**: attach provider-specific metadata as a string map with
  namespaced keys. This is used for features like scrobbling:

```go
playlist.Track{
    Path:         "https://my-server/stream/123",
    Title:        "Song Title",
    Artist:       "Artist Name",
    Stream:       true,
    ProviderMeta: map[string]string{"jellyfin.id": "123"},
}
```

### 3. Add configuration

Add a config struct to `config/config.go`:

```go
type JellyfinConfig struct {
    URL   string `toml:"url"`
    Token string `toml:"token"`
}
```

Add the field to the top-level `Config` struct and a TOML section:

```toml
[jellyfin]
url = "https://jellyfin.example.com"
token = "your-api-key"
```

### 4. Register in main.go

Wire up the provider in the `run()` function in `main.go`:

```go
if cfg.Jellyfin.URL != "" && cfg.Jellyfin.Token != "" {
    jfProv := jellyfin.New(cfg.Jellyfin.URL, cfg.Jellyfin.Token)
    providers = append(providers, ui.ProviderEntry{
        Key: "jellyfin", Name: "Jellyfin", Provider: jfProv,
    })
}
```

If your provider needs a custom audio pipeline (like Spotify's `spotify:` URIs),
register a streamer factory:

```go
if cs, ok := myProv.(provider.CustomStreamer); ok {
    for _, scheme := range cs.URISchemes() {
        p.RegisterStreamerFactory(scheme, cs.NewStreamer)
    }
}
```

If your provider needs the buffered download pipeline for its stream URLs
(like Navidrome's Subsonic endpoints), register a URL matcher:

```go
p.RegisterBufferedURLMatcher(jellyfin.IsStreamURL)
```

### 5. Add a `--provider` flag value

In `main.go`'s help text, add your provider key to the `--provider` line so
users can set it as their default.

## What the UI Does Automatically

You don't need to touch the UI code. Based on which interfaces your provider
implements, the UI will automatically:

- Show the browse overlay ("N") if any registered provider implements `ArtistBrowser` or `AlbumBrowser`
- Show the search overlay ("F") if any registered provider implements `Searcher`
- Enable add-to-playlist in search results if the searched provider implements `PlaylistWriter`
- Scrobble playback if `Scrobbler` is implemented
- Run interactive auth on first use if `Authenticator` is implemented
- Call `Close()` on shutdown if `Closer` is implemented

The "N" and "F" shortcuts work regardless of which provider is currently active
They find the first registered provider with the needed capability.
