use nana_domain::{
    AppInfo, DemoPackSummary, DOMAIN_SCHEMA_VERSION, PlayerView, ResourceBundle, ValidationIssue,
    validate_bundle,
};
use serde::Serialize;

const DEMO_PLAYER_VIEW: &str = include_str!("../../fixtures/player-view/initial.json");
const DEMO_BUNDLE: &str = include_str!("../../content/nana-demo/bundle.json");

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandError {
    code: &'static str,
    message: String,
    issues: Vec<ValidationIssue>,
}

impl CommandError {
    fn parse(target: &str, error: &serde_json::Error) -> Self {
        Self {
            code: "invalid_embedded_json",
            message: format!("failed to parse embedded {target}: {error}"),
            issues: Vec::new(),
        }
    }
}

#[tauri::command]
fn get_app_info() -> AppInfo {
    AppInfo {
        name: "听娜娜讲故事".to_owned(),
        version: env!("CARGO_PKG_VERSION").to_owned(),
        contract_version: DOMAIN_SCHEMA_VERSION,
    }
}

#[tauri::command]
fn get_demo_player_view() -> Result<PlayerView, CommandError> {
    serde_json::from_str(DEMO_PLAYER_VIEW)
        .map_err(|error| CommandError::parse("PlayerView fixture", &error))
}

#[tauri::command]
fn get_demo_pack_summary() -> Result<DemoPackSummary, CommandError> {
    let bundle: ResourceBundle = serde_json::from_str(DEMO_BUNDLE)
        .map_err(|error| CommandError::parse("content bundle", &error))?;
    let report = validate_bundle(&bundle);
    if !report.is_valid() {
        return Err(CommandError {
            code: "invalid_content_bundle",
            message: "the embedded content bundle failed domain validation".to_owned(),
            issues: report.issues,
        });
    }
    Ok(DemoPackSummary {
        id: bundle.id,
        display_name: bundle.display_name,
        revision: bundle.revision,
        characters: bundle.characters.len(),
        world_books: bundle.world_books.len(),
        personas: bundle.personas.len(),
        plot_modules: bundle.plot_modules.len(),
        item_specs: bundle.item_specs.len(),
    })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            get_app_info,
            get_demo_pack_summary,
            get_demo_player_view
        ])
        .run(tauri::generate_context!())
        .expect("failed to run nana-story");
}

#[cfg(test)]
mod tests {
    use super::{get_demo_pack_summary, get_demo_player_view};

    #[test]
    fn embedded_player_view_is_valid() {
        let view = get_demo_player_view().expect("valid PlayerView fixture");
        assert_eq!(view.character_name, "娜娜");
        assert!(!view.beats.is_empty());
    }

    #[test]
    fn embedded_content_bundle_is_valid() {
        let summary = get_demo_pack_summary().expect("valid demo bundle");
        assert_eq!(summary.characters, 1);
        assert_eq!(summary.plot_modules, 1);
    }
}
