xs_h3 0.2.0

Excerion Sun's Rust implementation of Uber's H3 geospatial indexing library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
use crate::base_cells::{
  _get_base_cell_direction,
  _is_base_cell_pentagon,
};
use crate::coords::face_ijk::{ADJACENT_FACE_DIR, FACE_NEIGHBORS, INVALID_FACE}; // This table is crucial
use crate::coords::ijk::{
  _down_ap7, _down_ap7r, _ijk_add, _ijk_normalize, _ijk_rotate60_ccw, _ijk_rotate60_cw,
  _ijk_sub, ij_to_ijk, ijk_to_ij,
};
use crate::h3_index::inspection::is_valid_cell;
use crate::h3_index::{
  _face_ijk_to_h3, _h3_leading_non_zero_digit, _h3_rotate_pent60_ccw,
  _h3_rotate_pent60_cw, _h3_to_face_ijk, get_base_cell, get_resolution, is_pentagon,
  is_resolution_class_iii,
};
use crate::types::{CoordIJ, CoordIJK, Direction, FaceIJK, H3Error, H3Index, H3_NULL};

// Constants for PENTAGON_ROTATIONS (from C's localij.c)
// These describe how to adjust IJK coordinates when one cell is a pentagon
// and the other is on an adjacent base cell.
// Indexed by: [origin_leading_digit_or_direction_to_neighbor][index_leading_digit_or_direction_from_neighbor]
// Values are number of 60deg CW rotations.
#[rustfmt::skip]
static PENTAGON_ROTATIONS: [[i32; 7]; 7] = [
    [0, -1, 0, 0, 0, 0, 0],        // 0 CENTER
    [-1, -1, -1, -1, -1, -1, -1],  // 1 K_AXES (invalid leading digit for pentagon)
    [0, -1, 0, 0, 0, 1, 0],        // 2 J_AXES
    [0, -1, 0, 0, 1, 1, 0],        // 3 JK_AXES
    [0, -1, 0, 5, 0, 0, 0],        // 4 I_AXES
    [0, -1, 5, 5, 0, 0, 0],        // 5 IK_AXES
    [0, -1, 0, 0, 0, 0, 0],        // 6 IJ_AXES
];

#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE: [[i32; 7]; 7] = [
    [0, 0, 0, 0, 0, 0, 0],        // 0 CENTER
    [-1, -1, -1, -1, -1, -1, -1],  // 1 K_AXES
    [0, 1, 0, 0, 0, 0, 0],        // 2 J_AXES
    [0, 1, 0, 0, 0, 1, 0],        // 3 JK_AXES
    [0, 5, 0, 0, 0, 0, 0],        // 4 I_AXES
    [0, 5, 0, 5, 0, 0, 0],        // 5 IK_AXES
    [0, 0, 0, 0, 0, 0, 0],        // 6 IJ_AXES - C is all 0s.
];

// For reversing rotations when the index is on a pentagon and origin is not.
// Indexed by: [rev_dir_from_pent_to_origin][index_leading_digit_on_pent]
// Values are number of 60deg CCW rotations.
#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE_NONPOLAR: [[i32; 7]; 7] = [
    [0, 0, 0, 0, 0, 0, 0],
    [-1, -1, -1, -1, -1, -1, -1],
    [0, 1, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 1, 0],
    [0, 5, 0, 0, 0, 0, 0],
    [0, 1, 0, 5, 1, 1, 0], // Note: C had PENTAGON_ROTATIONS_REVERSE for this, I'm using specific names
    [0, 0, 0, 0, 0, 0, 0],
];

#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE_POLAR: [[i32; 7]; 7] = [
    [0, 0, 0, 0, 0, 0, 0],
    [-1, -1, -1, -1, -1, -1, -1],
    [0, 1, 1, 1, 1, 1, 1],
    [0, 1, 0, 0, 0, 1, 0],
    [0, 1, 0, 0, 1, 1, 1],
    [0, 1, 0, 5, 1, 1, 0],
    [0, 1, 1, 0, 1, 1, 1],
];

