use std::path::PathBuf;

use anyhow::{Context, Result};
use clap::Parser;

const KEYMAP_HELP: &str = "\
KEYBINDINGS:
  h/j/k/l, arrows   Pan the viewport
  +/-               Zoom in/out
  ?                 Toggle help overlay
  q, Esc            Quit (or close the help overlay)
  Ctrl-C            Quit
";

#[derive(Parser, Debug)]
#[command(
    name = "jcv",
    version,
    about = "Terminal viewer for JSON Canvas 1.0 files",
    long_about = None,
    after_help = KEYMAP_HELP,
)]
struct Cli {
    /// Path to a .canvas file (JSON Canvas 1.0)
    file: PathBuf,
}

fn init_tracing() -> Result<()> {
    use tracing_subscriber::{fmt, EnvFilter};

    let log_dir = directories::ProjectDirs::from("", "", "jcv")
        .map(|d| {
            d.state_dir()
                .map(|p| p.to_path_buf())
                .unwrap_or_else(|| d.data_dir().to_path_buf())
        })
        .unwrap_or_else(|| PathBuf::from("."));

    std::fs::create_dir_all(&log_dir).ok();
    let log_path = log_dir.join("jcv.log");

    let file = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&log_path)
        .with_context(|| format!("opening log file at {}", log_path.display()))?;

    fmt()
        .with_env_filter(
            EnvFilter::try_from_env("JCV_LOG").unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .with_writer(file)
        .with_ansi(false)
        .try_init()
        .map_err(|e| anyhow::anyhow!("init tracing: {e}"))?;
    Ok(())
}

fn run(cli: Cli) -> Result<()> {
    if !cli.file.exists() {
        anyhow::bail!("file not found: {}", cli.file.display());
    }
    tracing::info!(path = %cli.file.display(), "opening canvas");
    jcv::app::run(&cli.file)
}

/// Restore the terminal on panic so a crash never leaves a corrupted TTY.
fn install_panic_hook() {
    let prev = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        let _ = crossterm::terminal::disable_raw_mode();
        let _ = crossterm::execute!(std::io::stdout(), crossterm::terminal::LeaveAlternateScreen);
        tracing::error!(?info, "panic in event loop");
        prev(info);
    }));
}

fn main() -> std::process::ExitCode {
    let cli = Cli::parse();
    let _ = init_tracing();
    install_panic_hook();
    match run(cli) {
        Ok(()) => std::process::ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("error: {e:#}");
            std::process::ExitCode::FAILURE
        }
    }
}
