<?php

declare(strict_types=1);

namespace App\Http\Resources;

use App\Models\Listing;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

/**
 * A listing as a buyer sees it.
 *
 * Every field here is one the API commits to. Three things are deliberately
 * absent, and each of them is a decision rather than an omission:
 *
 *  - The seller's minimum offer. Publish a floor and it becomes the price
 *    everybody offers, which is the same as not having one.
 *  - Suspension state and reason. Section 28 keeps enforcement signals away
 *    from normal users entirely.
 *  - The database primary key. Listings are addressed by their domain name,
 *    which is what a buyer already knows and what the URL should read as.
 *
 * Requires `domain.tld` and `seller` to be loaded. Lazy loading is prevented
 * application-wide, so a caller that forgets fails loudly in development rather
 * than quietly running a query per card in production.
 *
 * @mixin Listing
 */
class ListingResource extends JsonResource
{
    /** @return array<string, mixed> */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->public_id,
            'headline' => $this->headline(),
            'description' => $this->description,

            'type' => $this->type->value,
            'type_label' => $this->type->label(),
            'status' => $this->status->value,
            'status_label' => $this->status->label(),

            /*
             * Null for a make-offer listing, which publishes no price on
             * purpose. A zero would be a lie and a placeholder would break every
             * price filter, so the absence is carried through to the client.
             */
            'price' => $this->askingPrice(),

            'accepts_offers' => $this->type->allowsOffers() && $this->status->acceptsInterest(),
            'is_buy_now' => $this->type->allowsInstantPurchase() && $this->status->acceptsInterest(),

            /*
             * The effective value, not the stored flag. Placement is bought for
             * a period, and honouring a flag left set past `featured_until`
             * would give away promotion nobody paid for.
             */
            'is_featured' => $this->isCurrentlyFeatured(),

            'domain' => [
                'name' => $this->domain->name,
                'label' => $this->domain->label,
                'length' => (int) $this->domain->label_length,
                'tld' => $this->domain->tld->tld,
                'tld_display' => $this->domain->tld->displayTld(),
                'is_premium_tld' => (bool) $this->domain->tld->is_premium,
                'has_hyphen' => (bool) $this->domain->has_hyphen,
                'has_digit' => (bool) $this->domain->has_digit,

                /*
                 * The marketplace's central promise, stated on every card rather
                 * than only on the detail page. A listing cannot be published
                 * without it, so this is always true on a public surface -- and
                 * it is sent explicitly anyway, because a client should not have
                 * to infer the guarantee from the absence of a warning.
                 */
                'ownership_verified' => $this->domain->isVerified(),

                /*
                 * When a handover could actually complete. Surfaced before
                 * anyone commits, for the same reason the TLD resource states
                 * its lock: a 60-day wait discovered after payment is a dispute.
                 */
                'transfer_available_from' => $this->domain->transfer_lock_until?->toIso8601String(),

                /*
                 * Whether that lock is still in force, decided here rather than
                 * by comparing the date on the client.
                 *
                 * The server holds the authoritative clock. A visitor whose
                 * device clock is wrong -- or simply in a timezone that flips
                 * the comparison -- would otherwise be told a lapsed lock is
                 * active, or worse, that an active one is not.
                 */
                'is_transfer_locked' => $this->domain->transfer_lock_until !== null
                    && $this->domain->transfer_lock_until->isFuture(),
            ],

            'category' => $this->whenLoaded('category', fn (): ?array => $this->category === null ? null : [
                'slug' => $this->category->slug,
                'name' => $this->category->name,
            ]),

            'seller' => new PublicSellerResource($this->seller),

            'views_count' => (int) $this->views_count,
            'offers_count' => (int) $this->offers_count,
            'watchers_count' => (int) $this->watchers_count,

            'published_at' => $this->published_at?->toIso8601String(),
        ];
    }
}
