<?php
/**
 * Entity Block Service
 * Frontend helper for loading and rendering entity blocks
 */
namespace Mtc\Modules\BlockRegistry\Services;

use Mtc\Modules\BlockRegistry\Models\EntityBlock;
use Mtc\Modules\BlockRegistry\Models\BlockRegistry;
use Mtc\Shop\Category;
use App\Http\Controllers\CategoryController;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Twig\Environment;

class EntityBlockService
{
    /**
     * Twig environment for rendering templates
     */
    protected ?Environment $twig = null;

    /**
     * Set the Twig environment for rendering
     */
    public function setTwig(Environment $twig): self
    {
        $this->twig = $twig;
        return $this;
    }

    /**
     * Get the Twig environment (lazy load from app container if not set)
     */
    protected function getTwig(): ?Environment
    {
        if ($this->twig === null) {
            try {
                $this->twig = app('twig');
            } catch (\Exception $e) {
                Log::warning('Could not get Twig environment: ' . $e->getMessage());
                return null;
            }
        }
        return $this->twig;
    }

    /**
     * Get all entity blocks for any entity type (published only on frontend)
     */
    public function getBlocksForEntity(string $entityType, int $entityId): array
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->withBlockData()
            ->ordered()
            ->get();

        // Resolve global references: swap in source data, filter out broken refs
        $resolved = $entityBlocks->filter(function ($entityBlock) {
            if ($entityBlock->isGlobalReference()) {
                $source = $entityBlock->sourceBlock;
                if (!$source || $source->isDraft()) {
                    return false;
                }
                // Use source block's data and block definition for formatting
                $entityBlock->data = $source->data;
                $entityBlock->setRelation('block', $source->block);
            }
            return true;
        });

