//! Type definitions for the rewrite engine.

use serde::Serialize;

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
pub(super) enum RewriteCategory {
    Test,
    Build,
    Git,
    Read,
    Lint,
    Pkg,
    Infra,
    FileOps,
    Db,
}

pub(super) struct RewriteRule {
    pub(super) prefix: &'static [&'static str],
    pub(super) rewrite_to: &'static [&'static str],
    pub(super) skip_if_flag_prefix: &'static [&'static str],
    pub(super) category: RewriteCategory,
    /// When true, this rule is suppressed on the source side of a pipe
    /// expression (`cmd | ...`).  Prevents rewriting commands whose raw
    /// output is consumed by downstream pipe segments (e.g. `find . | head`
    /// must not rewrite `find` because `head` expects raw file listings).
    ///
    /// Set true for: file-listing commands (`ls`, `grep`, `find`, `rg`) whose
    /// output is commonly piped to filters.  SEE: AD-RW-2.
    pub(super) exclude_pipe_source: bool,
    /// When true, skip rewriting if any argument after the prefix contains `=`.
    ///
    /// Required for the `env` command: `env LANG=C sort file.txt` uses
    /// `LANG=C` as a per-invocation env-var assignment passed to `env`, not as
    /// a shell-level env-var prefix.  The standard `strip_env_vars` stripping
    /// cannot catch this because `env` itself is the command token — the
    /// `VAR=val` tokens appear *after* the command, not before it.
    ///
    /// Bare `env` (no `=`-containing args) is still rewritten normally.
    /// Set true only on the `env` rule.
    pub(super) skip_if_middle_contains_eq: bool,
    /// Global flags that precede the subcommand in the command line.
    ///
    /// When non-empty, the engine skips these flags (and their values where
    /// required) before matching the remaining prefix elements against the
    /// token stream.  This handles cases like `kubectl -n namespace get pods`
    /// or `docker --host tcp://host:port ps` where global flags appear between
    /// the tool name and its subcommand.
    ///
    /// Each element is either:
    /// - A value-consuming flag (e.g. `"--context"`, `"-n"`) — the engine
    ///   skips both the flag and the following token.
    /// - A boolean flag (starts with `-` but not listed here) — skipped with
    ///   no following token consumed.
    ///
    /// Empty slice (`&[]`) disables global-flag skipping (default behaviour).
    pub(super) global_value_flags: &'static [&'static str],
    /// Flags that must be present somewhere after the prefix for the rewrite
    /// to fire.
    ///
    /// When non-empty, the engine checks that at least one token in `middle`
    /// (the tokens after the matched prefix) equals one of these flag values.
    /// If none match, the rule is skipped.
    ///
    /// Use case: `psql` requires `-c` / `--command` to be present (batch mode);
    /// `mysql` requires `-e` / `--execute`.  Without this check a bare `psql`
    /// would be rewritten, which would intercept interactive sessions.
    ///
    /// Empty slice (`&[]`) disables the check (default behaviour — any
    /// invocation matching the prefix is rewritten).
    pub(super) require_flag: &'static [&'static str],
}

#[derive(Debug)]
pub(super) struct RewriteResult {
    pub(super) tokens: Vec<String>,
    pub(super) category: RewriteCategory,
}

// ---- Compound command types (#45) ----

/// Result of splitting a shell command string at compound operators.
#[derive(Debug)]
pub(super) enum CompoundSplitResult {
    /// No compound operators found — treat as a simple command.
    Simple(Vec<String>),
    /// Found compound operators — segments separated by `&&`, `||`, `;`, `|`.
    Compound(Vec<CommandSegment>),
    /// Unsupported shell syntax (heredocs, subshells, backticks, unmatched quotes).
    Bail,
}

/// A single command within a compound expression.
#[derive(Debug)]
pub(super) struct CommandSegment {
    pub(super) tokens: Vec<String>,
    pub(super) trailing_operator: Option<CompoundOp>,
    /// Redirects stripped from this segment for re-emission.
    ///
    /// Appended at segment end at emission time via `splice_redirects_back`.
    /// Shell semantics for trailing redirects are identical to mid-command
    /// placement (POSIX §2.7), so append-at-end preserves correctness even
    /// though original positions are not tracked.
    pub(super) stripped_redirects: Vec<String>,
}

/// Shell compound operators.
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum CompoundOp {
    And,       // &&
    Or,        // ||
    Semicolon, // ;
    Pipe,      // |
}

impl CompoundOp {
    pub(super) fn as_str(self) -> &'static str {
        match self {
            CompoundOp::And => "&&",
            CompoundOp::Or => "||",
            CompoundOp::Semicolon => ";",
            CompoundOp::Pipe => "|",
        }
    }
}

/// Quote-tracking state for the compound splitter.
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum QuoteState {
    None,
    SingleQuote,
    DoubleQuote,
}

#[derive(Serialize)]
pub(super) struct SuggestOutput<'a> {
    pub(super) version: u8,
    #[serde(rename = "match")]
    pub(super) is_match: bool,
    pub(super) original: &'a str,
    pub(super) rewritten: &'a str,
    #[serde(serialize_with = "serialize_category")]
    pub(super) category: Option<RewriteCategory>,
    pub(super) confidence: &'a str,
    pub(super) compound: bool,
    pub(super) skim_hook_version: &'a str,
}

pub(super) fn serialize_category<S: serde::Serializer>(
    cat: &Option<RewriteCategory>,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    match cat {
        Some(c) => c.serialize(serializer),
        None => serializer.serialize_str(""),
    }
}
