<?php
/**
 * Entity Block Model
 * Stores block data for any entity type (page, item, category)
 */
namespace Mtc\Modules\BlockRegistry\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Mtc\Modules\BlockRegistry\Services\EntityBlockService;

class EntityBlock extends Model
{
    protected $table = 'entity_blocks';

    protected $fillable = [
        'entity_type',
        'entity_id',
        'block_id',
        'order',
        'region',
        'data',
        'structure_hash',
        'status',
        'source_block_id',
        'name',
        'preview_image',
    ];

    /**
     * Default region name for blocks without an explicit region.
     */
    const DEFAULT_REGION = 'main';

    /**
     * Status constants
     */
    const STATUS_DRAFT = 'draft';
    const STATUS_PUBLISHED = 'published';

    protected $casts = [
        'entity_id' => 'integer',
        'block_id' => 'integer',
        'order' => 'integer',
        'data' => 'array',
    ];

    /**
     * Get the block definition from the registry
     */
    public function block(): BelongsTo
    {
        return $this->belongsTo(BlockRegistry::class, 'block_id');
    }

    /**
     * Get the source global block (for references)
     */
    public function sourceBlock(): BelongsTo
    {
        return $this->belongsTo(self::class, 'source_block_id');
    }

    /**
     * Get all references pointing to this global block
     */
    public function references(): HasMany
    {
        return $this->hasMany(self::class, 'source_block_id');
    }

    /**
     * Scope: Filter to global blocks only (entity_type = 'global')
     */
    public function scopeGlobal(Builder $query): Builder
    {
        return $query->where('entity_type', 'global')->whereNull('source_block_id');
    }

    /**
     * Check if this is a global block (the source)
     */
    public function isGlobalBlock(): bool
    {
        return $this->entity_type === 'global' && $this->source_block_id === null;
    }

    /**
     * Check if this is a reference to a global block
     */
    public function isGlobalReference(): bool
    {
        return $this->source_block_id !== null;
    }

    /**
     * Get effective data: own data for regular blocks, source data for references
     */
    public function getEffectiveData(): ?array
    {
        if ($this->isGlobalReference()) {
            $source = $this->sourceBlock;
            return $source ? $source->data : null;
        }
        return $this->data;
    }

    /**
     * Get display name: name for global blocks, block type name for regular blocks
     */
    public function getDisplayNameAttribute(): string
    {
        if ($this->name) {
            return $this->name;
        }
        if ($this->relationLoaded('block') && $this->block) {
            return $this->block->name ?? 'Unknown Block';
        }
        return 'Unknown Block';
    }

    /**
     * Scope: Eager-load block definition with fields and source block fields
     */
    public function scopeWithBlockData(Builder $query): Builder
    {
        return $query->with(['block', 'block.fields', 'sourceBlock.block.fields']);
    }

    /**
     * Scope: Filter by entity type and ID
     */
    public function scopeForEntity(Builder $query, string $type, int $id): Builder
    {
        return $query->where('entity_type', $type)->where('entity_id', $id);
    }

    /**
     * Scope: Filter for pages
     */
    public function scopeForPage(Builder $query, int $pageId): Builder
    {
        return $query->forEntity('page', $pageId);
    }

    /**
     * Scope: Order by display order
     */
    public function scopeOrdered(Builder $query): Builder
    {
        return $query->orderBy('order');
    }

    /**
     * Scope: Filter by region. Null/`main` matches both the explicit "main"
     * region and legacy rows where region is NULL.
     */
    public function scopeForRegion(Builder $query, ?string $region): Builder
    {
        if ($region === null || $region === self::DEFAULT_REGION) {
            return $query->where(function (Builder $q) {
                $q->whereNull('region')->orWhere('region', self::DEFAULT_REGION);
            });
        }
        return $query->where('region', $region);
    }

