//! Tauri v2 command handlers for TutorialDock.
//!
//! Each function is a thin `#[tauri::command]` wrapper that acquires the
//! relevant state lock(s) and delegates to the appropriate module.

use serde_json::{json, Value};
use std::path::Path;
use std::sync::atomic::Ordering;
use std::sync::Mutex;

use tauri::Manager;
use tauri_plugin_dialog::DialogExt;

use crate::ffmpeg;
use crate::library::Library;
use crate::prefs::Prefs;
use crate::recents;
use crate::AppPaths;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Extract a short display name from a folder path (last component).
fn display_name_for_path(p: &str) -> String {
    Path::new(p)
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| p.to_string())
}

// ===========================================================================
// 1. select_folder
// ===========================================================================

#[tauri::command]
pub async fn select_folder(
    app: tauri::AppHandle,
    library: tauri::State<'_, Mutex<Library>>,
    prefs: tauri::State<'_, Mutex<Prefs>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let folder = app.dialog().file().blocking_pick_folder();

    let folder_path = match folder {
        Some(fp) => fp.as_path().unwrap().to_path_buf(),
        None => return Ok(json!({"ok": false, "cancelled": true})),
    };

    let folder_str = folder_path.to_string_lossy().to_string();

    let result = {
        let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
        lib.set_root(&folder_str, &paths.state_dir)?
    };

    recents::push_recent(&paths.state_dir, &folder_str);

    {
        let mut p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
        p.last_folder_path = Some(folder_str);
        p.save(&paths.state_dir);
    }

    Ok(result)
}

// ===========================================================================
// 2. open_folder_path
// ===========================================================================

#[tauri::command]
pub fn open_folder_path(
    folder: String,
    library: tauri::State<'_, Mutex<Library>>,
    prefs: tauri::State<'_, Mutex<Prefs>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let result = {
        let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
        lib.set_root(&folder, &paths.state_dir)?
    };

    recents::push_recent(&paths.state_dir, &folder);

    {
        let mut p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
        p.last_folder_path = Some(folder);
        p.save(&paths.state_dir);
    }

    Ok(result)
}

// ===========================================================================
// 3. get_recents
// ===========================================================================

#[tauri::command]
pub fn get_recents(paths: tauri::State<'_, AppPaths>) -> Result<Value, String> {
    let all = recents::load_recents(&paths.state_dir);

    let items: Vec<Value> = all
        .into_iter()
        .filter(|p| Path::new(p).is_dir())
        .map(|p| {
            let name = display_name_for_path(&p);
            json!({"path": p, "name": name})
        })
        .collect();

    Ok(json!({"ok": true, "items": items}))
}

// ===========================================================================
// 4. remove_recent
// ===========================================================================

#[tauri::command]
pub fn remove_recent(path: String, paths: tauri::State<'_, AppPaths>) -> Result<Value, String> {
    recents::remove_recent(&paths.state_dir, &path);
    Ok(json!({"ok": true}))
}

// ===========================================================================
// 5. get_library
// ===========================================================================

#[tauri::command]
pub fn get_library(library: tauri::State<'_, Mutex<Library>>) -> Result<Value, String> {
    let lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.get_library_info())
}

// ===========================================================================
// 6. set_current
// ===========================================================================

#[tauri::command]
pub fn set_current(
    index: usize,
    timecode: f64,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_current(index, timecode))
}

// ===========================================================================
// 7. tick_progress
// ===========================================================================

#[tauri::command]
pub fn tick_progress(
    index: usize,
    current_time: f64,
    duration: Option<f64>,
    playing: bool,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.update_progress(index, current_time, duration, playing))
}

// ===========================================================================
// 8. set_folder_volume
// ===========================================================================

#[tauri::command]
pub fn set_folder_volume(
    volume: f64,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_folder_volume(volume))
}

// ===========================================================================
// 9. set_folder_autoplay
// ===========================================================================

#[tauri::command]
pub fn set_folder_autoplay(
    enabled: bool,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_folder_autoplay(enabled))
}

// ===========================================================================
// 10. set_folder_rate
// ===========================================================================

#[tauri::command]
pub fn set_folder_rate(
    rate: f64,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_folder_rate(rate))
}

// ===========================================================================
// 11. set_order
// ===========================================================================

#[tauri::command]
pub fn set_order(
    fids: Vec<String>,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_order(fids))
}

// ===========================================================================
// 12. start_duration_scan
// ===========================================================================

#[tauri::command]
pub fn start_duration_scan(
    app: tauri::AppHandle,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let (pending, stop_flag, ffprobe_path, ffmpeg_path) = {
        let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
        lib.start_duration_scan();
        let pending = lib.get_pending_scans();
        let stop_flag = lib.scan_stop_flag();
        let ffprobe_path = lib.ffprobe.clone();
        let ffmpeg_path = lib.ffmpeg.clone();
        (pending, stop_flag, ffprobe_path, ffmpeg_path)
    };

    let count = pending.len();
    if count == 0 {
        return Ok(json!({"ok": true, "pending": 0}));
    }

    let handle = app.clone();

    std::thread::spawn(move || {
        let ff_paths = ffmpeg::FfmpegPaths {
            ffprobe: ffprobe_path,
            ffmpeg: ffmpeg_path,
        };

        let library_state = handle.state::<Mutex<Library>>();

        for (fid, file_path) in &pending {
            if stop_flag.load(Ordering::SeqCst) {
                break;
            }
            if let Some(dur) = ffmpeg::duration_seconds(file_path, &ff_paths) {
                if let Ok(mut lib) = library_state.lock() {
                    lib.apply_scanned_duration(fid, dur);
                }
            }
        }
    });

    Ok(json!({"ok": true, "pending": count}))
}

