//! Schema-aware database open logic.

use std::sync::Arc;
use std::sync::atomic::AtomicU64;

use roaring::RoaringBitmap;

use crate::entities::{
    CreatorItemsBitmap, HardNegIndex, InteractionLedger, PreferenceVectors, UserStateIndex,
};
use crate::ranking::builtins::register_builtins;
use crate::ranking::registry::ProfileRegistry;
use crate::schema::{DurabilityError, EntityId, Schema, TidalError, Timestamp};
use crate::signals::{NoopWalWriter, SignalLedger, SignalTypeId};
use crate::storage::InMemoryBackend;
use crate::storage::indexes::bitmap::BitmapIndex;
use crate::storage::indexes::range::RangeIndex;
use crate::storage::vector::registry::EmbeddingSlotRegistry;
use crate::wal::{WalConfig, WalHandle};

use super::config::StorageMode;
use super::storage_box::StorageBox;
use super::wal_bridge::WalHandleWriter;

use super::state_rebuild::rebuild_entity_state;

/// Bundle returned by [`TidalDb::open_with_schema`] to avoid a fragile tuple.
///
/// Each field is consumed by [`TidalDb::from_parts`] during construction.
pub struct OpenResult {
    pub storage: StorageBox,
    pub ledger: Arc<SignalLedger>,
    pub wal: Option<WalHandle>,
    pub last_seq: Arc<AtomicU64>,
    pub profile_registry: ProfileRegistry,
    pub category_index: BitmapIndex,
    pub format_index: BitmapIndex,
    pub creator_index: BitmapIndex,
    pub tag_index: BitmapIndex,
    pub duration_index: RangeIndex<u32>,
    pub created_at_index: RangeIndex<u64>,
    pub universe: RoaringBitmap,
    pub embedding_registry: EmbeddingSlotRegistry,
    pub schema_def: Schema,
    // M3 entity state rebuilt from durable storage.
    pub creator_items: CreatorItemsBitmap,
    pub user_state: UserStateIndex,
    pub hard_negatives: HardNegIndex,
    pub interaction_ledger: InteractionLedger,
    pub preference_vectors: PreferenceVectors,
    /// Session journal events recovered on startup (for session crash recovery).
    pub session_events: Vec<crate::wal::format::SessionWalEvent>,
}

