# `Sagents.FileSystem.Persistence`

Behaviour for persisting files to storage.

Custom persistence implementations must implement all required callbacks.
The default implementation writes to the local filesystem.

## Usage

Create a `FileSystemConfig` with your persistence module:

    alias Sagents.FileSystem.{FileSystemServer, FileSystemConfig}

    {:ok, config} = FileSystemConfig.new(%{
      base_directory: "user_files",
      persistence_module: MyApp.DBPersistence,
      storage_opts: [path: "/data/agents"]
    })

    FileSystemServer.start_link(
      agent_id: "agent-123",
      persistence_configs: [config]
    )

## Storage Options

The `:storage_opts` from your `FileSystemConfig` are passed to your persistence
module's callbacks via the `opts` parameter. Use it to configure storage location,
DB connection, credentials, etc.

The `FileSystemConfig` also automatically adds `:agent_id` and `:base_directory`
to the opts for convenience.

## Callbacks

All callbacks receive `opts` as their second parameter, which includes:
- `:agent_id` - The agent's unique identifier
- `:base_directory` - The virtual directory (from `FileSystemConfig`). Note: this key
  is **not present** for default configs (`default: true`), since the default config
  catches all paths and has no meaningful base directory.
- All custom options from `FileSystemConfig.storage_opts`

### write_to_storage/2

Write a file entry to persistent storage. Called after the debounce timer fires.

### load_from_storage/2

Load a file's content from persistent storage. Called during lazy loading when a
file is read but content is not yet in memory.

### delete_from_storage/2

Delete a file from persistent storage. Called immediately when a persisted file
is deleted (no debounce).

### list_persisted_entries/2

List all persisted file entries for an agent. Called during initialization
to index existing files with their metadata (title, type, tags, etc.)
without loading content.

# `delete_from_storage`

```elixir
@callback delete_from_storage(
  file_entry :: Sagents.FileSystem.FileEntry.t(),
  opts :: keyword()
) ::
  :ok | {:error, term()}
```

Delete a file from persistent storage.

## Parameters

- `file_entry` - The FileEntry to delete (uses path field)
- `opts` - Storage configuration options

## Returns

- `:ok` on success (even if file doesn't exist)
- `{:error, reason}` on failure

# `list_persisted_entries`

```elixir
@callback list_persisted_entries(agent_id :: String.t(), opts :: keyword()) ::
  {:ok, [Sagents.FileSystem.FileEntry.t()]} | {:error, term()}
```

List all persisted file entries for an agent.

Used during initialization to index existing files with their metadata
(title, entry_type, custom metadata, etc.) without loading content.

Returns FileEntry structs with `content: nil, loaded: false` (for files)
or `loaded: true` (for directories, which have no content to load).

## Parameters

- `agent_id` - The agent's unique identifier
- `opts` - Storage configuration options

## Returns

- `{:ok, entries}` where entries is a list of FileEntry structs
- `{:error, reason}` on failure

# `load_from_storage`

```elixir
@callback load_from_storage(
  file_entry :: Sagents.FileSystem.FileEntry.t(),
  opts :: keyword()
) ::
  {:ok, Sagents.FileSystem.FileEntry.t()} | {:error, term()}
```

Load a file from persistent storage.

The persistence backend should return a complete FileEntry with all metadata
populated from the storage system (size, MIME type, timestamps, etc.).

## Parameters

- `file_entry` - The FileEntry with path to load (content may be nil)
- `opts` - Storage configuration options

## Returns

- `{:ok, file_entry}` where file_entry is a complete FileEntry with content and metadata
- `{:error, :enoent}` if file doesn't exist
- `{:error, reason}` on other failures

# `move_in_storage`

*optional* 

```elixir
@callback move_in_storage(
  file_entry :: Sagents.FileSystem.FileEntry.t(),
  new_path :: String.t(),
  opts :: keyword()
) :: {:ok, Sagents.FileSystem.FileEntry.t()} | {:error, term()}
```

Move a file entry to a new path in persistent storage.

This is an optional callback invoked by `move_file/3` for each entry being moved.
If not implemented, moved entries are marked dirty and persisted via the normal
`write_to_storage/2` or `update_metadata_in_storage/2` cycle.

## Parameters

- `file_entry` - The FileEntry at its **old** path (before the move)
- `new_path` - The target path
- `opts` - Storage configuration options

## Returns

- `{:ok, file_entry}` with the entry updated to reflect the new path
- `{:error, reason}` on failure (entry will be marked dirty as fallback)

# `update_metadata_in_storage`

*optional* 

```elixir
@callback update_metadata_in_storage(
  file_entry :: Sagents.FileSystem.FileEntry.t(),
  opts :: keyword()
) ::
  {:ok, Sagents.FileSystem.FileEntry.t()} | {:error, term()}
```

Update only metadata for a persisted file entry (no content write).

This is an optional callback used when only non-content fields change
(`dirty_non_content: true`) — e.g., custom metadata, title, or file_type
updates. Falls back to `write_to_storage/2` if not implemented.

## Parameters

- `file_entry` - The FileEntry with updated metadata
- `opts` - Storage configuration options

## Returns

- `{:ok, file_entry}` with updated metadata
- `{:error, reason}` on failure

# `write_to_storage`

```elixir
@callback write_to_storage(
  file_entry :: Sagents.FileSystem.FileEntry.t(),
  opts :: keyword()
) ::
  {:ok, Sagents.FileSystem.FileEntry.t()} | {:error, term()}
```

Write a file entry to persistent storage.

The persistence backend should return the FileEntry with updated metadata
after the write completes (actual size on disk, updated timestamps, etc.).

## Parameters

- `file_entry` - The FileEntry to persist (includes path, content, metadata)
- `opts` - Storage configuration options (e.g., [path: "/data/agents", agent_id: "agent-123"])

## Returns

- `{:ok, file_entry}` where file_entry has updated metadata from the storage system
- `{:error, reason}` on failure

---

*Consult [api-reference.md](api-reference.md) for complete listing*
