agent-p 0.1.0

A fast podcast downloader with customizable ID3 tagging
use clap::{ArgGroup, Args, Parser, Subcommand};

use crate::podcast::{BannerStyle, DefaultMode};

#[derive(Parser)]
#[command(
    name = "agentp",
    version,
    about = "AgentP — a fast podcast downloader with customizable ID3 tagging",
    long_about = "AgentP is a fast podcast downloader with customizable ID3 tagging.\n\n\
        Run without a subcommand to launch the default mode (TUI by default).\n\
        Use subcommands like 'podcast', 'download', 'config', and 'open' for\n\
        headless CLI operation. Set default_mode in config to change the\n\
        default behavior."
)]
pub struct Cli {
    #[arg(
        long,
        visible_alias = "user-mode",
        conflicts_with = "cli",
        global = true,
        help = "Force TUI mode, overriding default_mode in config"
    )]
    pub tui: bool,
    #[arg(
        long,
        visible_alias = "agent-mode",
        global = true,
        help = "Force CLI mode, overriding default_mode in config"
    )]
    pub cli: bool,
    #[command(subcommand)]
    pub command: Option<Command>,
}

#[derive(Subcommand)]
pub enum Command {
    #[command(about = "Manage podcasts (list, show, add, edit, delete, episodes)")]
    Podcast {
        #[command(subcommand)]
        command: PodcastCommand,
    },
    #[command(about = "Download episodes from a podcast")]
    Download(DownloadArgs),
    #[command(about = "Show or update application configuration")]
    Config {
        #[command(subcommand)]
        command: ConfigCommand,
    },
    #[command(about = "Open download or podcast folders")]
    Open {
        #[command(subcommand)]
        command: OpenCommand,
    },
}

#[derive(Subcommand)]
pub enum PodcastCommand {
    #[command(about = "List all configured podcasts with latest episode info")]
    List(ListPodcastArgs),
    #[command(about = "Show details for a podcast")]
    Show(ShowPodcastArgs),
    #[command(about = "List episodes for a podcast")]
    Episodes(EpisodesArgs),
    #[command(about = "Add a new podcast")]
    Add(AddPodcastArgs),
    #[command(about = "Edit an existing podcast")]
    Edit(EditPodcastArgs),
    #[command(about = "Delete a podcast")]
    Delete(DeletePodcastArgs),
}

#[derive(Args)]
pub struct ListPodcastArgs {
    #[arg(long, help = "Skip fetching latest episode info (faster)")]
    pub no_fetch: bool,
}

#[derive(Args)]
pub struct ShowPodcastArgs {
    #[arg(long, help = "Podcast name or 1-based index")]
    pub podcast: String,
}

#[derive(Args)]
pub struct EpisodesArgs {
    #[arg(long, help = "Podcast name or 1-based index")]
    pub podcast: String,
    #[arg(long, help = "Maximum number of episodes to show")]
    pub limit: Option<usize>,
}

#[derive(Args)]
#[command(group(
    ArgGroup::new("download_selector")
        .required(true)
        .args(["episodes", "latest", "oldest", "all"])
))]
pub struct DownloadArgs {
    #[arg(long, help = "Podcast name or 1-based index")]
    pub podcast: String,
    #[arg(long, value_delimiter = ',', num_args = 1.., help = "Episode indices to download (comma-separated)")]
    pub episodes: Option<Vec<usize>>,
    #[arg(long, help = "Download the N newest episodes")]
    pub latest: Option<usize>,
    #[arg(long, help = "Download the N oldest episodes")]
    pub oldest: Option<usize>,
    #[arg(long, help = "Download all episodes")]
    pub all: bool,
}

#[derive(Args)]
pub struct PodcastFieldArgs {
    #[arg(long, help = "Album name for downloads folder and ID3 tag")]
    pub album_name: Option<String>,
    #[arg(long, help = "Artist name for ID3 tag")]
    pub artist: Option<String>,
    #[arg(
        long,
        help = "Custom User-Agent for this podcast's downloads (blank = built-in default)"
    )]
    pub user_agent: Option<String>,
    #[arg(long, help = "Write ID3 tags to downloaded files")]
    pub overwrite_tags: Option<bool>,
    #[arg(
        long,
        help = "Remove source file's existing ID3 tag before writing a fresh minimal one \
            (useful when the source has huge embedded metadata)"
    )]
    pub remove_existing_tags: Option<bool>,
    #[arg(
        long,
        visible_alias = "remove-image",
        help = "Remove embedded cover art from downloaded audio files"
    )]
    pub remove_images: Option<bool>,
    #[arg(long, help = "Write album name into ID3 album tag")]
    pub overwrite_album_name: Option<bool>,
    #[arg(long, help = "Write artist into ID3 artist tag")]
    pub overwrite_artist: Option<bool>,
    #[arg(long, help = "Rename files to match episode title")]
    pub overwrite_file_name: Option<bool>,
    #[arg(long, help = "Write episode title into ID3 title tag")]
    pub overwrite_title: Option<bool>,
    #[arg(long, help = "Prepend episode number to title tag")]
    pub append_number_to_title: Option<bool>,
    #[arg(long, help = "Number of leading zeros before episode number (0 = off)")]
    pub leading_zeros_amount: Option<usize>,
}

#[derive(Args)]
pub struct AddPodcastArgs {
    #[arg(long, help = "Podcast display name (optional with --from-feed)")]
    pub name: Option<String>,
    #[arg(long, help = "RSS feed URL")]
    pub feed_url: String,
    #[arg(long, help = "Auto-fill name, album, and artist from the RSS feed")]
    pub from_feed: bool,
    #[command(flatten)]
    pub fields: PodcastFieldArgs,
}

#[derive(Args)]
pub struct EditPodcastArgs {
    #[arg(long, help = "Podcast name or 1-based index to edit")]
    pub podcast: String,
    #[arg(long, help = "New podcast display name")]
    pub name: Option<String>,
    #[arg(long, help = "New RSS feed URL")]
    pub feed_url: Option<String>,
    #[command(flatten)]
    pub fields: PodcastFieldArgs,
}

#[derive(Args)]
pub struct DeletePodcastArgs {
    #[arg(long, help = "Podcast name or 1-based index to delete")]
    pub podcast: String,
}

#[derive(Subcommand)]
pub enum ConfigCommand {
    #[command(about = "Show current configuration")]
    Show,
    #[command(about = "Update configuration settings")]
    Set(ConfigSetArgs),
    #[command(about = "Print the config directory path")]
    Path,
}

#[derive(Args)]
pub struct ConfigSetArgs {
    #[arg(long, help = "Set the download directory path")]
    pub download_dir: Option<String>,
    #[arg(long, value_enum, help = "Set the default launch mode (tui or cli)")]
    pub default_mode: Option<DefaultMode>,
    #[arg(
        long,
        value_enum,
        help = "Set the TUI banner wordmark (joined or ascii)"
    )]
    pub banner_style: Option<BannerStyle>,
}

#[derive(Subcommand)]
pub enum OpenCommand {
    #[command(about = "Open the download folder")]
    Downloads {
        #[arg(long, help = "Create the folder if it does not exist")]
        create: bool,
    },
    #[command(about = "Open a podcast's download folder")]
    Podcast(OpenPodcastArgs),
}

#[derive(Args)]
pub struct OpenPodcastArgs {
    #[arg(long, help = "Podcast name or 1-based index")]
    pub podcast: String,
    #[arg(long, help = "Create the folder if it does not exist")]
    pub create: bool,
}