//! Per-type "core" structs for typed items.
//!
//! Each variant lives in its own submodule. The `ItemCore` enum + match
//! exhaustiveness is the extension mechanism — adding a new variant later
//! means: create the submodule, add the enum variant, fix the match arms
//! the compiler points at, register the popup form (Plan 1C).

use serde::{Deserialize, Serialize};

pub mod login;
pub mod secure_note;
pub mod identity;
pub mod card;
pub mod key;
pub mod document;
pub mod totp;

pub use login::LoginCore;
pub use secure_note::SecureNoteCore;
pub use identity::IdentityCore;
pub use card::{CardCore, CardKind};
pub use key::KeyCore;
pub use document::DocumentCore;
pub use totp::{TotpCore, TotpConfig, TotpAlgorithm, TotpKind, compute_totp_code};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ItemType {
    Login,
    SecureNote,
    Identity,
    Card,
    Key,
    Document,
    Totp,
}

// INVARIANT: no *Core struct may have a field serialized as "type" —
// that key is reserved for serde's internal tag. Use "kind" for
// type-discriminant fields within core structs (CardKind, TotpKind).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ItemCore {
    Login(LoginCore),
    SecureNote(SecureNoteCore),
    Identity(IdentityCore),
    Card(CardCore),
    Key(KeyCore),
    Document(DocumentCore),
    Totp(TotpCore),
}

impl ItemCore {
    pub fn item_type(&self) -> ItemType {
        match self {
            ItemCore::Login(_) => ItemType::Login,
            ItemCore::SecureNote(_) => ItemType::SecureNote,
            ItemCore::Identity(_) => ItemType::Identity,
            ItemCore::Card(_) => ItemType::Card,
            ItemCore::Key(_) => ItemType::Key,
            ItemCore::Document(_) => ItemType::Document,
            ItemCore::Totp(_) => ItemType::Totp,
        }
    }
}

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

    #[test]
    fn item_type_serializes_snake_case() {
        let json = serde_json::to_string(&ItemType::SecureNote).unwrap();
        assert_eq!(json, "\"secure_note\"");
    }

    #[test]
    fn item_core_login_round_trip_via_tag() {
        use zeroize::Zeroizing;
        let core = ItemCore::Login(LoginCore {
            username: Some("alice".into()),
            password: Some(Zeroizing::new("hunter2".into())),
            url: None,
            totp: None,
        });
        let json = serde_json::to_string(&core).unwrap();
        // Tag-based: outer object has "type": "login"
        assert!(json.contains("\"type\":\"login\""));
        let parsed: ItemCore = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.item_type(), ItemType::Login);
    }

    #[test]
    fn item_core_secure_note_round_trip_via_tag() {
        use zeroize::Zeroizing;
        let core = ItemCore::SecureNote(SecureNoteCore { body: Zeroizing::new("hello".into()) });
        let json = serde_json::to_string(&core).unwrap();
        assert!(json.contains("\"type\":\"secure_note\""));
        let parsed: ItemCore = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.item_type(), ItemType::SecureNote);
    }

    #[test]
    fn item_core_round_trips_for_all_seven_types() {
        use crate::ids::AttachmentId;

        let cores = vec![
            ItemCore::Login(LoginCore::default()),
            ItemCore::SecureNote(SecureNoteCore::default()),
            ItemCore::Identity(IdentityCore::default()),
            ItemCore::Card(CardCore::default()),
            ItemCore::Key(KeyCore::default()),
            ItemCore::Document(DocumentCore {
                filename: "x".into(),
                mime_type: "text/plain".into(),
                primary_attachment: AttachmentId("0123456789abcdef".into()),
            }),
            ItemCore::Totp(TotpCore::default()),
        ];
        for core in cores {
            let expected_type = core.item_type();
            let json = serde_json::to_string(&core).unwrap();
            let parsed: ItemCore = serde_json::from_str(&json).expect("round-trip failed");
            assert_eq!(parsed.item_type(), expected_type);
        }
    }
}
