# Forge Arma 3 Server Extension

This extension provides the core server-side functionality for the Forge framework, handling persistent data storage, actor management, and game state synchronization through a high-performance Rust backend.

## Architecture

The extension follows a layered architecture designed for reliability, performance, and maintainability:

- **Extension Layer**: Handles the raw Arma 3 `callExtension` interface, parameter parsing, and command routing.
- **Service Layer**: Implements business logic, validation, and orchestration of operations (e.g., `ActorService`).
- **Repository Layer**: Manages data persistence and retrieval using Redis (e.g., `RedisActorRepository`).
- **Model Layer**: Defines strict data structures and validation rules (e.g., `Actor` model).

This separation ensures that game logic is decoupled from data storage and that all data entering the system is validated before persistence.

### Module Documentation

For detailed information about specific modules, see:

- **[Redis Operations](src/redis/README.md)**: Comprehensive guide to Redis commands (hash, list, set, common operations)
- **[Adapters](src/adapters/README.md)**: Adapter pattern implementation bridging repositories with Redis

## Organization Management

The Organization module handles guild/clan management, allowing players to form groups, manage members, and persist organizational data. It supports role management, automatic UID resolution, and robust error handling.

### Available Commands

| Command | Description |
|---------|-------------|
| `org:get` | Retrieve organization data by key or ID. |
| `org:create` | Create a new organization with provided JSON data. |
| `org:update` | Update an existing organization with partial JSON data. |
| `org:delete` | Permanently remove an organization and its data. |
| `org:exists` | Check if an organization exists. |
| `org:get_members` | Retrieve a list of organization members. |
| `org:add_member` | Add a member to an organization. |
| `org:remove_member` | Remove a member from an organization. |

### SQF Examples

#### Retrieving an Organization

```sqf
// Get organization by ID
private _result = "forge_server" callExtension ["org:get", ["elite_squad"]];
private _orgData = fromJSON (_result select 0);

// Access data
private _name = _orgData get "name";
private _leader = _orgData get "leader";
```

#### Creating an Organization

```sqf
// Prepare data using HashMap
private _data = createHashMapFromArray [
    ["name", "Elite Squad"],
    ["description", "Best players"],
    ["leader", getPlayerUID player],
    ["max_members", 50],
    ["type", "military"]
];

// Create the organization
private _result = "forge_server" callExtension ["org:create", ["elite_squad", toJSON _data]];

if ((_result select 0) find "Error:" == 0) then {
    diag_log format ["Failed to create org: %1", _result select 0];
} else {
    private _createdOrg = fromJSON (_result select 0);
    systemChat format ["Created organization: %1", _createdOrg get "name"];
};
```

#### Updating an Organization

```sqf
// Prepare partial update
private _update = createHashMapFromArray [
    ["description", "Updated description"],
    ["max_members", 100]
];

// Apply update
private _result = "forge_server" callExtension ["org:update", ["elite_squad", toJSON _update]];
```

#### Managing Members

```sqf
// Get members
private _result = "forge_server" callExtension ["org:get_members", ["elite_squad"]];
private _members = fromJSON (_result select 0);

// Add a member
private _addResult = "forge_server" callExtension ["org:add_member", ["elite_squad", "76561198123456789"]];

// Remove a member
private _removeResult = "forge_server" callExtension ["org:remove_member", ["elite_squad", "76561198123456789"]];
```

#### Checking Existence

```sqf
private _exists = "forge_server" callExtension ["org:exists", ["elite_squad"]];

if ((_exists select 0) == "true") then {
    systemChat "Organization exists.";
};
```

#### Deleting an Organization

```sqf
// Permanently delete organization
private _result = "forge_server" callExtension ["org:delete", ["elite_squad"]];

if ((_result select 0) == "OK") then {
    systemChat "Organization deleted.";
};
```

## Actor Management

The Actor module handles all player-related operations, including data retrieval, creation, updates, and existence checks. It features automatic Steam UID resolution and robust error handling.

### Available Commands

| Command | Description |
|---------|-------------|
| `actor:get` | Retrieve actor data by key or UID. |
| `actor:create` | Create a new actor with provided JSON data. |
| `actor:update` | Update an existing actor with partial JSON data. |
| `actor:exists` | Check if an actor exists in the database. |
| `actor:delete` | Permanently remove an actor and their data. |

