pub const CHUNK_ROWID_MULTIPLIER: i64 = 1000;

pub fn encode_rowid(document_id: i64, chunk_index: i64) -> i64 {
    assert!(
        (0..CHUNK_ROWID_MULTIPLIER).contains(&chunk_index),
        "chunk_index {chunk_index} out of range [0, {CHUNK_ROWID_MULTIPLIER})"
    );
    document_id
        .checked_mul(CHUNK_ROWID_MULTIPLIER)
        .and_then(|v| v.checked_add(chunk_index))
        .unwrap_or_else(|| {
            panic!("encode_rowid overflow: document_id={document_id}, chunk_index={chunk_index}")
        })
}

pub fn decode_rowid(rowid: i64) -> (i64, i64) {
    assert!(
        rowid >= 0,
        "decode_rowid called with negative rowid: {rowid}"
    );
    let document_id = rowid / CHUNK_ROWID_MULTIPLIER;
    let chunk_index = rowid % CHUNK_ROWID_MULTIPLIER;
    (document_id, chunk_index)
}

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

    #[test]
    fn test_encode_single_chunk() {
        assert_eq!(encode_rowid(1, 0), 1000);
    }

    #[test]
    fn test_encode_multi_chunk() {
        assert_eq!(encode_rowid(1, 5), 1005);
    }

    #[test]
    fn test_encode_specific_values() {
        assert_eq!(encode_rowid(42, 0), 42000);
        assert_eq!(encode_rowid(42, 5), 42005);
    }

    #[test]
    fn test_decode_zero_chunk() {
        assert_eq!(decode_rowid(42000), (42, 0));
    }

    #[test]
    fn test_decode_roundtrip() {
        for doc_id in [0, 1, 42, 100, 999, 10000] {
            for chunk_idx in [0, 1, 5, 99, 999] {
                let rowid = encode_rowid(doc_id, chunk_idx);
                let (decoded_doc, decoded_chunk) = decode_rowid(rowid);
                assert_eq!(
                    (decoded_doc, decoded_chunk),
                    (doc_id, chunk_idx),
                    "Roundtrip failed for doc_id={doc_id}, chunk_idx={chunk_idx}"
                );
            }
        }
    }

    #[test]
    fn test_multiplier_value() {
        assert_eq!(CHUNK_ROWID_MULTIPLIER, 1000);
    }
}
