//! Typed Lab execution input envelope.
//!
//! This is intentionally small for the first migration step: it captures the
//! argv plus the Lab-relevant input refs discovered before `--`. Existing
//! rewrite helpers can consume the typed refs one path at a time while preserving
//! the exact argv output they already produced.

use super::path_remap::try_rewrite_flag_value_args;

const PROVIDER_CONFIG_FLAGS: &[&str] = &["--provider-config", "--dispatch-provider-config"];

#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::core::runner) struct ExecutionEnvelope {
    argv: Vec<String>,
    pub inputs: LabCommandInputs,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(in crate::core::runner) struct LabCommandInputs {
    pub provider_configs: Vec<ArgRef>,
    pub agent_task_text_specs: Vec<ArgRef>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::core::runner) struct ArgRef {
    pub flag: &'static str,
    pub value: ArgValue,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::core::runner) enum ArgValue {
    InlineText(String),
    PathRef(String),
    Stdin,
    Missing,
}

impl ExecutionEnvelope {
    pub fn from_args(args: &[String]) -> Self {
        let mut inputs = LabCommandInputs::default();
        let mut iter = args.iter().peekable();
        let mut passthrough = false;
        while let Some(arg) = iter.next() {
            if passthrough {
                continue;
            }
            if arg == "--" {
                passthrough = true;
                continue;
            }
            if let Some(flag) = provider_config_flag(arg) {
                inputs.provider_configs.push(ArgRef {
                    flag,
                    value: iter
                        .next()
                        .map_or(ArgValue::Missing, |spec| provider_config_value(spec)),
                });
                continue;
            }
            if let Some((flag, spec)) = provider_config_inline_arg(arg) {
                inputs.provider_configs.push(ArgRef {
                    flag,
                    value: provider_config_value(spec),
                });
                continue;
            }
            if let Some(flag) = agent_task_text_flag(arg) {
                inputs.agent_task_text_specs.push(ArgRef {
                    flag,
                    value: iter
                        .next()
                        .map_or(ArgValue::Missing, |spec| agent_task_text_value(spec)),
                });
                continue;
            }
            if let Some((flag, spec)) = agent_task_text_inline_arg(arg) {
                inputs.agent_task_text_specs.push(ArgRef {
                    flag,
                    value: agent_task_text_value(spec),
                });
            }
        }

        Self {
            argv: args.to_vec(),
            inputs,
        }
    }

    pub fn try_rewrite_provider_config_values(
        &self,
        mut rewrite: impl FnMut(&str) -> crate::core::Result<String>,
    ) -> crate::core::Result<Vec<String>> {
        try_rewrite_flag_value_args(&self.argv, |arg, iter, out| {
            if provider_config_flag(arg).is_some() {
                out.push(arg.to_string());
                if let Some(spec) = iter.next() {
                    out.push(rewrite(spec)?);
                }
                return Ok(());
            }
            if let Some((flag, spec)) = provider_config_inline_arg(arg) {
                out.push(format!("{}={}", flag, rewrite(spec)?));
                return Ok(());
            }
            out.push(arg.to_string());
            Ok(())
        })
    }

    pub fn rewrite_agent_task_text_values(
        &self,
        mut rewrite: impl FnMut(&str, &str) -> crate::core::Result<String>,
    ) -> crate::core::Result<Vec<String>> {
        try_rewrite_flag_value_args(&self.argv, |arg, iter, out| {
            if let Some(flag) = agent_task_text_flag(arg) {
                out.push(arg.to_string());
                if let Some(spec) = iter.next() {
                    out.push(rewrite(spec, flag)?);
                }
                return Ok(());
            }
            if let Some((flag, spec)) = agent_task_text_inline_arg(arg) {
                out.push(format!("{}={}", flag, rewrite(spec, flag)?));
                return Ok(());
            }
            out.push(arg.to_string());
            Ok(())
        })
    }
}

fn provider_config_flag(arg: &str) -> Option<&'static str> {
    PROVIDER_CONFIG_FLAGS
        .iter()
        .copied()
        .find(|flag| arg == *flag)
}

fn provider_config_inline_arg(arg: &str) -> Option<(&'static str, &str)> {
    for flag in PROVIDER_CONFIG_FLAGS {
        if let Some(value) = arg
            .strip_prefix(flag)
            .and_then(|rest| rest.strip_prefix('='))
        {
            return Some((*flag, value));
        }
    }
    None
}

