<?php
namespace App;

/**
 * Minimal RTL + Arabic shaping helper for GD.
 *
 * - Not a full Unicode bidi engine.
 * - Works well for Persian bank tables where most content is Arabic letters + numbers.
 */
class RTL {
    /** @var array<string,array{0:int,1:int,2:int,3:int}> */
    private static array $forms = [];
    /** @var array<string,bool> */
    private static array $joinRight = []; // can connect to previous (on the right side visually)
    /** @var array<string,bool> */
    private static array $joinLeft  = []; // can connect to next (on the left side visually)

    private static function init(): void {
        if (!empty(self::$forms)) return;

        // Map base char => [isolated, final, initial, medial]
        // Values are Unicode codepoints (Arabic Presentation Forms-A/B)
        // Persian letters included.
        $m = [
            'ا' => [0xFE8D,0xFE8E,0xFE8D,0xFE8E],
            'آ' => [0xFE81,0xFE82,0xFE81,0xFE82],
            'أ' => [0xFE83,0xFE84,0xFE83,0xFE84],
            'إ' => [0xFE87,0xFE88,0xFE87,0xFE88],
            'ب' => [0xFE8F,0xFE90,0xFE91,0xFE92],
            'پ' => [0xFB56,0xFB57,0xFB58,0xFB59],
            'ت' => [0xFE95,0xFE96,0xFE97,0xFE98],
            'ث' => [0xFE99,0xFE9A,0xFE9B,0xFE9C],
            'ج' => [0xFE9D,0xFE9E,0xFE9F,0xFEA0],
            'چ' => [0xFB7A,0xFB7B,0xFB7C,0xFB7D],
            'ح' => [0xFEA1,0xFEA2,0xFEA3,0xFEA4],
            'خ' => [0xFEA5,0xFEA6,0xFEA7,0xFEA8],
            'د' => [0xFEA9,0xFEAA,0xFEA9,0xFEAA],
            'ذ' => [0xFEAB,0xFEAC,0xFEAB,0xFEAC],
            'ر' => [0xFEAD,0xFEAE,0xFEAD,0xFEAE],
            'ز' => [0xFEAF,0xFEB0,0xFEAF,0xFEB0],
            'ژ' => [0xFB8A,0xFB8B,0xFB8A,0xFB8B],
            'س' => [0xFEB1,0xFEB2,0xFEB3,0xFEB4],
            'ش' => [0xFEB5,0xFEB6,0xFEB7,0xFEB8],
            'ص' => [0xFEB9,0xFEBA,0xFEBB,0xFEBC],
            'ض' => [0xFEBD,0xFEBE,0xFEBF,0xFEC0],
            'ط' => [0xFEC1,0xFEC2,0xFEC3,0xFEC4],
            'ظ' => [0xFEC5,0xFEC6,0xFEC7,0xFEC8],
            'ع' => [0xFEC9,0xFECA,0xFECB,0xFECC],
            'غ' => [0xFECD,0xFECE,0xFECF,0xFED0],
            'ف' => [0xFED1,0xFED2,0xFED3,0xFED4],
            'ق' => [0xFED5,0xFED6,0xFED7,0xFED8],
            'ك' => [0xFED9,0xFEDA,0xFEDB,0xFEDC],
            'ک' => [0xFB8E,0xFB8F,0xFB90,0xFB91],
            'گ' => [0xFB92,0xFB93,0xFB94,0xFB95],
            'ل' => [0xFEDD,0xFEDE,0xFEDF,0xFEE0],
            'م' => [0xFEE1,0xFEE2,0xFEE3,0xFEE4],
            'ن' => [0xFEE5,0xFEE6,0xFEE7,0xFEE8],
            'و' => [0xFEED,0xFEEE,0xFEED,0xFEEE],
            'ه' => [0xFEE9,0xFEEA,0xFEEB,0xFEEC],
            'ة' => [0xFE93,0xFE94,0xFE93,0xFE94],
            'ی' => [0xFBFC,0xFBFD,0xFBFE,0xFBFF],
            'ي' => [0xFEF1,0xFEF2,0xFEF3,0xFEF4],
            'ئ' => [0xFE89,0xFE8A,0xFE8B,0xFE8C],
            'ؤ' => [0xFE85,0xFE86,0xFE85,0xFE86],
            'ء' => [0xFE80,0xFE80,0xFE80,0xFE80],
        ];

        self::$forms = $m;

        // Joining behavior. Many letters do not connect to the next letter (on the left).
        // Non-joining-to-left: ا آ أ إ د ذ ر ز ژ و ء ؤ
        $noLeft = ['ا','آ','أ','إ','د','ذ','ر','ز','ژ','و','ء','ؤ'];

        foreach ($m as $ch => $_) {
            self::$joinRight[$ch] = true; // all listed can join to previous (right) in general
            self::$joinLeft[$ch]  = !in_array($ch, $noLeft, true);
        }
        // Hamza alone doesn't really join; keep it isolated.
        self::$joinRight['ء'] = false;
        self::$joinLeft['ء']  = false;
    }