// ===========================================================================
// 13. get_prefs
// ===========================================================================

#[tauri::command]
pub fn get_prefs(prefs: tauri::State<'_, Mutex<Prefs>>) -> Result<Value, String> {
    let p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(json!({"ok": true, "prefs": serde_json::to_value(&*p).unwrap_or(json!({}))}))
}

// ===========================================================================
// 14. set_prefs
// ===========================================================================

#[tauri::command]
pub fn set_prefs(
    patch: Value,
    prefs: tauri::State<'_, Mutex<Prefs>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let mut p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
    p.update(&patch, &paths.state_dir);
    Ok(json!({"ok": true}))
}

// ===========================================================================
// 15. set_always_on_top
// ===========================================================================

#[tauri::command]
pub fn set_always_on_top(
    enabled: bool,
    app: tauri::AppHandle,
    prefs: tauri::State<'_, Mutex<Prefs>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    {
        let mut p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
        p.always_on_top = enabled;
        p.save(&paths.state_dir);
    }

    if let Some(window) = app.get_webview_window("main") {
        window
            .set_always_on_top(enabled)
            .map_err(|e| format!("set_always_on_top failed: {}", e))?;
    }

    Ok(json!({"ok": true}))
}

// ===========================================================================
// 16. save_window_state
// ===========================================================================

#[tauri::command]
pub fn save_window_state(
    app: tauri::AppHandle,
    prefs: tauri::State<'_, Mutex<Prefs>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let window = app
        .get_webview_window("main")
        .ok_or_else(|| "main window not found".to_string())?;

    let pos = window
        .outer_position()
        .map_err(|e| format!("outer_position failed: {}", e))?;
    let size = window
        .outer_size()
        .map_err(|e| format!("outer_size failed: {}", e))?;

    let mut p = prefs.lock().map_err(|e| format!("lock error: {}", e))?;
    p.window.x = Some(pos.x);
    p.window.y = Some(pos.y);
    p.window.width = size.width as i32;
    p.window.height = size.height as i32;
    p.save(&paths.state_dir);

    Ok(json!({"ok": true}))
}

// ===========================================================================
// 17. get_note
// ===========================================================================

#[tauri::command]
pub fn get_note(fid: String, library: tauri::State<'_, Mutex<Library>>) -> Result<Value, String> {
    let lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    let note = lib.get_note(&fid);
    Ok(json!({"ok": true, "note": note}))
}

// ===========================================================================
// 18. set_note
// ===========================================================================

#[tauri::command]
pub fn set_note(
    fid: String,
    note: String,
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_note(&fid, &note))
}

// ===========================================================================
// 19. get_current_video_meta
// ===========================================================================

#[tauri::command]
pub fn get_current_video_meta(
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.get_current_video_metadata())
}

// ===========================================================================
// 20. get_current_subtitle
// ===========================================================================

#[tauri::command]
pub fn get_current_subtitle(
    library: tauri::State<'_, Mutex<Library>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.get_subtitle_for_current(&paths.state_dir))
}

// ===========================================================================
// 21. get_embedded_subtitles
// ===========================================================================

#[tauri::command]
pub fn get_embedded_subtitles(
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.get_embedded_subtitles())
}

// ===========================================================================
// 22. extract_embedded_subtitle
// ===========================================================================

#[tauri::command]
pub fn extract_embedded_subtitle(
    track_index: u32,
    library: tauri::State<'_, Mutex<Library>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.extract_embedded_subtitle(track_index, &paths.state_dir))
}

// ===========================================================================
// 23. get_available_subtitles
// ===========================================================================

#[tauri::command]
pub fn get_available_subtitles(
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.get_available_subtitles())
}

// ===========================================================================
// 24. load_sidecar_subtitle
// ===========================================================================

#[tauri::command]
pub fn load_sidecar_subtitle(
    file_path: String,
    library: tauri::State<'_, Mutex<Library>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.load_sidecar_subtitle(&file_path, &paths.state_dir))
}

// ===========================================================================
// 25. choose_subtitle_file
// ===========================================================================

#[tauri::command]
pub async fn choose_subtitle_file(
    app: tauri::AppHandle,
    library: tauri::State<'_, Mutex<Library>>,
    paths: tauri::State<'_, AppPaths>,
) -> Result<Value, String> {
    let file = app
        .dialog()
        .file()
        .add_filter("Subtitles", &["srt", "vtt"])
        .blocking_pick_file();

    let file_path = match file {
        Some(fp) => fp.as_path().unwrap().to_path_buf(),
        None => return Ok(json!({"ok": false, "cancelled": true})),
    };

    let file_str = file_path.to_string_lossy().to_string();

    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.set_subtitle_for_current(&file_str, &paths.state_dir))
}

// ===========================================================================
// 26. reset_watch_progress
// ===========================================================================

#[tauri::command]
pub fn reset_watch_progress(
    library: tauri::State<'_, Mutex<Library>>,
) -> Result<Value, String> {
    let mut lib = library.lock().map_err(|e| format!("lock error: {}", e))?;
    Ok(lib.reset_watch_progress())
}