fn provider_config_value(spec: &str) -> ArgValue {
    let trimmed = spec.trim();
    if trimmed == "-" {
        ArgValue::Stdin
    } else if let Some(path) = trimmed.strip_prefix('@') {
        ArgValue::PathRef(path.to_string())
    } else {
        ArgValue::InlineText(spec.to_string())
    }
}

fn agent_task_text_value(spec: &str) -> ArgValue {
    let trimmed = spec.trim();
    if trimmed == "-" {
        ArgValue::Stdin
    } else if let Some(path) = trimmed.strip_prefix('@') {
        ArgValue::PathRef(path.to_string())
    } else {
        ArgValue::InlineText(spec.to_string())
    }
}

fn agent_task_text_flag(arg: &str) -> Option<&'static str> {
    match arg {
        "--prompt" => Some("--prompt"),
        "--task" => Some("--task"),
        "--tasks" => Some("--tasks"),
        _ => None,
    }
}

fn agent_task_text_inline_arg(arg: &str) -> Option<(&'static str, &str)> {
    for flag in ["--prompt", "--task", "--tasks"] {
        if let Some(value) = arg
            .strip_prefix(flag)
            .and_then(|rest| rest.strip_prefix('='))
        {
            return Some((flag, value));
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn envelope_captures_provider_config_refs_before_passthrough() {
        let args = vec![
            "homeboy".to_string(),
            "agent-task".to_string(),
            "cook".to_string(),
            "--provider-config".to_string(),
            "@config.json".to_string(),
            "--provider-config={\"provider\":\"example-oauth\"}".to_string(),
            "--provider-config=-".to_string(),
            "--dispatch-provider-config".to_string(),
            "@dispatch.json".to_string(),
            "--dispatch-provider-config={\"provider\":\"controller\"}".to_string(),
            "--".to_string(),
            "--provider-config".to_string(),
            "ignored".to_string(),
        ];

        let envelope = ExecutionEnvelope::from_args(&args);

        assert_eq!(envelope.inputs.provider_configs.len(), 5);
        assert_eq!(
            envelope.inputs.provider_configs[0].flag,
            "--provider-config"
        );
        assert_eq!(
            envelope.inputs.provider_configs[0].value,
            ArgValue::PathRef("config.json".to_string())
        );
        assert_eq!(
            envelope.inputs.provider_configs[1].value,
            ArgValue::InlineText("{\"provider\":\"example-oauth\"}".to_string())
        );
        assert_eq!(envelope.inputs.provider_configs[2].value, ArgValue::Stdin);
        assert_eq!(
            envelope.inputs.provider_configs[3].flag,
            "--dispatch-provider-config"
        );
        assert_eq!(
            envelope.inputs.provider_configs[3].value,
            ArgValue::PathRef("dispatch.json".to_string())
        );
        assert_eq!(
            envelope.inputs.provider_configs[4].value,
            ArgValue::InlineText("{\"provider\":\"controller\"}".to_string())
        );
    }

    #[test]
    fn envelope_captures_missing_provider_config_value() {
        let args = vec![
            "homeboy".to_string(),
            "agent-task".to_string(),
            "cook".to_string(),
            "--provider-config".to_string(),
        ];

        let envelope = ExecutionEnvelope::from_args(&args);

        assert_eq!(envelope.inputs.provider_configs.len(), 1);
        assert_eq!(envelope.inputs.provider_configs[0].value, ArgValue::Missing);
    }

    #[test]
    fn envelope_captures_agent_task_text_refs_before_passthrough() {
        let args = vec![
            "homeboy".to_string(),
            "agent-task".to_string(),
            "cook".to_string(),
            "--prompt".to_string(),
            "@prompt.md".to_string(),
            "--task=Fix it".to_string(),
            "--tasks=-".to_string(),
            "--".to_string(),
            "--prompt".to_string(),
            "@ignored.md".to_string(),
        ];

        let envelope = ExecutionEnvelope::from_args(&args);

        assert_eq!(envelope.inputs.agent_task_text_specs.len(), 3);
        assert_eq!(envelope.inputs.agent_task_text_specs[0].flag, "--prompt");
        assert_eq!(
            envelope.inputs.agent_task_text_specs[0].value,
            ArgValue::PathRef("prompt.md".to_string())
        );
        assert_eq!(envelope.inputs.agent_task_text_specs[1].flag, "--task");
        assert_eq!(
            envelope.inputs.agent_task_text_specs[1].value,
            ArgValue::InlineText("Fix it".to_string())
        );
        assert_eq!(envelope.inputs.agent_task_text_specs[2].flag, "--tasks");
        assert_eq!(
            envelope.inputs.agent_task_text_specs[2].value,
            ArgValue::Stdin
        );
    }
}