// Prohibited directions when unfolding a pentagon across faces.
// Indexed by: [origin_dir_from_pent_or_leading_digit][index_dir_to_pent_or_leading_digit]
#[rustfmt::skip]
static FAILED_DIRECTIONS: [[bool; 7]; 7] = [
    [false, false, false, false, false, false, false], // 0
    [false, false, false, false, false, false, false], // 1 (K - invalid leading for pent)
    [false, false, false, false, true,  true,  false], // 2 (J)
    [false, false, false, false, true,  false, true ], // 3 (JK)
    [false, false, true,  true,  false, false, false], // 4 (I)
    [false, false, true,  false, false, false, true ], // 5 (IK)
    [false, false, false, true,  false, true,  false], // 6 (IJ)
];

/// Transform the IJK coordinates of the `index` cell to be relative to the
/// `origin` H3 cell.
///
/// # Arguments
/// * `origin` - The H3 cell defining the origin of the local IJK coordinate system.
/// * `index` - The H3 cell whose IJK coordinates are to be determined.
/// * `out_ijk` - Output: The local IJK coordinates of `index` relative to `origin`.
///
/// # Returns
/// `Ok(())` on success, or an `H3Error` on failure.
// This is the Rust port of C's `_h3ToLocalIJK`
pub fn cell_to_local_ijk(origin: H3Index, index: H3Index, out_ijk: &mut CoordIJK) -> Result<(), H3Error> {
  // eprintln!(
  //   "  DEBUG: cell_to_local_ijk called with origin=0x{:x}, index=0x{:x}",
  //   origin.0, index.0
  // );

  let res = get_resolution(origin);

  // Basic validation (already in grid_distance, but good for standalone use)
  if res != get_resolution(index) {
    // eprintln!("    DEBUG: cell_to_local_ijk -> ResMismatch");
    return Err(H3Error::ResMismatch);
  }
  if !is_valid_cell(origin) || !is_valid_cell(index) {
    // eprintln!("    DEBUG: cell_to_local_ijk -> CellInvalid (input validation)");
    return Err(H3Error::CellInvalid);
  }

  let origin_base_cell = get_base_cell(origin);
  let index_base_cell = get_base_cell(index);

  // Get canonical FaceIJK for origin and index
  let mut fijk_origin_canonical = FaceIJK::default();
  _h3_to_face_ijk(origin, &mut fijk_origin_canonical)?; // This will have its own eprint
  // eprintln!(
  //   "    DEBUG: cell_to_local_ijk: fijk_origin_canonical (for origin 0x{:x}) = {:?}",
  //   origin.0, fijk_origin_canonical
  // );

  let mut fijk_index_canonical = FaceIJK::default();
  _h3_to_face_ijk(index, &mut fijk_index_canonical)?;
  // eprintln!(
  //   "    DEBUG: cell_to_local_ijk: fijk_index_canonical (for index 0x{:x}) = {:?}",
  //   index.0, fijk_index_canonical
  // );

  // If they ended up on the same canonical face, simple subtraction.
  if fijk_origin_canonical.face == fijk_index_canonical.face {
    _ijk_sub(&fijk_index_canonical.coord, &fijk_origin_canonical.coord, out_ijk);
    // eprintln!(
    //   "    DEBUG: cell_to_local_ijk: Same canonical face ({}), subtracted coords to get out_ijk = {:?}",
    //   fijk_origin_canonical.face, out_ijk
    // );
  } else {
    // Different canonical faces. Translate index's Fijk to be on origin's canonical face.
    // This uses a simplified version of C's _faceToFaceIjk.
    // We need a mutable copy of fijk_index_canonical to pass to _face_to_face_ijk_inplace
    let mut fijk_index_on_origin_face = fijk_index_canonical;
    // eprintln!("    DEBUG: cell_to_local_ijk: Different canonical faces. Origin face: {}, Index face: {}. Translating index to origin's face.", fijk_origin_canonical.face, fijk_index_canonical.face);

    if _face_to_face_ijk_inplace(&mut fijk_index_on_origin_face, res, fijk_origin_canonical.face).is_err() {
      // eprintln!("    DEBUG: cell_to_local_ijk -> Failed (_face_to_face_ijk_inplace failed)");
      return Err(H3Error::Failed); // Could not translate
    }
    // eprintln!(
    //   "    DEBUG: cell_to_local_ijk: fijk_index_on_origin_face (after translate) = {:?}",
    //   fijk_index_on_origin_face
    // );
    _ijk_sub(&fijk_index_on_origin_face.coord, &fijk_origin_canonical.coord, out_ijk);
    // eprintln!(
    //   "    DEBUG: cell_to_local_ijk: Subtracted coords (after translate) to get out_ijk = {:?}",
    //   out_ijk
    // );
  }

  // Pentagon distortion adjustments
  let origin_is_pent = is_pentagon(origin);
  let index_is_pent = is_pentagon(index); // Note: This is on the global H3Index `index`

  if origin_is_pent || index_is_pent {
    // eprintln!(
    //   "    DEBUG: cell_to_local_ijk: Pentagon distortion logic entered. OriginPent={}, IndexPent={}",
    //   origin_is_pent, index_is_pent
    // );
    let origin_center_digit_for_rotation_logic = _h3_leading_non_zero_digit(origin);

    // If index is also on the same base cell as origin, its leading digit is used.
    // If index is on a different base cell, the direction *to* that base cell is used.
    let index_related_digit_for_rotation_logic: Direction;

    if origin_base_cell == index_base_cell {
      index_related_digit_for_rotation_logic = _h3_leading_non_zero_digit(index);
    } else {
      // Direction from origin's base cell to index's base cell
      index_related_digit_for_rotation_logic = _get_base_cell_direction(origin_base_cell, index_base_cell);
      if index_related_digit_for_rotation_logic == Direction::InvalidDigit && origin_base_cell != index_base_cell {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic: Base cells not neighbors and not same.");
        return Err(H3Error::Failed);
      }
    }
    // eprintln!(
    //   "    DEBUG: cell_to_local_ijk: Pentagon logic: origin_digit_for_rot={:?}, index_related_digit_for_rot={:?}",
    //   origin_center_digit_for_rotation_logic, index_related_digit_for_rotation_logic
    // );

    if origin_is_pent && index_is_pent {
      if origin_base_cell != index_base_cell {
        // Should not happen, pentagons don't neighbor
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic: Two different pentagons, should not be neighbors.");
        return Err(H3Error::NotNeighbors);
      }
      // Both on same pentagon base cell. Rotations based on their leading digits.
      if FAILED_DIRECTIONS[origin_center_digit_for_rotation_logic as usize]
        [index_related_digit_for_rotation_logic as usize]
      {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (both pent): FAILED_DIRECTIONS");
        return Err(H3Error::Pentagon);
      }
      let num_rotations = PENTAGON_ROTATIONS[origin_center_digit_for_rotation_logic as usize]
        [index_related_digit_for_rotation_logic as usize];
      if num_rotations == -1 {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (both pent): num_rotations -1");
        return Err(H3Error::Pentagon);
      }
      for _ in 0..num_rotations {
        _ijk_rotate60_cw(out_ijk);
      }
      // eprintln!(
      //   "    DEBUG: cell_to_local_ijk: Pentagon logic (both pent): Applied {} CW rotations. out_ijk = {:?}",
      //   num_rotations, out_ijk
      // );
    } else if origin_is_pent {
      // Origin is pentagon, index is hexagon
      if FAILED_DIRECTIONS[origin_center_digit_for_rotation_logic as usize]
        [index_related_digit_for_rotation_logic as usize]
      {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (origin pent): FAILED_DIRECTIONS");
        return Err(H3Error::Pentagon);
      }
      let num_rotations = PENTAGON_ROTATIONS[origin_center_digit_for_rotation_logic as usize]
        [index_related_digit_for_rotation_logic as usize];
      if num_rotations == -1 {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (origin pent): num_rotations -1");
        return Err(H3Error::Pentagon);
      }
      for _ in 0..num_rotations {
        _ijk_rotate60_cw(out_ijk);
      }
      // eprintln!(
      //   "    DEBUG: cell_to_local_ijk: Pentagon logic (origin pent): Applied {} CW rotations. out_ijk = {:?}",
      //   num_rotations, out_ijk
      // );
    } else {
      // Index is pentagon, origin is hexagon
      // Direction here is from pentagon (index) to hexagon (origin)
      let dir_from_pent_to_origin_bc = _get_base_cell_direction(index_base_cell, origin_base_cell);
      if dir_from_pent_to_origin_bc == Direction::InvalidDigit && origin_base_cell != index_base_cell {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (index pent): Base cells not neighbors (for dir_from_pent_to_origin_bc).");
        return Err(H3Error::Failed);
      }
      let origin_related_digit_for_pent_rotation = if origin_base_cell == index_base_cell {
        origin_center_digit_for_rotation_logic // use origin's leading digit
      } else {
        dir_from_pent_to_origin_bc
      };

      let index_leading_digit = _h3_leading_non_zero_digit(index); // Leading digit of the pentagonal index itself

      if FAILED_DIRECTIONS[index_leading_digit as usize][origin_related_digit_for_pent_rotation as usize] {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (index pent): FAILED_DIRECTIONS");
        return Err(H3Error::Pentagon);
      }

      let rotations_table_ref = if crate::base_cells::_is_base_cell_polar_pentagon(index_base_cell) {
        &PENTAGON_ROTATIONS_REVERSE_POLAR
      } else {
        &PENTAGON_ROTATIONS_REVERSE_NONPOLAR
      };
      let num_rotations =
        rotations_table_ref[origin_related_digit_for_pent_rotation as usize][index_leading_digit as usize];
      if num_rotations == -1 {
        // eprintln!("    DEBUG: cell_to_local_ijk -> Pentagon logic (index pent): num_rotations -1");
        return Err(H3Error::Pentagon);
      }
      for _ in 0..num_rotations {
        _ijk_rotate60_ccw(out_ijk);
      }
      // eprintln!(
      //   "    DEBUG: cell_to_local_ijk: Pentagon logic (index pent): Applied {} CCW rotations. out_ijk = {:?}",
      //   num_rotations, out_ijk
      // );
    }
  }

  // eprintln!(
  //   "  DEBUG: cell_to_local_ijk for origin=0x{:x}, index=0x{:x} FINISHED, output out_ijk = {:?}",
  //   origin.0, index.0, out_ijk
  // );
  Ok(())
}

