<?php

namespace Mtc\Plugins\BlockManager\Services;

use Mtc\Plugins\BlockManager\Support\InstanceClass;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;

/**
 * Renders a composed block (in-memory structure) to HTML for the Composer's
 * live canvas. Each element instance is rendered through its real published
 * variant partial and wrapped in a `data-bm-instance` selector so the canvas
 * iframe can map clicks back to instances.
 *
 * Styling is NOT inlined here — the canvas iframe loads the compiled site
 * app.css (which contains every variant's rules), so components render exactly
 * as they will on the live site. This service only produces the body markup.
 *
 * Pure read service: never persists, never throws on a bad instance (emits a
 * placeholder instead) so the live preview stays resilient while authoring.
 */
class BlockPreviewService
{
    private ?Environment $twig = null;

    public function __construct(
        private ElementCatalogueService $catalogue,
        private ContainerCssCompiler $css,
    ) {
    }

    /**
     * Token-resolved CSS for every (possibly nested) container instance in the
     * structure, scoped to each instance's `.bm-ci-…` class. Injected as a
     * <style> block in the composer canvas so per-instance layout/styling and
     * responsive @media rules render live. Returns '' when nothing is styled.
     *
     * @param array<string, mixed> $structure
     */
    public function collectStyleCss(array $structure): string
    {
        $elements = (is_array($structure['elements'] ?? null)) ? $structure['elements'] : [];
        $parts = [];
        $this->collectFromElements($elements, $parts);
        return implode("\n", $parts);
    }

    /**
     * @param array<int, mixed> $elements
     * @param array<int, string> $parts  appended to by reference
     */
    private function collectFromElements(array $elements, array &$parts): void
    {
        foreach ($elements as $instance) {
            if (!is_array($instance)) {
                continue;
            }
            $type = (string) ($instance['element_type'] ?? '');
            $manifest = $type !== '' ? $this->catalogue->get($type) : null;
            if ($manifest === null || empty($manifest['accepts_children'])) {
                continue;
            }
            $styleProps = is_array($instance['style_props'] ?? null) ? $instance['style_props'] : [];
            if ($styleProps !== []) {
                $selector = '.' . InstanceClass::for((string) ($instance['instance_id'] ?? ''));
                $compiled = $this->css->compile($styleProps, $selector, true);
                if ($compiled['css'] !== '') {
                    $parts[] = $compiled['css'];
                }
            }
            $children = is_array($instance['children'] ?? null) ? $instance['children'] : [];
            $this->collectFromElements($children, $parts);
        }
    }

    /**
     * @param array<string, mixed> $structure  { elements: [ {instance_id, element_type, variant, field_name, default_value?}, … ] }
     */
    public function render(array $structure): string
    {
        $elements = (is_array($structure['elements'] ?? null)) ? $structure['elements'] : [];

        $html = '';
        foreach ($elements as $instance) {
            if (!is_array($instance)) {
                continue;
            }
            $instanceId = (string) ($instance['instance_id'] ?? '');
            $html .= '<div data-bm-instance="' . htmlspecialchars($instanceId, ENT_QUOTES) . '" class="bm-pv-el">'
                . $this->renderInstance($instance)
                . '</div>';
        }

        if ($html === '') {
            $html = '<div class="bm-pv-empty">Add an element from the left to begin.</div>';
        }

        return $html;
    }

    /**
     * @param array<string, mixed> $instance
     */
    private function renderInstance(array $instance): string
    {
        $type = (string) ($instance['element_type'] ?? '');
        $manifest = $type !== '' ? $this->catalogue->get($type) : null;
        if ($manifest === null) {
            return $this->placeholder("Unknown element: {$type}");
        }

        $variant = (string) ($instance['variant'] ?? ($manifest['default_variant'] ?? 'default'));
        $fields = $this->sampleFields($manifest, $instance);

        // Container elements receive their children as pre-rendered HTML, plus
        // a stable per-instance class so their generated layout/style CSS
        // (collected separately in collectStyleCss()) has a scope to target.
        if (!empty($manifest['accepts_children'])) {
            $parentId = (string) ($instance['instance_id'] ?? '');
            $fields['children_html'] = $this->renderChildren(
                (array) ($instance['children'] ?? []),
                $parentId
            );
            $fields['instance_class'] = InstanceClass::for($parentId);
        }

        try {
            return $this->twig()->render("{$type}/variants/{$variant}.twig", $fields);
        } catch (\Throwable $e) {
            return $this->placeholder("{$type}/{$variant} could not render");
        }
    }

