<?php

namespace App\Domain\Identity\Support;

/**
 * The words one branch uses for the two kinds of person it serves.
 *
 * `participants.membership_type` holds 'member' and 'non_member' forever —
 * this object is the only place that decides what a human is shown instead.
 * A club branch says عضو / غير عضو, a compound says مقيم / غير مقيم, a beach
 * says مشترك / زائر يومي. Nothing downstream of this class knows which.
 *
 * Two families of key:
 *
 *   base      the eight Arabic forms stored per branch, plus their English
 *             counterparts. Stored rather than derived because Arabic does not
 *             derive: العضو but غير العضو, أعضاء but مقيمين.
 *
 *   composed  everything a screen actually asks for — نوع العضوية, رقم
 *             العضوية, سعر العضو — built from the base forms here so that a
 *             branch configures eight words and gets every label in the app.
 *
 * Composition is deliberately shallow: a prefix and a base form, in an order
 * that stays grammatical for every preset. "نوع " . "الإقامة" is "نوع الإقامة";
 * "سعر " . "غير المقيم" is "سعر غير المقيم". Anything that needed more than
 * that belongs in a sentence at the call site, as __(':member ...', [...]).
 */
final class Terminology
{
    /**
     * Every key a caller may ask for. Used by the settings screen to render
     * a live preview, and by term() to fail loudly on a typo.
     */
    public const BASE_KEYS = [
        'member',
        'member_definite',
        'members',
        'non_member',
        'non_member_definite',
        'non_members',
        'membership',
        'membership_indefinite',
    ];

    public const ENGLISH_KEYS = [
        'member_en',
        'members_en',
        'non_member_en',
        'non_members_en',
        'membership_en',
    ];

    public const COMPOSED_KEYS = [
        'membership_type',
        'membership_types',
        'membership_id',
        'membership_card',
        'member_price',
        'non_member_price',
        'for_member',
        'for_non_member',
        'for_members',
        'for_non_members',
    ];

    /**
     * @param  array<string, string>  $terms
     */
    public function __construct(
        private readonly array $terms,
        private readonly string $locale = 'ar',
        public readonly string $preset = 'club',
    ) {
    }

    public function get(string $key): string
    {
        if (in_array($key, self::BASE_KEYS, true)) {
            return $this->base($key);
        }

        $arabic = $this->locale === 'ar';

        return match ($key) {
            'membership_type'  => $arabic ? 'نوع ' . $this->base('membership') : $this->base('membership') . ' type',
            'membership_types' => $arabic ? 'أنواع ' . $this->base('membership') : $this->base('membership') . ' types',
            'membership_id'    => $arabic ? 'رقم ' . $this->base('membership') : $this->base('membership') . ' number',
            'membership_card'  => $arabic ? 'بطاقة ' . $this->base('membership_indefinite') : $this->base('membership') . ' card',
            'member_price'     => $arabic ? 'سعر ' . $this->base('member_definite') : $this->base('member') . ' price',
            'non_member_price' => $arabic ? 'سعر ' . $this->base('non_member_definite') : $this->base('non_member') . ' price',
            'for_member'       => $arabic ? $this->lam($this->base('member_definite')) : $this->base('member'),
            'for_non_member'   => $arabic ? $this->lam($this->base('non_member_definite')) : $this->base('non_member'),
            'for_members'      => $arabic ? $this->lam($this->base('members')) : $this->base('members'),
            'for_non_members'  => $arabic ? $this->lam($this->base('non_members')) : $this->base('non_members'),

            default => throw new \InvalidArgumentException("Unknown terminology key [{$key}]."),
        };
    }

    public function __get(string $key): string
    {
        return $this->get($key);
    }

    public function __isset(string $key): bool
    {
        return in_array($key, self::BASE_KEYS, true)
            || in_array($key, self::COMPOSED_KEYS, true);
    }

    /**
     * The stored form, in the active locale.
     *
     * English is coarser than Arabic on purpose: it has no definite/indefinite
     * distinction worth storing, so five stored words answer eight questions.
     * A blank English field falls back to Arabic rather than to an empty label.
     */
    private function base(string $key): string
    {
        if ($this->locale !== 'ar') {
            $english = match ($key) {
                'member', 'member_definite'         => 'member_en',
                'members'                           => 'members_en',
                'non_member', 'non_member_definite' => 'non_member_en',
                'non_members'                       => 'non_members_en',
                'membership', 'membership_indefinite' => 'membership_en',
                default                             => null,
            };

            if ($english !== null && filled($this->terms[$english] ?? null)) {
                return (string) $this->terms[$english];
            }
        }

        return (string) ($this->terms[$key] ?? '');
    }

    /**
     * Attach the Arabic preposition ل to a definite noun.
     *
     * ل + العضو is للعضو, not لالعضو — the alif of the article elides. But the
     * article is not always at the front: ل + غير العضو is لغير العضو and must
     * be left alone. Testing the composed string for the لال sequence is what
     * distinguishes the two without needing to know which preset is loaded.
     */
    private function lam(string $definite): string
    {
        $joined = 'ل' . $definite;

        return str_starts_with($joined, 'لال')
            ? 'لل' . mb_substr($joined, 3)
            : $joined;
    }

    /**
     * @return array<string, string>
     */
    public function toArray(): array
    {
        return $this->terms;
    }

    /**
     * Every label this vocabulary produces, for the settings preview.
     *
     * @return array<string, string>
     */
    public function all(): array
    {
        $out = [];

        foreach ([...self::BASE_KEYS, ...self::COMPOSED_KEYS] as $key) {
            $out[$key] = $this->get($key);
        }

        return $out;
    }
}
