mod config;
mod parser;
mod pull;
mod view;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "icaru", about = "iCal calendar viewer/syncer")]
struct Cli {
    /// Data directory (defaults to ~/.icaru/)
    #[arg(short = 'd', long)]
    dir: Option<PathBuf>,

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

    /// Show events for the current week (Monday–Sunday)
    #[arg(long, conflicts_with_all = ["month", "days"])]
    week: bool,

    /// Show events for the current month
    #[arg(long, conflicts_with_all = ["week", "days"])]
    month: bool,

    /// Show events for the next N days starting from today
    #[arg(long, conflicts_with_all = ["week", "month"])]
    days: Option<u32>,
}

#[derive(Subcommand)]
enum Commands {
    /// Sync calendars from Google Calendar
    Pull {
        #[arg(short = 'd', long)]
        dir: Option<PathBuf>,
    },
}

fn data_dir(dir_opt: &Option<PathBuf>) -> Result<PathBuf> {
    if let Some(d) = dir_opt {
        return Ok(d.clone());
    }
    let home = home::home_dir().context("could not determine home directory")?;
    Ok(home.join(".icaru"))
}

fn run() -> Result<()> {
    let cli = Cli::parse();

    match &cli.command {
        Some(Commands::Pull { dir }) => {
            let data_dir = data_dir(dir)?;
            let cfg = config::load_config(&data_dir)?;
            let fetcher = pull::UreqFetcher;
            let mut synced = 0;
            for cal in &cfg.cals {
                if let Some(url) = &cal.url {
                    let dest = data_dir.join(&cal.file);
                    println!("syncing: {} → {}", cal.name, dest.display());
                    pull::pull_calendar(&fetcher, url, &dest)
                        .with_context(|| format!("failed to sync {}", cal.name))?;
                    synced += 1;
                }
            }
            println!("synced {} calendar(s)", synced);
        }
        None => {
            let data_dir = data_dir(&cli.dir)?;
            let cfg = config::load_config(&data_dir).unwrap_or_else(|e| {
                eprintln!("warning: could not load config: {:#}", e);
                config::Config { cals: vec![] }
            });

            let mut all_events: Vec<parser::Event> = Vec::new();
            for cal in &cfg.cals {
                let path = data_dir.join(&cal.file);
                if !path.exists() {
                    continue;
                }
                let content = std::fs::read_to_string(&path)
                    .with_context(|| format!("failed to read {}", path.display()))?;
                let events = parser::parse_ics(&content, &cal.name, &cal.color)
                    .with_context(|| format!("failed to parse {}", cal.name))?;
                all_events.extend(events);
            }

            let today = chrono::Local::now().date_naive();
            let (from, to) = if cli.week {
                use chrono::Datelike;
                let days_from_monday = today.weekday().num_days_from_monday() as i64;
                let monday = today - chrono::Duration::days(days_from_monday);
                let sunday = monday + chrono::Duration::days(6);
                (monday, sunday)
            } else if cli.month {
                use chrono::{Datelike, NaiveDate};
                let first_day = NaiveDate::from_ymd_opt(today.year(), today.month(), 1).unwrap();
                let next_month = if today.month() == 12 {
                    NaiveDate::from_ymd_opt(today.year() + 1, 1, 1).unwrap()
                } else {
                    NaiveDate::from_ymd_opt(today.year(), today.month() + 1, 1).unwrap()
                };
                let end = next_month.pred_opt().unwrap();
                (first_day, end)
            } else if let Some(d) = cli.days {
                if d == 0 {
                    anyhow::bail!("--days must be at least 1");
                }
                (today, today + chrono::Duration::days(d as i64 - 1))
            } else {
                (today, today)
            };

            let all_events = parser::expand_recurring(all_events, from, to);
            let filtered = view::filter_events(&all_events, from, to);
            if filtered.is_empty() {
                println!("No events to display");
            } else {
                print!("{}", view::format_events(&filtered));
            }
        }
    }
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("error: {:#}", e);
        std::process::exit(1);
    }
}
