# FlowDropNodeProcessor Attribute Plugin

## Overview

The `FlowDropNodeProcessor` attribute is a PHP 8+ attribute that provides metadata for FlowDrop node processor plugins. It serves as the primary mechanism for defining node processors in the FlowDrop workflow system, providing LangflowComponent-equivalent metadata for visual workflow nodes.

## Class Definition

```php
#[\Attribute(\Attribute::TARGET_CLASS)]
final class FlowDropNodeProcessor extends Plugin
```

**Location**: `src/Attribute/FlowDropNodeProcessor.php`

## Constructor Parameters

| Parameter         | Type                 | Required | Description                                            |
| ----------------- | -------------------- | -------- | ------------------------------------------------------ |
| `$id`             | `string`             | ✅        | Unique plugin identifier                               |
| `$label`          | `TranslatableMarkup` | ✅        | Human-readable label for the UI                        |
| `$type`           | `string`             | ✅        | Node type for frontend rendering                       |
| `$supportedTypes` | `array`              | ✅        | All supported node types                               |
| `$category`       | `string`             | ✅        | Component category (e.g., "inputs", "models", "tools") |
| `$description`    | `string`             | ❌        | Component description (default: "")                    |
| `$version`        | `string`             | ❌        | Component version (default: "1.0.0")                   |
| `$tags`           | `array`              | ❌        | Component tags for categorization (default: [])        |

## Unified Parameter System

FlowDrop uses a **Unified Parameter System** where:

1. **Plugins** define the data contract via `getParameterSchema()` (types, defaults, constraints)
2. **Config entities** control UI/workflow behavior (connectable, configurable, required)
3. **Runtime** resolves values from inputs, workflow config, and defaults

### Key Concepts

- **`getParameterSchema()`**: Returns a JSON Schema defining parameter types and defaults
- **Config Entity**: Controls which parameters are input ports vs config fields
- **`ParameterBagInterface`**: DTO containing resolved parameter values at runtime

### Architecture

```
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Plugin Schema  │     │  Entity Config   │     │ System Defaults │
│  (type, default,│  +  │  (connectable,   │  +  │  (connectable=  │
│   enum, etc.)   │     │   configurable,  │     │   FALSE, etc.)  │
└────────┬────────┘     │   required)      │     └────────┬────────┘
         │              └────────┬─────────┘              │
         │                       │                        │
         └───────────────────────┴────────────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │   ParameterResolver     │
                    │   (merges all sources)  │
                    └────────────┬────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │    ParameterBag         │
                    │    (resolved values)    │
                    └─────────────────────────┘
```

### Schema Definition Example

Plugins define **standard JSON Schema** properties only. UI behavior is controlled by the config entity.

```php
public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "prompt" => [
        "type" => "string",
        "description" => "The input prompt text",
      ],
      "model" => [
        "type" => "string",
        "description" => "The AI model to use",
        "default" => "gpt-4",
        "enum" => ["gpt-4", "gpt-3.5-turbo", "claude-3"],
      ],
      "temperature" => [
        "type" => "number",
        "description" => "Sampling temperature",
        "default" => 0.7,
        "minimum" => 0,
        "maximum" => 2,
      ],
    ],
  ];
}
```

### Entity Configuration

The config entity controls how parameters behave in the workflow UI:

```yaml
# flowdrop_node_type.flowdrop_node_type.chat_model.yml
id: chat_model
label: 'Chat Model'
executor_plugin: chat_model
parameters:
  prompt:
    connectable: true    # Shows as input port
    configurable: true   # Shows in config form
    required: true       # Must have a value
  model:
    connectable: false   # Config only, no input port
    configurable: true
    required: false
  temperature:
    connectable: false
    configurable: true
    required: false
```

### System Defaults

When entity config doesn't specify a flag:

