// Derived from PDFium's core/fxcrt/fx_bidi.cpp
// Original: Copyright 2014 The PDFium Authors
// Licensed under BSD-3-Clause / Apache-2.0
// See pdfium-upstream/LICENSE for the original license.

//! Unicode Bidirectional Algorithm (UAX #9) support.
//!
//! This module corresponds to `core/fxcrt/fx_bidi.h/.cpp` in PDFium upstream.
//!
//! # Upstream mapping
//!
//! | PDFium | rpdfium | Notes |
//! |--------|---------|-------|
//! | `CFX_BidiChar` | [`BidiChar`] | Exact port |
//! | `CFX_BidiChar::Direction` | [`BidiDirection`] | Exact port |
//! | `CFX_BidiChar::Segment` | [`BidiSegment`] | Exact port |
//! | `CFX_BidiString` | [`BidiString`] | Exact port |
//! | `pdfium::unicode::GetBidiClass()` | [`bidi_class_of`] | Delegated to `unicode-bidi` crate |
//!
//! Additionally provides:
//! - [`mirror_char`] — Unicode Bidi_Mirroring_Glyph lookup (UAX #9 §3)
//!
//! High-level reordering of text characters (which depends on `TextCharacter`
//! defined in `rpdfium-text`) lives in `rpdfium-text::textpage::fx_bidi`.

// ── CFX_BidiChar::Direction ────────────────────────────────────────────────

/// Text-direction classification for a character segment.
///
/// Corresponds to `CFX_BidiChar::Direction` in PDFium upstream.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BidiDirection {
    /// Non-directional (spaces, punctuation, etc.).
    Neutral,
    /// Strong left-to-right character (Latin, CJK, etc.).
    Left,
    /// Strong right-to-left character (Arabic, Hebrew, etc.).
    Right,
    /// Weak left character (digits, math symbols, etc.).
    LeftWeak,
}

/// Type alias for upstream traceability: corresponds to `CFX_BidiChar::Direction`.
pub type Direction = BidiDirection;

// ── CFX_BidiChar::Segment ──────────────────────────────────────────────────

/// A contiguous run of characters sharing the same [`BidiDirection`].
///
/// Corresponds to `CFX_BidiChar::Segment` in PDFium upstream.
#[derive(Debug, Clone, Copy)]
pub struct BidiSegment {
    /// Start position (character index) in the source string.
    pub start: i32,
    /// Number of characters in this segment.
    pub count: i32,
    /// Direction of characters in this segment.
    pub direction: BidiDirection,
}

/// Type alias for upstream traceability: corresponds to `CFX_BidiChar::Segment`.
pub type Segment = BidiSegment;

// ── pdfium::unicode::GetBidiClass() equivalent ────────────────────────────

/// Return the coarse bidi direction of a Unicode character.
///
/// Corresponds to `pdfium::unicode::GetBidiClass()` in PDFium upstream.
/// Delegates to `unicode_bidi::bidi_class()` (UAX #9 property tables).
///
/// When the `icu` feature is disabled, always returns [`BidiDirection::Neutral`].
pub fn bidi_class_of(c: char) -> BidiDirection {
    #[cfg(feature = "icu")]
    {
        match unicode_bidi::bidi_class(c) {
            unicode_bidi::BidiClass::L => BidiDirection::Left,
            unicode_bidi::BidiClass::AN
            | unicode_bidi::BidiClass::EN
            | unicode_bidi::BidiClass::NSM
            | unicode_bidi::BidiClass::CS
            | unicode_bidi::BidiClass::ES
            | unicode_bidi::BidiClass::ET
            | unicode_bidi::BidiClass::BN => BidiDirection::LeftWeak,
            unicode_bidi::BidiClass::R | unicode_bidi::BidiClass::AL => BidiDirection::Right,
            _ => BidiDirection::Neutral,
        }
    }
    #[cfg(not(feature = "icu"))]
    {
        let _ = c;
        BidiDirection::Neutral
    }
}

