// Copyright 2026 California Institute of Technology
//
// You should have received a copy of the licensing terms for this
// software included in the file “LICENSE” located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE.txt

use clap::Parser;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::{Arc, Mutex};
use term_scope::{app, capture, parse, stp};

/// Quick TUI monitoring of live signals (EPICS channel access).
#[derive(Parser)]
#[command(version, about)]
struct Cli {
    /// Channel list: a plain text file, or a StripTool .stp file
    channel_file: PathBuf,

    /// History lookback in minutes, or "auto" for the 24 h maximum
    /// (adjustable at runtime with < and >)
    #[arg(
        long,
        visible_alias = "history",
        value_name = "MINUTES|auto",
        default_value = "auto",
        value_parser = parse_lookback
    )]
    lookback: usize,
}

/// `--lookback` values: "auto" is the 24 h maximum, otherwise minutes
/// within the history limits.
fn parse_lookback(value: &str) -> Result<usize, String> {
    if value.eq_ignore_ascii_case("auto") {
        return Ok(term_scope::MAX_HISTORY_MINUTES);
    }
    let bounds = format!(
        "{}-{} minutes or \"auto\"",
        term_scope::MIN_HISTORY_MINUTES,
        term_scope::MAX_HISTORY_MINUTES
    );
    let minutes: usize = value.parse().map_err(|_| format!("expected {bounds}"))?;
    if (term_scope::MIN_HISTORY_MINUTES..=term_scope::MAX_HISTORY_MINUTES).contains(&minutes) {
        Ok(minutes)
    } else {
        Err(format!("must be {bounds}"))
    }
}

fn main() -> ExitCode {
    let cli = Cli::parse();
    let contents = match std::fs::read_to_string(&cli.channel_file) {
        Ok(contents) => contents,
        Err(e) => {
            eprintln!("unable to read {}: {e}", cli.channel_file.display());
            return ExitCode::FAILURE;
        }
    };
    let channels = if stp::is_stp(&cli.channel_file, &contents) {
        stp::parse_stp(&contents)
    } else {
        parse::parse_channel_list(&contents)
    };
    if channels.is_empty() {
        eprintln!("no valid channels in {}", cli.channel_file.display());
        return ExitCode::FAILURE;
    }

    let store: capture::SignalStore = Arc::new(Mutex::new(HashMap::new()));
    let names: Vec<String> = channels.iter().map(|c| c.name.clone()).collect();
    let backend = capture::Backend::spawn(names, store.clone());

    // ratatui installs a panic hook that restores the terminal, but we still
    // restore before reporting errors (and shut the backend down) ourselves.
    let mut terminal = ratatui::init();
    // Mouse capture (for click-to-copy) is not part of ratatui's setup or its
    // panic hook, so chain a hook that turns it off before the terminal is
    // restored.  If enabling fails we just run without mouse support.
    let _ = execute!(std::io::stdout(), EnableMouseCapture);
    let ratatui_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        let _ = execute!(std::io::stdout(), DisableMouseCapture);
        ratatui_hook(info);
    }));
    let result = app::run(&mut terminal, channels, &store, cli.lookback);
    let _ = execute!(std::io::stdout(), DisableMouseCapture);
    ratatui::restore();
    backend.shutdown();

    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("terminal error: {e}");
            ExitCode::FAILURE
        }
    }
}

#[cfg(test)]
mod tests {
    use super::parse_lookback;

    #[test]
    fn lookback_accepts_auto_and_bounded_minutes() {
        assert_eq!(parse_lookback("auto"), Ok(term_scope::MAX_HISTORY_MINUTES));
        assert_eq!(parse_lookback("AUTO"), Ok(term_scope::MAX_HISTORY_MINUTES));
        assert_eq!(parse_lookback("5"), Ok(5));
        assert_eq!(parse_lookback("15"), Ok(15));
        assert_eq!(parse_lookback("1440"), Ok(1440));
        assert!(parse_lookback("4").is_err());
        assert!(parse_lookback("1441").is_err());
        assert!(parse_lookback("-5").is_err());
        assert!(parse_lookback("sometimes").is_err());
    }
}
