<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tenant;

/**
 * Which surface the current request is being served on, and where the other
 * one lives.
 *
 * Splitting the admin off onto its own domain means a link can now leave the
 * host it was generated on: "Angebot ansehen" in the admin points at the public
 * site, a reminder mail points back into the admin. This is what
 * {@see \Ferienpass\CoreBundle\Routing\SurfaceAwareUrlGenerator} asks to find
 * out whether that is the case, and which host to send the link to.
 *
 * Outside a request there is no resolved host, so the answers come from the
 * active tenant instead — a cron generating links for a municipality it has
 * activated, not for the host it happens to be configured with.
 */
class SurfaceContext
{
    private ?ResolvedHost $current = null;

    public function __construct(
        private readonly HostResolver $resolver,
        private readonly TenantContext $tenantContext,
    ) {
    }

    public function isEnabled(): bool
    {
        return $this->resolver->isEnabled();
    }

    public function current(): ?ResolvedHost
    {
        return $this->current;
    }

    public function setCurrent(?ResolvedHost $host): void
    {
        $this->current = $host;
    }

    /**
     * Whether admin URLs on the host currently being served carry no `/admin`
     * segment.
     */
    public function adminAtRoot(): bool
    {
        if (!$this->isEnabled()) {
            return false;
        }

        if (null !== $this->current) {
            return $this->current->adminAtRoot;
        }

        // No request: the router context points at the tenant's primary host,
        // which is its public site whenever it has one — and its admin domain
        // when it has not.
        $tenant = $this->tenantContext->getTenant();

        return null !== $tenant && !$tenant->hasFrontend();
    }

    /**
     * The host $surface is served on for the active tenant, or null when the
     * link cannot leave this host — no request context to derive a tenant
     * from, a tenant without a public site, or a single-tenant build, where
     * both surfaces share one host anyway.
     */
    public function hostFor(Surface $surface): ?string
    {
        if (!$this->isEnabled()) {
            return null;
        }

        $tenant = $this->tenantContext->getTenant() ?? $this->current?->tenant;

        return $tenant?->getPrimaryHostFor($surface);
    }

    /**
     * Whether the host currently being served answers $surface itself, so a
     * link to it can stay where it is.
     */
    public function serves(Surface $surface): bool
    {
        if (!$this->isEnabled()) {
            return true;
        }

        if (null !== $this->current) {
            return $this->current->serves($surface);
        }

        $tenant = $this->tenantContext->getTenant();
        if (null === $tenant) {
            return true;
        }

        // No request: the router points at the tenant's primary host.
        return match ($surface) {
            Surface::Frontend => $tenant->hasFrontend(),
            Surface::Admin => !$tenant->hasFrontend() || !$tenant->hasDedicatedAdminHost(),
        };
    }

    public function resolve(string $host): ?ResolvedHost
    {
        return $this->resolver->resolve($host);
    }
}
