<?php

declare(strict_types=1);

namespace App\Framework\Router;

use App\Framework\Core\PathProvider;
use App\Framework\DI\DefaultContainer;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Responses\RedirectResponse;
use App\Framework\Http\Responses\SseResponse;
use App\Framework\Http\Responses\WebSocketResponse;
use App\Framework\Router\Result\FileResult;
use App\Framework\Router\Result\JsonResult;
use App\Framework\Router\Result\Redirect;
use App\Framework\Router\Result\SseResult;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Router\Result\WebSocketResult;
use App\Framework\View\RenderContext;
use App\Framework\View\Template;
use App\Framework\View\TemplateRenderer;

final readonly class RouteResponder
{
    public function __construct(
        private PathProvider     $pathProvider,
        private DefaultContainer $container,
        private TemplateRenderer $templateRenderer,
        private Request $request,
    ) {
    }

    public function getContext(ViewResult $result): RenderContext
    {
        return new RenderContext(
            template: $result->model ? $this->resolveTemplate($result->model) : $result->template,
            metaData: $result->metaData,
            data: $result->model ? get_object_vars($result->model) + $result->data : $result->data,
            layout: '',
            slots: $result->slots,
            isPartial: $this->isSpaRequest(),
        );
    }

    public function respond(Response|ActionResult $result): Response
    {
        if ($result instanceof Response) {
            return $result;
        }

        if ($this->isSpaRequest() && $result instanceof ViewResult) {
            return $this->createSpaResponse($result);
        }

        return match(true) {
            $result instanceof ViewResult => new HttpResponse(
                status: $result->status,
                body: $this->renderTemplate($this->getContext($result))
            ),
            $result instanceof JsonResult => new JsonResponse(
                body   : $result->data,
                status : $result->status,
            ),
            $result instanceof Redirect => new RedirectResponse($result->target),
            $result instanceof SseResult => new SseResponse($result, $result->callback),
            $result instanceof WebSocketResult => $this->createWebSocketResponse($result),
            $result instanceof FileResult => new HttpResponse(
                #headers: new Headers()->with('Content-Type', $result->mimeType),
                body: $result->filePath
            ),

            default => throw new \RuntimeException('Unbekanntes Ergebnisobjekt: ' . get_class($result)),
        };
    }

    private function createWebSocketResponse(WebSocketResult $result): WebSocketResponse
    {
        $websocketKey = $this->request->server->getWebSocketKey()
            ?? throw new \RuntimeException('WebSocket key missing in request');

        return new WebSocketResponse($result, $websocketKey);
    }

    private function renderTemplate(RenderContext $context): string
    {
        return $this->templateRenderer->render($context);
    }

    private function isSpaRequest(): bool
    {
        return $this->request->server->isXmlHttpRequest()
            && $this->request->server->isSpaRequest();
    }

    private function createSpaResponse(ViewResult $result): JsonResponse
    {
        $context = $this->getContext($result);

        return new JsonResponse([
            'html' => $this->templateRenderer->renderPartial($context),
            'title' => $result->metaData->title ?? '',
            'meta' => [
                'description' => $result->metaData->description ?? '',
                // Add other meta data as needed
            ],
        ]);
    }

    private function resolveTemplate(?object $model): string
    {
        if ($model === null) {
            return 'test';
        }

        $ref = new \ReflectionClass($model);
        $attrs = $ref->getAttributes(Template::class);
        if ($attrs === []) {
            return 'test';
            #throw new \RuntimeException("Fehlendes #[Template] Attribut in {$ref->getName()}");
        }

        /** @var Template $attr */
        $attr = $attrs[0]->newInstance();

        return $attr->path;
    }
}