// ── CFX_BidiChar ──────────────────────────────────────────────────────────

/// Incremental character-by-character bidi classifier.
///
/// Processes characters one at a time and groups them into [`BidiSegment`]s
/// based on text direction.  Corresponds to `CFX_BidiChar` in PDFium upstream.
///
/// # Example
/// ```ignore
/// let mut bc = BidiChar::new();
/// for ch in "Hello מה שלומך".chars() {
///     if bc.append_char(ch) {
///         let seg = *bc.segment_info();
///         // process completed segment
///     }
/// }
/// if bc.end_char() {
///     let seg = *bc.segment_info(); // final segment
/// }
/// ```
pub struct BidiChar {
    current_segment: BidiSegment,
    last_segment: BidiSegment,
}

impl BidiChar {
    /// Create a new `BidiChar` with both segments initialised to neutral/empty.
    pub fn new() -> Self {
        let empty = BidiSegment {
            start: 0,
            count: 0,
            direction: BidiDirection::Neutral,
        };
        BidiChar {
            current_segment: empty,
            last_segment: empty,
        }
    }

    /// Append a character and classify it as left, left-weak, right, or neutral.
    ///
    /// Returns `true` if the character has a different direction than the
    /// existing run, indicating there is a completed segment to process via
    /// [`segment_info`](Self::segment_info).
    ///
    /// Corresponds to `CFX_BidiChar::AppendChar()`.
    pub fn append_char(&mut self, c: char) -> bool {
        let direction = bidi_class_of(c);
        let changed = direction != self.current_segment.direction;
        if changed {
            self.start_new_segment(direction);
        }
        self.current_segment.count += 1;
        changed
    }

    /// Signal end-of-string.  Must be called after the last character.
    ///
    /// Returns `true` if there is still a segment to process.
    /// [`append_char`](Self::append_char) must not be called after this.
    ///
    /// Corresponds to `CFX_BidiChar::EndChar()`.
    pub fn end_char(&mut self) -> bool {
        self.start_new_segment(BidiDirection::Neutral);
        self.last_segment.count > 0
    }

    /// Return the most recently completed segment.
    ///
    /// Call this after [`append_char`](Self::append_char) returns `true` or
    /// after [`end_char`](Self::end_char) returns `true`.
    ///
    /// Corresponds to `CFX_BidiChar::GetSegmentInfo()`.
    pub fn segment_info(&self) -> &BidiSegment {
        &self.last_segment
    }

    /// ADR-019 alias for [`segment_info()`](Self::segment_info).
    ///
    /// Corresponds to `CFX_BidiChar::GetSegmentInfo()`.
    #[inline]
    pub fn get_segment_info(&self) -> &BidiSegment {
        self.segment_info()
    }

    fn start_new_segment(&mut self, direction: BidiDirection) {
        self.last_segment = self.current_segment;
        self.current_segment.start += self.current_segment.count;
        self.current_segment.count = 0;
        self.current_segment.direction = direction;
    }
}

impl Default for BidiChar {
    fn default() -> Self {
        Self::new()
    }
}

// ── CFX_BidiString ────────────────────────────────────────────────────────

/// Analyse a string and expose its bidirectional segments.
///
/// Segments a string into [`BidiSegment`] runs and determines the overall
/// paragraph direction.  Corresponds to `CFX_BidiString` in PDFium upstream.
///
/// # Example
/// ```ignore
/// let bs = BidiString::new("Hello מה שלומך");
/// if bs.overall_direction() == BidiDirection::Right {
///     // RTL paragraph
/// }
/// for seg in bs.segments() { /* … */ }
/// ```
pub struct BidiString {
    segments: Vec<BidiSegment>,
    overall_direction: BidiDirection,
}

