//! Command execution for TUI workspace operations.
//!
//! Each function wraps a core workspace API call via block_on().
//! Operations that resolve links need a root-aware workspace, so we
//! construct one from the original ws + workspace root directory.

use std::collections::HashSet;
use std::path::{Path, PathBuf};

use diaryx_core::YamlValue;
use diaryx_core::config::Config;
use diaryx_core::entry::{DiaryxApp, slugify_title, sync_h1_in_body};
use diaryx_core::workspace::{Workspace, prepare_delete_plan, selection_includes_descendants};

use crate::cli::{CliWorkspace, block_on};

/// Build a workspace with the root path set for correct link resolution.
fn rooted_ws(ws: &CliWorkspace, workspace_root: &Path) -> CliWorkspace {
    let link_format = Config::load().map(|c| c.link_format).unwrap_or_default();
    Workspace::with_link_format(
        ws.fs_ref().clone(),
        workspace_root.to_path_buf(),
        link_format,
    )
}

/// Create a new child entry under the selected parent.
/// Returns the path to the new child.
pub fn exec_create(
    ws: &CliWorkspace,
    workspace_root: &Path,
    parent_path: &Path,
    title: &str,
) -> Result<PathBuf, String> {
    let rws = rooted_ws(ws, workspace_root);
    block_on(rws.create_child_entry(parent_path, Some(title)))
        .map_err(|e| format!("Create failed: {}", e))
}

/// Result of computing a deletion plan for TUI confirmation.
pub struct DeletePlanResult {
    /// Ordered list of paths to delete (children-first).
    pub paths: Vec<PathBuf>,
    /// Whether the plan includes descendant entries beyond the selected path.
    pub includes_descendants: bool,
}

/// Compute a deletion plan for the selected path.
/// Builds the tree, expands to descendants, and orders children-first.
pub fn exec_delete(
    ws: &CliWorkspace,
    workspace_root: &Path,
    path: &Path,
) -> Result<DeletePlanResult, String> {
    let rws = rooted_ws(ws, workspace_root);

    // Find root index for tree building
    let root_index = block_on(rws.detect_workspace(path))
        .map_err(|e| format!("Delete failed: {}", e))?
        .ok_or_else(|| "Delete failed: could not detect workspace root".to_string())?;

    let mut visited = HashSet::new();
    let tree = block_on(rws.build_tree_with_depth(&root_index, None, &mut visited))
        .map_err(|e| format!("Delete failed: could not build tree: {}", e))?;

    let selected = vec![path.to_path_buf()];
    let plan = prepare_delete_plan(&tree, &selected);
    let includes_descendants = selection_includes_descendants(&tree, &selected);

    if plan.is_empty() {
        return Err("Delete failed: nothing to delete".to_string());
    }

    Ok(DeletePlanResult {
        paths: plan,
        includes_descendants,
    })
}

/// Execute a previously computed deletion plan.
/// Returns the count of successfully deleted entries.
pub fn exec_delete_plan(
    ws: &CliWorkspace,
    workspace_root: &Path,
    plan: &[PathBuf],
) -> Result<usize, String> {
    let rws = rooted_ws(ws, workspace_root);
    let mut deleted = 0usize;
    for path in plan {
        block_on(rws.delete_entry(path))
            .map_err(|e| format!("Delete failed at {}: {}", path.display(), e))?;
        deleted += 1;
    }
    Ok(deleted)
}

/// Duplicate the selected entry.
/// Returns the path to the duplicate.
pub fn exec_duplicate(
    ws: &CliWorkspace,
    workspace_root: &Path,
    path: &Path,
) -> Result<PathBuf, String> {
    let rws = rooted_ws(ws, workspace_root);
    block_on(rws.duplicate_entry(path)).map_err(|e| format!("Duplicate failed: {}", e))
}

/// Rename the selected entry by moving it to a new filename derived from the title.
/// Also updates the frontmatter title.
/// Returns the new path.
pub fn exec_rename(
    ws: &CliWorkspace,
    workspace_root: &Path,
    path: &Path,
    new_title: &str,
) -> Result<PathBuf, String> {
    let rws = rooted_ws(ws, workspace_root);
    let parent_dir = path
        .parent()
        .ok_or_else(|| "Cannot rename: no parent directory".to_string())?;
    let new_filename = slugify_title(new_title);
    let new_path = parent_dir.join(&new_filename);

    let entry_ops = DiaryxApp::new(rws.fs_ref().clone());

    if new_path == path {
        // Just update the title in frontmatter
        block_on(rws.set_frontmatter_property(
            path,
            "title",
            YamlValue::String(new_title.to_string()),
        ))
        .map_err(|e| format!("Rename failed: {}", e))?;

        // Sync H1 heading to match
        let path_str = path.to_string_lossy();
        let body = block_on(entry_ops.get_content(&path_str)).unwrap_or_default();
        let new_body = sync_h1_in_body(&body, new_title);
        if new_body != body {
            block_on(entry_ops.set_content(&path_str, &new_body))
                .map_err(|e| format!("H1 sync failed: {}", e))?;
        }

        return Ok(path.to_path_buf());
    }

    if new_path.exists() {
        return Err(format!("Cannot rename: '{}' already exists", new_filename));
    }

    // Move the file (updates parent/child links)
    block_on(rws.move_entry(path, &new_path)).map_err(|e| format!("Rename failed: {}", e))?;

    // Update the title in frontmatter
    block_on(rws.set_frontmatter_property(
        &new_path,
        "title",
        YamlValue::String(new_title.to_string()),
    ))
    .map_err(|e| format!("Title update failed: {}", e))?;

    // Sync H1 heading to match
    let new_path_str = new_path.to_string_lossy();
    let body = block_on(entry_ops.get_content(&new_path_str)).unwrap_or_default();
    let new_body = sync_h1_in_body(&body, new_title);
    if new_body != body {
        block_on(entry_ops.set_content(&new_path_str, &new_body))
            .map_err(|e| format!("H1 sync failed: {}", e))?;
    }

    Ok(new_path)
}

/// Move an entry under a new parent.
/// Returns the new path of the moved entry.
pub fn exec_move(
    ws: &CliWorkspace,
    workspace_root: &Path,
    source: &Path,
    target_parent: &Path,
) -> Result<PathBuf, String> {
    let rws = rooted_ws(ws, workspace_root);
    let target_dir = target_parent
        .parent()
        .ok_or_else(|| "Cannot move: target has no parent directory".to_string())?;
    let filename = source
        .file_name()
        .ok_or_else(|| "Cannot move: source has no filename".to_string())?;
    let new_path = target_dir.join(filename);

    if new_path == source {
        return Err("Source and destination are the same".to_string());
    }

    block_on(rws.move_entry(source, &new_path)).map_err(|e| format!("Move failed: {}", e))?;

    Ok(new_path)
}

/// Combine/merge two index files. Source is merged into target.
pub fn exec_merge(
    ws: &CliWorkspace,
    workspace_root: &Path,
    source: &Path,
    target: &Path,
) -> Result<(), String> {
    let rws = rooted_ws(ws, workspace_root);
    block_on(rws.combine_indices(source, target)).map_err(|e| format!("Merge failed: {}", e))
}
