//! `node!` DSL parser -- Mode 3 explicit state machine.
//!
//! Parses the user-facing DSL into an intermediate representation
//! that [`node_codegen`] transforms into Rust code.
//!
//! # Syntax
//!
//! ```ignore
//! node! {
//!     PhaseName => async { body },
//!     PhaseName(field: Type, ...) => async { body },
//!     #[retry(max_attempts = 3)]
//!     PhaseName(field: Type) => async { body },
//! }
//! ```
//!
//! Within a phase body:
//! - `yield goto NextPhase { field: value }` transitions to another phase
//! - `complete expr` exits the node with a final update
//! - `interrupt { reason: "..." }` creates a durable suspension point

use proc_macro2::Span;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Token, Type, braced};

/// A complete `node!` DSL invocation.
#[derive(Debug)]
pub(crate) struct NodeDsl {
    pub phases: Vec<Phase>,
}

/// A single phase in the DSL.
#[derive(Debug)]
pub(crate) struct Phase {
    /// Phase name (becomes an enum variant).
    pub name: Ident,
    /// Fields captured from a previous phase (becomes variant fields).
    pub captures: Vec<PhaseCapture>,
    /// The async block body.
    pub body: syn::Block,
    /// Optional retry policy annotation.
    pub retry: Option<RetryAttr>,
}

/// A captured field passed between phases.
#[derive(Debug, Clone)]
pub(crate) struct PhaseCapture {
    pub name: Ident,
    pub ty: Type,
}

/// Parsed `#[retry(max_attempts = N, backoff = "exponential")]` attribute.
#[derive(Debug, Clone)]
pub(crate) struct RetryAttr {
    pub max_attempts: Option<u32>,
    /// Backoff strategy name. Parsed from the DSL `#[retry(backoff = "...")]`
    /// attribute but not yet consumed by codegen. Stored here so the parse
    /// doesn't silently discard user-specified values — codegen will read
    /// this when custom backoff strategies are implemented.
    #[allow(dead_code)]
    pub backoff: Option<String>,
}

impl Parse for NodeDsl {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut phases = Vec::new();

        while !input.is_empty() {
            let phase = parse_phase(input)?;
            phases.push(phase);

            // Optional trailing comma
            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        if phases.is_empty() {
            return Err(syn::Error::new(
                Span::call_site(),
                "node! requires at least one phase",
            ));
        }

        Ok(NodeDsl { phases })
    }
}

/// Parse a single phase: optional retry attr, name, optional captures, `=>`, `async`, block.
fn parse_phase(input: ParseStream) -> syn::Result<Phase> {
    // Optional #[retry(...)] attribute
    let retry = if input.peek(Token![#]) {
        Some(parse_retry_attr(input)?)
    } else {
        None
    };

    // Phase name
    let name: Ident = input.parse()?;

    // Optional captures: PhaseName(field: Type, ...)
    let captures = if input.peek(syn::token::Paren) {
        parse_captures(input)?
    } else {
        Vec::new()
    };

    // => async { ... }
    input.parse::<Token![=>]>()?;
    let _async: Token![async] = input.parse()?;
    let body: syn::Block = parse_block(input)?;

    Ok(Phase {
        name,
        captures,
        body,
        retry,
    })
}

/// Parse `(field: Type, field2: Type2)`.
fn parse_captures(input: ParseStream) -> syn::Result<Vec<PhaseCapture>> {
    let content;
    syn::parenthesized!(content in input);
    let mut captures = Vec::new();

    while !content.is_empty() {
        let name: Ident = content.parse()?;
        content.parse::<Token![:]>()?;
        let ty: Type = content.parse()?;
        captures.push(PhaseCapture { name, ty });

        if content.peek(Token![,]) {
            content.parse::<Token![,]>()?;
        }
    }

    Ok(captures)
}

/// Parse `#[retry(max_attempts = N, backoff = "exponential")]`.
fn parse_retry_attr(input: ParseStream) -> syn::Result<RetryAttr> {
    input.parse::<Token![#]>()?;
    let content;
    syn::bracketed!(content in input);

    let ident: Ident = content.parse()?;
    if ident != "retry" {
        return Err(syn::Error::new(ident.span(), "expected `retry`"));
    }

    let args;
    syn::parenthesized!(args in content);

    let mut max_attempts = None;
    let mut backoff = None;

    while !args.is_empty() {
        let key: Ident = args.parse()?;
        args.parse::<Token![=]>()?;

        match key.to_string().as_str() {
            "max_attempts" => {
                let lit: syn::LitInt = args.parse()?;
                max_attempts = Some(lit.base10_parse()?);
            }
            "backoff" => {
                let lit: syn::LitStr = args.parse()?;
                backoff = Some(lit.value());
            }
            other => {
                return Err(syn::Error::new(
                    key.span(),
                    format!("unknown retry attribute: {other}"),
                ));
            }
        }

        if args.peek(Token![,]) {
            args.parse::<Token![,]>()?;
        }
    }

    Ok(RetryAttr {
        max_attempts,
        backoff,
    })
}

/// Parse a `{ ... }` block. This uses syn's standard Block parser.
fn parse_block(input: ParseStream) -> syn::Result<syn::Block> {
    let content;
    braced!(content in input);
    let stmts = content.call(syn::Block::parse_within)?;
    Ok(syn::Block {
        brace_token: syn::token::Brace::default(),
        stmts,
    })
}

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

    #[test]
    fn test_parse_simple_phase() {
        let input = r#"
            Start => async {
                let x = 1;
            }
        "#;
        let dsl: NodeDsl = parse_str(input).unwrap();
        assert_eq!(dsl.phases.len(), 1);
        assert_eq!(dsl.phases[0].name, "Start");
        assert!(dsl.phases[0].captures.is_empty());
        assert!(dsl.phases[0].retry.is_none());
    }

    #[test]
    fn test_parse_phase_with_captures() {
        let input = r#"
            Process(data: String, count: u32) => async {
                let _ = data;
            }
        "#;
        let dsl: NodeDsl = parse_str(input).unwrap();
        assert_eq!(dsl.phases[0].captures.len(), 2);
        assert_eq!(dsl.phases[0].captures[0].name, "data");
        assert_eq!(dsl.phases[0].captures[1].name, "count");
    }

    #[test]
    fn test_parse_multiple_phases() {
        let input = r#"
            Phase1 => async { let x = 1; },
            Phase2(val: String) => async { let y = val; },
            Phase3 => async { let z = 3; }
        "#;
        let dsl: NodeDsl = parse_str(input).unwrap();
        assert_eq!(dsl.phases.len(), 3);
        assert_eq!(dsl.phases[0].name, "Phase1");
        assert_eq!(dsl.phases[1].name, "Phase2");
        assert_eq!(dsl.phases[2].name, "Phase3");
    }

    #[test]
    fn test_parse_retry_attribute() {
        let input = r#"
            #[retry(max_attempts = 5, backoff = "exponential")]
            Fetch(url: String) => async {
                let _ = url;
            }
        "#;
        let dsl: NodeDsl = parse_str(input).unwrap();
        let retry = dsl.phases[0].retry.as_ref().unwrap();
        assert_eq!(retry.max_attempts, Some(5));
        assert_eq!(retry.backoff.as_deref(), Some("exponential"));
    }

    #[test]
    fn test_empty_dsl_rejected() {
        let result: syn::Result<NodeDsl> = parse_str("");
        assert!(result.is_err());
    }
}