| Flag           | Default | Meaning                             |
| -------------- | ------- | ----------------------------------- |
| `connectable`  | `FALSE` | Parameter is NOT an input port      |
| `configurable` | `FALSE` | Parameter does NOT appear in config |
| `required`     | `FALSE` | Parameter is optional               |

## Usage Examples

### Basic Node Processor

```php
<?php

namespace Drupal\my_module\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

#[FlowDropNodeProcessor(
  id: "my_processor",
  label: new TranslatableMarkup("My Processor"),
  type: "default",
  supportedTypes: ["default"],
  category: "processing",
  description: "A custom node processor for data transformation",
  version: "1.0.0",
  tags: ["custom", "data", "transformation"]
)]
class MyProcessor extends AbstractFlowDropNodeProcessor {

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    protected LoggerChannelInterface $logger,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("logger.channel.flowdrop"),
    );
  }

  protected function getLogger(): LoggerChannelInterface {
    return $this->logger;
  }

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "data" => [
          "type" => "mixed",
          "description" => "Input data to transform",
        ],
        "format" => [
          "type" => "string",
          "description" => "Output format",
          "default" => "json",
          "enum" => ["json", "xml", "csv"],
        ],
      ],
    ];
  }

  public function process(ParameterBagInterface $params): array {
    $data = $params->get("data");
    $format = $params->getString("format", "json");

    // Transform data based on format...
    $transformed = $this->transform($data, $format);

    return ["result" => $transformed];
  }

  private function transform(mixed $data, string $format): mixed {
    // Implementation...
    return $data;
  }
}
```

### AI Model Node Processor

```php
#[FlowDropNodeProcessor(
  id: "chat_model",
  label: new TranslatableMarkup("Chat Model"),
  type: "default",
  supportedTypes: ["default"],
  category: "ai",
  description: "AI chat model integration for conversational AI",
  version: "1.0.0",
  tags: ["ai", "chat", "model", "conversation"]
)]
class ChatModel extends AbstractFlowDropNodeProcessor {

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "prompt" => [
          "type" => "string",
          "description" => "The input prompt",
        ],
        "system_message" => [
          "type" => "string",
          "description" => "System message for the AI",
          "default" => "",
        ],
        "model" => [
          "type" => "string",
          "description" => "Model identifier",
          "default" => "gpt-4",
          "enum" => ["gpt-4", "gpt-3.5-turbo"],
        ],
        "temperature" => [
          "type" => "number",
          "description" => "Sampling temperature",
          "default" => 0.7,
          "minimum" => 0,
          "maximum" => 2,
        ],
      ],
    ];
  }

  public function process(ParameterBagInterface $params): array {
    $prompt = $params->getString("prompt");
    $systemMessage = $params->getString("system_message", "");
    $model = $params->getString("model", "gpt-4");
    $temperature = $params->getFloat("temperature", 0.7);

    // Call AI model...
    $response = $this->callModel($prompt, $systemMessage, $model, $temperature);

    return ["response" => $response];
  }
}
```

### Input Node Processor

```php
#[FlowDropNodeProcessor(
  id: "text_input",
  label: new TranslatableMarkup("Text Input"),
  type: "default",
  supportedTypes: ["default"],
  category: "inputs",
  description: "Simple text input field for user data entry",
  version: "1.0.0",
  tags: ["input", "text", "user", "form"]
)]
class TextInput extends AbstractFlowDropNodeProcessor {

  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "value" => [
          "type" => "string",
          "description" => "The input text value",
          "default" => "",
        ],
        "placeholder" => [
          "type" => "string",
          "description" => "Placeholder text",
          "default" => "Enter text...",
        ],
      ],
    ];
  }

  public function process(ParameterBagInterface $params): array {
    return ["text" => $params->getString("value", "")];
  }
}
```

## ParameterBag Methods

The `ParameterBagInterface` provides typed accessors for retrieving parameter values:

