<?php

namespace Tests\Unit;

use App\Domain\Identity\Support\Terminology;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
 * The eight stored words, and the labels the whole app builds out of them.
 *
 * Every assertion here is grammar, not preference. A branch configures عضو or
 * مقيم or نزيل once; if composition is wrong for one of them, that branch sees
 * a broken sentence on a price field, a report header and a printed card, and
 * nothing in the app will have thrown to say so.
 */
class TerminologyTest extends TestCase
{
    /** @return array<string, array{label: string, terms: array<string, string>}> */
    private static function presets(): array
    {
        return require dirname(__DIR__, 2) . '/config/terminology.php';
    }

    private static function vocabulary(string $preset, string $locale = 'ar'): Terminology
    {
        $config = self::presets();

        return new Terminology($config['presets'][$preset]['terms'], $locale, $preset);
    }

    public static function presetNames(): array
    {
        $config = require dirname(__DIR__, 2) . '/config/terminology.php';

        return array_map(fn ($key) => [$key], array_combine(
            array_keys($config['presets']),
            array_keys($config['presets']),
        ));
    }

    #[DataProvider('presetNames')]
    public function test_every_preset_fills_every_stored_form(string $preset): void
    {
        $terms = self::presets()['presets'][$preset]['terms'];

        foreach ([...Terminology::BASE_KEYS, ...Terminology::ENGLISH_KEYS] as $key) {
            $this->assertArrayHasKey($key, $terms, "preset [{$preset}] is missing [{$key}]");
            $this->assertNotSame('', trim($terms[$key]), "preset [{$preset}] left [{$key}] blank");
        }
    }

    #[DataProvider('presetNames')]
    public function test_every_composed_label_is_produced_for_every_preset(string $preset): void
    {
        $vocabulary = self::vocabulary($preset);

        foreach (Terminology::COMPOSED_KEYS as $key) {
            $label = $vocabulary->get($key);

            $this->assertNotSame('', $label, "preset [{$preset}] produced no [{$key}]");
            // A composed label that still contains the ال of a stray article
            // twice, or a doubled space, is a composition bug rather than a
            // wording choice.
            $this->assertStringNotContainsString('  ', $label);
        }
    }

    public function test_the_club_preset_reproduces_the_wording_the_app_shipped_with(): void
    {
        $club = self::vocabulary('club');

        $this->assertSame('عضو', $club->member);
        $this->assertSame('غير عضو', $club->non_member);
        $this->assertSame('نوع العضوية', $club->membership_type);
        $this->assertSame('رقم العضوية', $club->membership_id);
        $this->assertSame('سعر العضو', $club->member_price);
        $this->assertSame('سعر غير العضو', $club->non_member_price);
        $this->assertSame('بطاقة عضوية', $club->membership_card);
    }

    public function test_a_compound_gets_its_own_composed_labels(): void
    {
        $compound = self::vocabulary('compound');

        $this->assertSame('نوع الإقامة', $compound->membership_type);
        $this->assertSame('رقم الإقامة', $compound->membership_id);
        $this->assertSame('سعر المقيم', $compound->member_price);
        $this->assertSame('سعر غير المقيم', $compound->non_member_price);
        $this->assertSame('بطاقة إقامة', $compound->membership_card);
    }

    /**
     * ل + العضو elides to للعضو; ل + غير العضو does not, because the article
     * is not at the front. Getting this wrong prints لالعضو on a form.
     */
    public function test_the_preposition_elides_the_article_only_when_the_article_leads(): void
    {
        $club = self::vocabulary('club');
        $this->assertSame('للعضو', $club->for_member);
        $this->assertSame('لغير العضو', $club->for_non_member);

        $beach = self::vocabulary('beach');
        $this->assertSame('للمشترك', $beach->for_member);
        $this->assertSame('للزائر اليومي', $beach->for_non_member);
    }

    public function test_english_uses_the_english_words_and_drops_the_definite_distinction(): void
    {
        $compound = self::vocabulary('compound', 'en');

        $this->assertSame('Resident', $compound->member);
        $this->assertSame('Resident', $compound->member_definite);
        $this->assertSame('Residents', $compound->members);
        $this->assertSame('Residency type', $compound->membership_type);
        $this->assertSame('Residency number', $compound->membership_id);
        $this->assertSame('Non-resident price', $compound->non_member_price);
    }

    public function test_a_blank_english_word_falls_back_to_arabic_rather_than_to_nothing(): void
    {
        $terms = self::presets()['presets']['club']['terms'];
        $terms['member_en'] = '';

        $partial = new Terminology($terms, 'en', 'club');

        $this->assertSame('عضو', $partial->member);
        $this->assertSame('Members', $partial->members);
    }

    public function test_a_branch_that_edited_one_word_keeps_the_rest_of_its_preset(): void
    {
        $terms = self::presets()['presets']['compound']['terms'];
        $terms['non_member'] = 'ضيف';
        $terms['non_member_definite'] = 'الضيف';

        $edited = new Terminology($terms, 'ar', 'compound');

        $this->assertSame('ضيف', $edited->non_member);
        $this->assertSame('سعر الضيف', $edited->non_member_price);
        $this->assertSame('للضيف', $edited->for_non_member);
        $this->assertSame('مقيم', $edited->member);
    }

    public function test_an_unknown_key_throws_instead_of_printing_itself(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        self::vocabulary('club')->get('membershipp_type');
    }

    public function test_a_missing_stored_word_yields_an_empty_string_not_a_crash(): void
    {
        $bare = new Terminology([], 'ar', 'club');

        $this->assertSame('', $bare->member);
        $this->assertSame('نوع ', $bare->membership_type);
    }
}
