# Processor Pattern

## Overview

The Processor Pattern is used to handle complex business logic that involves evaluating conditions and executing actions. It separates the evaluation logic from the action execution, making the code more maintainable and testable.

## Benefits

- **Separation of Concerns**: Conditions and actions are handled separately
- **Extensibility**: Easy to add new operators and action types
- **Testability**: Each processor can be tested independently
- **Flexibility**: Complex rules can be built from simple components

## Use Case: Automation Rules

The Building Management System uses processors for automation rules that trigger actions based on model state changes.

## Implementation Structure

```
app/Domains/Core/AutomationRule/Services/
├── ConditionProcessor.php    # Evaluates conditions
├── ActionProcessor.php       # Executes actions
└── AutomationRuleServices.php # Orchestrates processors
```

## Condition Processor

**Location**: `app/Domains/Core/AutomationRule/Services/ConditionProcessor.php`

```php
<?php

namespace App\Domains\Core\AutomationRule\Services;

class ConditionProcessor
{
    /**
     * Evaluate conditions against a model
     *
     * @param array $conditions Condition configuration
     * @param mixed $model The model to evaluate against
     * @return bool Whether conditions are met
     */
    public function evaluate(array $conditions, $model): bool
    {
        // Handle simple single condition
        if (isset($conditions['field'], $conditions['operator'], $conditions['value'])) {
            return $this->evaluateSingleCondition(
                $conditions['field'],
                $conditions['operator'],
                $conditions['value'],
                $model
            );
        }

        // Handle compound conditions (AND/OR)
        if (isset($conditions['logic'])) {
            return $this->evaluateLogicGroup(
                $conditions['logic'],
                $conditions['conditions'],
                $model
            );
        }

        return false;
    }

    /**
     * Evaluate a single condition
     */
    protected function evaluateSingleCondition(
        string $field,
        string $operator,
        $value,
        $model
    ): bool {
        $actualValue = data_get($model, $field);

        return match ($operator) {
            '=' => $actualValue == $value,
            '!=' => $actualValue != $value,
            '>' => $actualValue > $value,
            '>=' => $actualValue >= $value,
            '<' => $actualValue < $value,
            '<=' => $actualValue <= $value,
            'in' => in_array($actualValue, (array) $value),
            'not_in' => !in_array($actualValue, (array) $value),
            'contains' => str_contains((string) $actualValue, (string) $value),
            'starts_with' => str_starts_with((string) $actualValue, (string) $value),
            'ends_with' => str_ends_with((string) $actualValue, (string) $value),
            'exists' => !empty($actualValue),
            'not_exists' => empty($actualValue),
            'between' => $this->evaluateBetween($actualValue, $value),
            'regex' => preg_match($value, (string) $actualValue) === 1,
            default => false,
        };
    }

    /**
     * Evaluate a logic group (AND/OR)
     */
    protected function evaluateLogicGroup(
        string $logic,
        array $conditions,
        $model
    ): bool {
        $results = array_map(
            fn($condition) => $this->evaluate($condition, $model),
            $conditions
        );

        return match (strtoupper($logic)) {
            'AND' => !in_array(false, $results, true),
            'OR' => in_array(true, $results, true),
            default => false,
        };
    }

    /**
     * Evaluate between condition
     */
    protected function evaluateBetween($actualValue, array $range): bool
    {
        if (count($range) !== 2) {
            return false;
        }

        [$min, $max] = $range;
        return $actualValue >= $min && $actualValue <= $max;
    }

    /**
     * Get all supported operators
     */
    public function getSupportedOperators(): array
    {
        return [
            '=' => 'Equals',
            '!=' => 'Not Equals',
            '>' => 'Greater Than',
            '>=' => 'Greater Than or Equal',
            '<' => 'Less Than',
            '<=' => 'Less Than or Equal',
            'in' => 'In Array',
            'not_in' => 'Not In Array',
            'contains' => 'Contains String',
            'starts_with' => 'Starts With',
            'ends_with' => 'Ends With',
            'exists' => 'Exists (Not Empty)',
            'not_exists' => 'Not Exists (Empty)',
            'between' => 'Between Range',
            'regex' => 'Matches Regex',
        ];
    }
}
```