| Method                                         | Return Type | Description               |
| ---------------------------------------------- | ----------- | ------------------------- |
| `get(string $key, mixed $default = null)`      | `mixed`     | Get any parameter value   |
| `getString(string $key, string $default = "")` | `string`    | Get string parameter      |
| `getInt(string $key, int $default = 0)`        | `int`       | Get integer parameter     |
| `getFloat(string $key, float $default = 0.0)`  | `float`     | Get float parameter       |
| `getBool(string $key, bool $default = false)`  | `bool`      | Get boolean parameter     |
| `getArray(string $key, array $default = [])`   | `array`     | Get array parameter       |
| `has(string $key)`                             | `bool`      | Check if parameter exists |
| `all()`                                        | `array`     | Get all parameters        |

## Supported JSON Schema Properties

Plugins can use these standard JSON Schema properties:

| Property      | Type      | Description                                                       |
| ------------- | --------- | ----------------------------------------------------------------- |
| `type`        | `string`  | Data type: string, number, integer, boolean, array, object, mixed |
| `description` | `string`  | Human-readable description                                        |
| `default`     | `mixed`   | Default value                                                     |
| `enum`        | `array`   | Allowed values                                                    |
| `minimum`     | `number`  | Minimum numeric value                                             |
| `maximum`     | `number`  | Maximum numeric value                                             |
| `minLength`   | `integer` | Minimum string length                                             |
| `maxLength`   | `integer` | Maximum string length                                             |
| `pattern`     | `string`  | Regex pattern for strings                                         |
| `format`      | `string`  | Format: email, uri, date, date-time, uuid, etc.                   |
| `minItems`    | `integer` | Minimum array items                                               |
| `maxItems`    | `integer` | Maximum array items                                               |
| `uniqueItems` | `boolean` | Require unique array items                                        |

## Category Guidelines

Use these standardized categories for consistent organization:

### Core Categories
- **`inputs`** - Data input components (TextInput, FileUpload, etc.)
- **`outputs`** - Data output components (TextOutput, ChatOutput, etc.)
- **`models`** - AI model components (ChatModel, OpenAiChat, etc.)
- **`tools`** - Utility and tool components (HttpRequest, Webhook, etc.)
- **`processing`** - Data processing components (DataOperations, Calculator, etc.)
- **`logic`** - Logic and control flow components (Conditional, Loop, etc.)
- **`ai`** - AI-specific components (embeddings, vector stores, etc.)
- **`eca`** - ECA (Event-Condition-Action) components
- **`helpers`** - Helper and utility components
- **`memories`** - Memory and state management components
- **`prompts`** - Prompt-related components
- **`vector_store`** - Vector database components

### Custom Categories
You can create custom categories for specialized components:
- **`custom`** - Custom business logic components
- **`integration`** - Third-party service integrations
- **`analytics`** - Data analytics components

## Tag Guidelines

Tags help with categorization and search functionality. Use descriptive, lowercase tags:

### Common Tag Patterns
- **Functionality**: `ai`, `chat`, `data`, `file`, `http`, `webhook`
- **Data Types**: `text`, `json`, `csv`, `image`, `audio`
- **Operations**: `input`, `output`, `transform`, `filter`, `aggregate`
- **Providers**: `openai`, `huggingface`, `anthropic`, `google`
- **Use Cases**: `conversation`, `automation`, `analytics`, `reporting`

## Plugin Discovery

The attribute is discovered by the `FlowDropNodeProcessorPluginManager` using attribute-based discovery:

```php
$this->discovery = new AttributeClassDiscovery(
  "Plugin/FlowDropNodeProcessor",
  $namespaces,
  FlowDropNodeProcessor::class
);
```

### Discovery Process
1. **Scan Directories**: Searches `Plugin/FlowDropNodeProcessor` directories in all modules
2. **Attribute Detection**: Identifies classes with `#[FlowDropNodeProcessor]` attribute
3. **Metadata Extraction**: Extracts all attribute parameters as plugin metadata
4. **Caching**: Caches discovered plugins for performance

## Integration with Workflow Editor

The attribute metadata is used by the FlowDrop workflow editor to:

