azterm 0.1.6

Fast, modern native cross-platform terminal, SSH profile manager, and SFTP client
use crate::settings::AppSettings;
use crate::ssh::SshProfile;
use crate::theme::ThemeConfig;
use crate::tiling::WorkspaceTab;
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedSessionState {
    pub kind: String,
    pub title: String,
    pub target: String,
}

pub struct Database;

impl Database {
    pub fn db_path() -> PathBuf {
        let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
        let dir = PathBuf::from(home).join(".config/azterm");
        let _ = fs::create_dir_all(&dir);
        dir.join("azterm.db")
    }

    pub fn get_connection() -> Option<Connection> {
        let path = Self::db_path();
        let conn = Connection::open(path).ok()?;
        Self::init_tables(&conn).ok()?;
        Some(conn)
    }

    fn init_tables(conn: &Connection) -> rusqlite::Result<()> {
        conn.execute(
            "CREATE TABLE IF NOT EXISTS settings (
                id INTEGER PRIMARY KEY CHECK (id = 1),
                data TEXT NOT NULL
            )",
            [],
        )?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS ssh_profiles (
                id TEXT PRIMARY KEY,
                data TEXT NOT NULL
            )",
            [],
        )?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS open_sessions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                tab_order INTEGER NOT NULL,
                kind TEXT NOT NULL,
                title TEXT NOT NULL,
                target TEXT NOT NULL
            )",
            [],
        )?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS custom_themes (
                id TEXT PRIMARY KEY,
                data TEXT NOT NULL
            )",
            [],
        )?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS active_theme (
                id INTEGER PRIMARY KEY CHECK (id = 1),
                data TEXT NOT NULL
            )",
            [],
        )?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS saved_workspaces (
                id INTEGER PRIMARY KEY CHECK (id = 1),
                data TEXT NOT NULL
            )",
            [],
        )?;

        Ok(())
    }

    pub fn save_settings(settings: &AppSettings) {
        if let Some(conn) = Self::get_connection() {
            if let Ok(json) = serde_json::to_string(settings) {
                let _ = conn.execute(
                    "INSERT INTO settings (id, data) VALUES (1, ?1)
                     ON CONFLICT(id) DO UPDATE SET data = excluded.data",
                    params![json],
                );
            }
        }
    }

    pub fn load_settings() -> Option<AppSettings> {
        let conn = Self::get_connection()?;
        let mut stmt = conn.prepare("SELECT data FROM settings WHERE id = 1").ok()?;
        let json: String = stmt.query_row([], |row| row.get(0)).ok()?;
        serde_json::from_str(&json).ok()
    }

    pub fn save_profiles(profiles: &[SshProfile]) {
        if let Some(mut conn) = Self::get_connection() {
            if let Ok(tx) = conn.transaction() {
                let _ = tx.execute("DELETE FROM ssh_profiles", []);
                for p in profiles {
                    if let Ok(json) = serde_json::to_string(p) {
                        let _ = tx.execute(
                            "INSERT INTO ssh_profiles (id, data) VALUES (?1, ?2)",
                            params![p.id, json],
                        );
                    }
                }
                let _ = tx.commit();
            }
        }
    }

    pub fn load_profiles() -> Option<Vec<SshProfile>> {
        let conn = Self::get_connection()?;
        let mut stmt = conn.prepare("SELECT data FROM ssh_profiles").ok()?;
        let rows = stmt.query_map([], |row| {
            let json: String = row.get(0)?;
            Ok(json)
        }).ok()?;

        let mut list = Vec::new();
        for r in rows.flatten() {
            if let Ok(profile) = serde_json::from_str(&r) {
                list.push(profile);
            }
        }
        if list.is_empty() {
            None
        } else {
            Some(list)
        }
    }

    pub fn save_sessions(sessions: &[SavedSessionState]) {
        if let Some(mut conn) = Self::get_connection() {
            if let Ok(tx) = conn.transaction() {
                let _ = tx.execute("DELETE FROM open_sessions", []);
                for (idx, s) in sessions.iter().enumerate() {
                    let _ = tx.execute(
                        "INSERT INTO open_sessions (tab_order, kind, title, target) VALUES (?1, ?2, ?3, ?4)",
                        params![idx as i32, s.kind, s.title, s.target],
                    );
                }
                let _ = tx.commit();
            }
        }
    }

    pub fn load_sessions() -> Vec<SavedSessionState> {
        let mut list = Vec::new();
        if let Some(conn) = Self::get_connection() {
            if let Ok(mut stmt) = conn.prepare("SELECT kind, title, target FROM open_sessions ORDER BY tab_order ASC") {
                if let Ok(rows) = stmt.query_map([], |row| {
                    Ok(SavedSessionState {
                        kind: row.get(0)?,
                        title: row.get(1)?,
                        target: row.get(2)?,
                    })
                }) {
                    for r in rows.flatten() {
                        list.push(r);
                    }
                }
            }
        }
        list
    }

    pub fn save_active_theme(theme: &ThemeConfig) {
        if let Some(conn) = Self::get_connection() {
            if let Ok(json) = serde_json::to_string(theme) {
                let _ = conn.execute(
                    "INSERT INTO active_theme (id, data) VALUES (1, ?1)
                     ON CONFLICT(id) DO UPDATE SET data = excluded.data",
                    params![json],
                );
            }
        }
    }

    pub fn load_active_theme() -> Option<ThemeConfig> {
        let conn = Self::get_connection()?;
        let mut stmt = conn.prepare("SELECT data FROM active_theme WHERE id = 1").ok()?;
        let json: String = stmt.query_row([], |row| row.get(0)).ok()?;
        serde_json::from_str(&json).ok()
    }

    pub fn save_custom_themes(themes: &[ThemeConfig]) {
        if let Some(mut conn) = Self::get_connection() {
            if let Ok(tx) = conn.transaction() {
                let _ = tx.execute("DELETE FROM custom_themes", []);
                for t in themes {
                    if let Ok(json) = serde_json::to_string(t) {
                        let _ = tx.execute(
                            "INSERT INTO custom_themes (id, data) VALUES (?1, ?2)",
                            params![t.id, json],
                        );
                    }
                }
                let _ = tx.commit();
            }
        }
    }

    pub fn load_custom_themes() -> Vec<ThemeConfig> {
        let mut list = Vec::new();
        if let Some(conn) = Self::get_connection() {
            if let Ok(mut stmt) = conn.prepare("SELECT data FROM custom_themes") {
                if let Ok(rows) = stmt.query_map([], |row| {
                    let json: String = row.get(0)?;
                    Ok(json)
                }) {
                    for r in rows.flatten() {
                        if let Ok(theme) = serde_json::from_str(&r) {
                            list.push(theme);
                        }
                    }
                }
            }
        }
        list
    }

    pub fn save_workspaces(workspaces: &[WorkspaceTab]) {
        if let Some(conn) = Self::get_connection() {
            if let Ok(json) = serde_json::to_string(workspaces) {
                let _ = conn.execute(
                    "INSERT INTO saved_workspaces (id, data) VALUES (1, ?1)
                     ON CONFLICT(id) DO UPDATE SET data = excluded.data",
                    params![json],
                );
            }
        }
    }

    pub fn load_workspaces() -> Option<Vec<WorkspaceTab>> {
        let conn = Self::get_connection()?;
        let mut stmt = conn.prepare("SELECT data FROM saved_workspaces WHERE id = 1").ok()?;
        let json: String = stmt.query_row([], |row| row.get(0)).ok()?;
        serde_json::from_str(&json).ok()
    }
}