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

use serde::{Deserialize, Serialize};

use uuid::Uuid;

use kon_storage::{
    complete_subtask_and_check_parent as db_complete_subtask, complete_task as db_complete_task,
    delete_task as db_delete_task, get_task_by_id as db_get_task,
    insert_subtask as db_insert_subtask, insert_task as db_insert_task,
    list_subtasks as db_list_subtasks, list_tasks as db_list_tasks,
    uncomplete_task as db_uncomplete_task, update_task as db_update_task, TaskRow,
};

use crate::AppState;

/// Frontend-facing task shape. Matches the in-memory object in page.svelte.js.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskDto {
    pub id: String,
    pub text: String,
    pub bucket: String,
    pub list_id: Option<String>,
    pub effort: Option<String>,
    pub notes: String,
    pub done: bool,
    pub done_at: Option<String>,
    pub created_at: String,
    pub source_transcript_id: Option<String>,
    pub parent_task_id: Option<String>,
}

impl From<TaskRow> for TaskDto {
    fn from(r: TaskRow) -> Self {
        Self {
            id: r.id,
            text: r.text,
            bucket: r.bucket,
            list_id: r.list_id,
            effort: r.effort,
            notes: r.notes,
            done: r.done,
            done_at: r.done_at,
            created_at: r.created_at,
            source_transcript_id: r.source_transcript_id,
            parent_task_id: r.parent_task_id,
        }
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTaskRequest {
    pub id: String,
    pub text: String,
    pub bucket: String,
    #[serde(default)]
    pub source_transcript_id: Option<String>,
    #[serde(default)]
    pub list_id: Option<String>,
    #[serde(default)]
    pub effort: Option<String>,
}

#[tauri::command]
pub async fn create_task_cmd(
    state: tauri::State<'_, AppState>,
    request: CreateTaskRequest,
) -> Result<TaskDto, String> {
    db_insert_task(
        &state.db,
        &request.id,
        &request.text,
        &request.bucket,
        request.source_transcript_id.as_deref(),
        request.list_id.as_deref(),
        request.effort.as_deref(),
    )
    .await
    .map_err(|e| e.to_string())?;

    // Fetch the freshly-inserted row so the frontend can stop doing
    // client-side object construction. Mirrors list_tasks_cmd's shape.
    db_get_task(&state.db, &request.id)
        .await
        .map_err(|e| e.to_string())?
        .map(TaskDto::from)
        .ok_or_else(|| format!("Task {} not found after insert", request.id))
}

/// Patch-shaped update. Any field omitted (or explicit `null`) leaves the
/// column untouched via `COALESCE` in the storage layer. Matches
/// `update_transcript`'s partial-update philosophy. `done` / `doneAt` are
/// intentionally absent — those flow through `complete_task_cmd` /
/// `uncomplete_task_cmd` to stamp the server-side timestamp.
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTaskRequest {
    #[serde(default)]
    pub text: Option<String>,
    #[serde(default)]
    pub bucket: Option<String>,
    #[serde(default)]
    pub list_id: Option<String>,
    #[serde(default)]
    pub effort: Option<String>,
    #[serde(default)]
    pub notes: Option<String>,
}

#[tauri::command]
pub async fn update_task_cmd(
    state: tauri::State<'_, AppState>,
    id: String,
    patch: UpdateTaskRequest,
) -> Result<TaskDto, String> {
    let row = db_update_task(
        &state.db,
        &id,
        patch.text.as_deref(),
        patch.bucket.as_deref(),
        patch.list_id.as_deref(),
        patch.effort.as_deref(),
        patch.notes.as_deref(),
    )
    .await
    .map_err(|e| e.to_string())?;
    Ok(TaskDto::from(row))
}

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

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

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

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

#[tauri::command]
pub async fn decompose_and_store(
    state: tauri::State<'_, AppState>,
    parent_task_id: String,
) -> Result<Vec<TaskDto>, String> {
    let parent = db_get_task(&state.db, &parent_task_id)
        .await
        .map_err(|e| e.to_string())?
        .ok_or_else(|| format!("Task {parent_task_id} not found"))?;

    let engine = state.llm_engine.clone();
    let parent_text = parent.text.clone();
    let steps = tokio::task::spawn_blocking(move || engine.decompose_task(&parent_text))
        .await
        .map_err(|e| e.to_string())?
        .map_err(|e| e.to_string())?;

    let mut created = Vec::new();
    for text in steps {
        let id = Uuid::new_v4().to_string();
        db_insert_subtask(&state.db, &id, &text, &parent_task_id)
            .await
            .map_err(|e| e.to_string())?;
        if let Some(row) = db_get_task(&state.db, &id)
            .await
            .map_err(|e| e.to_string())?
        {
            created.push(TaskDto::from(row));
        }
    }

    Ok(created)
}

#[tauri::command]
pub async fn extract_tasks_from_transcript_cmd(
    state: tauri::State<'_, AppState>,
    transcript: String,
) -> Result<Vec<String>, String> {
    let engine = state.llm_engine.clone();
    tokio::task::spawn_blocking(move || engine.extract_tasks(&transcript))
        .await
        .map_err(|e| e.to_string())?
        .map_err(|e| e.to_string())
}

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

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