use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use obicompactvec::{PersistentBitVecBuilder, PersistentCompactIntVecBuilder};
use obilayeredmap::meta::PartitionMeta;
use obilayeredmap::{IndexMode, OLMError};
use obiskio::{SKError, SKResult};

// ── olm_to_sk ────────────────────────────────────────────────────────────────

pub(crate) fn olm_to_sk(e: OLMError, context: &'static str) -> SKError {
    match e {
        OLMError::Io(e) => SKError::Io(e),
        other => SKError::InvalidData {
            context,
            detail: other.to_string(),
        },
    }
}

// ── load_meta ────────────────────────────────────────────────────────────────

/// Load PartitionMeta, or recover it by probing layer directories.
/// Indexes built before meta.json was introduced lack the file.
pub(crate) fn load_meta(dir: &Path, context: &'static str) -> SKResult<PartitionMeta> {
    match PartitionMeta::load(dir) {
        Ok(m) => Ok(m),
        Err(e) if matches!(e, OLMError::Io(ref io_e) if io_e.kind() == std::io::ErrorKind::NotFound) =>
        {
            let mut n = 0usize;
            while dir.join(format!("layer_{n}")).exists() {
                n += 1;
            }
            let m = PartitionMeta {
                n_layers: n,
                mode: IndexMode::default(),
            };
            m.save(dir).map_err(|e| olm_to_sk(e, context))?;
            Ok(m)
        }
        Err(e) => Err(olm_to_sk(e, context)),
    }
}

// ── path helpers ──────────────────────────────────────────────────────────────

pub(crate) fn col_path_bit(dir: &Path, col: usize) -> PathBuf {
    dir.join(format!("col_{col:06}.pbiv"))
}

pub(crate) fn col_path_int(dir: &Path, col: usize) -> PathBuf {
    dir.join(format!("col_{col:06}.pciv"))
}

pub(crate) fn write_matrix_meta(dir: &Path, n: usize, n_cols: usize) -> io::Result<()> {
    fs::write(
        dir.join("meta.json"),
        format!("{{\"n\":{n},\"n_cols\":{n_cols}}}\n"),
    )
}

// ── ColBuilder ────────────────────────────────────────────────────────────────

pub(crate) enum ColBuilder {
    Bit(PersistentBitVecBuilder),
    Int(PersistentCompactIntVecBuilder),
}

impl ColBuilder {
    pub(crate) fn set_val(&mut self, slot: usize, value: u32) {
        match self {
            ColBuilder::Bit(b) => b.set(slot, value > 0),
            ColBuilder::Int(b) => b.set(slot, value),
        }
    }

    pub(crate) fn close(self) -> SKResult<()> {
        match self {
            ColBuilder::Bit(b) => b.close().map_err(SKError::Io),
            ColBuilder::Int(b) => b.close().map_err(SKError::Io),
        }
    }
}
