<?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\Application;

use Symfony\Component\Uid\Uuid;

/**
 * The vocabulary of an offer's extra application questions
 * ({@see \Ferienpass\CoreBundle\Entity\Offer\OfferInterface::getApplicationExtra()}):
 * the answer types, and — for `file` questions — which kinds of file the admin
 * allowed.
 *
 * Everything that reads or writes an extra question (entity normalisation, the
 * admin editor, both application forms, the exports) shares these lists so a new
 * type cannot be half-supported.
 */
final class ExtraQuestion
{
    public const TYPE_YES_NO = 'yn';
    public const TYPE_NUMBER = 'int';
    public const TYPE_TEXT = 'txt';
    public const TYPE_FILE = 'file';

    /** @var list<string> */
    public const TYPES = [self::TYPE_YES_NO, self::TYPE_NUMBER, self::TYPE_TEXT, self::TYPE_FILE];

    public const KIND_IMAGE = 'image';
    public const KIND_PDF = 'pdf';

    /** @var list<string> */
    public const KINDS = [self::KIND_IMAGE, self::KIND_PDF];

    /** Upload ceiling for a single answer. Well below the 15 MB inbound-mail cap: these are phone photos and certificates, not archives. */
    public const MAX_BYTES = 10 * 1024 * 1024;

    /**
     * Filename extensions accepted per kind. HEIC is in the image list on
     * purpose: it is what an iPhone hands over when a photo is picked from the
     * library rather than taken with the camera.
     *
     * @var array<string, list<string>>
     */
    private const EXTENSIONS = [
        self::KIND_IMAGE => ['jpg', 'jpeg', 'png', 'webp', 'heic'],
        self::KIND_PDF => ['pdf'],
    ];

    /**
     * Coerce a stored/submitted type to a supported one. Anything unknown becomes
     * free text — the same fallback the entity has always applied.
     */
    public static function type(mixed $type): string
    {
        return \in_array($type, self::TYPES, true) ? $type : self::TYPE_TEXT;
    }

    /**
     * The kinds a `file` question accepts, as a stable, de-duplicated list. An
     * empty or unrecognised selection means "everything we support", so a
     * mis-saved question stays answerable instead of rejecting every upload.
     *
     * @return list<string>
     */
    public static function kinds(mixed $kinds): array
    {
        $kinds = \is_array($kinds) ? array_values(array_intersect(self::KINDS, $kinds)) : [];

        return [] !== $kinds ? $kinds : self::KINDS;
    }

    /**
     * @param list<string> $kinds
     *
     * @return list<string>
     */
    public static function extensions(array $kinds): array
    {
        $extensions = [];
        foreach (self::kinds($kinds) as $kind) {
            $extensions = [...$extensions, ...self::EXTENSIONS[$kind]];
        }

        return array_values(array_unique($extensions));
    }

    /**
     * The `accept` attribute for the file input. `image/*` (rather than the exact
     * image MIME list) is deliberate: it is what makes mobile browsers offer the
     * camera alongside the file picker. The real gate is server-side anyway
     * ({@see \Ferienpass\CoreBundle\Comment\InboundAttachmentPolicy}).
     *
     * @param list<string> $kinds
     */
    public static function accept(array $kinds): string
    {
        $accept = [];
        foreach (self::kinds($kinds) as $kind) {
            $accept[] = self::KIND_IMAGE === $kind ? 'image/*' : 'application/pdf';
        }

        return implode(',', $accept);
    }

    /**
     * @param list<string> $kinds
     */
    public static function allowsImages(array $kinds): bool
    {
        return \in_array(self::KIND_IMAGE, self::kinds($kinds), true);
    }

    /**
     * The form a question id takes in a URL: base58, like every other id in the
     * admin and in the offer reader — never the dashed RFC 4122 form the JSON
     * stores.
     *
     * Questions that predate the uuid normalisation can carry an arbitrary string
     * as their id; those travel unchanged rather than breaking URL generation for
     * an offer nobody has re-saved since.
     */
    public static function urlId(string $uuid): string
    {
        try {
            return Uuid::fromString($uuid)->toBase58();
        } catch (\InvalidArgumentException) {
            return $uuid;
        }
    }

    /**
     * The stored id behind a URL segment produced by {@see self::urlId()}. Accepts
     * the RFC 4122 form too, so any link handed out before the switch to base58
     * keeps resolving.
     */
    public static function fromUrlId(string $id): string
    {
        try {
            return Uuid::fromString($id)->toRfc4122();
        } catch (\InvalidArgumentException) {
            return $id;
        }
    }
}