### SQF Examples

The extension is designed to work seamlessly with modern Arma 3 SQF features like `HashMap` and `toJSON`/`fromJSON`.

#### Retrieving an Actor

```sqf
// Get actor by Steam UID
private _result = "forge_server" callExtension ["actor:get", ["76561198123456789"]];
private _actorData = fromJSON (_result select 0);

// Access data
private _name = _actorData get "name";
private _bank = _actorData get "bank";
```

#### Creating an Actor

```sqf
// Prepare data using HashMap
private _data = createHashMapFromArray [
    ["name", "John Doe"],
    ["bank", 1000],
    ["cash", 100],
    ["level", 1],
    ["class", "civilian"]
];

// Create the actor
private _result = "forge_server" callExtension ["actor:create", ["player123", toJSON _data]];

if ((_result select 0) find "Error:" == 0) then {
    diag_log format ["Failed to create actor: %1", _result select 0];
} else {
    private _createdActor = fromJSON (_result select 0);
    systemChat format ["Welcome, %1!", _createdActor get "name"];
};
```

#### Updating an Actor

```sqf
// Prepare partial update
private _update = createHashMapFromArray [
    ["bank", 1500],
    ["level", 2]
];

// Apply update
private _result = "forge_server" callExtension ["actor:update", ["player123", toJSON _update]];
```

#### Checking Existence

```sqf
private _exists = "forge_server" callExtension ["actor:exists", ["player123"]];

if ((_exists select 0) == "true") then {
    systemChat "Player profile found.";
} else {
    systemChat "Player profile not found.";
};
```

#### Deleting an Actor

```sqf
// Permanently delete actor data
private _result = "forge_server" callExtension ["actor:delete", ["player123"]];

if ((_result select 0) == "OK") then {
    systemChat "Actor deleted successfully.";
};
```

## Error Handling

The extension uses a consistent error reporting format. If an operation fails, the returned string will start with `Error: ` followed by a descriptive message.

- **Consistent Responses**: All commands return JSON on success or an error message on failure.
- **No Fallbacks**: `actor:get` and `org:get` will return error messages if the requested entity cannot be found, rather than fallback objects with dummy data.
- **Validation**: All input data is validated against the strict schema defined in the models. Invalid data will result in an error message.

### Example Error Handling

```sqf
private _result = "forge_server" callExtension ["actor:get", ["76561198123456789"]];
private _response = _result select 0;

if (_response find "Error:" == 0) then {
    diag_log format ["Failed to get actor: %1", _response];
} else {
    private _actorData = fromJSON _response;
    systemChat format ["Welcome, %1!", _actorData get "name"];
};
```

## Performance

- **Asynchronous Core**: Built on `tokio`, the extension performs heavy I/O operations (like database writes) without blocking the Arma 3 simulation thread.
- **Connection Pooling**: Uses a Redis connection pool to efficiently manage database connections.
- **Lazy Initialization**: Services are initialized only when first needed, reducing startup time.
- **Minimal Serialization**: Only necessary data is serialized and transferred between Rust and SQF to minimize overhead.

## Contributing

We welcome contributions to the Forge Extension! This guide will help you understand how to add new commands and maintain the existing codebase.

### Adding a Command to an Existing Module

To add a new command to an existing module (e.g., `actor:set_position`), follow these steps:

1.  **Register the Command**: In the module file (e.g., `src/actor.rs`), add the command to the `group()` function.
    ```rust
    pub fn group() -> Group {
        Group::new()
            .command("get", get_actor)
            .command("exists", exists_actor)
            .command("create", create_actor)
            .command("update", update_actor)
            .command("delete", delete_actor)
            .command("set_position", set_actor_position)  // New command
    }
    ```