impl super::TidalDb {
    /// Open storage and ledger components for the given schema.
    ///
    /// Called from `TidalDbBuilder::open()` when `with_schema()` was called.
    /// Returns an `OpenResult` containing all components needed by `from_parts`.
    #[allow(clippy::too_many_lines)] // Construction logic is inherently verbose; splitting would scatter initialization flow.
    pub(crate) fn open_with_schema(
        config: &super::Config,
        schema: Schema,
    ) -> crate::Result<OpenResult> {
        let last_seq = Arc::new(AtomicU64::new(0));

        // Initialize profile registry with builtins.
        let mut profile_registry = ProfileRegistry::new();
        register_builtins(&mut profile_registry).map_err(|e| {
            TidalError::internal("open", format!("failed to register builtin profiles: {e}"))
        })?;

        // Initialize M2 indexes (empty -- populated as items are written).
        let category_index = BitmapIndex::new("category");
        let format_index = BitmapIndex::new("format");
        let creator_index = BitmapIndex::new("creator");
        let tag_index = BitmapIndex::new("tags");
        let duration_index = RangeIndex::new("duration");
        let created_at_index = RangeIndex::new("created_at");
        let universe = RoaringBitmap::new();
        let embedding_registry = EmbeddingSlotRegistry::new();

        let schema_def = schema.clone();

        match config.mode {
            StorageMode::Ephemeral => {
                let storage = StorageBox::Memory {
                    items: InMemoryBackend::default(),
                    users: InMemoryBackend::default(),
                    creators: InMemoryBackend::default(),
                };
                let ledger = Arc::new(SignalLedger::new(schema, Box::new(NoopWalWriter)));

                // Read preference vector dimensionality from the schema.
                let pref_dim = schema_def
                    .embedding_slots()
                    .first()
                    .map_or(128, |s| s.dimensions);

                Ok(OpenResult {
                    storage,
                    ledger,
                    wal: None,
                    last_seq,
                    profile_registry,
                    category_index,
                    format_index,
                    creator_index,
                    tag_index,
                    duration_index,
                    created_at_index,
                    universe,
                    embedding_registry,
                    schema_def,
                    creator_items: CreatorItemsBitmap::new(),
                    user_state: UserStateIndex::new(),
                    hard_negatives: HardNegIndex::new(),
                    interaction_ledger: InteractionLedger::new(),
                    preference_vectors: PreferenceVectors::new(pref_dim),
                    session_events: Vec::new(),
                })
            }
            StorageMode::Persistent => {
                let data_dir = config.data_dir.as_ref().ok_or_else(|| {
                    TidalError::internal("open", "persistent mode requires data_dir")
                })?;

                // Open fjall storage.
                let fjall_storage =
                    crate::storage::FjallStorage::open(data_dir).map_err(TidalError::from)?;
                let storage = StorageBox::Fjall(fjall_storage);

                // Build WAL config.  The WAL directory sits inside data_dir
                // but `WalConfig.dir` is the *parent* of the "wal/" subdirectory.
                let wal_config = WalConfig {
                    dir: data_dir.clone(),
                    ..WalConfig::default()
                };

                let (wal, replayed_events, session_events) =
                    WalHandle::open(wal_config).map_err(|e| {
                        TidalError::Durability(DurabilityError {
                            message: format!("WAL open failed: {e}"),
                        })
                    })?;

                // Build the WAL bridge for the ledger.
                let wal_writer =
                    Box::new(WalHandleWriter::new(wal.sender(), Arc::clone(&last_seq)));

                // Construct the ledger.
                let ledger = Arc::new(SignalLedger::new(schema, wal_writer));

                // Restore signal state from the last checkpoint.
                if let Err(e) = ledger.restore(storage.items_engine()) {
                    tracing::warn!(
                        error = %e,
                        "signal ledger restore failed; starting from empty state"
                    );
                }

                // Replay WAL events that post-date the checkpoint.
                for event in replayed_events {
                    let type_id = SignalTypeId::new(u16::from(event.signal_type));
                    let entity_id = EntityId::new(event.entity_id);
                    let weight = f64::from(event.weight);
                    let timestamp = Timestamp::from_nanos(event.timestamp_nanos);
                    ledger.apply_wal_event(type_id, entity_id, weight, timestamp);
                }

                // Rebuild in-memory entity state from durable storage.
                // This reconstructs blocked/hidden/follows user state, creator-items
                // bitmaps, and interaction weights from persisted relationship edges
                // and item metadata.
                let user_state = UserStateIndex::new();
                let creator_items = CreatorItemsBitmap::new();
                let interaction_ledger = InteractionLedger::new();
                rebuild_entity_state(&storage, &user_state, &creator_items, &interaction_ledger)?;

                // Read preference vector dimensionality from the schema.
                let pref_dim = schema_def
                    .embedding_slots()
                    .first()
                    .map_or(128, |s| s.dimensions);

                Ok(OpenResult {
                    storage,
                    ledger,
                    wal: Some(wal),
                    last_seq,
                    profile_registry,
                    category_index,
                    format_index,
                    creator_index,
                    tag_index,
                    duration_index,
                    created_at_index,
                    universe,
                    embedding_registry,
                    schema_def,
                    creator_items,
                    user_state,
                    hard_negatives: HardNegIndex::new(),
                    interaction_ledger,
                    preference_vectors: PreferenceVectors::new(pref_dim),
                    session_events,
                })
            }
        }
    }
}
