//! Best-effort stderr writers.
//!
//! All three functions (`print`, `progress`, `rateLimitWait`) are
//! non-throwing on purpose. A stderr-write failure shouldn't
//! propagate as an error to a CLI command's logic — the user's
//! command should still complete (or fail for its own reasons),
//! not get derailed because we couldn't paint a hint message.
//! Secondary failures get logged at debug level for forensics.
//!
//! Lives at the top level (not under `commands/`) so the portfolio
//! loader and the TUI can use it without a "TUI calls into
//! commands/" import smell.
//!
//! Under `zig build test` the writes are suppressed entirely:
//! tests that exercise error paths emit the same usage/hint
//! strings on every run, and that noise is more annoying than
//! useful. Real CLI users always reach the real stderr.

const std = @import("std");
const builtin = @import("builtin");
const fmt = @import("format.zig");

/// Default muted-text color used for progress headers. Matches the
/// CLI / TUI palette used elsewhere; defined here so this module
/// has no dependency on `commands/common.zig`.
const CLR_MUTED = [3]u8{ 0x80, 0x80, 0x80 };

/// Default red used for rate-limit warnings.
const CLR_NEGATIVE = [3]u8{ 0xe0, 0x6c, 0x75 };

pub fn print(io: std.Io, msg: []const u8) void {
    if (builtin.is_test) return;
    var buf: [1024]u8 = undefined;
    var writer = std.Io.File.stderr().writer(io, &buf);
    const out = &writer.interface;
    out.writeAll(msg) catch |err| {
        std.log.debug("stderr.print writeAll failed: {t}", .{err});
        return;
    };
    out.flush() catch |err| {
        std.log.debug("stderr.print flush failed: {t}", .{err});
    };
}

/// Print progress line to stderr: "  [N/M] SYMBOL (status)".
pub fn progress(io: std.Io, symbol: []const u8, status: []const u8, current: usize, total: usize, color: bool) void {
    if (builtin.is_test) return;
    progressImpl(io, symbol, status, current, total, color) catch |err| {
        std.log.debug("stderr.progress failed: {t}", .{err});
    };
}

fn progressImpl(io: std.Io, symbol: []const u8, status: []const u8, current: usize, total: usize, color: bool) !void {
    var buf: [256]u8 = undefined;
    var writer = std.Io.File.stderr().writer(io, &buf);
    const out = &writer.interface;
    if (color) try fmt.ansiSetFg(out, CLR_MUTED[0], CLR_MUTED[1], CLR_MUTED[2]);
    try out.print("  [{d}/{d}] ", .{ current, total });
    if (color) try fmt.ansiReset(out);
    try out.print("{s}", .{symbol});
    if (color) try fmt.ansiSetFg(out, CLR_MUTED[0], CLR_MUTED[1], CLR_MUTED[2]);
    try out.print("{s}\n", .{status});
    if (color) try fmt.ansiReset(out);
    try out.flush();
}

/// Print rate-limit wait message to stderr.
pub fn rateLimitWait(io: std.Io, wait_seconds: u64, color: bool) void {
    if (builtin.is_test) return;
    rateLimitWaitImpl(io, wait_seconds, color) catch |err| {
        std.log.debug("stderr.rateLimitWait failed: {t}", .{err});
    };
}

fn rateLimitWaitImpl(io: std.Io, wait_seconds: u64, color: bool) !void {
    var buf: [256]u8 = undefined;
    var writer = std.Io.File.stderr().writer(io, &buf);
    const out = &writer.interface;
    if (color) try fmt.ansiSetFg(out, CLR_NEGATIVE[0], CLR_NEGATIVE[1], CLR_NEGATIVE[2]);
    if (wait_seconds >= 60) {
        const mins = wait_seconds / 60;
        const secs = wait_seconds % 60;
        if (secs > 0) {
            try out.print("  (rate limit -- waiting {d}m {d}s)\n", .{ mins, secs });
        } else {
            try out.print("  (rate limit -- waiting {d}m)\n", .{mins});
        }
    } else {
        try out.print("  (rate limit -- waiting {d}s)\n", .{wait_seconds});
    }
    if (color) try fmt.ansiReset(out);
    try out.flush();
}