## Action Processor

**Location**: `app/Domains/Core/AutomationRule/Services/ActionProcessor.php`

```php
<?php

namespace App\Domains\Core\AutomationRule\Services;

use App\Domains\Core\Task\Services\TaskServices;
use App\Domains\Core\User\Services\RoleServices;
use App\Notifications\AutomationActionNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

class ActionProcessor
{
    public function __construct(
        private TaskServices $taskServices,
        private RoleServices $roleServices
    ) {}

    /**
     * Execute a list of actions
     */
    public function execute(array $actions, $model, array $conditions, string $event): void
    {
        foreach ($actions as $action) {
            try {
                $this->executeSingleAction($action, $model, $conditions, $event);
            } catch (\Throwable $e) {
                Log::error('Automation action failed', [
                    'action' => $action,
                    'model' => get_class($model),
                    'model_id' => $model->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    /**
     * Execute a single action
     */
    protected function executeSingleAction(
        array $action,
        $model,
        array $conditions,
        string $event
    ): void {
        $actionType = $action['type'] ?? 'task';

        match ($actionType) {
            'task' => $this->createTask($action, $model, $conditions, $event),
            'notification' => $this->sendNotification($action, $model),
            'email' => $this->sendEmail($action, $model),
            'webhook' => $this->callWebhook($action, $model),
            'update_field' => $this->updateField($action, $model),
            'log' => $this->logAction($action, $model),
            default => throw new \InvalidArgumentException("Unknown action type: {$actionType}"),
        };
    }

    /**
     * Create a task action
     */
    protected function createTask(array $action, $model, array $conditions, string $event): void
    {
        $description = $this->generateAutomationDescription([
            'model_type' => class_basename($model),
            'event' => $event,
            'conditions' => $conditions,
        ]);

        $taskData = [
            'title' => $action['title'] ?? 'Automated Task',
            'description' => $description,
            'priority' => $action['priority'] ?? 'normal',
            'due_date' => $action['due_date'] ?? now()->addDays(1),
            'related_type' => get_class($model),
            'related_id' => $model->id,
        ];

        // Assign to users based on recipient type
        $userIds = $this->getRecipientUserIds($action);

        foreach ($userIds as $userId) {
            $this->taskServices->create(array_merge($taskData, ['user_id' => $userId]));
        }
    }

    /**
     * Send notification action
     */
    protected function sendNotification(array $action, $model): void
    {
        $userIds = $this->getRecipientUserIds($action);
        $users = \App\Models\User::whereIn('id', $userIds)->get();

        Notification::send($users, new AutomationActionNotification(
            $action['message'] ?? 'Automation triggered',
            $model
        ));
    }

    /**
     * Send email action
     */
    protected function sendEmail(array $action, $model): void
    {
        $userIds = $this->getRecipientUserIds($action);
        $users = \App\Models\User::whereIn('id', $userIds)->get();

        foreach ($users as $user) {
            \Mail::to($user->email)->queue(
                new \App\Mail\AutomationActionMail($action, $model)
            );
        }
    }

    /**
     * Call webhook action
     */
    protected function callWebhook(array $action, $model): void
    {
        $url = $action['url'];
        $method = strtoupper($action['method'] ?? 'POST');

        $payload = [
            'model_type' => get_class($model),
            'model_id' => $model->id,
            'data' => $model->toArray(),
            'triggered_at' => now()->toISOString(),
        ];

        \Http::withHeaders($action['headers'] ?? [])
            ->timeout(30)
            ->$method($url, $payload);
    }

    /**
     * Update field action
     */
    protected function updateField(array $action, $model): void
    {
        $field = $action['field'];
        $value = $this->resolveValue($action['value'], $model);

        $model->update([$field => $value]);
    }

    /**
     * Log action
     */
    protected function logAction(array $action, $model): void
    {
        Log::channel($action['channel'] ?? 'automation')->info(
            $action['message'] ?? 'Automation action executed',
            [
                'model_type' => get_class($model),
                'model_id' => $model->id,
                'action' => $action,
            ]
        );
    }

    /**
     * Get recipient user IDs based on action configuration
     */
    protected function getRecipientUserIds(array $action): array
    {
        $recipientType = $action['recipient_type'] ?? 'user';

        return match ($recipientType) {
            'user' => [$action['user_id']],
            'users' => $action['user_ids'] ?? [],
            'role' => $this->getUsersFromRole($action['role_id']),
            'field' => [$this->getFieldValue($action['field'])],
            default => [],
        };
    }

    /**
     * Get users from a role
     */
    protected function getUsersFromRole(int $roleId): array
    {
        $role = $this->roleServices->find($roleId)->load('customUsers');
        return $role->customUsers->pluck('id')->toArray();
    }

    /**
     * Resolve dynamic value
     */
    protected function resolveValue($value, $model)
    {
        if (is_string($value) && str_starts_with($value, '{{') && str_ends_with($value, '}}')) {
            $field = trim($value, '{}');
            return data_get($model, $field);
        }

        return $value;
    }

    /**
     * Generate automation description
     */
    protected function generateAutomationDescription(array $context): string
    {
        return sprintf(
            "Automated task created for %s on %s event. Conditions: %s",
            $context['model_type'],
            $context['event'],
            json_encode($context['conditions'])
        );
    }

    /**
     * Get all supported action types
     */
    public function getSupportedActionTypes(): array
    {
        return [
            'task' => 'Create Task',
            'notification' => 'Send Notification',
            'email' => 'Send Email',
            'webhook' => 'Call Webhook',
            'update_field' => 'Update Field',
            'log' => 'Log Message',
        ];
    }
}
```

