// Tauri commands wrapping kon_storage task_lists CRUD.
// Pattern mirrors tasks.rs — TaskListDto is the camelCase frontend shape,
// storage functions are aliased with db_ prefix to avoid name collisions.

use serde::{Deserialize, Serialize};

use kon_storage::{
    create_task_list as db_create_task_list, delete_task_list as db_delete_task_list,
    import_task_lists as db_import_task_lists, list_task_lists as db_list_task_lists,
    update_task_list as db_update_task_list, ImportSummary, TaskListRow,
};

use crate::AppState;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskListDto {
    pub id: String,
    pub name: String,
    pub built_in: bool,
    pub profile_id: Option<String>,
    pub created_at: String,
}

impl From<TaskListRow> for TaskListDto {
    fn from(r: TaskListRow) -> Self {
        Self {
            id: r.id,
            name: r.name,
            built_in: r.built_in,
            profile_id: r.profile_id,
            created_at: r.created_at,
        }
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTaskListRequest {
    pub id: String,
    pub name: String,
    #[serde(default)]
    pub built_in: bool,
    #[serde(default)]
    pub profile_id: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTaskListPatch {
    #[serde(default)]
    pub name: Option<String>,
    // Triple-Option to distinguish absent from null:
    //   absent  -> Option::None        -> "leave profile_id alone"
    //   null    -> Some(None)          -> "set profile_id to NULL"
    //   "abc"   -> Some(Some("abc"))   -> "set profile_id to 'abc'"
    #[serde(default, deserialize_with = "deserialize_optional_field")]
    pub profile_id: Option<Option<String>>,
}

// Helper for the triple-Option pattern. See https://serde.rs/field-attrs.html
fn deserialize_optional_field<'de, T, D>(
    deserializer: D,
) -> Result<Option<Option<T>>, D::Error>
where
    T: serde::Deserialize<'de>,
    D: serde::Deserializer<'de>,
{
    Ok(Some(Option::<T>::deserialize(deserializer)?))
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskListImport {
    pub id: String,
    pub name: String,
    #[serde(default)]
    pub built_in: bool,
    #[serde(default)]
    pub profile_id: Option<String>,
    #[serde(default)]
    pub created_at: Option<String>, // accepted but ignored — DB sets datetime('now')
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportSummaryDto {
    pub imported: u32,
    pub skipped: u32,
}

impl From<ImportSummary> for ImportSummaryDto {
    fn from(s: ImportSummary) -> Self {
        Self {
            imported: s.imported as u32,
            skipped: s.skipped as u32,
        }
    }
}

#[tauri::command]
pub async fn list_task_lists_cmd(
    state: tauri::State<'_, AppState>,
) -> Result<Vec<TaskListDto>, String> {
    db_list_task_lists(&state.db)
        .await
        .map(|rows| rows.into_iter().map(TaskListDto::from).collect())
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn create_task_list_cmd(
    state: tauri::State<'_, AppState>,
    request: CreateTaskListRequest,
) -> Result<TaskListDto, String> {
    db_create_task_list(
        &state.db,
        &request.id,
        &request.name,
        request.built_in,
        request.profile_id.as_deref(),
    )
    .await
    .map(TaskListDto::from)
    .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn update_task_list_cmd(
    state: tauri::State<'_, AppState>,
    id: String,
    patch: UpdateTaskListPatch,
) -> Result<TaskListDto, String> {
    let profile_id_arg = patch.profile_id.as_ref().map(|inner| inner.as_deref());
    db_update_task_list(&state.db, &id, patch.name.as_deref(), profile_id_arg)
        .await
        .map(TaskListDto::from)
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn delete_task_list_cmd(
    state: tauri::State<'_, AppState>,
    id: String,
) -> Result<(), String> {
    db_delete_task_list(&state.db, &id)
        .await
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn import_task_lists_cmd(
    state: tauri::State<'_, AppState>,
    items: Vec<TaskListImport>,
) -> Result<ImportSummaryDto, String> {
    let rows: Vec<TaskListRow> = items
        .into_iter()
        .map(|i| TaskListRow {
            id: i.id,
            name: i.name,
            built_in: i.built_in,
            profile_id: i.profile_id,
            // The DB column has DEFAULT datetime('now'); this field is
            // overwritten by the INSERT for the live row and is only
            // used to build the in-memory TaskListRow shape passed to
            // import_task_lists. Use empty string as a placeholder.
            created_at: i.created_at.unwrap_or_default(),
        })
        .collect();
    db_import_task_lists(&state.db, &rows)
        .await
        .map(ImportSummaryDto::from)
        .map_err(|e| e.to_string())
}