impl BidiString {
    /// Analyse `s` and compute bidirectional segments.
    ///
    /// The overall direction is set to [`BidiDirection::Right`] when the
    /// number of RTL segments is ≥ the number of LTR segments (and > 0).
    /// Otherwise it defaults to [`BidiDirection::Left`].
    ///
    /// Corresponds to `CFX_BidiString::CFX_BidiString(const WideString&)`.
    pub fn new(s: &str) -> Self {
        let mut bidi = BidiChar::new();
        let mut segments = Vec::new();
        for c in s.chars() {
            if bidi.append_char(c) {
                segments.push(*bidi.segment_info());
            }
        }
        if bidi.end_char() {
            segments.push(*bidi.segment_info());
        }

        let n_r2l = segments
            .iter()
            .filter(|s| s.direction == BidiDirection::Right)
            .count();
        let n_l2r = segments
            .iter()
            .filter(|s| s.direction == BidiDirection::Left)
            .count();

        let mut result = BidiString {
            segments,
            overall_direction: BidiDirection::Left,
        };
        if n_r2l > 0 && n_r2l >= n_l2r {
            result.set_overall_direction_right();
        }
        result
    }

    /// Overall paragraph direction (`Left` or `Right`, never `Neutral`).
    ///
    /// Corresponds to `CFX_BidiString::OverallDirection()`.
    pub fn overall_direction(&self) -> BidiDirection {
        self.overall_direction
    }

    /// Force the paragraph to be treated as right-to-left.
    ///
    /// Reverses the segment order when the direction was not already RTL.
    ///
    /// Corresponds to `CFX_BidiString::SetOverallDirectionRight()`.
    pub fn set_overall_direction_right(&mut self) {
        if self.overall_direction != BidiDirection::Right {
            self.segments.reverse();
            self.overall_direction = BidiDirection::Right;
        }
    }

    /// Iterate over bidirectional segments.
    ///
    /// Corresponds to `CFX_BidiString::begin()` / `end()`.
    pub fn segments(&self) -> &[BidiSegment] {
        &self.segments
    }

    /// Return an iterator over segments.
    pub fn iter(&self) -> impl Iterator<Item = &BidiSegment> {
        self.segments.iter()
    }
}

// ── mirror_char ────────────────────────────────────────────────────────────

