SysmonView.java

package com.guinetik.hexafun.examples.sysmon;

import static com.guinetik.hexafun.examples.sysmon.SysmonKeys.*;
import static com.guinetik.hexafun.examples.tui.Ansi.*;

import com.guinetik.hexafun.examples.tui.View;

/**
 * Responsive view components for the System Monitor TUI.
 *
 * <p>All views use {@code state.width()} to render at the current terminal width.
 * Width is recalculated on each refresh.</p>
 */
public final class SysmonView {

    private SysmonView() {}

    private static final int PADDING = 4; // Left/right padding

    /**
     * Compose the full screen view.
     */
    public static View<SysmonState> screen() {
        return clear()
            .andThen(header())
            .andThen(metrics())
            .andThen(menu())
            .andThen(status())
            .andThen(prompt());
    }

    /**
     * Clear screen and reset cursor.
     */
    public static View<SysmonState> clear() {
        return state -> CLEAR + CURSOR_HOME;
    }

    /**
     * Render the header box - responsive to terminal width.
     */
    public static View<SysmonState> header() {
        return state -> {
            int boxWidth = state.width() - PADDING;
            int innerWidth = boxWidth - 2;

            return lines(
                "",
                color("  " + DBOX_TOP_LEFT + repeat(DBOX_HORIZONTAL, innerWidth) + DBOX_TOP_RIGHT, CYAN),
                color("  " + DBOX_VERTICAL, CYAN) +
                    color(center("SYSTEM MONITOR", innerWidth), BOLD, BRIGHT_WHITE) +
                    color(DBOX_VERTICAL, CYAN),
                color("  " + DBOX_VERTICAL, CYAN) +
                    color(center("HexaFun Adapter Demo", innerWidth), DIM) +
                    color(DBOX_VERTICAL, CYAN),
                color("  " + DBOX_BOTTOM_LEFT + repeat(DBOX_HORIZONTAL, innerWidth) + DBOX_BOTTOM_RIGHT, CYAN),
                ""
            );
        };
    }

    /**
     * Render the metrics output based on selected format - responsive.
     */
    public static View<SysmonState> metrics() {
        return state -> {
            SystemMetrics m = state.metrics();
            int boxWidth = state.width() - PADDING;

            String output = switch (state.format()) {
                case TUI -> renderTuiGauges(m, boxWidth);
                case CLI -> formatBox("CLI Output", state.app().adapt(TO_CLI, m), boxWidth, GREEN);
                case JSON -> formatBox("JSON Output", state.app().adapt(TO_JSON, m), boxWidth, YELLOW);
                case PROMETHEUS -> formatBox("Prometheus Output", state.app().adapt(TO_PROMETHEUS, m), boxWidth, MAGENTA);
            };
            return output + "\n";
        };
    }

    /**
     * Render TUI gauges - responsive bar width.
     */
    private static String renderTuiGauges(SystemMetrics metrics, int boxWidth) {
        StringBuilder sb = new StringBuilder();
        int innerWidth = boxWidth - 2;
        // Gauge content: space(1) + label(4) + space(1) + bracket(1) + bar + bracket(1) + percent(5) + warn(2) + space(1) = 16 fixed
        int barWidth = innerWidth - 16;

        String title = "─ System Monitor ";  // 17 chars

        // Header
        sb.append(color("  " + BOX_TOP_LEFT + title +
            repeat(BOX_HORIZONTAL, innerWidth - title.length()) + BOX_TOP_RIGHT, CYAN)).append("\n");

        // CPU gauge
        sb.append(color("  " + BOX_VERTICAL, CYAN))
          .append(gauge("CPU ", metrics.cpu(), metrics.cpuWarning(), barWidth))
          .append(color(BOX_VERTICAL, CYAN)).append("\n");

        // Memory gauge
        sb.append(color("  " + BOX_VERTICAL, CYAN))
          .append(gauge("MEM ", metrics.memory(), metrics.memoryWarning(), barWidth))
          .append(color(BOX_VERTICAL, CYAN)).append("\n");

        // Disk gauge
        sb.append(color("  " + BOX_VERTICAL, CYAN))
          .append(gauge("DISK", metrics.disk(), metrics.diskWarning(), barWidth))
          .append(color(BOX_VERTICAL, CYAN)).append("\n");

        // Footer
        sb.append(color("  " + BOX_BOTTOM_LEFT + repeat(BOX_HORIZONTAL, innerWidth) +
            BOX_BOTTOM_RIGHT, CYAN)).append("\n");

        return sb.toString();
    }

