par-term-settings-ui 0.9.0

Settings UI for par-term terminal emulator
//! Script list section: displays all configured scripts with status and controls.

use crate::section::{collapsing_section, collapsing_section_with_state};
use crate::settings_ui::SettingsUI;
use par_term_config::automation::RestartPolicy;
use std::collections::HashSet;

use super::editor::show_script_edit_form;

pub(super) fn show_scripts_section(
    ui: &mut egui::Ui,
    settings: &mut SettingsUI,
    changes_this_frame: &mut bool,
    collapsed: &mut HashSet<String>,
) {
    collapsing_section_with_state(
        ui,
        "Observer Scripts",
        "scripts_list",
        true,
        collapsed,
        |ui, collapsed_inner| {
            ui.label(
                "Define external scripts that receive terminal events and can send commands back.",
            );
            ui.add_space(4.0);

            // Collect mutations to apply after iteration
            let mut delete_index: Option<usize> = None;
            let mut start_edit_index: Option<usize> = None;
            let mut toggle_index: Option<usize> = None;

            // List existing scripts
            let script_count = settings.config.scripts.len();
            for i in 0..script_count {
                let is_editing =
                    settings.editing_script_index == Some(i) && !settings.adding_new_script;

                if is_editing {
                    show_script_edit_form(ui, settings, changes_this_frame, Some(i));
                } else {
                    show_script_row(
                        ui,
                        settings,
                        i,
                        &mut delete_index,
                        &mut start_edit_index,
                        &mut toggle_index,
                        collapsed_inner,
                    );
                }
            }

            // Apply mutations
            if let Some(i) = toggle_index {
                settings.config.scripts[i].enabled = !settings.config.scripts[i].enabled;
                settings.has_changes = true;
                *changes_this_frame = true;
            }
            if let Some(i) = delete_index {
                settings.config.scripts.remove(i);
                settings.has_changes = true;
                *changes_this_frame = true;
                if settings.editing_script_index == Some(i) {
                    settings.editing_script_index = None;
                }
            }
            if let Some(i) = start_edit_index {
                let script = &settings.config.scripts[i];
                settings.editing_script_index = Some(i);
                settings.adding_new_script = false;
                settings.temp_script_name = script.name.clone();
                settings.temp_script_path = script.script_path.clone();
                settings.temp_script_args = script.args.join(" ");
                settings.temp_script_auto_start = script.auto_start;
                settings.temp_script_enabled = script.enabled;
                settings.temp_script_restart_policy = script.restart_policy;
                settings.temp_script_restart_delay_ms = script.restart_delay_ms;
                settings.temp_script_subscriptions = script.subscriptions.join(", ");
                settings.temp_script_allow_write_text = script.allow_write_text;
                settings.temp_script_allow_run_command = script.allow_run_command;
                settings.temp_script_allow_change_config = script.allow_change_config;
                settings.temp_script_write_text_rate_limit = script.write_text_rate_limit;
                settings.temp_script_run_command_rate_limit = script.run_command_rate_limit;
            }

            ui.add_space(4.0);

            // Add new script button / form
            if settings.adding_new_script {
                ui.separator();
                ui.label(egui::RichText::new("New Script").strong());
                show_script_edit_form(ui, settings, changes_this_frame, None);
            } else if settings.editing_script_index.is_none()
                && ui
                    .button("+ Add Script")
                    .on_hover_text("Add a new observer script definition")
                    .clicked()
            {
                settings.adding_new_script = true;
                settings.editing_script_index = None;
                settings.temp_script_name = String::new();
                settings.temp_script_path = String::new();
                settings.temp_script_args = String::new();
                settings.temp_script_auto_start = false;
                settings.temp_script_enabled = true;
                settings.temp_script_restart_policy = RestartPolicy::Never;
                settings.temp_script_restart_delay_ms = 0;
                settings.temp_script_subscriptions = String::new();
                settings.temp_script_allow_write_text = false;
                settings.temp_script_allow_run_command = false;
                settings.temp_script_allow_change_config = false;
                settings.temp_script_write_text_rate_limit = 0;
                settings.temp_script_run_command_rate_limit = 0;
            }
        },
    );
}