/// Return the Unicode mirror of `c` for RTL display (UAX #9 Bidi_Mirroring_Glyph).
///
/// Covers ~70 bracket/parenthesis mirror pairs from the Unicode
/// Bidi_Mirroring_Glyph property (UAX #9).
pub fn mirror_char(c: char) -> char {
    match c {
        '(' => ')',
        ')' => '(',
        '[' => ']',
        ']' => '[',
        '{' => '}',
        '}' => '{',
        '<' => '>',
        '>' => '<',
        '\u{00AB}' => '\u{00BB}', // « → »
        '\u{00BB}' => '\u{00AB}', // » → «
        '\u{2018}' => '\u{2019}', // ' → '
        '\u{2019}' => '\u{2018}', // ' → '
        '\u{201C}' => '\u{201D}', // " → "
        '\u{201D}' => '\u{201C}', // " → "
        '\u{2039}' => '\u{203A}', // ‹ → ›
        '\u{203A}' => '\u{2039}', // › → ‹
        '\u{2045}' => '\u{2046}', // ⁅ → ⁆
        '\u{2046}' => '\u{2045}',
        '\u{207D}' => '\u{207E}', // ⁽ → ⁾
        '\u{207E}' => '\u{207D}',
        '\u{208D}' => '\u{208E}', // ₍ → ₎
        '\u{208E}' => '\u{208D}',
        '\u{2308}' => '\u{2309}', // ⌈ → ⌉
        '\u{2309}' => '\u{2308}',
        '\u{230A}' => '\u{230B}', // ⌊ → ⌋
        '\u{230B}' => '\u{230A}',
        '\u{2329}' => '\u{232A}', // 〈 → 〉
        '\u{232A}' => '\u{2329}',
        '\u{27E8}' => '\u{27E9}', // ⟨ → ⟩
        '\u{27E9}' => '\u{27E8}',
        '\u{27EA}' => '\u{27EB}', // ⟪ → ⟫
        '\u{27EB}' => '\u{27EA}',
        '\u{2768}' => '\u{2769}', // ❨ → ❩
        '\u{2769}' => '\u{2768}',
        '\u{276A}' => '\u{276B}', // ❪ → ❫
        '\u{276B}' => '\u{276A}',
        '\u{276C}' => '\u{276D}', // ❬ → ❭
        '\u{276D}' => '\u{276C}',
        '\u{276E}' => '\u{276F}', // ❮ → ❯
        '\u{276F}' => '\u{276E}',
        '\u{2770}' => '\u{2771}', // ❰ → ❱
        '\u{2771}' => '\u{2770}',
        '\u{2772}' => '\u{2773}', // ❲ → ❳
        '\u{2773}' => '\u{2772}',
        '\u{2774}' => '\u{2775}', // ❴ → ❵
        '\u{2775}' => '\u{2774}',
        '\u{27C5}' => '\u{27C6}', // ⟅ → ⟆
        '\u{27C6}' => '\u{27C5}',
        '\u{27E6}' => '\u{27E7}', // ⟦ → ⟧
        '\u{27E7}' => '\u{27E6}',
        '\u{27EC}' => '\u{27ED}', // ⟬ → ⟭
        '\u{27ED}' => '\u{27EC}',
        '\u{27EE}' => '\u{27EF}', // ⟮ → ⟯
        '\u{27EF}' => '\u{27EE}',
        '\u{2983}' => '\u{2984}', // ⦃ → ⦄
        '\u{2984}' => '\u{2983}',
        '\u{2985}' => '\u{2986}', // ⦅ → ⦆
        '\u{2986}' => '\u{2985}',
        '\u{2987}' => '\u{2988}', // ⦇ → ⦈
        '\u{2988}' => '\u{2987}',
        '\u{2989}' => '\u{298A}', // ⦉ → ⦊
        '\u{298A}' => '\u{2989}',
        '\u{298B}' => '\u{298C}', // ⦋ → ⦌
        '\u{298C}' => '\u{298B}',
        '\u{298D}' => '\u{2990}', // ⦍ → ⦐
        '\u{2990}' => '\u{298D}',
        '\u{298F}' => '\u{298E}', // ⦏ → ⦎
        '\u{298E}' => '\u{298F}',
        '\u{2991}' => '\u{2992}', // ⦑ → ⦒
        '\u{2992}' => '\u{2991}',
        '\u{2993}' => '\u{2994}', // ⦓ → ⦔
        '\u{2994}' => '\u{2993}',
        '\u{2995}' => '\u{2996}', // ⦕ → ⦖
        '\u{2996}' => '\u{2995}',
        '\u{2997}' => '\u{2998}', // ⦗ → ⦘
        '\u{2998}' => '\u{2997}',
        '\u{29FC}' => '\u{29FD}', // ⧼ → ⧽
        '\u{29FD}' => '\u{29FC}',
        '\u{3008}' => '\u{3009}', // 〈 → 〉
        '\u{3009}' => '\u{3008}',
        '\u{300A}' => '\u{300B}', // 《 → 》
        '\u{300B}' => '\u{300A}',
        '\u{300C}' => '\u{300D}', // 「 → 」
        '\u{300D}' => '\u{300C}',
        '\u{300E}' => '\u{300F}', // 『 → 』
        '\u{300F}' => '\u{300E}',
        '\u{3010}' => '\u{3011}', // 【 → 】
        '\u{3011}' => '\u{3010}',
        '\u{3014}' => '\u{3015}', // 〔 → 〕
        '\u{3015}' => '\u{3014}',
        '\u{3016}' => '\u{3017}', // 〖 → 〗
        '\u{3017}' => '\u{3016}',
        '\u{3018}' => '\u{3019}', // 〘 → 〙
        '\u{3019}' => '\u{3018}',
        '\u{301A}' => '\u{301B}', // 〚 → 〛
        '\u{301B}' => '\u{301A}',
        '\u{FE59}' => '\u{FE5A}', // ﹙ → ﹚
        '\u{FE5A}' => '\u{FE59}',
        '\u{FE5B}' => '\u{FE5C}', // ﹛ → ﹜
        '\u{FE5C}' => '\u{FE5B}',
        '\u{FE5D}' => '\u{FE5E}', // ﹝ → ﹞
        '\u{FE5E}' => '\u{FE5D}',
        '\u{FF08}' => '\u{FF09}', // （ → ）
        '\u{FF09}' => '\u{FF08}',
        '\u{FF3B}' => '\u{FF3D}', // ［ → ］
        '\u{FF3D}' => '\u{FF3B}',
        '\u{FF5B}' => '\u{FF5D}', // ｛ → ｝
        '\u{FF5D}' => '\u{FF5B}',
        '\u{FF5F}' => '\u{FF60}', // ⦅ → ⦆
        '\u{FF60}' => '\u{FF5F}',
        '\u{FF62}' => '\u{FF63}', // ｢ → ｣
        '\u{FF63}' => '\u{FF62}',
        _ => c,
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;

    // --- BidiChar ---

    #[test]
    fn test_bidi_char_new_is_neutral() {
        let bc = BidiChar::new();
        assert_eq!(bc.current_segment.count, 0);
        assert_eq!(bc.current_segment.direction, BidiDirection::Neutral);
    }

    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_char_append_ltr_causes_change() {
        let mut bc = BidiChar::new();
        let changed = bc.append_char('A');
        assert!(changed);
    }

    #[test]
    fn test_bidi_char_append_same_direction_no_segment() {
        let mut bc = BidiChar::new();
        bc.append_char('A');
        let changed = bc.append_char('B');
        assert!(!changed);
        assert_eq!(bc.current_segment.count, 2);
    }

    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_char_direction_change_emits_segment() {
        let mut bc = BidiChar::new();
        bc.append_char('A');
        let changed = bc.append_char('\u{05D0}'); // alef = RTL
        assert!(changed);
        let seg = bc.segment_info();
        assert_eq!(seg.direction, BidiDirection::Left);
        assert_eq!(seg.count, 1);
        assert_eq!(seg.start, 0);
    }

    #[test]
    fn test_bidi_char_end_char_flushes_last_segment() {
        let mut bc = BidiChar::new();
        bc.append_char('A');
        bc.append_char('B');
        let has_segment = bc.end_char();
        assert!(has_segment);
        let seg = bc.segment_info();
        assert_eq!(seg.count, 2);
    }

    #[test]
    fn test_bidi_char_end_char_empty_returns_false() {
        let mut bc = BidiChar::new();
        let has_segment = bc.end_char();
        assert!(!has_segment);
    }

    // --- BidiString ---

    #[test]
    fn test_bidi_string_ltr_overall_direction() {
        let bs = BidiString::new("Hello");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
    }

    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_string_rtl_overall_direction() {
        let bs = BidiString::new("שלום");
        assert_eq!(bs.overall_direction(), BidiDirection::Right);
    }

    #[test]
    fn test_bidi_string_set_overall_direction_right() {
        let mut bs = BidiString::new("Hello");
        let segs_before = bs.segments().len();
        bs.set_overall_direction_right();
        assert_eq!(bs.overall_direction(), BidiDirection::Right);
        assert_eq!(bs.segments().len(), segs_before);
    }

    #[test]
    fn test_bidi_string_set_overall_direction_right_idempotent() {
        let mut bs = BidiString::new("Hello");
        bs.set_overall_direction_right();
        let starts_first: Vec<i32> = bs.segments().iter().map(|s| s.start).collect();
        bs.set_overall_direction_right();
        let starts_second: Vec<i32> = bs.segments().iter().map(|s| s.start).collect();
        assert_eq!(starts_first, starts_second);
    }

    #[test]
    fn test_bidi_string_iter_matches_segments() {
        let bs = BidiString::new("abc");
        assert_eq!(bs.iter().count(), bs.segments().len());
    }

    #[test]
    fn test_bidi_string_empty() {
        let bs = BidiString::new("");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        assert!(bs.segments().is_empty());
    }

    // --- mirror_char ---

    #[test]
    fn test_mirror_char_basic_brackets() {
        assert_eq!(mirror_char('('), ')');
        assert_eq!(mirror_char(')'), '(');
        assert_eq!(mirror_char('['), ']');
        assert_eq!(mirror_char(']'), '[');
    }

    #[test]
    fn test_mirror_char_no_mirror_for_regular() {
        assert_eq!(mirror_char('A'), 'A');
        assert_eq!(mirror_char('5'), '5');
        assert_eq!(mirror_char(' '), ' ');
    }

    #[test]
    fn test_mirror_char_guillemet() {
        assert_eq!(mirror_char('\u{00AB}'), '\u{00BB}');
        assert_eq!(mirror_char('\u{00BB}'), '\u{00AB}');
    }

    // --- Upstream ports: BidiChar multi-direction transition tests ---

    /// Upstream: TEST(fxcrt, BidiCharLeft)
    #[cfg(feature = "icu")]
    #[test]
    fn test_fxcrt_bidi_char_left() {
        let left = 'A'; // kLeftChar

        let mut bidi = BidiChar::new();

        // First left char -> direction changes from Neutral to Left
        assert!(bidi.append_char(left));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 0);

        // Two more left chars, no direction change
        assert!(!bidi.append_char(left));
        assert!(!bidi.append_char(left));

        // segment_info still shows the initial neutral segment
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Neutral);
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 0);

        // End -> emits left segment
        assert!(bidi.end_char());
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Left);
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 3);

        // Second end_char returns false
        assert!(!bidi.end_char());
    }

    /// Upstream: TEST(fxcrt, BidiCharLeftNeutralRight)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_char_left_neutral_right() {
        let left = 'A'; // kLeftChar
        let neutral = ' '; // kNeutralChar
        let right = '\u{05D0}'; // kRightChar (alef)

        let mut bidi = BidiChar::new();

        // Append first left char -> direction changes from Neutral to Left
        assert!(bidi.append_char(left));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 0);

        // Two more left chars, no direction change
        assert!(!bidi.append_char(left));
        assert!(!bidi.append_char(left));

        // Switch to neutral -> emits left segment
        assert!(bidi.append_char(neutral));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 3);

        // Three more neutral chars
        assert!(!bidi.append_char(neutral));
        assert!(!bidi.append_char(neutral));
        assert!(!bidi.append_char(neutral));

        // Switch to right -> emits neutral segment
        assert!(bidi.append_char(right));
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Neutral);
        assert_eq!(info.start, 3);
        assert_eq!(info.count, 4);

        // End -> emits right segment
        assert!(bidi.end_char());
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Right);
        assert_eq!(info.start, 7);
        assert_eq!(info.count, 1);

        assert!(!bidi.end_char());
    }

    /// Upstream: TEST(fxcrt, BidiCharLeftLeftWeakRight)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_char_left_left_weak_right() {
        let left = 'A';
        let left_weak = '.'; // kLeftWeakChar
        let right = '\u{05D0}';

        let mut bidi = BidiChar::new();

        assert!(bidi.append_char(left));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 0);

        assert!(!bidi.append_char(left));
        assert!(!bidi.append_char(left));

        // Switch to left-weak -> emits left segment
        assert!(bidi.append_char(left_weak));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 3);

        assert!(!bidi.append_char(left_weak));
        assert!(!bidi.append_char(left_weak));
        assert!(!bidi.append_char(left_weak));

        // Switch to right -> emits left-weak segment
        assert!(bidi.append_char(right));
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::LeftWeak);
        assert_eq!(info.start, 3);
        assert_eq!(info.count, 4);

        assert!(bidi.end_char());
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Right);
        assert_eq!(info.start, 7);
        assert_eq!(info.count, 1);

        assert!(!bidi.end_char());
    }

    /// Upstream: TEST(fxcrt, BidiCharLeftRightLeft)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_char_left_right_left() {
        let left = 'A';
        let right = '\u{05D0}';

        let mut bidi = BidiChar::new();

        assert!(bidi.append_char(left));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 0);

        assert!(!bidi.append_char(left));
        assert!(!bidi.append_char(left));

        // Switch to right -> emits left segment
        assert!(bidi.append_char(right));
        let info = bidi.segment_info();
        assert_eq!(info.start, 0);
        assert_eq!(info.count, 3);

        assert!(!bidi.append_char(right));
        assert!(!bidi.append_char(right));
        assert!(!bidi.append_char(right));

        // Switch back to left -> emits right segment
        assert!(bidi.append_char(left));
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Right);
        assert_eq!(info.start, 3);
        assert_eq!(info.count, 4);

        assert!(bidi.end_char());
        let info = bidi.segment_info();
        assert_eq!(info.direction, BidiDirection::Left);
        assert_eq!(info.start, 7);
        assert_eq!(info.count, 1);

        assert!(!bidi.end_char());
    }

    // --- Upstream ports: BidiString tests ---

    /// Upstream: TEST(fxcrt, BidiStringAllNeutral)
    #[test]
    fn test_bidi_string_all_neutral() {
        // Single neutral character
        let bs = BidiString::new(" ");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        let segs = bs.segments();
        assert_eq!(segs.len(), 1);
        assert_eq!(segs[0].start, 0);
        assert_eq!(segs[0].count, 1);
        assert_eq!(segs[0].direction, BidiDirection::Neutral);

        // Three neutral characters
        let bs = BidiString::new("   ");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        let segs = bs.segments();
        assert_eq!(segs.len(), 1);
        assert_eq!(segs[0].start, 0);
        assert_eq!(segs[0].count, 3);
        assert_eq!(segs[0].direction, BidiDirection::Neutral);
    }

    /// Upstream: TEST(fxcrt, BidiStringAllLeftWeak)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_string_all_left_weak() {
        // Single left-weak character ('.')
        let bs = BidiString::new(".");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        let segs = bs.segments();
        // Expect: initial neutral segment (start=0, count=0), then left-weak segment
        assert_eq!(segs.len(), 2);
        assert_eq!(segs[0].direction, BidiDirection::Neutral);
        assert_eq!(segs[0].start, 0);
        assert_eq!(segs[0].count, 0);
        assert_eq!(segs[1].direction, BidiDirection::LeftWeak);
        assert_eq!(segs[1].start, 0);
        assert_eq!(segs[1].count, 1);

        // Three left-weak characters
        let bs = BidiString::new("...");
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        let segs = bs.segments();
        assert_eq!(segs.len(), 2);
        assert_eq!(segs[0].direction, BidiDirection::Neutral);
        assert_eq!(segs[0].start, 0);
        assert_eq!(segs[0].count, 0);
        assert_eq!(segs[1].direction, BidiDirection::LeftWeak);
        assert_eq!(segs[1].start, 0);
        assert_eq!(segs[1].count, 3);
    }

    /// Upstream: TEST(fxcrt, BidiStringLeftNeutralLeftRight)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_string_left_neutral_left_right() {
        // "A A\u{05D0}" = left, neutral, left, right
        let s = "A A\u{05D0}";
        let bs = BidiString::new(s);
        assert_eq!(bs.overall_direction(), BidiDirection::Left);
        let segs = bs.segments();
        // Upstream expects 5 segments:
        // [0] Neutral(0,0) [1] Left(0,1) [2] Neutral(1,1) [3] Left(2,1) [4] Right(3,1)
        assert_eq!(segs.len(), 5);
        assert_eq!(segs[0].direction, BidiDirection::Neutral);
        assert_eq!(segs[0].start, 0);
        assert_eq!(segs[0].count, 0);

        assert_eq!(segs[1].direction, BidiDirection::Left);
        assert_eq!(segs[1].start, 0);
        assert_eq!(segs[1].count, 1);

        assert_eq!(segs[2].direction, BidiDirection::Neutral);
        assert_eq!(segs[2].start, 1);
        assert_eq!(segs[2].count, 1);

        assert_eq!(segs[3].direction, BidiDirection::Left);
        assert_eq!(segs[3].start, 2);
        assert_eq!(segs[3].count, 1);

        assert_eq!(segs[4].direction, BidiDirection::Right);
        assert_eq!(segs[4].start, 3);
        assert_eq!(segs[4].count, 1);
    }

    /// Upstream: TEST(fxcrt, BidiStringRightNeutralLeftRight)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_string_right_neutral_left_right() {
        // "\u{05D0} A\u{05D0}" = right, neutral, left, right
        let s = "\u{05D0} A\u{05D0}";
        let bs = BidiString::new(s);
        assert_eq!(bs.overall_direction(), BidiDirection::Right);
        let segs = bs.segments();
        // RTL overall -> segments reversed.
        // Forward segments: Neutral(0,0), Right(0,1), Neutral(1,1), Left(2,1), Right(3,1)
        // Reversed: Right(3,1), Left(2,1), Neutral(1,1), Right(0,1), Neutral(0,0)
        assert_eq!(segs.len(), 5);
        assert_eq!(segs[0].direction, BidiDirection::Right);
        assert_eq!(segs[0].start, 3);
        assert_eq!(segs[0].count, 1);

        assert_eq!(segs[1].direction, BidiDirection::Left);
        assert_eq!(segs[1].start, 2);
        assert_eq!(segs[1].count, 1);

        assert_eq!(segs[2].direction, BidiDirection::Neutral);
        assert_eq!(segs[2].start, 1);
        assert_eq!(segs[2].count, 1);

        assert_eq!(segs[3].direction, BidiDirection::Right);
        assert_eq!(segs[3].start, 0);
        assert_eq!(segs[3].count, 1);

        assert_eq!(segs[4].direction, BidiDirection::Neutral);
        assert_eq!(segs[4].start, 0);
        assert_eq!(segs[4].count, 0);
    }

    /// Upstream: TEST(fxcrt, BidiStringRightLeftWeakLeftRight)
    #[cfg(feature = "icu")]
    #[test]
    fn test_bidi_string_right_left_weak_left_right() {
        // "\u{05D0}.A\u{05D0}" = right, left-weak, left, right
        let s = "\u{05D0}.A\u{05D0}";
        let bs = BidiString::new(s);
        assert_eq!(bs.overall_direction(), BidiDirection::Right);
        let segs = bs.segments();
        // Forward: Neutral(0,0), Right(0,1), LeftWeak(1,1), Left(2,1), Right(3,1)
        // Reversed: Right(3,1), Left(2,1), LeftWeak(1,1), Right(0,1), Neutral(0,0)
        assert_eq!(segs.len(), 5);
        assert_eq!(segs[0].direction, BidiDirection::Right);
        assert_eq!(segs[0].start, 3);
        assert_eq!(segs[0].count, 1);

        assert_eq!(segs[1].direction, BidiDirection::Left);
        assert_eq!(segs[1].start, 2);
        assert_eq!(segs[1].count, 1);

        assert_eq!(segs[2].direction, BidiDirection::LeftWeak);
        assert_eq!(segs[2].start, 1);
        assert_eq!(segs[2].count, 1);

        assert_eq!(segs[3].direction, BidiDirection::Right);
        assert_eq!(segs[3].start, 0);
        assert_eq!(segs[3].count, 1);

        assert_eq!(segs[4].direction, BidiDirection::Neutral);
        assert_eq!(segs[4].start, 0);
        assert_eq!(segs[4].count, 0);
    }
}