    /**
     * Shape + simple bidi for a single-line string for GD.
     */
    public static function fix(string $text): string {
        self::init();

        $text = str_replace(["\r", "\n"], ' ', $text);
        $text = preg_replace('/\s+/u', ' ', $text);
        $text = trim((string)$text);
        if ($text === '') return '';

        // Tokenize into Arabic-ish vs Latin/number chunks.
        $parts = preg_split('/(\p{Arabic}+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
        if (!is_array($parts) || empty($parts)) return $text;

        $tokens = [];
        foreach ($parts as $p) {
            if (preg_match('/^\p{Arabic}+$/u', $p)) {
                $tokens[] = self::shapeArabic($p);
            } else {
                $tokens[] = $p; // keep as-is (numbers/latin)
            }
        }

        // For overall RTL feel: reverse tokens order, but keep Latin tokens intact.
        $tokens = array_reverse($tokens);
        return implode('', $tokens);
    }

    /**
     * Arabic shaping for a chunk containing only Arabic-range characters.
     */
    private static function shapeArabic(string $s): string {
        $chars = self::utf8Split($s);
        $out = [];
        $n = count($chars);

        for ($i = 0; $i < $n; $i++) {
            $ch = $chars[$i];

            // Keep non-mapped chars as-is.
            if (!isset(self::$forms[$ch])) {
                $out[] = $ch;
                continue;
            }

            $prev = $chars[$i - 1] ?? '';
            $next = $chars[$i + 1] ?? '';

            $canJoinPrev = ($prev !== '' && isset(self::$forms[$prev]) && (self::$joinLeft[$prev] ?? false) && (self::$joinRight[$ch] ?? false));
            $canJoinNext = ($next !== '' && isset(self::$forms[$next]) && (self::$joinLeft[$ch] ?? false) && (self::$joinRight[$next] ?? false));

            // Choose form index
            // 0 isolated, 1 final, 2 initial, 3 medial
            if ($canJoinPrev && $canJoinNext) {
                $form = self::$forms[$ch][3];
            } elseif ($canJoinPrev && !$canJoinNext) {
                $form = self::$forms[$ch][1];
            } elseif (!$canJoinPrev && $canJoinNext) {
                $form = self::$forms[$ch][2];
            } else {
                $form = self::$forms[$ch][0];
            }

            $out[] = self::cpToUtf8($form);
        }

        // Reverse shaped string for RTL display in LTR renderer
        $out = array_reverse($out);
        return implode('', $out);
    }

    /** @return string[] */
    private static function utf8Split(string $s): array {
        return preg_split('//u', $s, -1, PREG_SPLIT_NO_EMPTY) ?: [];
    }

    private static function cpToUtf8(int $cp): string {
        if ($cp <= 0x7F) {
            return chr($cp);
        } elseif ($cp <= 0x7FF) {
            return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F));
        } elseif ($cp <= 0xFFFF) {
            return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        } else {
            return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F));
        }
    }
}
