/// Terminal display helpers — table-style command rows with live spinner.
///
/// Layout (no borders, aligned columns):
///   {2 spaces}{action_emoji (2 display cols)}{2 spaces}{name:<NAME_W}{2 spaces}{payload:<payload_w}{2 spaces}{status_emoji}{2 spaces}{result}
use std::io::Write;

use colored::Colorize;
use unicode_width::UnicodeWidthStr;

/// Pad an emoji/symbol string to exactly 2 terminal display columns.
/// Some symbols (ℹ, ☀, ⚠, # …) render as 1-wide; we add a space so columns align.
fn emoji_cell(s: &str) -> String {
    let w = UnicodeWidthStr::width(s);
    if w < 2 {
        format!("{s} ")
    } else {
        s.to_string()
    }
}

// Fixed column widths (in terminal display columns, ASCII-only content assumed for name/payload/result)
const NAME_W: usize = 14;
const MIN_PAYLOAD_W: usize = 10;
const MIN_RESULT_W: usize = 10;
// Overhead: 2 (indent) + 2 (action emoji) + 2 (gap) + NAME_W + 2 (gap) + 2 (gap) + 2 (status emoji) + 2 (gap)
const FIXED_OVERHEAD: usize = 2 + 2 + 2 + NAME_W + 2 + 2 + 2 + 2;

pub fn terminal_width() -> usize {
    terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(120)
        .max(60)
}

/// Split remaining space between payload and result columns.
/// payload gets ~55%, result gets the rest; both have minimums.
fn col_widths() -> (usize, usize) {
    let tw = terminal_width();
    let remaining = tw.saturating_sub(FIXED_OVERHEAD).max(MIN_PAYLOAD_W + MIN_RESULT_W);
    let payload_w = (remaining * 55 / 100).max(MIN_PAYLOAD_W);
    let result_w = remaining.saturating_sub(payload_w).max(MIN_RESULT_W);
    (payload_w, result_w)
}

/// Truncate a string to at most `max` Unicode chars, appending `…` if cut.
/// Must be called on PLAIN (uncolored) text — ANSI codes confuse char counting.
pub fn trunc(s: &str, max: usize) -> String {
    if max == 0 {
        return String::new();
    }
    let mut chars = s.chars();
    let truncated: String = chars.by_ref().take(max).collect();
    if chars.next().is_some() {
        let mut t: String = truncated.chars().take(max.saturating_sub(1)).collect();
        t.push('…');
        t
    } else {
        truncated
    }
}

/// Format one table row into a String (no trailing newline).
/// Truncation happens on plain `result` BEFORE colorizing, so ANSI reset codes are never cut off.
fn format_row(action: &str, name: &str, payload: &str, status: &str, result: &str, err: bool) -> String {
    let (payload_w, result_w) = col_widths();
    let p = trunc(payload, payload_w);
    // Truncate BEFORE colorizing — avoids dangling ANSI escape sequences
    let r_plain = trunc(result, result_w);
    let r = if err { r_plain.red().to_string() } else { r_plain };
    format!(
        "  {}  {:<name_w$}  {:<payload_w$}  {}  {}",
        emoji_cell(action),
        name,
        p,
        emoji_cell(status),
        r,
        name_w = NAME_W,
        payload_w = payload_w,
    )
}

/// Print the "running" row (🔄 spinner).
pub fn cmd_start(action: &str, name: &str, payload: &str) {
    let line = format_row(action, name, payload, "🔄", "", false);
    println!("{}", line);
    let _ = std::io::stdout().flush();
}

/// Overwrite the previous line (cursor up + clear) with the completed row.
pub fn cmd_done(action: &str, name: &str, payload: &str, success: bool, result: &str) {
    let status = if success { "✅" } else { "❌" };
    let line = format_row(action, name, payload, status, result, !success);
    // \x1b[1A = cursor up 1, \r = go to col 0, \x1b[2K = clear line
    print!("\x1b[1A\r\x1b[2K{}\n", line);
    let _ = std::io::stdout().flush();
    crate::logger::write_line(if success { "OK" } else { "ERROR" }, &format!("{name} {payload} → {result}"));
}

/// Info line for the startup header — uses same column alignment as table rows.
pub fn info_line(emoji: &str, key: &str, value: &str) {
    // Match table layout: 2 spaces + 2-wide emoji + 2 spaces + name (NAME_W) + 2 spaces + value
    println!("  {}  {:<name_w$}  {}", emoji_cell(emoji), key, value, name_w = NAME_W);
}

/// Print the prompt "awaiting input" row + the 🎤 answer input prefix.
/// The caller should read stdin immediately after this returns.
pub fn prompt_waiting(message: &str) {
    let (payload_w, _result_w) = col_widths();
    let p = trunc(message, payload_w);
    // 🌀 row: show message + 🔄 + "awaiting input"
    println!(
        "  {}  {:<name_w$}  {:<payload_w$}  {}  awaiting input",
        emoji_cell("🌀"), "prompt", p, emoji_cell("🔄"),
        name_w = NAME_W, payload_w = payload_w,
    );
    // 🎤 answer input prefix — no newline, user types here
    print!("  {}  {:<name_w$}  › ", emoji_cell("🎤"), "answer", name_w = NAME_W);
    let _ = std::io::stdout().flush();
}

/// Overwrite the 🌀 and 🎤 lines with the final state after input was received.
/// Must be called after the user pressed Enter (cursor is on the line after 🎤).
pub fn prompt_done(message: &str, answer: &str) {
    let (payload_w, result_w) = col_widths();
    let p = trunc(message, payload_w);
    // Go up 2 lines (🎤 line + 🌀 line), rewrite both
    print!("\x1b[2A\r\x1b[2K");
    // Rewrite 🌀 row as done
    println!(
        "  {}  {:<name_w$}  {:<payload_w$}  {}  done",
        emoji_cell("🌀"), "prompt", p, emoji_cell("✅"),
        name_w = NAME_W, payload_w = payload_w,
    );
    // Clear 🎤 line + rewrite with purple answer
    print!("\r\x1b[2K");
    let a = trunc(answer, payload_w + result_w + 4); // answer spans both columns
    println!(
        "  {}  {:<name_w$}  {}",
        emoji_cell("🎤"), "answer", a.purple(),
        name_w = NAME_W,
    );
    let _ = std::io::stdout().flush();
    crate::logger::write_line("OK", &format!("prompt → {answer}"));
}

pub fn err(emoji: &str, msg: &str) {
    println!("  {}  {}", emoji_cell(emoji), msg.red());
    crate::logger::write_line("ERROR", msg);
}
