//! Top-level configuration handed to [`super::ProcServ`].
//!
//! Mirrors C procServ's command-line flag set 1:1 so existing
//! deployments can switch to `procserv-rs` with the same wrapper
//! scripts. Defaults match C procServ's defaults.

use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;

use crate::procserv::restart::{RestartMode, RestartPolicy};

/// Listen-side configuration. procServ historically accepts both TCP
/// (`--port` / `--allow`) and Unix-socket (`--unixpath`) consoles
/// concurrently. The Rust port keeps both, gated by which fields the
/// caller supplies.
#[derive(Debug, Clone, Default)]
pub struct ListenConfig {
    /// TCP listen port (`--port`). `None` disables TCP.
    pub tcp_port: Option<u16>,
    /// Bind address for the TCP listener. C procServ `--allow`
    /// flips between localhost-only and any-interface; the Rust port
    /// takes the explicit `SocketAddr` so both forms collapse onto
    /// one field. Defaults to `127.0.0.1` if `tcp_port` is set.
    pub tcp_bind: Option<SocketAddr>,
    /// Unix-domain socket path (`--unixpath`). `None` disables UNIX.
    pub unix_path: Option<PathBuf>,
}

/// Per-input-key bindings. Matches C procServ's `restartChar`
/// (toggle restart mode), `killChar` (send signal to child),
/// `restartChar` for kill-then-restart, `quitChar` for "shut down
/// procServ entirely", and `logoutChar` for "disconnect this client
/// only".
///
/// Each is `Option<u8>` because C procServ accepts the literal byte
/// 0 to disable any given binding individually.
#[derive(Debug, Clone, Copy, Default)]
pub struct KeyBindings {
    /// Send signal to child (default `Ctrl-X` = `0x18`).
    pub kill: Option<u8>,
    /// Toggle restart mode (default `Ctrl-T` = `0x14`).
    pub toggle_restart: Option<u8>,
    /// Restart child once after manual kill (default `Ctrl-R` = `0x12`).
    pub restart: Option<u8>,
    /// Shut down procserv entirely (default disabled in C).
    pub quit: Option<u8>,
    /// Disconnect this client only (default `Ctrl-]` = `0x1d`).
    pub logout: Option<u8>,
}

/// Configuration for the supervised child process.
#[derive(Debug, Clone)]
pub struct ChildConfig {
    /// Executable name (display only; goes into welcome banner).
    pub name: String,
    /// Argv[0] — actual program to exec.
    pub program: PathBuf,
    /// Remaining argv.
    pub args: Vec<String>,
    /// Working directory for the child (optional `--chdir`).
    pub cwd: Option<PathBuf>,
    /// Signal sent on `kill` keybinding. C procServ defaults to
    /// `SIGKILL`; many sites override with `SIGINT` for graceful IOC
    /// shutdown.
    pub kill_signal: i32,
    /// Characters to discard from PTY-master writes (`--ignore`).
    /// Empty = no filtering.
    pub ignore_chars: Vec<u8>,
}

/// Sidecar/log configuration.
#[derive(Debug, Clone, Default)]
pub struct LoggingConfig {
    /// Path to the log file (`--logfile`). `None` disables logging.
    pub log_path: Option<PathBuf>,
    /// Path to write the supervisor's PID (`--pidfile`).
    pub pid_path: Option<PathBuf>,
    /// Path to write a status info file consumed by `manage-procs`.
    pub info_path: Option<PathBuf>,
    /// Per-line timestamp format for the log file. Defaults to ISO-8601.
    pub time_format: String,
}

/// Full procserv configuration.
#[derive(Debug, Clone)]
pub struct ProcServConfig {
    /// Foreground mode. When `false`, [`super::daemon::fork_and_go`]
    /// runs and the parent exits.
    pub foreground: bool,
    pub listen: ListenConfig,
    pub keys: KeyBindings,
    pub child: ChildConfig,
    pub logging: LoggingConfig,
    pub restart: RestartPolicy,
    pub restart_mode: RestartMode,
    /// Hold-off between child restarts (matches C `holdoffTime`).
    pub holdoff: Duration,
    /// Wait for first manual restart command before launching the
    /// child (`--wait`).
    pub wait_for_manual_start: bool,
}

impl ProcServConfig {
    /// Validate the config — bail at construction time rather than
    /// surfacing an error mid-run.
    pub fn validate(&self) -> Result<(), String> {
        if self.listen.tcp_port.is_none() && self.listen.unix_path.is_none() {
            return Err("at least one of tcp_port / unix_path must be set".into());
        }
        if self.child.program.as_os_str().is_empty() {
            return Err("child.program is required".into());
        }
        Ok(())
    }
}