2.  **Implement the Handler Function**: Create the function that handles the command logic.
    ```rust
    use crate::log::log;

    /// Sets the position of an actor.
    pub fn set_actor_position(call_context: CallContext, key: String, position: String) -> String {
        log("actor", "DEBUG", &format!("Setting position for key: {}", key));

        // 1. Resolve UID
        let resolved_uid = match resolve_uid(&key, &call_context) {
            Some(uid) => uid,
            None => {
                let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
                log("actor", "ERROR", &error_msg);
                return error_msg;
            }
        };

        // 2. Parse and validate input
        let position_data: Vec<f64> = match serde_json::from_str(&position) {
            Ok(data) => data,
            Err(e) => {
                let error_msg = format!("Error: Invalid position JSON: {}", e);
                log("actor", "ERROR", &error_msg);
                return error_msg;
            }
        };

        // 3. Get the actor, update position, and save
        match ACTOR_SERVICE.get_actor(resolved_uid.clone()) {
            Ok(mut actor) => {
                actor.set_position(position_data);
                
                match ACTOR_SERVICE.update_actor(actor.clone()) {
                    Ok(_) => {
                        log("actor", "INFO", &format!("Updated position for: {}", resolved_uid));
                        match serde_json::to_string(&actor) {
                            Ok(json) => json,
                            Err(e) => format!("Error: Failed to serialize actor: {}", e),
                        }
                    }
                    Err(e) => format!("Error: {}", e),
                }
            }
            Err(e) => format!("Error: {}", e),
        }
    }
    ```

### Creating a New Module

To create a new module (e.g., `vehicle`), follow these steps:

1.  **Create the Module File**: Add `src/vehicle.rs`.
2.  **Create the Global Service Instance**: Define a lazily initialized singleton service.
    ```rust
    use std::sync::LazyLock;
    use forge_services::VehicleService;
    use forge_repositories::RedisVehicleRepository;
    use crate::adapters::ExtensionRedisClient;

    static VEHICLE_SERVICE: LazyLock<VehicleService<RedisVehicleRepository<ExtensionRedisClient>>> =
        LazyLock::new(|| {
            let redis_client = ExtensionRedisClient::new();
            let repository = RedisVehicleRepository::new(redis_client);
            VehicleService::new(repository)
        });
    ```
3.  **Register the Command**: In the module file, register the command in the `group()` function.
    ```rust
    pub fn group() -> Group {
        Group::new()
            .command("get", get_vehicle)
            .command("create", create_vehicle)
            // ... other commands
    }
    ```
4.  **Use Logging**: Import and use the generic `log` function in your handler functions.
    ```rust
    use crate::log::log;

    pub fn get_vehicle(key: String) -> String {
        log("vehicle", "DEBUG", &format!("Getting vehicle for key: {}", key));

        // Call service layer
        match VEHICLE_SERVICE.get_vehicle(key.clone()) {
            Ok(vehicle) => {
                log("vehicle", "INFO", &format!("Successfully retrieved vehicle: {}", key));
                match serde_json::to_string(&vehicle) {
                    Ok(json) => {
                        log("vehicle", "DEBUG", &format!("Serialized vehicle to JSON: {}", json));
                        json
                    }
                    Err(e) => {
                        let error_msg = format!("Error: Failed to serialize vehicle: {}", e);
                        log("vehicle", "ERROR", &error_msg);
                        error_msg
                    }
                }
            }
            Err(e) => {
                let error_msg = format!("Error: {}", e);
                log("vehicle", "ERROR", &format!("Failed to get vehicle '{}': {}", key, e));
                error_msg
            }
        }
    }
    ```
    
    The `log` function takes three parameters:
    - `category`: The log category (e.g., "vehicle", "actor", "org")
    - `level`: The log level ("INFO", "DEBUG", "WARN", "ERROR")
    - `message`: The message to log

    Log files are created automatically in `@forge_server/logs/{category}.log`.

5.  **Register the Module** (if new): If you created a new module, add it to `src/lib.rs`.
    ```rust
    pub mod vehicle;
    
    // In the extension function, register the group
    extension.group("vehicle", vehicle::group());
    ```

### Testing

- **In-Game Testing**: Test your commands in Arma 3 to ensure they work correctly with SQF.
- **Error Cases**: Test error scenarios (invalid input, missing entities, etc.) to ensure proper error messages.

### Best Practices

- **Return Types**: Always return `String` (JSON on success, error message on failure).
- **Error Messages**: Prefix all error messages with `"Error: "` for consistency.
- **Logging**: Use the `log(category, level, message)` function to track operations.
- **Service Layer**: Delegate business logic to the service layer. The extension layer should only handle parameter parsing and response formatting.
- **Validation**: Validate inputs before calling the service layer to provide clear error messages.
