//! Terminal progress renderer — the indicatif spinner + milestone scrollback.
//!
//! Implements the core [`Progress`] trait; this is the CLI's live output, moved
//! out of core so core stays front-end agnostic. Rendering is byte-for-byte the
//! same as the pre-split reporter (the CLI is snapshot-gated).

use hephaes_core::progress::{Event, Progress};
use hephaes_core::util;
use indicatif::{ProgressBar, ProgressStyle};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verbosity {
    Quiet,
    Normal,
    Verbose,
}

impl Verbosity {
    pub fn from_flags(quiet: bool, verbose: bool) -> Verbosity {
        if quiet {
            Verbosity::Quiet
        } else if verbose {
            Verbosity::Verbose
        } else {
            Verbosity::Normal
        }
    }
}

pub struct TerminalProgress {
    pb: Option<ProgressBar>,
    verbosity: Verbosity,
    pages: AtomicUsize,
    reads: AtomicUsize,
}

impl TerminalProgress {
    pub fn new(verbosity: Verbosity, tty: bool) -> TerminalProgress {
        let pb = if tty && verbosity != Verbosity::Quiet {
            let pb = ProgressBar::new_spinner();
            pb.set_style(
                ProgressStyle::with_template("{spinner:.cyan} {msg}")
                    .unwrap_or_else(|_| ProgressStyle::default_spinner()),
            );
            pb.enable_steady_tick(Duration::from_millis(120));
            Some(pb)
        } else {
            None
        };
        TerminalProgress {
            pb,
            verbosity,
            pages: AtomicUsize::new(0),
            reads: AtomicUsize::new(0),
        }
    }

    /// Scrollback line above the spinner (stderr; unbuffered so it survives kills).
    fn line(&self, s: String) {
        match &self.pb {
            Some(pb) => pb.println(s),
            None => eprintln!("{s}"),
        }
    }

    fn status(&self, current: &str) {
        if let Some(pb) = &self.pb {
            pb.set_message(format!(
                "{current} · {} reads · {} pages",
                self.reads.load(Ordering::Relaxed),
                self.pages.load(Ordering::Relaxed)
            ));
        }
    }

    pub fn finish(&self) {
        if let Some(pb) = &self.pb {
            pb.finish_and_clear();
        }
    }
}

impl Progress for TerminalProgress {
    fn event(&self, ev: Event) {
        match ev {
            Event::Tool { name, target } => {
                if is_read_tool(&name) {
                    self.reads.fetch_add(1, Ordering::Relaxed);
                }
                if self.verbosity == Verbosity::Verbose {
                    if target.is_empty() {
                        self.line(format!("  → {name}"));
                    } else {
                        self.line(format!("  → {name} {target}"));
                    }
                }
                let current = if target.is_empty() {
                    name
                } else {
                    format!("{name} {target}")
                };
                self.status(&current);
            }
            Event::Page { path } => {
                if is_special_page(&path) {
                    return;
                }
                self.pages.fetch_add(1, Ordering::Relaxed);
                if self.verbosity != Verbosity::Quiet {
                    self.line(format!("  ✓ {path}"));
                }
                self.status(&format!("writing {path}"));
            }
            Event::Note(text) => {
                if self.verbosity == Verbosity::Verbose {
                    let t = text.trim();
                    if !t.is_empty() {
                        self.line(format!("  · {}", util::truncate_chars(t, 200)));
                    }
                }
            }
        }
    }
}

fn is_read_tool(name: &str) -> bool {
    matches!(
        name.to_ascii_lowercase().as_str(),
        "read" | "read_file" | "grep" | "glob" | "ls" | "list_dir" | "read_page" | "read_repo_map"
    )
}

fn is_special_page(path: &str) -> bool {
    matches!(
        path.rsplit('/').next().unwrap_or(path),
        "HEPHAES.md" | "index.md" | "log.md"
    )
}