/// Render a single script summary row (when not in edit mode).
fn show_script_row(
    ui: &mut egui::Ui,
    settings: &mut SettingsUI,
    i: usize,
    delete_index: &mut Option<usize>,
    start_edit_index: &mut Option<usize>,
    toggle_index: &mut Option<usize>,
    collapsed: &mut HashSet<String>,
) {
    let script = &settings.config.scripts[i];
    let is_running = settings.script_running.get(i).copied().unwrap_or(false);
    let has_error = settings.script_errors.get(i).is_some_and(|e| !e.is_empty());

    // First row: status + enabled + name + buttons
    ui.horizontal(|ui| {
        // Status indicator: green=running, red=error, gray=stopped
        if is_running {
            ui.label(egui::RichText::new("\u{25cf}").color(egui::Color32::from_rgb(100, 200, 100)));
        } else if has_error {
            ui.label(egui::RichText::new("\u{25cf}").color(egui::Color32::from_rgb(220, 80, 80)));
        } else if script.auto_start {
            ui.label(
                egui::RichText::new("[auto]")
                    .color(egui::Color32::from_rgb(100, 200, 100))
                    .small(),
            );
        } else {
            ui.label(egui::RichText::new("\u{25cb}").color(egui::Color32::GRAY));
        }

        // Enabled checkbox
        let mut enabled = script.enabled;
        if ui.checkbox(&mut enabled, "").changed() {
            *toggle_index = Some(i);
        }

        // Name (bold)
        ui.label(egui::RichText::new(&script.name).strong());

        // Right-align buttons
        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
            // Delete button (rightmost)
            if ui
                .small_button(
                    egui::RichText::new("Delete").color(egui::Color32::from_rgb(200, 80, 80)),
                )
                .clicked()
            {
                *delete_index = Some(i);
            }

            // Edit button
            if ui.small_button("Edit").clicked() {
                *start_edit_index = Some(i);
            }

            // Start/Stop button
            if is_running {
                if ui
                    .small_button(
                        egui::RichText::new("Stop").color(egui::Color32::from_rgb(220, 160, 50)),
                    )
                    .clicked()
                {
                    settings.pending_script_actions.push((i, false));
                }
            } else if ui.small_button("Start").clicked() {
                log::debug!("Script Start button clicked for index {}", i);
                // Clear any previous error message
                if let Some(err) = settings.script_errors.get_mut(i) {
                    err.clear();
                }
                settings.pending_script_actions.push((i, true));
            }
        });
    });

    let script = &settings.config.scripts[i];

    // Second row: script path (indented, monospace)
    let path_display = if script.args.is_empty() {
        script.script_path.clone()
    } else {
        format!("{} {}", script.script_path, script.args.join(" "))
    };
    ui.indent(format!("script_path_{}", i), |ui| {
        ui.horizontal(|ui| {
            ui.label(
                egui::RichText::new(&path_display)
                    .monospace()
                    .small()
                    .color(egui::Color32::from_rgb(150, 150, 200)),
            );
            // Show restart policy info
            if script.restart_policy != RestartPolicy::Never {
                let restart_text = if script.restart_delay_ms > 0 {
                    format!(
                        "[restart: {}, delay: {}ms]",
                        script.restart_policy.display_name(),
                        script.restart_delay_ms
                    )
                } else {
                    format!("[restart: {}]", script.restart_policy.display_name())
                };
                ui.label(
                    egui::RichText::new(restart_text)
                        .small()
                        .color(egui::Color32::from_rgb(180, 180, 100)),
                );
            }
            // Show subscriptions if any
            if !script.subscriptions.is_empty() {
                ui.label(
                    egui::RichText::new(format!("[{}]", script.subscriptions.join(", ")))
                        .small()
                        .color(egui::Color32::from_rgb(140, 180, 220)),
                );
            }
        });
    });

    // Show error message if script died with stderr output
    if has_error && !is_running {
        let err_text = &settings.script_errors[i];
        ui.indent(format!("script_err_{}", i), |ui| {
            ui.label(
                egui::RichText::new(format!("Error: {}", err_text))
                    .small()
                    .color(egui::Color32::from_rgb(220, 80, 80)),
            );
        });
    }

    // Output viewer (collapsible)
    let has_output = settings
        .script_output
        .get(i)
        .is_some_and(|lines| !lines.is_empty());
    if has_output {
        let is_expanded = settings
            .script_output_expanded
            .get(i)
            .copied()
            .unwrap_or(false);
        let line_count = settings.script_output[i].len();
        ui.indent(format!("script_out_{}", i), |ui| {
            let toggle_text = if is_expanded {
                format!("\u{25bc} Output ({} lines)", line_count)
            } else {
                format!("\u{25b6} Output ({} lines)", line_count)
            };
            if ui
                .small_button(
                    egui::RichText::new(&toggle_text)
                        .small()
                        .color(egui::Color32::from_rgb(140, 180, 140)),
                )
                .clicked()
                && let Some(expanded) = settings.script_output_expanded.get_mut(i)
            {
                *expanded = !*expanded;
            }
            if is_expanded {
                let output_text = settings.script_output[i].join("\n");
                egui::ScrollArea::vertical()
                    .id_salt(format!("script_output_scroll_{}", i))
                    .max_height(150.0)
                    .stick_to_bottom(true)
                    .show(ui, |ui| {
                        ui.label(
                            egui::RichText::new(&output_text)
                                .monospace()
                                .small()
                                .color(egui::Color32::from_rgb(180, 180, 180)),
                        );
                    });
                if ui.small_button("Clear").clicked() {
                    settings.script_output[i].clear();
                }
            }
        });
    }

    // Panel viewer (collapsible)
    if let Some(Some((title, content))) = settings.script_panels.get(i) {
        let panel_title = format!("Panel: {}", title);
        let panel_id = format!("script_panel_{}", i);
        let panel_scroll_id = format!("script_panel_scroll_{}", i);
        ui.indent(&panel_id, |ui| {
            collapsing_section(ui, &panel_title, &panel_id, false, collapsed, |ui| {
                egui::ScrollArea::vertical()
                    .id_salt(&panel_scroll_id)
                    .max_height(200.0)
                    .show(ui, |ui| {
                        ui.label(
                            egui::RichText::new(content)
                                .monospace()
                                .small()
                                .color(egui::Color32::from_rgb(200, 200, 200)),
                        );
                    });
            });
        });
    }

    ui.add_space(2.0);
}