## Orchestration Service

**Location**: `app/Domains/Core/AutomationRule/Services/AutomationRuleServices.php`

```php
<?php

namespace App\Domains\Core\AutomationRule\Services;

use App\Domains\Core\AutomationRule\Models\AutomationRule;

class AutomationRuleServices
{
    public function __construct(
        private ConditionProcessor $conditionProcessor,
        private ActionProcessor $actionProcessor
    ) {}

    /**
     * Execute automation rules for a model event
     */
    public function executeRules($model, string $event): void
    {
        // Find applicable rules
        $rules = AutomationRule::query()
            ->where('automatable_type', get_class($model))
            ->where('trigger_status', $event)
            ->where('is_active', true)
            ->get();

        foreach ($rules as $rule) {
            $this->executeRule($rule, $model, $event);
        }
    }

    /**
     * Execute a single automation rule
     */
    protected function executeRule(AutomationRule $rule, $model, string $event): void
    {
        // Evaluate conditions
        if (!$this->conditionProcessor->evaluate($rule->conditions, $model)) {
            return;
        }

        // Execute actions
        $this->actionProcessor->execute(
            $rule->actions,
            $model,
            $rule->conditions,
            $event
        );

        // Log execution
        $rule->executions()->create([
            'model_type' => get_class($model),
            'model_id' => $model->id,
            'event' => $event,
            'executed_at' => now(),
        ]);
    }
}
```

## Approval Action Handler

Another processor example for approval workflows:

**Location**: `app/Domains/Accounting/ApprovalSetting/Services/ApprovalActionHandler.php`

