# EntityEngine

> Generic entity type registry. Seeded once via `CrmEntityTypeSeeder`.
> Admin UI (CRUD for entity types) is Phase 3.2.

---

## Migrations

### `entity_types`
```php
Schema::create('entity_types', function (Blueprint $table) {
    $table->id();
    $table->string('slug')->unique();       // 'lead', 'deal', 'contact'
    $table->string('name');
    $table->string('model_class');          // fully qualified class name
    $table->json('capabilities')->nullable(); // ['has_pipeline','has_custom_fields',...]
    $table->timestamps();
});
```

### `entity_relationships`
```php
Schema::create('entity_relationships', function (Blueprint $table) {
    $table->id();
    $table->foreignId('from_entity_type_id')->constrained('entity_types');
    $table->foreignId('to_entity_type_id')->constrained('entity_types');
    $table->string('relationship_type');    // 'converts_to' | 'links_to'
    $table->timestamps();
});
```

---

## Seed Data: `AdminApp/data/crm/entity_types.json`

```json
{
  "entity_types": [
    {
      "slug": "lead",
      "name": "Lead",
      "model_class": "CrmApp\\Lead\\Models\\Lead",
      "capabilities": ["has_pipeline","has_custom_fields","has_activity_timeline","fires_workflow_events"]
    },
    {
      "slug": "deal",
      "name": "Deal",
      "model_class": "CrmApp\\Deal\\Models\\Deal",
      "capabilities": ["has_pipeline","has_custom_fields","has_activity_timeline","fires_workflow_events"]
    },
    {
      "slug": "contact",
      "name": "Contact",
      "model_class": "App\\Models\\Contact",
      "capabilities": ["has_custom_fields","has_activity_timeline"]
    }
  ],
  "entity_relationships": [
    { "from": "lead", "to": "deal", "relationship_type": "converts_to" }
  ]
}
```

> **Per-domain override:** the default `lead → converts_to → deal` row above ships in the base seed.
> A pack JSON can override this for its profile (e.g. `pharma_pack.json` could ship `lead → converts_to → sample_visit` once that entity exists). The override flow:
> 1. Pack JSON declares its own `entity_relationships[]` block keyed by `from = 'lead'`.
> 2. `FeaturePackSeeder::seedEntityRelationships()` upserts on `(from_entity_type_id, to_entity_type_id, relationship_type)`.
> 3. `EntityRegistry::convertsTo('lead')` returns the target model class for the active tenant — pharma → SampleVisit, education → Deal, etc.

---

## Seeder: `CrmEntityTypeSeeder`

```php
// CoreApp/database/seeders/CrmEntityTypeSeeder.php
namespace CoreApp\Database\Seeders;

use CoreApp\Models\EntityEngine\{EntityType, EntityRelationship};
use Illuminate\Database\Seeder;

class CrmEntityTypeSeeder extends Seeder
{
    public function run(): void
    {
        $data = json_decode(
            file_get_contents(base_path('AdminApp/data/crm/entity_types.json')),
            true
        );

        foreach ($data['entity_types'] ?? [] as $type) {
            EntityType::updateOrCreate(['slug' => $type['slug']], $type);
        }

        foreach ($data['entity_relationships'] ?? [] as $rel) {
            $from = EntityType::where('slug', $rel['from'])->firstOrFail();
            $to   = EntityType::where('slug', $rel['to'])->firstOrFail();
            EntityRelationship::updateOrCreate(
                [
                    'from_entity_type_id' => $from->id,
                    'to_entity_type_id'   => $to->id,
                    'relationship_type'   => $rel['relationship_type'],
                ],
                []
            );
        }
    }
}
```

---

## Service: `EntityRegistry`

```php
// CoreApp/app/Services/EntityEngine/EntityRegistry.php
namespace CoreApp\Services\EntityEngine;

use CoreApp\Models\EntityEngine\{EntityType, EntityRelationship};

class EntityRegistry
{
    private array $cache = [];

    public function all(): \Illuminate\Support\Collection
    {
        return EntityType::all();
    }

    public function forSlug(string $slug): ?EntityType
    {
        return $this->cache[$slug] ??= EntityType::where('slug', $slug)->first();
    }

    public function modelClass(string $slug): string
    {
        return $this->forSlug($slug)?->model_class
            ?? throw new \InvalidArgumentException("Unknown entity type: {$slug}");
    }

    public function hasCapability(string $slug, string $capability): bool
    {
        $type = $this->forSlug($slug);
        return in_array($capability, $type?->capabilities ?? []);
    }

    /**
     * Resolve the target model class for a 'converts_to' relationship.
     *
     * Used by LeadService::convert() to discover what to create — Deal for general/education,
     * potentially SampleVisit for pharma, etc. Returns null if no relationship registered.
     *
     * @return class-string|null  fully qualified target model class
     */
    public function convertsTo(string $fromSlug): ?string
    {
        $from = $this->forSlug($fromSlug);
        if (!$from) return null;

        $rel = EntityRelationship::where('from_entity_type_id', $from->id)
            ->where('relationship_type', 'converts_to')
            ->with('toEntityType')
            ->first();

        return $rel?->toEntityType?->model_class;
    }
}
```

Bind in `CoreAppServiceProvider::register()`:
```php
$this->app->singleton(EntityRegistry::class);
```

The `EntityRelationship` model needs the relation:
```php
// CoreApp/app/Models/EntityEngine/EntityRelationship.php
public function fromEntityType(): BelongsTo { return $this->belongsTo(EntityType::class, 'from_entity_type_id'); }
public function toEntityType():   BelongsTo { return $this->belongsTo(EntityType::class, 'to_entity_type_id'); }
```
