note-to-self-cli 0.1.1

Encrypted local-first journaling CLI with sync and locked journals.
use chrono::NaiveDate;
use clap::{Args, Parser, Subcommand, ValueEnum};
use note_to_self_lib::Priority;
use std::path::PathBuf;

#[derive(Debug, Parser)]
#[command(name = "note", about = "Encrypted local-first journaling")]
pub struct Cli {
    #[arg(short = 'j', long = "journal", global = true)]
    pub journal: Option<String>,

    #[arg(long = "img", value_name = "PATH", global = true)]
    pub images: Vec<PathBuf>,

    #[command(subcommand)]
    pub command: Option<Command>,

    #[arg(
        value_name = "TEXT",
        trailing_var_arg = true,
        allow_hyphen_values = true
    )]
    pub text: Vec<String>,
}

#[derive(Debug, Subcommand)]
pub enum Command {
    New(NewArgs),
    List(ListArgs),
    Edit {
        id: String,
    },
    Delete(DeleteArgs),
    Search(SearchArgs),
    #[command(
        alias = "browse",
        alias = "tui",
        about = "Browse journal entries in a terminal reader"
    )]
    Read(ReadArgs),
    Todo(TodoArgs),
    Journal {
        #[command(subcommand)]
        command: JournalCommand,
    },
    Init(InitArgs),
    Deinit(DeinitArgs),
    Login(AuthArgs),
    Config(ConfigArgs),
    Export(ExportArgs),
    Import {
        file: PathBuf,
    },
}

#[derive(Debug, Args)]
pub struct NewArgs {
    #[arg(long = "img", value_name = "PATH")]
    pub images: Vec<PathBuf>,
    #[arg(value_name = "TEXT", trailing_var_arg = true)]
    pub text: Vec<String>,
    #[arg(short = 't', long = "tag")]
    pub tags: Vec<String>,
    #[arg(long = "star")]
    pub starred: bool,
}

#[derive(Debug, Args)]
pub struct ListArgs {
    #[arg(long)]
    pub today: bool,
    #[arg(long)]
    pub from: Option<NaiveDate>,
    #[arg(long)]
    pub to: Option<NaiveDate>,
    #[arg(long = "tag")]
    pub tag: Option<String>,
    #[arg(long)]
    pub starred: bool,
    #[arg(short = 'n', default_value_t = 20)]
    pub limit: usize,
}

#[derive(Debug, Args)]
pub struct DeleteArgs {
    pub id: String,
    #[arg(short = 'y', long)]
    pub yes: bool,
}

#[derive(Debug, Args)]
pub struct SearchArgs {
    pub query: String,
    #[arg(short = 'n', default_value_t = 10)]
    pub limit: usize,
}

#[derive(Debug, Args)]
pub struct ReadArgs {
    #[arg(long, value_name = "YYYY-MM-DD", help = "Start the reader on this day")]
    pub date: Option<NaiveDate>,
}

#[derive(Debug, Args)]
pub struct TodoArgs {
    #[arg(value_name = "TEXT", num_args = 0..)]
    pub text: Vec<String>,
    #[arg(short = 'p', long = "priority", default_value = "medium")]
    pub priority: PriorityArg,
    #[arg(long)]
    pub due: Option<NaiveDate>,
    #[arg(short = 't', long = "tag")]
    pub tags: Vec<String>,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum PriorityArg {
    Low,
    Medium,
    High,
    Urgent,
}

impl From<PriorityArg> for Priority {
    fn from(value: PriorityArg) -> Self {
        match value {
            PriorityArg::Low => Priority::Low,
            PriorityArg::Medium => Priority::Medium,
            PriorityArg::High => Priority::High,
            PriorityArg::Urgent => Priority::Urgent,
        }
    }
}

#[derive(Debug, Subcommand)]
pub enum JournalCommand {
    List,
    New {
        name: String,
        #[arg(long, visible_alias = "encrypted", conflicts_with = "plain")]
        locked: bool,
        #[arg(long, conflicts_with = "locked")]
        plain: bool,
    },
    Default {
        name: Option<String>,
    },
    Lock {
        name: String,
    },
    Unlock {
        name: String,
    },
    Password {
        #[command(subcommand)]
        command: JournalPasswordCommand,
    },
    Delete {
        name: String,
        #[arg(short = 'y', long)]
        yes: bool,
    },
}

#[derive(Debug, Subcommand)]
pub enum JournalPasswordCommand {
    #[command(alias = "set")]
    Add {
        name: String,
    },
    Change {
        name: String,
    },
    #[command(alias = "delete", alias = "rm")]
    Remove {
        name: String,
    },
}

#[derive(Debug, Args)]
pub struct InitArgs {
    #[arg(long, conflicts_with = "server")]
    pub local: bool,
    #[arg(long)]
    pub server: Option<String>,
    #[arg(long)]
    pub username: Option<String>,
    #[arg(long)]
    pub journal: Option<String>,
}

#[derive(Debug, Args)]
pub struct DeinitArgs {
    #[arg(short = 'y', long)]
    pub yes: bool,
}

#[derive(Debug, Args)]
pub struct AuthArgs {
    #[arg(long)]
    pub server: Option<String>,
    #[arg(long)]
    pub username: Option<String>,
}

#[derive(Debug, Args)]
pub struct ConfigArgs {
    #[arg(long, conflicts_with = "edit")]
    pub show: bool,
    #[arg(long)]
    pub edit: bool,
}

#[derive(Debug, Args)]
pub struct ExportArgs {
    #[arg(short = 'o', long)]
    pub output: Option<PathBuf>,
    #[arg(long, default_value = "json")]
    pub format: String,
}

pub fn joined_text(parts: &[String]) -> String {
    parts.join(" ").trim().to_string()
}