# Getting Started with winterforge-channels

Complete guide to building communication systems with winterforge-channels.

---

## Table of Contents

1. [Installation](#installation)
2. [Core Concepts](#core-concepts)
3. [Basic Usage](#basic-usage)
4. [Permission System](#permission-system)
5. [Transport Configuration](#transport-configuration)
6. [Message Threading](#message-threading)
7. [Advanced Patterns](#advanced-patterns)
8. [CLI Tools](#cli-tools)
9. [Production Deployment](#production-deployment)

---

## Installation

### Prerequisites

- Python 3.10+
- WinterForge framework
- Storage backend configured (SQLite, PostgreSQL, or YAML)

### Install via WinterForge Extras

```bash
pip install winterforge[channels]
```

This installs:
- winterforge-channels
- HTTP support (httpx, fastapi)
- WebSocket support (websockets)

### Optional Dependencies

```bash
# Queue transport (Celery + Redis)
pip install winterforge-channels[queue]

# Email transport (SMTP)
pip install winterforge-channels[email]

# Everything
pip install winterforge-channels[all]
```

### Verify Installation

```python
import asyncio
from winterforge_channels.primitives import Message, Channel

async def verify():
    channel = Channel()
    channel.set_title("Test Channel")
    print(f"✓ winterforge-channels installed: {channel.title}")

asyncio.run(verify())
```

---

## Core Concepts

### Primitives

**Message** - The fundamental communication unit
- Transport-agnostic (doesn't know HOW it's delivered)
- Contains content, author, metadata
- Supports threading and references
- Tracks canonical conversations

**Channel** - The routing mechanism
- Permission-controlled
- Configurable transports (HTTP, WebSocket, Queue, Email)
- Manages subscribers
- Orchestrates message delivery

**Subscription** - Links subscribers to channels
- Connects ANY Frag (User, Bot, Service) to a channel
- Lightweight join table
- Queryable by subscriber or channel

### Traits

**messageable** - Content and metadata
- `content`: Message text/data
- `author_id`: Author Frag ID
- `content_type`: MIME type (default: 'text/plain')

**conversable** - Conversation tracking
- `conversation_id`: Canonical conversation
- Links messages to conversations

**transportable** - Channel delivery
- `send_to_channels(channels)`: Send via channels
- Handles permission checks
- Coordinates with transports

**subscribable** - Subscriber management
- `get_subscribers()`: Get all subscribers
- `subscribe(subscriber_id)`: Add subscriber
- `unsubscribe(subscriber_id)`: Remove subscriber

**routable** - Message routing
- `route_message(message)`: Orchestrate delivery
- Gets egress transport
- Delivers to subscribers

---

## Basic Usage

### Step 1: Create a Channel

```python
from winterforge_channels.primitives import Channel

channel = Channel()
channel.set_title("General Chat")
await channel.save()

print(f"Channel created: {channel.id}")
```

### Step 2: Create a User with Permissions

```python
from winterforge.frags import Frag

# Create permission
perm = Frag(
    affinities=['permission'],
    traits=['titled', 'persistable'],
)
perm.set_title('channel.submit')
await perm.save()

# Create role with permission
role = Frag(
    affinities=['role'],
    traits=['titled', 'permissioned', 'persistable'],
)
role.set_title('member')
role.add_permission(perm.id)
await role.save()

# Create user with role
alice = Frag(
    affinities=['user'],
    traits=['userable', 'authorizable', 'persistable'],
)
alice.set_username('alice')
alice.set_email('alice@example.com')
alice.add_role(role.id)
await alice.save()
```

### Step 3: Subscribe to Channel

```python
# Subscribe alice to channel
subscription = await channel.subscribe(alice.id)

# Verify subscription
subscribers = await channel.get_subscribers()
print(f"Subscribers: {len(subscribers)}")
```

### Step 4: Send a Message

```python
from winterforge_channels.primitives import Message
from winterforge.frags import Manifest

# Create message
message = Message()
message.set_content("Hello everyone!")
message.set_author_id(alice.id)
await message.save()

# Send via channel
results = await message.send(Manifest([channel]))

for result in results:
    print(f"Delivered: {result.delivered_count}")
    print(f"Failed: {result.failed_count}")
```

---

## Permission System

winterforge-channels uses WinterForge's authorization system for
fine-grained access control.

### Three Channel Permissions

1. **channel.submit** - Send messages to channel
2. **channel.subscribe** - Subscribe to channel
3. **channel.manage** - Configure channel settings

### Setting Up Permissions

```python
# Create all three permissions
permissions = {}

for perm_name in ['channel.submit', 'channel.subscribe', 'channel.manage']:
    perm = Frag(affinities=['permission'], traits=['titled', 'persistable'])
    perm.set_title(perm_name)
    await perm.save()
    permissions[perm_name] = perm

# Create roles with different permissions
member_role = Frag(
    affinities=['role'],
    traits=['titled', 'permissioned', 'persistable'],
)
member_role.set_title('member')
member_role.add_permission(permissions['channel.submit'].id)
member_role.add_permission(permissions['channel.subscribe'].id)
await member_role.save()

admin_role = Frag(
    affinities=['role'],
    traits=['titled', 'permissioned', 'persistable'],
)
admin_role.set_title('admin')
admin_role.add_permission(permissions['channel.submit'].id)
admin_role.add_permission(permissions['channel.subscribe'].id)
admin_role.add_permission(permissions['channel.manage'].id)
await admin_role.save()
```

### Checking Permissions

```python
# Check if user can submit
can_submit = await channel.can_submit(alice.id)
if can_submit:
    await message.send(Manifest([channel]))
else:
    print("Permission denied: cannot submit to channel")

# Check if user can subscribe
can_subscribe = await channel.can_subscribe(bob.id)
if can_subscribe:
    await channel.subscribe(bob.id)

# Check if user can manage
can_manage = await channel.can_manage(alice.id)
if can_manage:
    channel.set_alias('egress_transport', 'websocket')
    await channel.save()
```

### Permission Enforcement in Action

```python
# User WITH permission
alice_can_submit = await channel.can_submit(alice.id)
# Returns: True

# User WITHOUT permission
mallory = Frag(affinities=['user'], traits=['persistable'])
await mallory.save()

mallory_can_submit = await channel.can_submit(mallory.id)
# Returns: False

# Attempting to send without permission
message.set_author_id(mallory.id)
results = await message.send(Manifest([channel]))
# Returns: [] (empty - permission check failed)
```

---

## Transport Configuration

Channels can use different transports for receiving (ingress) and
sending (egress) messages.

### Available Transports

**HTTP Ingress** - Receive via POST endpoint
```python
channel.set_alias('ingress_transport', 'http')
```

**HTTP Egress** - Send via POST to subscriber endpoints
```python
channel.set_alias('egress_transport', 'http')
```

**WebSocket Egress** - Real-time delivery to connected clients
```python
channel.set_alias('egress_transport', 'websocket')
```

### Configuring Transports

```python
# HTTP for both
channel.set_alias('ingress_transport', 'http')
channel.set_alias('egress_transport', 'http')
await channel.save()

# HTTP ingress, WebSocket egress (common pattern)
channel.set_alias('ingress_transport', 'http')
channel.set_alias('egress_transport', 'websocket')
await channel.save()
```

### Transport Resolution

Channels use first-match strategy:
1. Channel-preferred transport (via aliases)
2. First available transport in repository

```python
# Get egress repository for channel
egress_repo = channel.get_egress_repository()

# Resolve transport
transport = egress_repo.resolve()
# Uses channel's preferred transport, or first available
```

### Subscriber Requirements

**HTTP Egress** - Subscriber needs `endpoint` field
```python
subscriber = Frag(affinities=['user'], traits=['persistable'])
subscriber.set_alias('endpoint', 'https://api.example.com/webhooks/user')
await subscriber.save()
```

**WebSocket Egress** - Subscriber needs `websocket_id` field
```python
subscriber.set_alias('websocket_id', 'ws_abc123')
await subscriber.save()

# Register WebSocket connection
transport.register_connection(subscriber.id, websocket)
```

---

## Message Threading

Messages support two relationship types for organizing conversations.

### reply_to - Direct Thread Parent

```python
# Original message
msg1 = Message()
msg1.set_content("What's the project status?")
msg1.set_author_id(alice.id)
await msg1.save()

# Reply
msg2 = Message()
msg2.set_content("We're on track for Q2 release")
msg2.set_author_id(bob.id)
msg2.set_reply_to_id(msg1.id)
await msg2.save()

# Get parent
parent = await msg2.get_reply_to()
print(parent.content)  # "What's the project status?"
```

### Thread Traversal

```python
from winterforge_channels.registries import MessageRegistry

registry = MessageRegistry()

# Get complete thread (follows reply_to chain backward)
thread = await registry.get_thread(msg2.id)

# thread is a Manifest containing [msg1, msg2]
for msg in thread:
    print(f"{msg.author_id}: {msg.content}")
```

### references - Semantic Links

```python
# Bug report
bug = Message()
bug.set_content("Users can't login")
await bug.save()

# Related messages
msg1 = Message()
msg1.set_content("Same issue here")
msg1.add_reference(bug.id)
await msg1.save()

msg2 = Message()
msg2.set_content("Fixed in PR #123")
msg2.add_reference(bug.id)
msg2.add_reference(msg1.id)
await msg2.save()

# Get all references
refs = await msg2.get_references()
# Returns: [bug, msg1]
```

### Conversation Tracking

```python
# Create conversation
conversation = Frag(
    affinities=['conversation'],
    traits=['titled', 'persistable'],
)
conversation.set_title("Sprint Planning")
await conversation.save()

# Link messages to conversation
msg1.set_conversation_id(conversation.id)
msg2.set_conversation_id(conversation.id)
msg3.set_conversation_id(conversation.id)
await msg1.save()
await msg2.save()
await msg3.save()

# Query all messages in conversation
registry = MessageRegistry()
messages = await registry.get_for_conversation(conversation.id)
```

---

## Advanced Patterns

### Multi-Channel Broadcasting

```python
# Create multiple channels
general = Channel()
general.set_title("General")
await general.save()

announcements = Channel()
announcements.set_title("Announcements")
await announcements.save()

support = Channel()
support.set_title("Support")
await support.save()

# Send to all channels at once
from winterforge.frags import Manifest

channels = Manifest([general, announcements, support])
results = await message.send(channels)

for i, result in enumerate(results):
    print(f"Channel {i+1}: {result.delivered_count} delivered")
```

### Subscriber Filtering

```python
# Get all subscribers
all_subscribers = await channel.get_subscribers()

# Filter for active users
active = [s for s in all_subscribers if s.active]

# Filter by role
admins = []
for subscriber in all_subscribers:
    if hasattr(subscriber, 'has_role'):
        if await subscriber.has_role('admin'):
            admins.append(subscriber)
```

### Custom Message Types

```python
# JSON message
json_msg = Message()
json_msg.set_content('{"action": "update", "count": 42}')
json_msg.set_content_type('application/json')
await json_msg.save()

# Markdown message
md_msg = Message()
md_msg.set_content("# Important\n\nPlease review **ASAP**")
md_msg.set_content_type('text/markdown')
await md_msg.save()

# Binary data (base64 encoded)
binary_msg = Message()
binary_msg.set_content(base64.b64encode(image_data).decode())
binary_msg.set_content_type('image/png')
await binary_msg.save()
```

### Bot Subscribers

```python
# Create bot subscriber
bot = Frag(affinities=['bot'], traits=['persistable'])
bot.set_alias('name', 'Notification Bot')
bot.set_alias('endpoint', 'https://bots.example.com/notify')
await bot.save()

# Subscribe bot to channel
await channel.subscribe(bot.id)

# Messages sent to channel will be delivered to bot
```

### Service Integration

```python
# External service as subscriber
slack_integration = Frag(
    affinities=['integration'],
    traits=['persistable'],
)
slack_integration.set_alias('service', 'slack')
slack_integration.set_alias('endpoint', 'https://hooks.slack.com/...')
await slack_integration.save()

await channel.subscribe(slack_integration.id)
```

---

## CLI Tools

winterforge-channels provides complete CLI management.

### Create Channel

```bash
# Basic channel
winterforge channel create "General Chat"

# With transport configuration
winterforge channel create "Support" --ingress http --egress websocket

# Output:
# ✓ Channel created: General Chat (ID: 1)
```

### Send Message

```bash
# Send message to channel
winterforge channel send 1 "Hello everyone!" --author-id 1

# Output:
# ✓ Message sent (ID: 1)
```

### Manage Subscribers

```bash
# Subscribe user to channel
winterforge channel subscribe 1 42

# List subscribers
winterforge channel list-subscribers 1

# Unsubscribe
winterforge channel unsubscribe 1 42
```

### List Channels

```bash
# List all channels
winterforge channel list

# Output:
# ID  Title            Subscribers
# 1   General Chat     5
# 2   Support          12
# 3   Announcements    245
```

---

## Production Deployment

### Storage Configuration

```python
from winterforge.frags.traits.persistable import set_storage
from winterforge.plugins import StorageManager

# SQLite (development)
storage = StorageManager.get('sqlite', db_path='channels.db')
set_storage(storage)

# PostgreSQL (production)
storage = StorageManager.get(
    'postgresql',
    host='db.example.com',
    database='winterforge',
    user='app',
    password='secret',
)
set_storage(storage)
```

### WebSocket Server Setup

```python
import asyncio
import websockets
from winterforge_channels.plugins.transports import (
    WebSocketEgressTransport,
)

# Get transport instance
transport = WebSocketEgressTransport()

async def handle_connection(websocket, path):
    """Handle WebSocket connection."""
    # Extract subscriber ID from path or auth
    subscriber_id = authenticate_websocket(websocket)

    # Register connection
    await transport.register_connection(subscriber_id, websocket)

    try:
        # Keep connection alive
        async for message in websocket:
            pass  # Handle incoming messages if needed
    finally:
        # Unregister on disconnect
        await transport.unregister_connection(subscriber_id)

# Start WebSocket server
async def main():
    async with websockets.serve(handle_connection, "0.0.0.0", 8765):
        await asyncio.Future()  # Run forever

asyncio.run(main())
```

### HTTP Ingress Setup

```python
from fastapi import FastAPI, Request
from winterforge_channels.primitives import Message
from winterforge_channels.registries import ChannelRegistry

app = FastAPI()

@app.post("/channels/{channel_id}/messages")
async def receive_message(channel_id: int, request: Request):
    """Receive message via HTTP POST."""
    data = await request.json()

    # Load channel
    channels = ChannelRegistry()
    channel = await channels.get(channel_id)

    if not channel:
        return {"error": "Channel not found"}, 404

    # Permission check
    author_id = data.get('author_id')
    if not await channel.can_submit(author_id):
        return {"error": "Permission denied"}, 403

    # Create message
    message = Message()
    message.set_content(data.get('content'))
    message.set_author_id(author_id)

    if 'conversation_id' in data:
        message.set_conversation_id(data['conversation_id'])

    await message.save()

    return {
        "success": True,
        "message_id": message.id,
    }

# Run with: uvicorn app:app --host 0.0.0.0 --port 8000
```

### Monitoring

```python
# Track delivery metrics
from winterforge_channels.registries import MessageRegistry

registry = MessageRegistry()

# Get recent messages
recent = await registry.filter(
    lambda m: m.created_at > datetime.now() - timedelta(hours=1)
)

# Count by conversation
from collections import Counter
conversations = Counter(m.conversation_id for m in recent)

print(f"Messages last hour: {len(recent)}")
print(f"Active conversations: {len(conversations)}")
```

---

## Next Steps

- Explore [API Reference](api_reference.md)
- Build [Custom Transports](custom_transports.md)
- Review [Architecture Details](architecture.md)
- See [Example Applications](examples/)

---

## Support

- **Documentation:** https://winterforge-channels.readthedocs.io
- **Issues:** https://github.com/yourusername/winterforge-channels/issues
- **Discussions:** https://github.com/yourusername/winterforge/discussions

---

Built with ❄️ by the WinterForge team