/// Internal helper: Transforms `fijk_target` to be on the coordinate system of `target_face`.
/// Modifies `fijk_target` in place.
fn _face_to_face_ijk_inplace(fijk_target: &mut FaceIJK, res: i32, target_face: i32) -> Result<(), H3Error> {
  if fijk_target.face == target_face {
    return Ok(()); // Already on the target face
  }
  
  // Now transform this center point to the target_face coordinate system
  // This uses the Fijk translation logic found in C's _faceToFaceIjk
  // It involves finding the path of faces from fijk_target.face to target_face
  // For a single step:
  let dir_to_target = ADJACENT_FACE_DIR[fijk_target.face as usize][target_face as usize];
  if dir_to_target == INVALID_FACE {
    return Err(H3Error::Failed); // Faces are not adjacent
  }

  let orient = &FACE_NEIGHBORS[fijk_target.face as usize][dir_to_target as usize];

  // Rotate the target's IJK coordinates into the new face's system
  for _ in 0..orient.ccw_rot60 {
    _ijk_rotate60_ccw(&mut fijk_target.coord);
  }

  // Apply translation vector for the new face
  // The translation vector is defined for res 0. We need to scale it.
  // This is tricky because downAp7/r is for cell centers, not pure vectors.
  // C code: `_ijkScale(&fijkOrient->translate, scale); _ijkAdd(ijk, &fijkOrient->translate, ijk);`
  // where scale depends on resolution.
  // A simpler way for res 0 is just add the translate. For finer res, it's complex.
  // The C code's _faceToFaceIjk applies this logic:
  //  1. Rotate fijk_target.coord according to orient.ccwRot60
  //  2. Scale orient.translate by powers of 7 based on resolution parity.
  //  3. Add scaled translate to fijk_target.coord.
  //  4. Normalize.
  //  5. Update fijk_target.face.

  let mut scaled_translate = orient.translate;
  // The translation vector needs to be scaled to the resolution.
  // For res 0, scale is 1. For res 1 (ClassIII), _downAp7 applied to translate.
  // For res 2 (ClassII), _downAp7r applied to res1, then _downAp7 applied to res0 translate.
  // This needs careful implementation matching C's _faceToFaceIjk scaling.
  // For simplicity if this is only used for res 0 by cellToLocalIjk internal:
  if res == 0 {
    let original_coord = fijk_target.coord;
    _ijk_add(&original_coord, &scaled_translate, &mut fijk_target.coord);
  } else {
    // For finer resolutions, the C code effectively scales the base
    // translation vector by applying down-aperture operations.
    // E.g. for res 1, it applies _downAp7 to the base translation vector.
    // For res 2, it applies _downAp7r(_downAp7(base_translate_vec)).
    for r_val in 1..=res {
      if is_resolution_class_iii(r_val) {
        _down_ap7(&mut scaled_translate);
      } else {
        _down_ap7r(&mut scaled_translate);
      }
    }
    let original_coord = fijk_target.coord;
    _ijk_add(&original_coord, &scaled_translate, &mut fijk_target.coord);
  }

  _ijk_normalize(&mut fijk_target.coord);
  fijk_target.face = target_face;

  Ok(())
}

