<?php

namespace Mtc\Plugins\BlockManager\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Mtc\Plugins\BlockManager\Models\BlockManagerVariantDraft;
use Mtc\Plugins\BlockManager\Services\ElementCatalogueService;
use Mtc\Plugins\BlockManager\Services\VariantConflictDetector;
use Mtc\Plugins\BlockManager\Services\VariantImportService;
use Mtc\Plugins\BlockManager\Services\VariantPreviewService;

/**
 * CRUD over BlockManagerVariantDraft — the Designer's working-state store.
 *
 * Extends Illuminate\Routing\Controller directly (not the project base) so the
 * JSON endpoints don't run admin-session bootstrap. Same pattern as
 * BlockManagerController, ElementCatalogueController, etc.
 */
class VariantDesignerController extends Controller
{
    public function __construct(
        private ElementCatalogueService $catalogue,
        private VariantPreviewService $preview,
        private VariantImportService $importService,
        private VariantConflictDetector $conflictDetector,
    ) {
    }

    public function index(Request $request): JsonResponse
    {
        $query = BlockManagerVariantDraft::orderBy('id');

        $elementType = $request->query('element_type');
        if (is_string($elementType) && $elementType !== '') {
            $query->where('element_type', $elementType);
        }

        return response()->json([
            'data' => $query->get()->all(),
        ]);
    }

    /**
     * Surfaces VariantConflictDetector::detect() as a JSON endpoint so the
     * Designer SPA can render a banner when the on-disk LESS has drifted
     * from what the generator would emit (typically a hand-edit between publishes).
     */
    public function conflict(int $id): JsonResponse
    {
        $draft = BlockManagerVariantDraft::findOrFail($id);
        return response()->json(['data' => $this->conflictDetector->detect($draft)]);
    }

    public function show(int $id): JsonResponse
    {
        $draft = BlockManagerVariantDraft::findOrFail($id);
        return response()->json(['data' => $draft]);
    }

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'element_type'    => ['required', 'string', $this->elementTypeRule()],
            'variant_slug'    => [
                'required',
                'string',
                'max:128',
                Rule::unique('block_manager_variant_drafts')
                    ->where('element_type', $request->input('element_type')),
            ],
            'label'           => 'sometimes|nullable|string|max:255',
            'properties_json' => 'sometimes|nullable|array',
        ]);

        $draft = BlockManagerVariantDraft::create([
            'uuid'            => (string) Str::uuid(),
            'element_type'    => $data['element_type'],
            'variant_slug'    => $data['variant_slug'],
            'label'           => $data['label'] ?? null,
            'properties_json' => $data['properties_json'] ?? null,
        ]);

        return response()->json(['data' => $draft], 201);
    }

    public function update(Request $request, int $id): JsonResponse
    {
        $draft = BlockManagerVariantDraft::findOrFail($id);

        $data = $request->validate([
            'element_type' => ['sometimes', 'string', $this->elementTypeRule()],
            'variant_slug' => [
                'sometimes',
                'string',
                'max:128',
                Rule::unique('block_manager_variant_drafts')
                    ->ignore($draft->id)
                    ->where('element_type', $request->input('element_type') ?? $draft->element_type),
            ],
            'label'           => 'sometimes|nullable|string|max:255',
            'properties_json' => 'sometimes|nullable|array',
        ]);

        $draft->fill($data)->save();

        return response()->json(['data' => $draft]);
    }

    public function destroy(int $id): JsonResponse
    {
        $draft = BlockManagerVariantDraft::findOrFail($id);
        $draft->delete();
        return response()->json(null, 204);
    }

    /**
     * Render the draft (or an in-memory override of its properties) to HTML+CSS
     * for the Designer's live preview canvas. Never persists.
     */
    public function preview(Request $request, int $id): JsonResponse
    {
        $draft = BlockManagerVariantDraft::findOrFail($id);

        $override = $request->input('properties_json');
        $sample = $request->input('sample_content');
        $state = $request->input('state');
        $breakpoint = $request->input('breakpoint');

        $result = $this->preview->render(
            $draft,
            is_array($override) ? $override : null,
            (is_string($sample) && $sample !== '') ? $sample : null,
            is_string($state) ? $state : null,
            is_string($breakpoint) ? $breakpoint : null,
        );

        return response()->json(['data' => $result]);
    }

    /**
     * Re-open a published variant. Designer-authored variants (those carrying
     * a @designer-meta header in their LESS file) return a draft id so the
     * Designer can keep editing. Foreign variants (no header) are flagged
     * read-only so the SPA can disable the property panels.
     */
    public function importExisting(Request $request): JsonResponse
    {
        $data = $request->validate([
            'element_type' => 'required|string',
            'variant_slug' => 'required|string',
        ]);

        $result = $this->importService->importExisting(
            $data['element_type'],
            $data['variant_slug'],
        );

        return response()->json(['data' => $result]);
    }

    /**
     * Closure rule that validates an element_type against the live catalogue.
     * Reject unknown types early (422) rather than letting them fail at ship time.
     */
    private function elementTypeRule(): \Closure
    {
        $catalogue = $this->catalogue;
        return function ($attribute, $value, $fail) use ($catalogue) {
            if (!is_string($value) || $catalogue->get($value) === null) {
                $fail("Unknown element type: '" . (is_scalar($value) ? $value : gettype($value)) . "'.");
            }
        };
    }
}