### Frontend Rendering
- **Component Library**: Displays available components organized by category
- **Node Configuration**: Provides configuration forms based on entity config
- **Input/Output Ports**: Renders connection points for connectable parameters
- **Search & Filtering**: Enables component discovery using `$tags`

### API Integration
- **Node Metadata**: Exposes component information via REST API
- **Configuration Validation**: Validates node configurations against schemas
- **Execution Context**: Provides metadata for workflow execution

## Best Practices

### 1. Unique Plugin IDs
```php
// ✅ Good - Descriptive and unique
id: "custom_data_transformer"

// ❌ Bad - Generic and may conflict
id: "processor"
```

### 2. Descriptive Labels
```php
// ✅ Good - Clear and descriptive
label: new TranslatableMarkup("Customer Data Transformer")

// ❌ Bad - Too generic
label: new TranslatableMarkup("Processor")
```

### 3. Appropriate Categories
```php
// ✅ Good - Specific category
category: "processing"

// ❌ Bad - Generic category
category: "other"
```

### 4. Meaningful Tags
```php
// ✅ Good - Descriptive tags
tags: ["data", "transformation", "customer", "business-logic"]

// ❌ Bad - Too generic
tags: ["custom"]
```

### 5. Version Management
```php
// ✅ Good - Semantic versioning
version: "1.2.0"

// ❌ Bad - No version tracking
version: "1.0.0" // Always default
```

### 6. Clear Parameter Schemas
```php
// ✅ Good - Well-defined schema with descriptions and constraints
public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "input_text" => [
        "type" => "string",
        "description" => "Text to process",
        "minLength" => 1,
      ],
      "max_tokens" => [
        "type" => "integer",
        "description" => "Maximum output tokens",
        "default" => 1000,
        "minimum" => 1,
        "maximum" => 4096,
      ],
    ],
  ];
}

// ❌ Bad - Missing descriptions and constraints
public function getParameterSchema(): array {
  return [
    "type" => "object",
    "properties" => [
      "input_text" => ["type" => "string"],
    ],
  ];
}
```

## Error Handling

### Common Issues

1. **Missing Required Parameters**
```php
// ❌ Error - Missing required parameters
#[FlowDropNodeProcessor(
  id: "my_processor",
  label: new TranslatableMarkup("My Processor")
  // Missing type, supportedTypes, and category
)]
```

2. **Invalid Category**
```php
// ❌ Error - Invalid category
category: "invalid_category"
```

3. **Duplicate Plugin IDs**
```php
// ❌ Error - Duplicate ID
id: "text_input" // Already exists in core
```

### Validation
The plugin manager validates:
- Required parameters are present
- Plugin IDs are unique
- Categories are valid
- Attribute syntax is correct

## Testing

### Unit Testing
```php
use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop\DTO\ParameterBag;

class FlowDropNodeProcessorTest extends UnitTestCase {

  public function testPluginDiscovery() {
    $plugin_manager = $this->createMock(FlowDropNodeProcessorPluginManager::class);
    $plugins = $plugin_manager->getDefinitions();

    $this->assertArrayHasKey("my_processor", $plugins);
    $this->assertEquals("processing", $plugins["my_processor"]["category"]);
  }

  public function testNodeExecution() {
    $plugin = $this->createPlugin();
    $params = new ParameterBag([
      "data" => ["key" => "value"],
      "format" => "json",
    ]);

    $output = $plugin->execute($params);

    $this->assertEquals("success", $output->getStatus());
  }
}
```

### Integration Testing
```php
use Drupal\Tests\BrowserTestBase;

class FlowDropNodeProcessorIntegrationTest extends BrowserTestBase {

  public function testPluginInWorkflowEditor() {
    $this->drupalGet("/admin/structure/flowdrop-workflow/foo/flowdrop-editor");
    $this->assertSession()->elementExists("css", "[data-component-id=\"my_processor\"]");
  }
}
```