    /**
     * Render the child instances nested inside a container element.
     * Each child is wrapped with data-bm-instance + data-bm-parent so the canvas
     * can map clicks to instances even inside nested containers.
     *
     * @param array<int, mixed> $children
     */
    private function renderChildren(array $children, string $parentId): string
    {
        if ($children === []) {
            return '<div class="bm-container__empty">Drop elements here.</div>';
        }

        $html = '';
        foreach ($children as $child) {
            if (!is_array($child)) {
                continue;
            }
            $childId = (string) ($child['instance_id'] ?? '');
            $html .= '<div data-bm-instance="' . htmlspecialchars($childId, ENT_QUOTES) . '"'
                . ' data-bm-parent="' . htmlspecialchars($parentId, ENT_QUOTES) . '"'
                . ' class="bm-pv-el">'
                . $this->renderInstance($child)
                . '</div>';
        }

        return $html;
    }

    /**
     * A dedicated Twig environment rooted at the element catalogue, so variant
     * partials resolve by their short path ("button/variants/primary.twig")
     * in every context — web, CLI and the per-test container (where the global
     * app('twig') binding isn't carried across createApplication()).
     */
    private function twig(): Environment
    {
        if ($this->twig === null) {
            $loader = new FilesystemLoader([base_path('plugins/BlockManager/elements')]);
            $this->twig = new Environment($loader, ['cache' => false, 'autoescape' => 'html']);
        }
        return $this->twig;
    }

    /**
     * Build a field bag for the partial: instance default_value first, then the
     * manifest's declared default, then a type-based placeholder so the partial
     * never renders blank or errors on a missing variable.
     *
     * @param array<string, mixed> $manifest
     * @param array<string, mixed> $instance
     * @return array<string, mixed>
     */
    private function sampleFields(array $manifest, array $instance): array
    {
        $defaultValue = $instance['default_value'] ?? null;
        $perName = (is_array($defaultValue)) ? $defaultValue : [];

        $fields = [];
        foreach ($manifest['inputs'] ?? [] as $input) {
            $name = $input['name'] ?? null;
            if (!is_string($name) || $name === '') {
                continue;
            }
            if (array_key_exists($name, $perName) && $perName[$name] !== null && $perName[$name] !== '') {
                $fields[$name] = $perName[$name];
                continue;
            }
            if (array_key_exists('default', $input) && $input['default'] !== null && $input['default'] !== '') {
                $fields[$name] = $input['default'];
                continue;
            }
            $fields[$name] = $this->sampleFor($input, $manifest['type'] ?? '');
        }

        // Scalar default_value (e.g. a single-field element seeded with a string).
        if (is_string($defaultValue) && $defaultValue !== '' && isset($manifest['inputs'][0]['name'])) {
            $fields[$manifest['inputs'][0]['name']] = $defaultValue;
        }

        return $fields;
    }

    /**
     * @param array<string, mixed> $input
     */
    private function sampleFor(array $input, string $elementType): mixed
    {
        $type = (string) ($input['type'] ?? 'text');
        $name = (string) ($input['name'] ?? '');

        if (!empty($input['options']) && is_array($input['options'])) {
            return $input['options'][0];
        }

        return match ($type) {
            'link'            => ['url' => '#', 'title' => 'Sample link'],
            'image'           => '/public/build/assets/placeholder.png',
            'textarea'        => 'Sample paragraph text — the quick brown fox jumps over the lazy dog.',
            'checkbox'        => false,
            'select'          => '',
            default           => $this->sampleText($name, $elementType),
        };
    }

    private function sampleText(string $name, string $elementType): string
    {
        return match (true) {
            $name === 'label'    => 'Button label',
            $name === 'level'    => 'h2',
            $name === 'summary'  => 'Summary',
            $name === 'caption'  => 'Sample table',
            $name === 'headers'  => 'Column A, Column B',
            $name === 'rows'     => "Row 1A | Row 1B\nRow 2A | Row 2B",
            $name === 'alt'      => 'Sample image',
            $elementType === 'heading' => 'Sample Heading',
            default              => 'Sample text',
        };
    }

    private function placeholder(string $message): string
    {
        return '<div class="bm-pv-placeholder" style="padding:12px;border:1px dashed #cbd5e1;color:#64748b;font:13px sans-serif;">'
            . htmlspecialchars($message, ENT_QUOTES)
            . '</div>';
    }
}