/// Produces an H3 cell index for IJK+ coordinates anchored by an origin H3 cell.
///
/// # Arguments
/// * `origin` - The H3 cell defining the origin of the local IJK coordinate system.
/// * `ijk` - The local IJK+ coordinates.
/// * `out_h3` - Output: The H3 cell corresponding to the local IJK coordinates.
///
/// # Returns
/// `Ok(())` on success, or an `H3Error` on failure.
// This is the Rust port of C's `_localIJKToH3`
// Let's define these as C does, then use them.
// For now, I'll assume PENTAGON_ROTATIONS, PENTAGON_ROTATIONS_REVERSE_NONPOLAR, PENTAGON_ROTATIONS_REVERSE_POLAR are defined
// as in the provided Rust files. The C PENTAGON_ROTATIONS_REVERSE seems to map to our Rust PENTAGON_ROTATIONS
// when used for the `originOnPent && indexOnPent && dir == CENTER_DIGIT` case, applied CCW.
pub fn local_ijk_to_cell(origin: H3Index, ijk: &CoordIJK, out_h3: &mut H3Index) -> Result<(), H3Error> {
  // eprintln!(
  //   "  DEBUG: local_ijk_to_cell called with origin=0x{:x}, ijk={:?}",
  //   origin.0, ijk
  // );
  // ... (rest of the function, with its own eprint before returning Ok or Err) ...
  // At the end of local_ijk_to_cell, before `Ok(())` or `Err(...)`:
  // eprintln!("    DEBUG: local_ijk_to_cell: output H3 = 0x{:x}", out_h3.0);

  let res = get_resolution(origin);
  let origin_base_cell = get_base_cell(origin);
  let origin_is_pent = _is_base_cell_pentagon(origin_base_cell);

  let mut fijk_origin_canonical = FaceIJK::default();
  _h3_to_face_ijk(origin, &mut fijk_origin_canonical)?;
  // eprintln!(
  //   "    DEBUG: local_ijk_to_cell: fijk_origin_canonical = {:?}",
  //   fijk_origin_canonical
  // );

  let mut fijk_target_on_origin_plane = fijk_origin_canonical;
  let temp_coord = fijk_target_on_origin_plane.coord; // Copy for _ijk_add
  _ijk_add(&temp_coord, ijk, &mut fijk_target_on_origin_plane.coord);
  _ijk_normalize(&mut fijk_target_on_origin_plane.coord);
  // eprintln!(
  //   "    DEBUG: local_ijk_to_cell: fijk_target_on_origin_plane (after add & norm) = {:?}",
  //   fijk_target_on_origin_plane
  // );

  *out_h3 = _face_ijk_to_h3(&fijk_target_on_origin_plane, res);
  // eprintln!(
  //   "    DEBUG: local_ijk_to_cell: _face_ijk_to_h3 initial result H3 = 0x{:x}",
  //   out_h3.0
  // );

  if *out_h3 == H3_NULL {
    // eprintln!("    DEBUG: local_ijk_to_cell -> Failed (_face_ijk_to_h3 returned NULL)");
    return Err(H3Error::Failed);
  }

  // Pentagon un-distortion (reverse of cell_to_local_ijk)
  let index_base_cell = get_base_cell(*out_h3);
  let index_is_pent = _is_base_cell_pentagon(index_base_cell);

  if origin_is_pent || index_is_pent {
    // eprintln!(
    //   "    DEBUG: local_ijk_to_cell: Pentagon un-distortion logic. OriginPent={}, IndexPent={}",
    //   origin_is_pent, index_is_pent
    // );
    let origin_center_digit = _h3_leading_non_zero_digit(origin);

    if origin_base_cell == index_base_cell {
      if origin_is_pent {
        // Implies index is also on same pentagon BC
        let index_center_digit = _h3_leading_non_zero_digit(*out_h3);
        // eprintln!(
        //   "      DEBUG: Both on same pentagon BC. OriginLeading={:?}, IndexLeading={:?}",
        //   origin_center_digit, index_center_digit
        // );
        if FAILED_DIRECTIONS[origin_center_digit as usize][index_center_digit as usize] {
          // eprintln!("      DEBUG: FAILED_DIRECTIONS check failed for same BC pentagon case.");
          return Err(H3Error::Pentagon); // Should match error from cell_to_local_ijk
        }
        let num_rotations_cw_orig = PENTAGON_ROTATIONS[origin_center_digit as usize][index_center_digit as usize];
        if num_rotations_cw_orig == -1 {
          // eprintln!("      DEBUG: num_rotations_cw_orig was -1");
          return Err(H3Error::Pentagon);
        }
        for _ in 0..num_rotations_cw_orig {
          *out_h3 = _h3_rotate_pent60_ccw(*out_h3);
        } // Reverse CW with CCW
        // eprintln!(
        //   "      DEBUG: Applied {} CCW rotations (reverse of same BC pent). New H3 = 0x{:x}",
        //   num_rotations_cw_orig, out_h3.0
        // );
      }
    } else {
      // Different base cells
      let dir_to_index_bc = _get_base_cell_direction(origin_base_cell, index_base_cell);
      if dir_to_index_bc == Direction::InvalidDigit {
        // eprintln!("      DEBUG: dir_to_index_bc was InvalidDigit");
        return Err(H3Error::Failed);
      } // Should not happen if _face_ijk_to_h3 was good

      if origin_is_pent {
        // eprintln!(
        //   "      DEBUG: Origin is pent, index hex. DirToIdxBC={:?}",
        //   dir_to_index_bc
        // );
        if FAILED_DIRECTIONS[origin_center_digit as usize][dir_to_index_bc as usize] {
          // eprintln!("      DEBUG: FAILED_DIRECTIONS check failed for origin_is_pent case.");
          return Err(H3Error::Pentagon);
        }
        let num_rotations_cw_orig = PENTAGON_ROTATIONS[origin_center_digit as usize][dir_to_index_bc as usize];
        if num_rotations_cw_orig == -1 {
          // eprintln!("      DEBUG: num_rotations_cw_orig was -1 for origin_is_pent.");
          return Err(H3Error::Pentagon);
        }
        for _ in 0..num_rotations_cw_orig {
          *out_h3 = _h3_rotate_pent60_ccw(*out_h3);
        }
        // eprintln!(
        //   "      DEBUG: Applied {} CCW rotations (reverse of origin_is_pent). New H3 = 0x{:x}",
        //   num_rotations_cw_orig, out_h3.0
        // );
      } else {
        // index_is_pent, origin_is_hex
        let index_leading_digit = _h3_leading_non_zero_digit(*out_h3); // Leading digit of the PENTAGONAL cell *out_h3*
        let dir_from_pent_to_origin_bc = _get_base_cell_direction(index_base_cell, origin_base_cell);
        if dir_from_pent_to_origin_bc == Direction::InvalidDigit {
          // eprintln!("      DEBUG: dir_from_pent_to_origin_bc was InvalidDigit");
          return Err(H3Error::Failed);
        }

        // eprintln!(
        //   "      DEBUG: Index is pent, origin hex. IndexLeading={:?}, DirFromPentToOriginBC={:?}",
        //   index_leading_digit, dir_from_pent_to_origin_bc
        // );

        if FAILED_DIRECTIONS[index_leading_digit as usize][dir_from_pent_to_origin_bc as usize] {
          // eprintln!("      DEBUG: FAILED_DIRECTIONS check failed for index_is_pent case.");
          return Err(H3Error::Pentagon);
        }
        let rotations_table_ref = if crate::base_cells::_is_base_cell_polar_pentagon(index_base_cell) {
          &PENTAGON_ROTATIONS_REVERSE_POLAR
        } else {
          &PENTAGON_ROTATIONS_REVERSE_NONPOLAR
        };
        let num_rotations_ccw_orig =
          rotations_table_ref[dir_from_pent_to_origin_bc as usize][index_leading_digit as usize];
        if num_rotations_ccw_orig == -1 {
          // eprintln!("      DEBUG: num_rotations_ccw_orig was -1 for index_is_pent.");
          return Err(H3Error::Pentagon);
        }
        for _ in 0..num_rotations_ccw_orig {
          *out_h3 = _h3_rotate_pent60_cw(*out_h3);
        } // Reverse CCW with CW
        // eprintln!(
        //   "      DEBUG: Applied {} CW rotations (reverse of index_is_pent). New H3 = 0x{:x}",
        //   num_rotations_ccw_orig, out_h3.0
        // );
      }
    }
  }
  // eprintln!(
  //   "DEBUG: local_ijk_to_cell for origin=0x{:x}, ijk={:?} FINISHED, output H3 = 0x{:x}",
  //   origin.0, ijk, out_h3.0
  // );
  Ok(())
}