    /**
     * Build a single gauge line with color-coded bar.
     */
    private static String gauge(String label, double percent, boolean warning, int barWidth) {
        int filled = (int) ((barWidth * percent) / 100);
        int empty = barWidth - filled;

        String barColor = warning ? RED : GREEN;
        String warnIcon = warning ? color(" ⚠", YELLOW) : "  ";

        return " " + color(label, BOLD) + " " +
            color("[", DIM) +
            color(repeat(BLOCK_FULL, filled), barColor) +
            color(repeat(BLOCK_LIGHT, empty), BRIGHT_BLACK) +
            color("]", DIM) +
            color(String.format(" %3.0f%%", percent), warning ? RED : WHITE) +
            warnIcon + " ";
    }

    /**
     * Format content in a responsive box with title.
     */
    private static String formatBox(String title, String content, int boxWidth, String borderColor) {
        StringBuilder sb = new StringBuilder();
        int innerWidth = boxWidth - 2;
        String titlePrefix = "─ " + title + " ";  // Calculate actual length

        // Top border with title
        sb.append(color("  " + BOX_TOP_LEFT + titlePrefix +
            repeat(BOX_HORIZONTAL, innerWidth - titlePrefix.length()) + BOX_TOP_RIGHT, borderColor))
          .append("\n");

        // Content lines: border(1) + space(1) + content + space(1) + border(1) = boxWidth
        // So content width = boxWidth - 4 = innerWidth - 2
        int contentWidth = innerWidth - 2;
        for (String line : content.split("\n")) {
            if (!line.isEmpty()) {
                String truncated = line.length() > contentWidth
                    ? line.substring(0, Math.max(0, contentWidth - 3)) + "..."
                    : line;
                sb.append(color("  " + BOX_VERTICAL, borderColor))
                  .append(" ")
                  .append(pad(truncated, contentWidth))
                  .append(" ")
                  .append(color(BOX_VERTICAL, borderColor))
                  .append("\n");
            }
        }

        // Bottom border
        sb.append(color("  " + BOX_BOTTOM_LEFT + repeat(BOX_HORIZONTAL, innerWidth) +
            BOX_BOTTOM_RIGHT, borderColor))
          .append("\n");

        return sb.toString();
    }

    /**
     * Render the format selection menu.
     */
    public static View<SysmonState> menu() {
        return state -> {
            int boxWidth = state.width() - PADDING;
            StringBuilder sb = new StringBuilder();

            sb.append(color("  " + repeat(BOX_HORIZONTAL, boxWidth), DIM))
              .append("\n\n");

            // Format options
            sb.append("  ");
            int i = 1;
            for (SysmonFormat f : SysmonFormat.values()) {
                String sel = f == state.format() ? "●" : "○";
                sb.append(color("[" + i + "]", f.color, BOLD));
                sb.append(
                    color(
                        " " + sel + " " + f.label + "  ",
                        f == state.format() ? f.color : BRIGHT_BLACK
                    )
                );
                i++;
            }
            sb.append("\n\n");

            // Other options
            sb.append("  ");
            sb.append(color("[r]", YELLOW, BOLD))
              .append(color(" Refresh  ", YELLOW));
            sb.append(color("[q]", BRIGHT_BLACK, BOLD))
              .append(color(" Quit", BRIGHT_BLACK));
            sb.append("\n\n");

            return sb.toString();
        };
    }

    /**
     * Render status message.
     */
    public static View<SysmonState> status() {
        return state ->
            state.status().isEmpty()
                ? "\n"
                : color(
                      "  " + ARROW_RIGHT + " " + state.status(),
                      state.statusColor()
                  ) +
                  "\n\n";
    }

    /**
     * Render input prompt.
     */
    public static View<SysmonState> prompt() {
        return state -> color("  > ", CYAN, BOLD);
    }
}