<?php

declare(strict_types=1);

namespace App\Domain\Blog;

use App\Domain\Plugin\Hook;
use App\Models\Article;
use App\Models\User;
use Illuminate\Support\Facades\Session;

class ArticleAccess
{
    /**
     * Pure decision — no HTTP side effects.
     *
     * @param  list<int>|null  $unlockedArticleIds  session unlock list; null = read from session
     */
    public function resolve(Article $article, ?User $user = null, ?array $unlockedArticleIds = null): AccessDecision
    {
        if ($user !== null && $this->isPrivileged($article, $user)) {
            return AccessDecision::allow();
        }

        if (filled($article->read_password)) {
            $unlocked = $unlockedArticleIds ?? (array) Session::get('unlocked_articles', []);
            if (! in_array($article->id, $unlocked, true)) {
                return AccessDecision::needPassword();
            }
        }

        $decision = AccessDecision::allow();
        $context = [
            'article' => $article,
            'user' => $user,
        ];

        // Fold every listener result instead of letting the last one win, so a
        // plugin can only tighten access and a broken listener cannot re-open it.
        foreach (Hook::listeners('article.access') as $listener) {
            $result = $listener($decision, $context);

            if ($result instanceof AccessDecision) {
                $decision = $decision->tightenWith($result);
            }
        }

        return $decision;
    }

    /**
     * HTML safe to expose for the given decision (full body or teaser/description).
     */
    public function publicHtml(Article $article, AccessDecision $decision): string
    {
        if ($decision->isAllow()) {
            return $article->renderedHtml();
        }

        if (filled($decision->teaserHtml)) {
            return $decision->teaserHtml;
        }

        return e((string) ($article->description ?: ''));
    }

    protected function isPrivileged(Article $article, User $user): bool
    {
        if ((int) $article->user_id === (int) $user->id) {
            return true;
        }

        return $user->hasRole('admin');
    }
}