    /**
     * Scope: Filter to published blocks only
     */
    public function scopePublished(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_PUBLISHED);
    }

    /**
     * Scope: Filter to draft blocks only
     */
    public function scopeDraft(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_DRAFT);
    }

    /**
     * Check if this block is published
     */
    public function isPublished(): bool
    {
        return $this->status === self::STATUS_PUBLISHED;
    }

    /**
     * Check if this block is a draft
     */
    public function isDraft(): bool
    {
        return $this->status === self::STATUS_DRAFT;
    }

    /**
     * Publish this block
     */
    public function publish(): self
    {
        $this->status = self::STATUS_PUBLISHED;
        $this->save();
        return $this;
    }

    /**
     * Unpublish this block (set to draft)
     */
    public function unpublish(): self
    {
        $this->status = self::STATUS_DRAFT;
        $this->save();
        return $this;
    }

    /**
     * Get a specific field value from data
     */
    public function getFieldValue(string $fieldName, $default = null)
    {
        $data = $this->data ?? [];
        return $data[$fieldName] ?? $default;
    }

    /**
     * Set a specific field value in data
     */
    public function setFieldValue(string $fieldName, $value): self
    {
        $data = $this->data ?? [];
        $data[$fieldName] = $value;
        $this->data = $data;
        return $this;
    }

    /**
     * Get all field values merged with defaults from block definition
     */
    public function getFieldsWithDefaults(): array
    {
        $block = $this->relationLoaded('block') ? $this->block : $this->block()->with('fields')->first();
        if (!$block) {
            return $this->data ?? [];
        }
        if (!$block->relationLoaded('fields')) {
            $block->load('fields');
        }

        $result = [];
        foreach ($block->fields as $field) {
            $fieldName = $field->name;
            $result[$fieldName] = $this->getFieldValue($fieldName, $field->default_value);
        }

        return $result;
    }

    /**
     * Create initial data structure from block definition
     */
    public static function createInitialData(BlockRegistry $block): array
    {
        $data = [];
        foreach ($block->fields as $field) {
            $data[$field->name] = $field->default_value ?? '';
        }
        return $data;
    }

    /**
     * Selector field types whose values are environment-specific (local IDs).
     */
    const SELECTOR_FIELD_TYPES = ['page_selector', 'product_selector'];

    /**
     * Blank selector-type fields in example_data before seeding a new block.
     *
     * Selector fields store local entity IDs that don't map across
     * environments, so example content cannot supply them — the editor wires
     * them up after prefill. Operates on both top-level fields and repeater
     * sub-fields. Requires `$block` to have `fields.subFields` loaded.
     */
    public static function sanitizeExampleData(array $data, BlockRegistry $block): array
    {
        foreach ($block->fields as $field) {
            $name = $field->name;
            if (!array_key_exists($name, $data)) {
                continue;
            }

            if (in_array($field->type, self::SELECTOR_FIELD_TYPES, true)) {
                $data[$name] = '';
                continue;
            }

            if ($field->type === 'repeater' && is_array($data[$name])) {
                $selectorSubs = $field->subFields
                    ->whereIn('type', self::SELECTOR_FIELD_TYPES)
                    ->pluck('name')
                    ->all();

                if (!empty($selectorSubs)) {
                    $data[$name] = array_map(function ($row) use ($selectorSubs) {
                        if (is_array($row)) {
                            foreach ($selectorSubs as $sub) {
                                if (array_key_exists($sub, $row)) {
                                    $row[$sub] = '';
                                }
                            }
                        }
                        return $row;
                    }, $data[$name]);
                }
            }
        }

        return $data;
    }

    /**
     * Field types whose values are stored as a JSON string in `data` (matching
     * what the admin data editor saves). Repeaters are handled separately.
     */
    const JSON_STRING_FIELD_TYPES = ['link', 'image', 'file'];

    /**
     * Normalize authored example_data (clean nested shape) to the exact storage
     * shape the admin data editor and renderer expect, so a prefilled block is
     * byte-identical to an admin-saved one.
     *
     * Top-level `link`/`image`/`file` objects and `repeater` arrays are
     * JSON-encoded to strings; simple scalar fields pass through unchanged.
     * (Inside a repeater, sub-field objects stay nested — the renderer decodes
     * the outer string then reads nested rows.) Requires `$block` fields loaded.
     */
    public static function normalizeExampleData(array $data, BlockRegistry $block): array
    {
        foreach ($block->fields as $field) {
            $name = $field->name;
            if (!array_key_exists($name, $data)) {
                continue;
            }
            $value = $data[$name];

            if (in_array($field->type, self::JSON_STRING_FIELD_TYPES, true) && is_array($value)) {
                $data[$name] = json_encode($value);
            } elseif ($field->type === 'repeater' && is_array($value)) {
                $data[$name] = json_encode($value);
            }
        }

        return $data;
    }

    /**
     * Get blocks for a page with block definitions loaded
     */
    public static function getBlocksForPage(int $pageId): \Illuminate\Database\Eloquent\Collection
    {
        return self::forPage($pageId)
            ->with(['block', 'block.fields', 'block.category'])
            ->ordered()
            ->get();
    }

    /**
     * Get blocks for any entity type with block definitions loaded
     */
    public static function getBlocksForEntity(string $type, int $id): \Illuminate\Database\Eloquent\Collection
    {
        return self::forEntity($type, $id)
            ->with(['block', 'block.fields', 'block.category'])
            ->ordered()
            ->get();
    }

    /**
     * Get maximum order for an entity's blocks, scoped to a region.
     * Null region means the default ("main") region — which matches both
     * NULL rows and rows with region = 'main'.
     */
    public static function getMaxOrder(string $type, int $id, ?string $region = null): int
    {
        return self::forEntity($type, $id)
            ->forRegion($region)
            ->max('order') ?? -1;
    }

    /**
     * Boot method for model events
     */
    protected static function boot(): void
    {
        parent::boot();

        static::creating(function ($entityBlock) {
            // Auto-set order if not provided — scoped to (entity, region) so
            // each region has its own ordering sequence.
            if ($entityBlock->order === null) {
                $entityBlock->order = self::getMaxOrder(
                    $entityBlock->entity_type,
                    $entityBlock->entity_id,
                    $entityBlock->region
                ) + 1;
            }

            // Initialize data with defaults if empty (skip for global references)
            if (empty($entityBlock->data) && !$entityBlock->source_block_id) {
                $block = BlockRegistry::with('fields')->find($entityBlock->block_id);
                if ($block) {
                    $entityBlock->data = self::createInitialData($block);
                }
            }
        });

        // Clear rendered block cache when any entity block changes
        $clearCache = function ($entityBlock) {
            EntityBlockService::clearCache($entityBlock->entity_type, $entityBlock->entity_id);

            // If this is a global source block, also clear all referencing entities
            if ($entityBlock->entity_type === 'global') {
                foreach ($entityBlock->references as $ref) {
                    EntityBlockService::clearCache($ref->entity_type, $ref->entity_id);
                }
            }
        };

        static::saved($clearCache);
        static::deleted($clearCache);
    }
}
