use std::collections::HashSet;
use std::path::{Path, PathBuf};

#[cfg(test)]
use crate::app_config::load_app_config;
use crate::file_ops::portable_path;
use cosimo::{Datum, DatumKey, ReviewStoreError, SqliteReviewStore};

pub(crate) const DEFAULT_DECK_PATH: &str = "decks/deck.cosimo-deck";

#[cfg(test)]
pub(crate) fn startup_deck_path(config_path: impl AsRef<Path>) -> PathBuf {
    let default_path = default_deck_path();
    let Some(saved_path) = load_app_config(config_path)
        .ok()
        .and_then(|config| config.last_open_deck_path)
    else {
        return default_path;
    };

    startup_deck_path_from_saved(default_path, saved_path)
}

pub(crate) fn startup_deck_path_from_saved(default_path: PathBuf, saved_path: PathBuf) -> PathBuf {
    if saved_path.exists() {
        saved_path
    } else {
        default_path
    }
}

pub(crate) fn default_deck_path() -> PathBuf {
    portable_path(DEFAULT_DECK_PATH)
}

pub(crate) fn review_db_path_for_deck(deck_path: &Path) -> PathBuf {
    let parent = deck_path.parent().unwrap_or_else(|| Path::new("."));
    let stem = deck_path
        .file_stem()
        .and_then(|stem| stem.to_str())
        .filter(|stem| !stem.is_empty())
        .unwrap_or("deck");

    parent.join(format!("{stem}.reviews.sqlite3"))
}

pub(crate) fn enumerate_deck_cards(
    data: Vec<Datum>,
    store: &SqliteReviewStore,
) -> Result<(Vec<Datum>, bool), ReviewStoreError> {
    let mut changed = false;
    // Old beta decks did not carry numeric card ids. Assign ids once and
    // migrate the review-store key before the deck is rewritten, so future
    // prompt/response edits keep their scheduling history.
    let mut used_ids: HashSet<u64> = store.used_card_ids()?.into_iter().collect();
    for card_id in data.iter().filter_map(Datum::card_id) {
        used_ids.insert(card_id);
    }

    let mut enumerated = Vec::with_capacity(data.len());
    for datum in data {
        if datum.card_id().is_some() {
            enumerated.push(datum);
            continue;
        }

        let card_id = next_unused_card_id(&used_ids);
        used_ids.insert(card_id);
        let old_key = DatumKey::for_legacy_content(&datum);
        let upgraded = datum.with_card_id(card_id);
        let new_key = DatumKey::for_datum(&upgraded);
        store.migrate_datum_key(&old_key, &new_key)?;
        enumerated.push(upgraded);
        changed = true;
    }

    Ok((enumerated, changed))
}

pub(crate) fn deck_needs_card_ids(data: &[Datum]) -> bool {
    data.iter().any(|datum| datum.card_id().is_none())
}

pub(crate) fn next_unused_card_id(used_ids: &HashSet<u64>) -> u64 {
    let mut card_id = 1;
    while used_ids.contains(&card_id) {
        card_id += 1;
    }
    card_id
}
