# Plugin Development Guide

## Plugin Structure

Each plugin must be a directory containing at least:
- `index.js` - Main entry point
- `plugin-metadata.json` - Plugin metadata

### Example Directory Structure
```
my-plugin/
├── index.js
├── plugin-metadata.json
└── assets/
    └── icon.png
```

## Plugin Metadata

The `plugin-metadata.json` file contains information about your plugin:

```json
{
  "name": "My Cool Plugin",
  "version": "1.0.0",
  "description": "Does cool things",
  "author": "Your Name",
  "homepage": "https://github.com/yourusername/my-plugin",
  "thumbnail": "https://example.com/thumbnail.png",
  "tags": ["utility", "ui"]
}
```

## Plugin Entry Point (index.js)

Your `index.js` must export a default plugin object with an `onLoad` function:

```javascript
// index.js
module.exports = {
  name: "My Plugin",
  version: "1.0.0",
  dependencies: {
    "paarrot.utils": "^1.0.0"  // Optional: depend on other plugins
  },
  
  /**
   * Called when the plugin is loaded
   * @param {PluginContext} ctx - The plugin context
   */
  onLoad: async (ctx) => {
    ctx.log('Plugin loaded!');
    
    // Your plugin code here
  },
  
  /**
   * Called when the plugin is unloaded (optional)
   */
  onUnload: async () => {
    ctx.log('Plugin unloaded!');
    // Cleanup resources here
  },
  
  /**
   * Exports for other plugins to use (optional)
   */
  exports: {
    myFunction: () => { /* ... */ }
  }
};
```

## Plugin Context API

The `ctx` object provided to your plugin contains:

### 🎯 Commands API

Register slash commands with argument parsing:

```javascript
// Simple command
ctx.commands.register({
  name: "shrug",
  run: () => "¯\\_(ツ)_/¯"
});

// Command with args
ctx.commands.register({
  name: "echo",
  args: ["text"],
  run: ({ text }) => text
});

// Advanced command
ctx.commands.register({
  name: "greet",
  description: "Greet someone",
  args: ["name", "greeting"],
  run: ({ name, greeting }) => {
    return `${greeting || 'Hello'}, ${name || 'stranger'}!`;
  }
});

// Execute commands programmatically
ctx.commands.execute("shrug", {});

// Unregister a command
ctx.commands.unregister("mycommand");
```

### 💬 Messages API

Intercept and modify messages:

```javascript
// Before send interceptor
ctx.messages.onBeforeSend((msg) => {
  if (msg.content === "brb") {
    msg.content = "be right back!";
  }
  
  // Add prefix
  msg.content = `[Bot] ${msg.content}`;
  
  // Access metadata
  msg.metadata = { processed: true };
});

// Receive interceptor
ctx.messages.onReceive((msg) => {
  if (msg.content.includes("@bot")) {
    ctx.log('Bot was mentioned!');
  }
});
```

MessageContext interface:
```typescript
interface MessageContext {
  content: string;
  roomId: string;
  eventType: string;
  formatted?: string;
  metadata?: Record<string, any>;
}
```

### 🎨 UI API