        return $this->formatBlocksForTemplate($resolved);
    }

    /**
     * Format entity blocks for template use
     * Structure: block_slug => [ { field_name => value, ... }, ... ]
     */
    protected function formatBlocksForTemplate(Collection $entityBlocks): array
    {
        $result = [];

        foreach ($entityBlocks as $entityBlock) {
            if (!$entityBlock->block) {
                continue;
            }

            $blockSlug = $entityBlock->block->slug;
            $blockName = $entityBlock->block->name;
            $data = $entityBlock->data ?? [];

            // Build formatted data with field metadata
            $formattedData = $this->formatFieldValues($entityBlock->block->fields, $data, $entityBlock)['formatted'];

            // Group by block slug (allows multiple instances of same block type)
            if (!isset($result[$blockSlug])) {
                $result[$blockSlug] = [];
            }
            $result[$blockSlug][] = $formattedData;

            // Also index by block name for convenience
            if ($blockSlug !== $blockName) {
                if (!isset($result[$blockName])) {
                    $result[$blockName] = [];
                }
                $result[$blockName][] = $formattedData;
            }
        }

        return $result;
    }

    /**
     * Get a single entity block by ID
     */
    public function getBlock(int $entityBlockId): ?array
    {
        $entityBlock = EntityBlock::with(['block', 'block.fields'])->find($entityBlockId);
        if (!$entityBlock) {
            return null;
        }

        $formatted = $this->formatBlocksForTemplate(collect([$entityBlock]));
        return $formatted[$entityBlock->block->slug][0] ?? null;
    }

    /**
     * Check if an entity has any blocks
     */
    public function hasBlocks(string $entityType, int $entityId): bool
    {
        return EntityBlock::forEntity($entityType, $entityId)->exists();
    }

    /**
     * Get raw entity block models for an entity
     */
    public function getRawBlocks(string $entityType, int $entityId): Collection
    {
        return EntityBlock::getBlocksForEntity($entityType, $entityId);
    }

    /**
     * Get count of blocks for an entity
     */
    public function getBlockCount(string $entityType, int $entityId): int
    {
        return EntityBlock::forEntity($entityType, $entityId)->count();
    }

    /**
     * Render a single entity block using its linked template
     * Returns HTML string or null if no template configured
     */
    public function renderBlock(EntityBlock $entityBlock, array $extraContext = []): ?string
    {
        // Handle global references: resolve to source block
        if ($entityBlock->isGlobalReference()) {
            $source = $entityBlock->sourceBlock;
            if (!$source || $source->isDraft()) {
                return null;
            }
            $source->loadMissing(['block.fields']);
            return $this->renderBlock($source, $extraContext);
        }

        $block = $entityBlock->block;

        if (!$block || !$block->hasTemplate()) {
            return null;
        }

        $twig = $this->getTwig();
        if (!$twig) {
            return null;
        }

        $templatePath = $block->getTemplatePath();

        // Prepare data in the format templates expect
        $templateData = $this->prepareBlockDataForTemplate($entityBlock);

        // Merge any extra context (e.g. class overrides) from the caller
        if (!empty($extraContext)) {
            $templateData = array_merge($templateData, $extraContext);
        }

        try {
            return $twig->render($templatePath, $templateData);
        } catch (\Exception $e) {
            Log::error("Failed to render block template {$templatePath}: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Render a block's authored example content for the /example-blocks and
     * /component previews.
     *
     * Uses `example_data` (one sample instance in entity_blocks.data storage
     * shape, read from the block's JSON file) hydrated through the real render
     * path, so the preview is identical to a published instance. Falls back to
     * the template's inline `show_dummy_data` when no example_data is authored
     * yet — this lets blocks migrate one at a time without breaking previews.
     *
     * @param string      $slug          Block definition slug
     * @param string|null $themeOverride Forced section theme (white|grey|dark)
     */
    public function renderExample(string $slug, ?string $themeOverride = null): string
    {
        // Mirror ComponentRendererService::renderBlockExample(): only active
        // blocks are previewable, so inactive slugs render nothing here too.
        $block = BlockRegistry::with('fields')
            ->where('is_active', true)
            ->where('slug', $slug)
            ->first();
        if (!$block || !$block->hasTemplate()) {
            return '';
        }

        $extraContext = $themeOverride !== null ? ['theme_override' => $themeOverride] : [];

        $exampleData = app(BlockSyncService::class)->getExampleData($slug);
        if ($exampleData !== null) {
            $entityBlock = new EntityBlock([
                'entity_type' => 'page',
                'entity_id'   => 0,
                'block_id'    => $block->id,
                'data'        => EntityBlock::normalizeExampleData($exampleData, $block),
                'status'      => EntityBlock::STATUS_PUBLISHED,
            ]);
            $entityBlock->setRelation('block', $block);

            $rendered = $this->renderBlock($entityBlock, $extraContext);
            if ($rendered !== null) {
                return $rendered;
            }
            // Primary render failed (null) — fall through to the inline
            // show_dummy_data fallback below rather than returning blank.
        }

        // Fallback: template still owns its inline `show_dummy_data` block.
        $twig = $this->getTwig();
        if (!$twig) {
            return '';
        }
        try {
            return $twig->render(
                $block->getTemplatePath(),
                array_merge(['show_dummy_data' => true], $extraContext)
            );
        } catch (\Exception $e) {
            Log::error("Failed to render example block {$slug}: " . $e->getMessage());
            return '';
        }
    }

    /**
     * Render all entity blocks for an entity that have templates configured
     * Returns concatenated HTML of all rendered blocks (published only).
     *
     * If $region is provided, only blocks in that region render; otherwise
     * all regions are rendered in order. The "main" region also matches
     * legacy rows where region IS NULL.
     */
    public function renderEntityBlocks(string $entityType, int $entityId, ?string $region = null): string
    {
        $cacheKey = self::cacheKey($entityType, $entityId, $region);
        $ttl = config('cache.block_registry.ttl', 3600);

        if (config('cache.block_registry.enabled', true)) {
            $cached = Cache::get($cacheKey);
            if ($cached !== null) {
                return $cached;
            }
        }

        $query = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->withBlockData()
            ->ordered();

        if ($region !== null) {
            $query->forRegion($region);
        }

        $entityBlocks = $query->get();

        $extraContext = $this->resolveEntityContext($entityType, $entityId);
        $output = '';

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock, $extraContext);
            if ($rendered !== null) {
                $output .= $rendered;
            }
        }

        if (config('cache.block_registry.enabled', true)) {
            Cache::put($cacheKey, $output, $ttl);
        }

        return $output;
    }

    /**
     * Resolve entity object from type and ID for block template context
     */
    protected function resolveEntityContext(string $entityType, int $entityId): array
    {
        $context = [
            'entity_type' => $entityType,
            'entity_id' => $entityId,
        ];

        switch ($entityType) {
            case 'category':
                $category = Category::find($entityId);
                if ($category) {
                    $context['category'] = $category;
                    $context['items_doctor'] = CategoryController::getDisplayItems($category, ['doctor']);
                    $context['items_standard'] = CategoryController::getDisplayItems($category, ['general', 'pharmacy']);
                }
                break;
        }

        return $context;
    }

    /**
     * Render entity blocks of a specific block type (published only)
     */
    public function renderBlocksByType(string $entityType, int $entityId, string $blockSlug): string
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->published()
            ->whereHas('block', function ($q) use ($blockSlug) {
                $q->where('slug', $blockSlug);
            })
            ->withBlockData()
            ->ordered()
            ->get();

        $output = '';

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock);
            if ($rendered !== null) {
                $output .= $rendered;
            }
        }

        return $output;
    }

    /**
     * Prepare block data in the format templates expect
     * Templates typically access: block_data['Title']['value'] or p.pagedata['Block'][0]['Field']['value']
     */
    protected function prepareBlockDataForTemplate(EntityBlock $entityBlock): array
    {
        $block = $entityBlock->block;
        $data = $entityBlock->data ?? [];

        // Build formatted data matching existing template expectations
        $fieldValues = $this->formatFieldValues($block->fields, $data, $entityBlock);
        $formattedFields = $fieldValues['formatted'];
        $simpleFields = $fieldValues['simple'];

        // Build slides array for slideshow-type templates
        // Maps repeater data to the format slideshow.twig expects
        $slides = $this->buildSlidesFromRepeater($formattedFields, $simpleFields);

        // Return multiple access patterns for flexibility
        return [
            // The block definition
            'block' => $block,
            'block_name' => $block->name,
            'block_slug' => $block->slug,

            // Primary data access
            'block_data' => $formattedFields,
            'fields' => $simpleFields,

            // Slides array for slideshow templates
            'slides' => $slides,

            // Pagedata-compatible nested format
            'p' => [
                'pagedata' => [
                    $block->name => [$formattedFields],
                    $block->slug => [$formattedFields],
                ]
            ],
        ];
    }

    /**
     * Format field values from a block's field definitions and data
     * Returns both 'formatted' (with metadata) and 'simple' (raw values) arrays
     */
    private function formatFieldValues(Collection $fields, array $data, ?EntityBlock $entityBlock = null): array
    {
        $formatted = [];
        $simple = [];

        foreach ($fields as $field) {
            $fieldName = $field->name;
            $rawValue = $data[$fieldName] ?? $field->default_value ?? '';

            if ($field->type === 'image' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $imageData = json_decode($rawValue, true);
                if (is_array($imageData)) {
                    $formatted[$fieldName] = $this->formatImageField($imageData, $field);
                    $simple[$fieldName] = $imageData['url'] ?? '';
                    continue;
                }
            }

            if ($field->type === 'file' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $fileData = json_decode($rawValue, true);
                if (is_array($fileData)) {
                    // Unwrap multi-encoded url values (from prior double-encoding bug)
                    $url = $fileData['url'] ?? '';
                    while (is_string($url) && str_starts_with($url, '{')) {
                        $nested = json_decode($url, true);
                        if (is_array($nested) && isset($nested['url'])) {
                            $url = $nested['url'];
                        } else {
                            break;
                        }
                    }
                    $fileData['url'] = $url;

                    $formatted[$fieldName] = array_merge($fileData, [
                        'value' => $url,
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                        'required' => $field->required,
                    ]);
                    $simple[$fieldName] = $url;
                    continue;
                }
            }

            if ($field->type === 'link' && is_string($rawValue) && str_starts_with($rawValue, '{')) {
                $linkData = json_decode($rawValue, true);
                if (is_array($linkData)) {
                    $url = $this->rewriteAssessmentUrl($linkData['url'] ?? '', $entityBlock);
                    $linkData['url'] = $url;
                    $formatted[$fieldName] = [
                        'value' => $url,
                        'url' => $url,
                        'title' => $linkData['title'] ?? '',
                        'target' => $linkData['target'] ?? '',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                        'required' => $field->required,
                    ];
                    $simple[$fieldName] = $linkData;
                    continue;
                }
            }

            if ($field->type === 'repeater' && is_string($rawValue) && str_starts_with($rawValue, '[')) {
                $result = $this->formatRepeaterField($rawValue, $field);
                if ($result !== null) {
                    $formatted[$fieldName] = $result;
                    $simple[$fieldName] = $result['rows'];
                    continue;
                }
            }

            if ($field->type === 'page_selector') {
                $settings = is_string($field->settings) ? json_decode($field->settings, true) : ($field->settings ?? []);
                $psConfig = $settings['page_selector_settings'] ?? [];
                $mode = $psConfig['mode'] ?? 'manual';

                if ($mode === 'auto') {
                    $containerId = (int) $rawValue ?: null;
                    $limit = $psConfig['limit'] ?? 8;
                    $pages = $this->fetchPagesForAutoMode($containerId, $limit);
                    $formatted[$fieldName] = [
                        'value' => $pages,
                        'pages' => $pages,
                        'mode' => 'auto',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                    ];
                    $simple[$fieldName] = $pages;
                } else {
                    $pageId = (int) $rawValue;
                    $pageData = $pageId ? $this->resolvePageData($pageId) : null;
                    $formatted[$fieldName] = [
                        'value' => $pageId,
                        'page_id' => $pageId,
                        'page' => $pageData,
                        'mode' => 'manual',
                        'type' => $field->type,
                        'label' => $field->label ?? $field->name,
                    ];
                    $simple[$fieldName] = $pageData;
                }
                continue;
            }

            $formatted[$fieldName] = $this->formatStandardField($rawValue, $field);
            $simple[$fieldName] = $rawValue;
        }

        return ['formatted' => $formatted, 'simple' => $simple];
    }

    /**
     * If the URL points at an assessment form and the block is hosted on a
     * category or product, append the matching query param so the assessment
     * knows what it's being launched from. Returns the URL unchanged if it
     * already has a query string or isn't an assessment link.
     */
    private function rewriteAssessmentUrl(string $url, ?EntityBlock $entityBlock): string
    {
        if ($url === '' || $entityBlock === null || $entityBlock->entity_id <= 0) {
            return $url;
        }

        if (!preg_match('#^/assessment/\d+/?$#', $url)) {
            return $url;
        }

        $param = match ($entityBlock->entity_type) {
            'category' => 'c',
            'item' => 'p',
            default => null,
        };

        if ($param === null) {
            return $url;
        }

        return rtrim($url, '/') . '?' . $param . '=' . $entityBlock->entity_id;
    }

    /**
     * Format an image field from decoded JSON data
     */
    private function formatImageField(array $imageData, $field): array
    {
        $result = [
            'value' => $imageData['url'] ?? '',
            'url' => $imageData['url'] ?? '',
            'alt' => $imageData['alt'] ?? '',
            'crop' => $imageData['crop'] ?? null,
            'srcset' => $imageData['srcset'] ?? '',
            'processed' => $imageData['processed'] ?? [],
            'original_width' => $imageData['original_width'] ?? null,
            'original_height' => $imageData['original_height'] ?? null,
        ];

        // Add field metadata when a field definition is provided
        if (is_object($field)) {
            $result['type'] = $field->type;
            $result['label'] = $field->label ?? $field->name;
            $result['required'] = $field->required;
        }

        return $result;
    }

    /**
     * Format a repeater field from raw JSON string
     * Returns null if the JSON is invalid
     */
    private function formatRepeaterField(string $rawValue, $field): ?array
    {
        $repeaterData = json_decode($rawValue, true);
        if (!is_array($repeaterData)) {
            return null;
        }

        $formattedRows = [];
        foreach ($repeaterData as $row) {
            $formattedRow = [];
            foreach ($row as $subFieldName => $subFieldValue) {
                if (is_array($subFieldValue) && array_key_exists('url', $subFieldValue) && array_key_exists('title', $subFieldValue)) {
                    // Link sub-field (uses array_key_exists to detect even when values are null)
                    $formattedRow[$subFieldName] = [
                        'value' => $subFieldValue['url'] ?? '',
                        'url' => $subFieldValue['url'] ?? '',
                        'title' => $subFieldValue['title'] ?? '',
                        'target' => $subFieldValue['target'] ?? '',
                    ];
                } elseif (is_array($subFieldValue) && array_key_exists('url', $subFieldValue)) {
                    // Image/file sub-field (uses array_key_exists to detect even when url is null)
                    $formattedRow[$subFieldName] = [
                        'value' => $subFieldValue['url'] ?? '',
                        'url' => $subFieldValue['url'] ?? '',
                        'alt' => $subFieldValue['alt'] ?? '',
                        'crop' => $subFieldValue['crop'] ?? null,
                        'srcset' => $subFieldValue['srcset'] ?? '',
                        'processed' => $subFieldValue['processed'] ?? [],
                        'original_width' => $subFieldValue['original_width'] ?? null,
                        'original_height' => $subFieldValue['original_height'] ?? null,
                    ];
                } elseif (is_numeric($subFieldValue) && $this->isPageSelectorSubField($field, $subFieldName)) {
                    // Page selector sub-field — resolve to full page data
                    $pageData = $this->resolvePageData((int) $subFieldValue);
                    $formattedRow[$subFieldName] = [
                        'value' => (int) $subFieldValue,
                        'page_id' => (int) $subFieldValue,
                        'page' => $pageData,
                    ];
                } else {
                    // Safety: if value is an unexpected array, extract a string to prevent
                    // "Array to string conversion" errors in Twig templates
                    $formattedRow[$subFieldName] = [
                        'value' => is_array($subFieldValue)
                            ? ($subFieldValue['value'] ?? $subFieldValue['url'] ?? '')
                            : $subFieldValue,
                    ];
                }
            }
            $formattedRows[] = $formattedRow;
        }

        return [
            'rows' => $formattedRows,
            'count' => count($formattedRows),
            'type' => $field->type,
            'label' => $field->label ?? $field->name,
        ];
    }

    /**
     * Check if a sub-field is a page_selector type
     */
    private function isPageSelectorSubField($field, string $subFieldName): bool
    {
        // Try eager-loaded relation first
        if ($field->relationLoaded('subFields')) {
            return $field->subFields->contains(function ($sf) use ($subFieldName) {
                return $sf->name === $subFieldName && $sf->type === 'page_selector';
            });
        }

        // Fall back to a query if not eager-loaded
        return $field->subFields()
            ->where('name', $subFieldName)
            ->where('type', 'page_selector')
            ->exists();
    }

    /**
     * Resolve a page ID to full page data including pagedata
     */
    private function resolvePageData(int $pageId): ?array
    {
        try {
            $page = new \Page($pageId);
            if (!$page->id) {
                return null;
            }
            return [
                'id' => $page->id,
                'title' => $page->title,
                'url' => $page->url,
                'pagedata' => $page->pagedata ?? [],
            ];
        } catch (\Exception $e) {
            Log::warning("BlockRegistry: Failed to resolve page {$pageId}: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Fetch pages from a listing container for auto mode page_selector
     */
    private function fetchPagesForAutoMode(?int $containerId, int $limit): array
    {
        if (!$containerId) {
            return [];
        }
        try {
            $childIds = \Mtc\Cms\Models\Page::query()
                ->where('sub_id', $containerId)
                ->where('type', 'default')
                ->where('published', 1)
                ->orderByDesc('id')
                ->limit($limit)
                ->pluck('id')
                ->toArray();

            $pages = [];
            foreach ($childIds as $id) {
                $page = new \Page($id);
                if ($page->id) {
                    $pages[] = [
                        'id' => $page->id,
                        'title' => $page->title,
                        'url' => $page->url,
                        'pagedata' => $page->pagedata ?? [],
                    ];
                }
            }
            return $pages;
        } catch (\Exception $e) {
            Log::warning("BlockRegistry: Failed to fetch auto pages for container {$containerId}: " . $e->getMessage());
            return [];
        }
    }

    /**
     * Format a standard (non-image, non-repeater) field
     */
    private function formatStandardField($rawValue, $field): array
    {
        return [
            'value' => $rawValue,
            'type' => $field->type,
            'label' => $field->label ?? $field->name,
            'required' => $field->required,
        ];
    }

    /**
     * Build slides array from repeater field data
     * Converts repeater rows to the format slideshow.twig expects
     */
    protected function buildSlidesFromRepeater(array $formattedFields, array $simpleFields): array
    {
        $slides = [];

        // Look for repeater fields and convert to slides format
        foreach ($formattedFields as $fieldName => $fieldData) {
            if (isset($fieldData['type']) && $fieldData['type'] === 'repeater' && isset($fieldData['rows'])) {
                foreach ($fieldData['rows'] as $row) {
                    $slide = [];

                    // Map common field names to slideshow expected keys
                    foreach ($row as $subFieldName => $subFieldData) {
                        $value = $subFieldData['value'] ?? '';
                        $lowerName = strtolower($subFieldName);

                        // Map field names to slideshow expected properties
                        if (in_array($lowerName, ['image', 'img', 'photo', 'picture'])) {
                            $slide['image'] = $value;
                            if (isset($subFieldData['alt'])) {
                                $slide['image_alt'] = $subFieldData['alt'];
                            }
                            // Pass through full image data for responsive_image()
                            if (isset($subFieldData['srcset']) && !empty($subFieldData['srcset'])) {
                                $slide['image_data'] = $subFieldData;
                            }
                        } elseif (in_array($lowerName, ['title', 'heading', 'headline'])) {
                            $slide['title'] = $value;
                        } elseif (in_array($lowerName, ['description', 'desc', 'content', 'text', 'body'])) {
                            $slide['description'] = $value;
                        } elseif (in_array($lowerName, ['link', 'url', 'href'])) {
                            $slide['link'] = $value;
                        } elseif (in_array($lowerName, ['link_text', 'linktext', 'button', 'button_text', 'cta'])) {
                            $slide['link_text'] = $value;
                        } else {
                            // Pass through any other fields with original name
                            $slide[$subFieldName] = $value;
                        }
                    }

                    if (!empty($slide)) {
                        $slides[] = $slide;
                    }
                }

                // Only process the first repeater field as slides
                break;
            }
        }

        return $slides;
    }

    /**
     * Get all global blocks with reference counts
     */
    public function getGlobalBlocks(): Collection
    {
        return EntityBlock::global()
            ->with(['block', 'block.fields', 'block.category'])
            ->withCount('references')
            ->ordered()
            ->get();
    }

    /**
     * Add a global block reference to an entity (page, item, etc.)
     */
    public function addGlobalBlockReference(int $globalBlockId, string $entityType, int $entityId, ?string $region = null): EntityBlock
    {
        $globalBlock = EntityBlock::findOrFail($globalBlockId);

        return EntityBlock::create([
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'block_id' => $globalBlock->block_id,
            'source_block_id' => $globalBlock->id,
            'region' => $region,
            'status' => EntityBlock::STATUS_PUBLISHED,
        ]);
    }

    /**
     * Promote a populated entity block to a new global block, converting the
     * original placement into a reference so the entity keeps its content
     * but is now driven from the global record.
     */
    public function promoteToGlobal(EntityBlock $original, string $name): EntityBlock
    {
        return DB::transaction(function () use ($original, $name) {
            $global = EntityBlock::create([
                'entity_type' => 'global',
                'entity_id'   => 0,
                'block_id'    => $original->block_id,
                'data'        => $original->data,
                'name'        => $name,
                'status'      => EntityBlock::STATUS_PUBLISHED,
            ]);

            $original->source_block_id = $global->id;
            $original->data = null;
            $original->save();

            return $global;
        });
    }

    /**
     * Get entity blocks with their rendered HTML where templates exist
     */
    public function getBlocksWithRendering(string $entityType, int $entityId): array
    {
        $entityBlocks = EntityBlock::forEntity($entityType, $entityId)
            ->withBlockData()
            ->ordered()
            ->get();

        $result = [];

        foreach ($entityBlocks as $entityBlock) {
            $rendered = $this->renderBlock($entityBlock);

            $result[] = [
                'id' => $entityBlock->id,
                'block_id' => $entityBlock->block_id,
                'block_name' => $entityBlock->block->name,
                'block_slug' => $entityBlock->block->slug,
                'template' => $entityBlock->block->template,
                'has_template' => $entityBlock->block->hasTemplate(),
                'data' => $entityBlock->data,
                'rendered_html' => $rendered,
            ];
        }

        return $result;
    }

    /**
     * Build the cache key for an entity's rendered blocks. Region is
     * normalised to "all" when null so cache keys are deterministic.
     */
    public static function cacheKey(string $entityType, int $entityId, ?string $region = null): string
    {
        $regionKey = $region ?? 'all';
        return "block_html:{$entityType}:{$entityId}:{$regionKey}";
    }

    /**
     * Clear the rendered block cache for an entity. Forgets the no-region
     * "all" key plus a key per region the entity might have rendered into:
     * regions currently in DB, regions the entity's template declares, and
     * the implicit "main" default. Belt-and-braces because a row may have
     * just been deleted (so its region is gone from DB but still in cache).
     */
    public static function clearCache(string $entityType, int $entityId): void
    {
        // Combined-render cache (no region argument)
        Cache::forget(self::cacheKey($entityType, $entityId, null));

        $regions = collect();

        // Regions currently in DB for this entity
        $regions = $regions->merge(
            \Mtc\Modules\BlockRegistry\Models\EntityBlock::query()
                ->where('entity_type', $entityType)
                ->where('entity_id', $entityId)
                ->pluck('region')
                ->map(fn($r) => $r === null ? \Mtc\Modules\BlockRegistry\Models\EntityBlock::DEFAULT_REGION : $r)
        );

        // Regions the entity's template declares (covers cache entries from
        // a region whose last row was just deleted)
        $templateName = \Mtc\Modules\BlockRegistry\Services\TemplateRegionRegistry::resolveTemplateName($entityType, $entityId);
        if ($templateName) {
            $regions = $regions->merge(
                \Mtc\Modules\BlockRegistry\Services\TemplateRegionRegistry::regionsFor($templateName)
            );
        }

        // Always include the default region
        $regions->push(\Mtc\Modules\BlockRegistry\Models\EntityBlock::DEFAULT_REGION);

        foreach ($regions->unique() as $region) {
            Cache::forget(self::cacheKey($entityType, $entityId, $region));
        }
    }

    /**
     * Clear all rendered block caches across all entities. Builds the full
     * key set from a single distinct query so this stays one DB round-trip
     * regardless of how many entities/regions exist.
     */
    public static function clearAllBlockCaches(): void
    {
        $rows = \Mtc\Modules\BlockRegistry\Models\EntityBlock::query()
            ->select('entity_type', 'entity_id', 'region')
            ->distinct()
            ->get();

        $entities = [];
        foreach ($rows as $row) {
            $key = $row->entity_type . ':' . $row->entity_id;
            if (!isset($entities[$key])) {
                $entities[$key] = [
                    'type' => $row->entity_type,
                    'id' => (int) $row->entity_id,
                    'regions' => [],
                ];
            }
            $region = $row->region ?? \Mtc\Modules\BlockRegistry\Models\EntityBlock::DEFAULT_REGION;
            $entities[$key]['regions'][$region] = true;
        }

        foreach ($entities as $entity) {
            // Combined-render key (no region scope)
            Cache::forget(self::cacheKey($entity['type'], $entity['id'], null));

            // Always include the default region in case a region's last row
            // was just deleted but its cache key still exists.
            $entity['regions'][\Mtc\Modules\BlockRegistry\Models\EntityBlock::DEFAULT_REGION] = true;

            foreach (array_keys($entity['regions']) as $region) {
                Cache::forget(self::cacheKey($entity['type'], $entity['id'], $region));
            }
        }
    }
}
