//! Importer trait and config-shape types consumed by the UI (dynamic form
//! rendering) and the CLI (future). Scripts can serialise ImportConfig as JSON.

use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use thiserror::Error;
use uuid::Uuid;

use crate::coordinator::Archivist;
use super::progress::ImportProgressSink;

#[async_trait]
pub trait Importer: Send + Sync {
    fn source_name(&self) -> &'static str;
    fn config_shape(&self) -> ImportConfigShape;

    async fn discover(
        &self,
        cfg: &ImportConfig,
    ) -> Result<super::ImportDiscovery, ImportError>;

    async fn import(
        &self,
        cfg:        &ImportConfig,
        archivist:  &Archivist,
        target:     ImportTarget,
        progress:   ImportProgressSink,
    ) -> Result<super::ImportStats, ImportError>;

    /// Attempt to auto-detect default configuration values.
    ///
    /// Importers that can discover their source location automatically
    /// (e.g., Claude Code's `~/.claude` directory) should override this.
    /// Returns `None` when auto-detection is not supported or fails.
    fn detect_defaults(&self) -> Option<ImportConfig> {
        None
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImporterInfo {
    pub source_name: String,
    pub display_name: String,
    pub config_shape: ImportConfigShape,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportConfigShape {
    pub fields: Vec<ConfigField>,
    pub example: ImportConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigField {
    pub key:      String,
    pub label:    String,
    pub kind:     ConfigFieldKind,
    pub required: bool,
    pub help:     Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ConfigFieldKind {
    Path     { directory: bool },
    File     { extension: Option<String> },
    String,
    Bool,
    Enum     { variants: Vec<String> },
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportConfig {
    pub source: String,
    #[serde(default)]
    pub params: BTreeMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ImportTarget {
    pub archive:         Option<String>,
    pub connector_alias: Option<String>,
    pub project_id:      Option<Uuid>,
    /// Maps normalized project_path -> project_id (as string UUID).
    /// When a session's project_path is found in this map, the corresponding
    /// project_id is injected into the session metadata during import.
    #[serde(default)]
    pub project_map:     HashMap<String, String>,
}

#[derive(Debug, Error)]
pub enum ImportError {
    #[error("source not found: {0}")]           SourceNotFound(String),
    #[error("config: {0}")]                     Config(String),
    #[error("discovery: {0}")]                  Discovery(String),
    #[error("I/O: {0}")]                        Io(#[from] std::io::Error),
    #[error("archivist: {0}")]                  Archivist(String),
    #[error("parser: {0}")]                     Parser(String),
    #[error("cancelled")]                       Cancelled,
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn config_round_trips() {
        let cfg = ImportConfig { source: "claude".into(), params: BTreeMap::new() };
        let json = serde_json::to_string(&cfg).unwrap();
        let back: ImportConfig = serde_json::from_str(&json).unwrap();
        assert_eq!(back.source, "claude");
    }
}