Plugins also receive `ctx.lucide` — the full [lucide-react](https://lucide.dev/) icon set for custom renderers and UI.

```javascript
const { Link, Search, X } = ctx.lucide;

ctx.ui.registerRenderer('message', (msg, defaultRenderer) => {
  return ctx.React.createElement(Link, { size: 16 });
});
```

Register custom renderers:

```javascript
ctx.ui.registerRenderer("message", (msg, defaultRenderer) => {
  if (msg.type === "image") {
    return `<img src="${msg.url}" style="border-radius:12px"/>`;
  }
  // Fall back to default
  return defaultRenderer?.();
});

// Unregister renderer
ctx.ui.unregisterRenderer("message");
```

### ⚙️ Settings API

Define plugin settings with UI:

```javascript
ctx.settings.define({
  theme: {
    type: "select",
    label: "Theme",
    description: "Choose your theme",
    options: [
      { value: "dark", label: "Dark" },
      { value: "light", label: "Light" }
    ],
    default: "dark"
  },
  spamFilter: {
    type: "boolean",
    label: "Spam Filter",
    description: "Enable spam filtering",
    default: true
  },
  prefix: {
    type: "string",
    label: "Command Prefix",
    default: "!"
  },
  maxMessages: {
    type: "number",
    label: "Max Messages",
    default: 100
  },
  accentColor: {
    type: "color",
    label: "Accent Color",
    default: "#6366f1"
  }
});

// Get/set settings
const theme = ctx.settings.get('theme');
ctx.settings.set('theme', 'light');
```

### 🪝 Matrix Events API

Listen to raw Matrix events:

```javascript
ctx.matrix.on("m.room.message", (event) => {
  const content = event.getContent();
  ctx.log('Message received:', content.body);
});

ctx.matrix.on("Room.timeline", (event) => {
  if (event.getType() === 'm.room.member') {
    ctx.log('Member event:', event.getSender());
  }
});

// Remove listener
ctx.matrix.off("m.room.message", handler);
```

Common events:
- `"m.room.message"` - Room messages
- `"m.room.member"` - Membership changes
- `"Room.timeline"` - Timeline events
- `"sync"` - Sync state changes
- `"RoomState.events"` - State events

### ⏱️ Timers API

Run background tasks:

```javascript
// Set interval
const intervalId = ctx.timers.setInterval(() => {
  ctx.log('Running every 10 seconds');
  // Auto-cleanup bots, sync data, etc.
}, 10000);

// Set timeout
const timeoutId = ctx.timers.setTimeout(() => {
  ctx.log('Running once after 5 seconds');
}, 5000);

// Clear timers
ctx.timers.clearInterval(intervalId);
ctx.timers.clearTimeout(timeoutId);

// Timers are auto-cleaned on plugin unload
```

### 📣 Notifications API

Show notifications to users:

```javascript
// Simple notification
ctx.notify("Plugin loaded!");

// Advanced notification
ctx.notify({
  title: "New Message",
  body: "You have a new message from Steve",
  type: "info",  // info, success, error, warning
  duration: 5000,
  actions: [
    {
      label: "View",
      action: () => {
        // Handle action
      }
    }
  ]
});
```

### 📝 Logging API

Per-plugin logging:

```javascript
ctx.log('Info message', { data: 123 });
ctx.warn('Warning message');
ctx.error('Error message', error);

// Logs are visible in plugin logs panel
// Format: [Plugin pluginId] message
```

### 🔗 Require API

Use other plugins:

```javascript
// Require another plugin's exports
const utils = ctx.require("paarrot.utils");
utils.formatDate(new Date());

// Check if plugin exists
try {
  const plugin = ctx.require("optional.plugin");
} catch (err) {
  ctx.warn('Optional plugin not available');
}
```

### 🔌 Matrix Client

Direct access to Matrix.js SDK:

```javascript
const mx = ctx.matrixClient;

// Get user info
const userId = mx.getUserId();
const user = mx.getUser(userId);

// Get rooms
const rooms = mx.getRooms();
const room = mx.getRoom(roomId);

// Send messages
await mx.sendTextMessage(roomId, "Hello!");

// Full Matrix.js SDK available
```

### ⚛️ React

Access to React:

```javascript
const { useState, useEffect } = ctx.React;

// Create custom UI components
const MyComponent = () => {
  const [count, setCount] = useState(0);
  return ctx.React.createElement('button', {
    onClick: () => setCount(count + 1)
  }, `Count: ${count}`);
};
```

## Complete Example

See [`example-plugin/index.js`](../../../../example-plugin/index.js) for a full-featured example.

```javascript
module.exports = {
  name: "Awesome Plugin",
  version: "1.0.0",
  
  onLoad: async (ctx) => {
    // Register commands
    ctx.commands.register({
      name: "shrug",
      run: () => "¯\\_(ツ)_/¯"
    });
    
    // Message interceptors
    ctx.messages.onBeforeSend((msg) => {
      if (msg.content === "brb") {
        msg.content = "be right back!";
      }
    });
    
    // Settings
    ctx.settings.define({
      enabled: { type: "boolean", default: true }
    });
    
    // Matrix events
    ctx.matrix.on("m.room.message", (event) => {
      ctx.log('Message:', event.getContent().body);
    });
    
    // Background task
    ctx.timers.setInterval(() => {
      ctx.log('Checking for updates...');
    }, 60000);
    
    ctx.notify("Plugin loaded!");
  },
  
  onUnload: async () => {
    // Cleanup happens automatically
  },
  
  exports: {
    version: "1.0.0"
  }
};
```

## Best Practices

### 1. Error Handling
Always wrap your code in try-catch blocks:

```javascript
onLoad: async (ctx) => {
  try {
    // Your code
  } catch (error) {
    ctx.error('Failed to load:', error);
  }
}
```

### 2. Cleanup Resources
Timers and Matrix handlers are auto-cleaned, but clean up other resources:

```javascript
onUnload: async () => {
  // Close connections, save state, etc.
}
```

### 3. Performance
Don't block the main thread:

```javascript
// Bad
for (let i = 0; i < 1000000; i++) { /* ... */ }

// Good
ctx.timers.setTimeout(() => {
  // Heavy work in background
}, 0);
```

### 4. Settings Best Practices
Provide defaults and descriptions:

```javascript
ctx.settings.define({
  maxRetries: {
    type: "number",
    label: "Max Retries",
    description: "Number of retry attempts",
    default: 3
  }
});
```

### 5. Logging
Use ctx.log() instead of console.log():

```javascript
ctx.log('Info');     // Shows in plugin logs panel
ctx.warn('Warning'); // Highlighted as warning
ctx.error('Error');  // Highlighted as error
```

## Advanced Patterns

### Plugin Dependencies

Require and use other plugins:

```javascript
onLoad: async (ctx) => {
  try {
    const utils = ctx.require("paarrot.utils");
    const formatted = utils.formatDate(new Date());
    ctx.log('Using utils:', formatted);
  } catch (err) {
    ctx.warn('Utils plugin not available, using fallback');
  }
}
```

### Conditional Features

Enable features based on settings:

```javascript
const autoReply = ctx.settings.get('autoReply');
if (autoReply) {
  ctx.matrix.on("m.room.message", (event) => {
    // Auto-reply logic
  });
}
```

### State Management

Store plugin state in settings:

```javascript
// Save state
ctx.settings.set('lastRun', Date.now());
ctx.settings.set('messageCount', count);

// Load state
const lastRun = ctx.settings.get('lastRun');
```

### Command Aliases

Create multiple commands for the same function:

```javascript
const shrugFn = () => "¯\\_(ツ)_/¯";

ctx.commands.register({ name: "shrug", run: shrugFn });
ctx.commands.register({ name: "dunno", run: shrugFn });
```

### Hot Reload Support

Make your plugin hot-reload friendly:

```javascript
let messageHandler = null;

onLoad: async (ctx) => {
  messageHandler = (event) => { /* ... */ };
  ctx.matrix.on("m.room.message", messageHandler);
},

onUnload: async () => {
  if (messageHandler) {
    // Cleanup happens automatically but explicit is good
  }
}
```

## Security Considerations

1. **Never store passwords or tokens in plaintext**
2. **Validate all user input**
3. **Don't execute arbitrary code from messages**
4. **Use HTTPS for external requests**
5. **Respect user privacy - don't log sensitive data**

## Packaging

To package your plugin:

1. Create a ZIP file containing your plugin directory
2. The ZIP should contain the plugin folder at the root:
   ```
   my-plugin.zip
   └── my-plugin/
       ├── index.js
       ├── plugin-metadata.json
       └── assets/
   ```
3. Upload to a URL accessible by users
4. Add your plugin to the Plugin Directory repository

## Plugin Directory

To list your plugin in the marketplace:

1. Fork the [Paarrot Plugin Directory](https://github.com/Paarrot/Plugin-Directory)
2. Add your plugin metadata to `plugins/index.json`
3. Create a plugin JSON file in `plugins/your-plugin.json`
4. Submit a pull request

## Debugging

### Browser Console
Check the browser console (F12) for plugin logs:
- `[PluginLoader]` - Plugin loading messages
- `[Plugin yourplugin]` - Your plugin's logs

### Plugin Logs Panel
View per-plugin logs in Settings → Plugins → Logs (coming soon)

### Common Issues

**Plugin won't load:**
- Check `index.js` exists
- Verify `module.exports` is correct
- Look for JavaScript syntax errors

**Command not working:**
- Ensure command is registered in onLoad
- Check command name doesn't conflict
- Verify run function returns a value

**Settings not saving:**
- Use ctx.settings.set() not localStorage
- Check setting key matches defined schema

## Testing

Test your plugin locally:

1. Copy plugin folder to `%APPDATA%/paarrot/plugins/` (Windows) or `~/.config/Paarrot/plugins/` (Linux)
2. Restart Paarrot
3. Enable plugin in Settings → Plugins
4. Check console for errors
5. Test all features

## Resources

- [Matrix.js SDK Docs](https://matrix-org.github.io/matrix-js-sdk/)
- [Paarrot Plugin Examples](../../../../example-plugin/)
- [Plugin Directory](https://github.com/Paarrot/Plugin-Directory)
- [Report Issues](https://github.com/Paarrot/cinny-desktop/issues)