```php
<?php

namespace App\Domains\Accounting\ApprovalSetting\Services;

use App\Domains\Accounting\ApprovalSetting\Models\ApprovalRecord;
use App\Domains\Accounting\ApprovalSetting\Models\ApprovalSetting;
use App\Domains\Accounting\ApprovalSetting\Models\TraditionalApprove;
use Illuminate\Support\Facades\Log;

class ApprovalActionHandler
{
    public function __construct(
        private ApprovalRecordService $recordService
    ) {}

    /**
     * Check and move to next approval level
     */
    public function checkAndMoveToNextLevel(
        TraditionalApprove $approval,
        ApprovalSetting $setting
    ): void {
        // Parallel execution doesn't need level advancement
        if ($setting->approval_path === 'parallel') {
            return;
        }

        if (!$this->recordService->isCurrentLevelCompleted($approval)) {
            return;
        }

        $currentRecord = ApprovalRecord::where('approval_id', $approval->id)
            ->where('is_current_level', true)
            ->first();

        if (!$currentRecord || $currentRecord->status !== 'approved') {
            return;
        }

        $movedToNext = $this->recordService->moveToNextLevel($approval);

        if (!$movedToNext) {
            Log::info("All approval levels completed", [
                'approval_id' => $approval->id,
            ]);
        }
    }

    /**
     * Check if all approval records are approved
     */
    public function areAllApproved(
        TraditionalApprove $approval,
        ApprovalSetting $setting
    ): bool {
        $records = ApprovalRecord::where('approval_id', $approval->id)->get();

        if ($records->isEmpty()) {
            return false;
        }

        return $records->every(fn($record) => $record->status === 'approved');
    }

    /**
     * Process approval action
     */
    public function processApproval(
        TraditionalApprove $approval,
        int $userId,
        string $action,
        ?string $comment = null
    ): void {
        $record = ApprovalRecord::where('approval_id', $approval->id)
            ->where('user_id', $userId)
            ->where('is_current_level', true)
            ->firstOrFail();

        $record->update([
            'status' => $action,
            'comment' => $comment,
            'responded_at' => now(),
        ]);

        $setting = $approval->setting;

        match ($action) {
            'approved' => $this->handleApproval($approval, $setting),
            'rejected' => $this->handleRejection($approval, $setting),
            default => null,
        };
    }

    protected function handleApproval(
        TraditionalApprove $approval,
        ApprovalSetting $setting
    ): void {
        $this->checkAndMoveToNextLevel($approval, $setting);

        if ($this->areAllApproved($approval, $setting)) {
            $approval->update(['status' => 'approved']);
            $this->executeApprovalActions($approval);
        }
    }

    protected function handleRejection(
        TraditionalApprove $approval,
        ApprovalSetting $setting
    ): void {
        $approval->update(['status' => 'rejected']);
        $this->executeRejectionActions($approval);
    }

    protected function executeApprovalActions(TraditionalApprove $approval): void
    {
        // Execute post-approval actions
        event(new \App\Events\ApprovalCompleted($approval));
    }

    protected function executeRejectionActions(TraditionalApprove $approval): void
    {
        // Execute post-rejection actions
        event(new \App\Events\ApprovalRejected($approval));
    }
}
```

## Usage Example

```php
<?php

// In an Observer or Event Listener
class InvoiceObserver
{
    public function __construct(
        private AutomationRuleServices $automationService
    ) {}

    public function created(Invoice $invoice): void
    {
        // Execute automation rules for 'created' event
        $this->automationService->executeRules($invoice, 'created');
    }

    public function updated(Invoice $invoice): void
    {
        // Execute automation rules for status changes
        if ($invoice->wasChanged('status')) {
            $this->automationService->executeRules(
                $invoice,
                'status_changed_to_' . $invoice->status
            );
        }
    }
}
```

## Condition Configuration Example

```json
{
    "logic": "AND",
    "conditions": [
        {
            "field": "total_amount",
            "operator": ">",
            "value": 10000
        },
        {
            "logic": "OR",
            "conditions": [
                {
                    "field": "customer.type",
                    "operator": "=",
                    "value": "vip"
                },
                {
                    "field": "priority",
                    "operator": "=",
                    "value": "high"
                }
            ]
        }
    ]
}
```

## Best Practices

### Do

- Keep processors focused on single responsibility
- Use dependency injection for services
- Handle exceptions gracefully
- Log processor executions for debugging
- Make processors testable with clear inputs/outputs

### Don't

- Mix condition evaluation with action execution
- Hard-code operators or action types
- Skip error handling in action execution
- Create circular processor dependencies

## Related Patterns

- [Strategy Pattern](STRATEGY_PATTERN.md) - Different evaluation strategies
- [Service Pattern](SERVICE_PATTERN.md) - Processors used by services
- [Event Pattern](EVENT_PATTERN.md) - Processors triggered by events
