use super::format::format_header_datetime;
use super::host::HostKind;
use super::render::ContextRenderStats;

const ANSI_RESET: &str = "\x1b[0m";
const ANSI_BOLD_CYAN: &str = "\x1b[1;36m";
const ANSI_BOLD: &str = "\x1b[1m";

pub(in crate::context) fn context_header(
    project: &str,
    current_branch: Option<&str>,
    hook_source: Option<&str>,
    host: HostKind,
    use_colors: bool,
) -> String {
    let mut rows = vec![("project", project.to_string())];
    if let Some(branch) = current_branch {
        rows.push(("branch", branch.to_string()));
    }
    let source = context_source_footer(hook_source);
    if source != "-" {
        rows.push(("source", source.to_string()));
    }
    rows.push(("updated", format_header_datetime()));

    let mut header = String::new();
    let row_indent = if host == HostKind::CodexCli && use_colors {
        "              "
    } else {
        ""
    };
    push_rail(&mut header, "remem context", &rows, row_indent, use_colors);
    header.push('\n');
    header
}

pub(in crate::context) fn context_delta_title_line_like(
    _first_line: &str,
    use_colors: bool,
) -> String {
    let mut header = String::new();
    header.push_str(&rail_title_line("remem context delta", use_colors));
    header
}

pub(in crate::context) fn context_stats_footer(
    stats: &ContextRenderStats,
    use_colors: bool,
) -> String {
    let estimated_tokens = estimate_display_tokens(stats.output_chars);
    let rows = vec![
        (
            "Memories",
            format!(
                "{} total, {} core, {} lessons, {} indexed",
                stats.memories_loaded, stats.core.count, stats.lessons.count, stats.index.count
            ),
        ),
        (
            "Preferences",
            format!(
                "{} total, {} project, {} global",
                stats.preferences.count, stats.project_preferences, stats.global_preferences
            ),
        ),
        ("Sessions", stats.sessions.count.to_string()),
        ("Workstreams", stats.workstreams.count.to_string()),
        (
            "Budget",
            format!(
                "{} chars (~{} tokens) / {}, truncated: {}",
                stats.output_chars,
                estimated_tokens,
                stats.total_char_limit,
                if stats.truncated { "yes" } else { "no" }
            ),
        ),
    ];
    let mut footer = String::new();
    footer.push('\n');
    push_rail(&mut footer, "Loaded", &rows, "", use_colors);
    footer
}

pub(in crate::context) fn strip_ansi(input: &str) -> String {
    let mut output = String::with_capacity(input.len());
    let mut chars = input.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch != '\x1b' {
            output.push(ch);
            continue;
        }

        if chars.next_if_eq(&'[').is_some() {
            for code in chars.by_ref() {
                if ('@'..='~').contains(&code) {
                    break;
                }
            }
        }
    }
    output
}

pub(in crate::context) fn contains_ansi(input: &str) -> bool {
    input.contains("\x1b[")
}

fn push_rail(
    output: &mut String,
    title: &str,
    rows: &[(&str, String)],
    row_indent: &str,
    use_colors: bool,
) {
    output.push_str(&rail_title_line(title, use_colors));
    for (idx, (label, value)) in rows.iter().enumerate() {
        let marker = if idx + 1 == rows.len() {
            "└─"
        } else {
            "├─"
        };
        output.push_str(&rail_row_line(row_indent, marker, label, value, use_colors));
    }
}

fn rail_title_line(title: &str, use_colors: bool) -> String {
    let title = if use_colors {
        paint(title, ANSI_BOLD_CYAN)
    } else {
        title.to_string()
    };
    format!("{title}\n")
}

fn rail_row_line(
    row_indent: &str,
    marker: &str,
    label: &str,
    value: &str,
    use_colors: bool,
) -> String {
    let label = if use_colors {
        paint(label, ANSI_BOLD)
    } else {
        label.to_string()
    };
    format!("{row_indent}{marker} {label}: {value}\n")
}

fn context_source_footer(source: Option<&str>) -> &'static str {
    match source
        .map(|value| value.trim().to_ascii_lowercase())
        .as_deref()
    {
        Some("compact") => "compact",
        Some("clear") => "clear",
        _ => "-",
    }
}

fn estimate_display_tokens(chars: usize) -> usize {
    (chars + 3) / 4
}

fn paint(value: &str, ansi_code: &str) -> String {
    format!("{ansi_code}{value}{ANSI_RESET}")
}