// Public wrappers converting IJK to IJ for the API
pub fn cell_to_local_ij(origin: H3Index, index: H3Index, _mode: u32, out_ij: &mut CoordIJ) -> Result<(), H3Error> {
  // Mode is currently unused, validate it's 0 as per C API.
  if _mode != 0 {
    return Err(H3Error::OptionInvalid);
  }

  let mut ijk = CoordIJK::default();
  cell_to_local_ijk(origin, index, &mut ijk)?; // Call the IJK version
  ijk_to_ij(&ijk, out_ij); // Convert IJK to IJ
  Ok(())
}

pub fn local_ij_to_cell(origin: H3Index, ij: &CoordIJ, _mode: u32, out_h3: &mut H3Index) -> Result<(), H3Error> {
  if _mode != 0 {
    return Err(H3Error::OptionInvalid);
  }

  let mut ijk = CoordIJK::default();
  ij_to_ijk(ij, &mut ijk)?; // Convert IJ to IJK, can fail on overflow
  local_ijk_to_cell(origin, &ijk, out_h3) // Call the IJK version
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::indexing::lat_lng_to_cell;
  use crate::latlng::_set_geo_degs;
  use crate::types::{CoordIJ, LatLng, H3_NULL};

  #[test]
  fn test_cell_to_local_ijk_identity() {
    let mut geo = LatLng::default();
    _set_geo_degs(&mut geo, 37.779, -122.419);
    let origin = lat_lng_to_cell(&geo, 5).unwrap();

    let mut ijk = CoordIJK::default();
    assert!(cell_to_local_ijk(origin, origin, &mut ijk).is_ok());
    let expected_ijk = CoordIJK { i: 0, j: 0, k: 0 };
    assert_eq!(
      ijk, expected_ijk,
      "IJK of origin relative to self is {:?}",
      expected_ijk
    );
  }

  #[test]
  fn test_local_ijk_to_cell_identity() {
    let mut geo = LatLng::default();
    _set_geo_degs(&mut geo, 37.779, -122.419);
    let origin = lat_lng_to_cell(&geo, 5).unwrap();

    let ijk = CoordIJK { i: 0, j: 0, k: 0 };
    let mut h3_out = H3_NULL;
    assert!(local_ijk_to_cell(origin, &ijk, &mut h3_out).is_ok());
    assert_eq!(h3_out, origin, "H3 from local IJK {:?} should be origin", ijk);
  }

  // Roundtrip test (H3 -> LocalIJK -> H3)
  fn assert_local_ijk_roundtrip(origin: H3Index, target: H3Index) {
    let mut ijk = CoordIJK::default();
    let to_ijk_res = cell_to_local_ijk(origin, target, &mut ijk);

    if to_ijk_res.is_err() {
      // If cellToLocalIjk failed, it might be a case H3 doesn't support (e.g. too far, across pentagon).
      // This is acceptable for some pairs. We can't test roundtrip then.
      // Consider asserting specific error types if known.
      // For now, we'll just print a warning for test debugging.
      // println!("Warning: cellToLocalIjk failed for origin {:x}, target {:x}: {:?}", origin.0, target.0, to_ijk_res.unwrap_err());
      return;
    }

    let mut h3_rt = H3_NULL;
    let to_h3_res = local_ijk_to_cell(origin, &ijk, &mut h3_rt);
    assert!(
      to_h3_res.is_ok(),
      "localIjkToCell failed. Origin: {:x}, IJK: {:?}, Target: {:x}, Error: {:?}",
      origin.0,
      ijk,
      target.0,
      to_h3_res.unwrap_err()
    );
    assert_eq!(
      h3_rt, target,
      "Roundtrip H3->IJK->H3 mismatch. Origin: {:x}, Target: {:x}, IJK: {:?}, Got: {:x}",
      origin.0, target.0, ijk, h3_rt.0
    );
  }

  #[test]
  fn test_local_ijk_roundtrip_neighbors() {
    let mut geo = LatLng::default();
    _set_geo_degs(&mut geo, 37.779, -122.419); // SF
    let origin = lat_lng_to_cell(&geo, 5).unwrap();

    let mut k1_ring = [H3_NULL; 7];
    crate::traversal::grid_disk::grid_disk(origin, 1, &mut k1_ring).unwrap();

    assert_local_ijk_roundtrip(origin, origin); // Identity
    for neighbor_h3 in k1_ring {
      if neighbor_h3 != H3_NULL && neighbor_h3 != origin {
        assert_local_ijk_roundtrip(origin, neighbor_h3);
      }
    }
  }

  // Test cellToLocalIj and localIjToCell (the IJ wrappers)
  #[test]
  fn test_local_ij_roundtrip() {
    let mut geo = LatLng::default();
    _set_geo_degs(&mut geo, 37.779, -122.419);
    let origin = lat_lng_to_cell(&geo, 5).unwrap();

    let mut ij = CoordIJ::default();
    let to_ij_res = cell_to_local_ij(origin, origin, 0, &mut ij);
    assert!(to_ij_res.is_ok());
    assert_eq!(ij, CoordIJ { i: 0, j: 0 }, "IJ of origin to self");

    let mut h3_rt = H3_NULL;
    let to_h3_res = local_ij_to_cell(origin, &ij, 0, &mut h3_rt);
    assert!(to_h3_res.is_ok());
    assert_eq!(h3_rt, origin, "Roundtrip IJ origin");
  }

  // Add C's testCellToLocalIj.c cases (ijBaseCells, ijOutOfRange, cellToLocalIjFailed)
  // Add C's testGridDistance.c cases (testIndexDistance, testIndexDistance2, gridDistanceBaseCells)
  // These will require the full port of cell_to_local_ijk and local_ijk_to_cell.
}