## Система скриптов
- Все действия должны выполняться от имени авторизованного пользователя.
- Скриптами может управлять только администратор или выше (роль `admin` / `superadmin` или право `scripts.edit` / `scripts.run`).

### SCRIPT_STRUCT
```json
{
    "alias": "bad_weather_light",
    "filename": "bad_weather_light.php",
    "path": "/srv/http/smart-home-serv.local/automation/Scopes",
    "state": "enabled",
    "created_by": "Eugene Sukhodolskiy",
    "create_at": "2025-06-02 23:35:11",
    "danger_level": "safe",
    "indicators": [
        {"label": "On", "variant": "success"}
    ]
}
```

**`danger_level`** — `safe` | `cautious` | `dangerous`. Задаётся в Scope через `$extra_attrs["danger_level"]`.

**`indicators`** — массив `{"label": "...", "variant": "..."}`. Формируется сервером через `state_callback` из Scope. Отсутствует, если callback не задан.

---

### GET `/api/v1/scripts/actions/list`
Получить список action-скриптов.

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "scripts": [
            {
                "alias": "script_alias",
                "name": "script name",
                "state": "enabled",
                "description": "script description",
                "filename": "TestScriptsScope.php",
                "path": "/srv/http/smart-home-serv.local/automation/Scopes",
                "created_by": "Eugene Sukhodolskiy",
                "danger_level": "safe",
                "indicators": [
                    {"label": "On", "variant": "success"}
                ],
                "params_schema": {
                    "level": {
                        "type": "range",
                        "label": "Яркость, %",
                        "min": 0,
                        "max": 100,
                        "default": 50,
                        "required": true
                    }
                }
            }
        ],
        "total": 1
    }
}
```

---

### GET `/api/v1/scripts/regular/list`
Получить список regular-скриптов.

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "scripts": [
            {
                "alias": "script_alias",
                "name": "script name",
                "state": "enabled",
                "description": "script description",
                "filename": "TestScriptsScope.php",
                "path": "/srv/http/smart-home-serv.local/automation/Scopes",
                "created_by": "Eugene Sukhodolskiy"
            }
        ],
        "total": 1
    }
}
```

---

### GET `/api/v1/scripts/scopes/list`
Получить список Scope-классов.

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "scopes": [
            {
                "name": "TestScriptsScope",
                "filename": "TestScriptsScope.php",
                "state": "enabled",
                "path": "/srv/http/smart-home-serv.local/automation/Scopes"
            }
        ],
        "total": 1
    }
}
```

---

### GET `/api/v1/scripts/scopes/name/{filename}`
Получить исходный код PHP-файла Scope.

**Требуемое право:** `scripts.view`

#### Пример ответа
```php
<?php /* code */ ?>
```

---

### POST `/api/v1/scripts/scopes/update`
Обновить исходный код Scope. Перед записью выполняется проверка синтаксиса (`php -l`), валидация namespace (`ControlScripts\Scopes`) и имени класса.

**Требуемое право:** `scripts.edit`

#### Пример запроса
```json
{
    "name": "LightHubScope",
    "source": "<?php\nnamespace ControlScripts\\Scopes;\nclass LightHubScope extends ..."
}
```

#### Пример НЕ успешного ответа
```json
{
    "status": false,
    "error_alias": "syntax_error",
    "failed_fields": ["source"],
    "msg": "..."
}
```

**Возможные ошибки:** `invalid_alias` | `invalid_source` | `syntax_error` | `invalid_namespace` | `class_name_mismatch` | `write_failed`

---

### GET `/api/v1/scripts/scopes/name/{name}/remove`
Удалить Scope из системы. Удаляет PHP-файл и связанную запись в БД.

**Требуемое право:** `scripts.edit`

---

### GET `/api/v1/scripts/actions/alias/{alias}/enable`
### GET `/api/v1/scripts/actions/alias/{alias}/disable`
Включить / выключить action-скрипт.

**Требуемое право:** `scripts.edit`

---

### GET `/api/v1/scripts/regular/alias/{alias}/enable`
### GET `/api/v1/scripts/regular/alias/{alias}/disable`
Включить / выключить regular-скрипт.

**Требуемое право:** `scripts.edit`

---

### GET `/api/v1/scripts/actions/scope/{name}/enable`
### GET `/api/v1/scripts/actions/scope/{name}/disable`
Включить / выключить Scope (все его скрипты перестают регистрироваться при следующем старте сервера).

**Требуемое право:** `scripts.edit`

---

### POST `/api/v1/scripts/actions/run`
Запустить action-скрипт. Проверяется, что скрипт включён (state = enabled). Если у скрипта есть `params_schema`, параметры валидируются перед вызовом.

**Требуемое право:** `scripts.run`

#### Пример запроса (без params)
```json
{
    "alias": "script_alias",
    "params": {}
}
```

#### Пример запроса (с params)
```json
{
    "alias": "dim_lights",
    "params": {
        "level": 80,
        "room": "kitchen",
        "instant": true
    }
}
```

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "return": {
            "result": {},
            "exec_time": "0.042 seconds"
        }
    }
}
```

**Ошибки:**
- `action_script_not_found` — alias не существует или скрипт disabled
- `invalid_params` — параметры не прошли валидацию по `params_schema` (в `failed_fields` перечислены поля с ошибками)

---

## Таймеры

### GET `/api/v1/scripts/timers/list`
Получить список таймеров (все статусы: pending, executed, cancelled, failed).

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "timers": [
            {
                "id": 1,
                "timer_alias": "kitchen_auto_off",
                "scope_name": "LightHubScope",
                "target_type": "action",
                "target_alias": "kitchen_light_switcher",
                "params": "{}",
                "execute_at": "2026-06-08 15:30:00",
                "status": "pending",
                "created_at": "2026-06-08 15:25:00"
            }
        ],
        "total": 1
    }
}
```

---

### POST `/api/v1/scripts/timers/cancel`
Отменить pending-таймер по `timer_alias`.

**Требуемое право:** `scripts.edit`

#### Пример запроса
```json
{
    "timer_alias": "kitchen_auto_off"
}
```

---

## Размещение в областях

### POST `/api/v1/scripts/place-in-area`
Поместить скрипт в область.

**Тело:** `{ "target_id": 5, "place_in_area_id": 2 }`

---

### GET `/api/v1/scripts/id/{id}/unassign-from-area`
Отвязать скрипт от области.

---

## Моды (Modes)

### GET `/api/v1/modes/list`
Получить список всех системных модов с метаданными (label, description) и текущим состоянием (`is_active`).

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "modes": [
            {
                "tag": "home",
                "label": "Дома",
                "description": "Основной режим присутствия...",
                "is_active": true,
                "updated_at": "2026-06-08 14:30:00"
            },
            {
                "tag": "away",
                "label": "Не дома",
                "description": "Никого нет дома...",
                "is_active": false,
                "updated_at": "2026-06-08 14:30:00"
            }
        ],
        "total": 2
    }
}
```

---

### GET `/api/v1/modes/active`
Массив активных тегов модов.

**Требуемое право:** `scripts.view`

#### Пример ответа
```json
{
    "status": true,
    "data": {
        "active": ["home", "night"]
    }
}
```

---

### POST `/api/v1/modes/{tag}/enable`
### POST `/api/v1/modes/{tag}/disable`
Активировать / деактивировать мод. `tag` должен соответствовать реестру `automation/ModesRegistry.php`.

**Требуемое право:** `scripts.edit`

**Ошибка:** `invalid_alias` (если тег не соответствует `^[a-z0-9_]+$`).
