//! Source Segmenter (Stage 0)
//!
//! The Segmenter is the **only stage that implements the Oceans Model**. It scans raw source
//! bytes and produces a `SourceMap` — an ordered list of segments that partitions the entire
//! source file into typed regions: native code, pragmas, and @@system blocks.
//!
//! ## Architecture
//!
//! - Uses `SyntaxSkipper` to avoid false `@@` detection inside strings/comments
//! - Uses `BodyCloser` to find matching close brace for `@@system` blocks
//! - Downstream stages (Lexer, Parser, etc.) receive only their segment — they never
//!   see raw source bytes for boundary detection
//!
//! ## Reuse
//!
//! Consolidates all source-boundary detection (pragma scanning,
//! native-preamble skipping, system-body extraction) into one stage.

use std::convert::TryFrom;

use crate::frame_c::compiler::native_region_scanner::unified::SyntaxSkipper;
use crate::frame_c::visitors::TargetLanguage;

// ============================================================================
// SourceMap and Segment types
// ============================================================================

/// Span within the source bytes
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Span {
    pub start: usize,
    pub end: usize,
}

impl Span {
    pub fn new(start: usize, end: usize) -> Self {
        Span { start, end }
    }
}

/// The kind of Frame pragma
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PragmaKind {
    /// @@target <language>
    Target,
    /// @@persist (optionally with library)
    Persist,
    /// @@[main] — RFC-0014. Marks the next `@@system` declaration as
    /// the file's primary system for targets that privilege one class
    /// per file (GDScript, Java, etc.).
    Main,
    /// @@[create(<name>?)] — RFC-0015. System-level lifecycle attribute
    /// that renames the auto-generated factory. The signature is
    /// dictated entirely by the @@system three-group header; only
    /// the user-facing name is overridable. Body is framework-
    /// generated; users never write factory bodies.
    Create,
    /// @@[save(<name>?)] — RFC-0015. System-level lifecycle attribute
    /// that renames the auto-generated save op. Signature dictated by
    /// `@@[persist(<Format>)]` (returns Format). Body framework-
    /// generated.
    Save,
    /// @@[load(<name>?)] — RFC-0015. System-level lifecycle attribute
    /// that renames the auto-generated load op. Signature dictated by
    /// `@@[persist(<Format>)]` (takes Format). Body framework-
    /// generated.
    Load,
    /// @@[async] — RFC-0043. System-level attribute that opts a system
    /// into the layered casing/machine codegen architecture. **Required**
    /// on any system that declares one or more `async` interface,
    /// action, or operation members (E720 from the implementing
    /// release; no warning grace period).
    Async,
    /// @@import "<path>" — RFC-0022. Module-scope directive declaring a
    /// cross-file dependency. The path is the importer-relative path to
    /// another Frame source file. The codegen emits the target's native
    /// import form (`from .x import *` for Python, `preload(...)` for
    /// GDScript, etc.).
    Import,
    /// @@codegen { ... }
    Codegen,
    /// @@run-expect <pattern>
    RunExpect,
    /// @@skip-if <condition>
    SkipIf,
    /// @@timeout <seconds>
    Timeout,
    /// Unknown or future pragma
    Other,
}

/// A segment in the source file
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
    /// Native code region — passes through verbatim to output
    Native { span: Span },
    /// Pragma line (@@target, @@persist, @@run-expect, etc.)
    Pragma {
        kind: PragmaKind,
        span: Span,
        /// Parsed pragma value (e.g., "python_3" for @@target)
        value: Option<String>,
    },
    /// @@system block — contents will be lexed, parsed, and compiled
    System {
        /// Span of the entire block including `@@system Name { ... }`
        outer_span: Span,
        /// Span of just the system body (between { and }, exclusive of braces)
        body_span: Span,
        /// Span of the optional header parameter list (between `(` and `)`,
        /// exclusive of the parens). `None` if the header had no `(...)`.
        header_params_span: Option<Span>,
        /// System name extracted during segmentation
        name: String,
        /// Base classes/interfaces from `: Base1, Base2` syntax
        bases: Vec<String>,
        /// Visibility modifier: "private" or None (default = public).
        /// "public" is rejected by the validator as redundant.
        visibility: Option<String>,
    },
}

/// The result of source segmentation — an ordered, non-overlapping partition of the source
#[derive(Debug)]
pub struct SourceMap {
    /// Ordered, non-overlapping segments covering the entire source
    pub segments: Vec<Segment>,
    /// Original source bytes (owned, for content extraction by Lexer)
    pub source: Vec<u8>,
    /// Target language (from @@target pragma, if present)
    pub target: Option<TargetLanguage>,
}

impl SourceMap {
    /// Get all system segments
    pub fn systems(&self) -> Vec<&Segment> {
        self.segments
            .iter()
            .filter(|s| matches!(s, Segment::System { .. }))
            .collect()
    }

    /// Get all native segments
    pub fn natives(&self) -> Vec<&Segment> {
        self.segments
            .iter()
            .filter(|s| matches!(s, Segment::Native { .. }))
            .collect()
    }

    /// Get all pragma segments
    pub fn pragmas(&self) -> Vec<&Segment> {
        self.segments
            .iter()
            .filter(|s| matches!(s, Segment::Pragma { .. }))
            .collect()
    }

    /// Extract text content for a span
    pub fn text(&self, span: &Span) -> &str {
        let start = span.start.min(self.source.len());
        let end = span.end.min(self.source.len());
        std::str::from_utf8(&self.source[start..end]).unwrap_or("")
    }

    /// Get the @@persist pragma if present
    pub fn persist_pragma(&self) -> Option<&Segment> {
        self.segments.iter().find(|s| {
            matches!(
                s,
                Segment::Pragma {
                    kind: PragmaKind::Persist,
                    ..
                }
            )
        })
    }

    /// Get the @@run-expect value if present
    pub fn run_expect(&self) -> Option<&str> {
        self.segments.iter().find_map(|s| {
            if let Segment::Pragma {
                kind: PragmaKind::RunExpect,
                value: Some(v),
                ..
            } = s
            {
                Some(v.as_str())
            } else {
                None
            }
        })
    }
}

// ============================================================================
// Segmentation errors
// ============================================================================

/// Error during segmentation
#[derive(Debug)]
pub enum SegmentError {
    /// Unterminated @@system block (no matching close brace)
    UnterminatedSystem { name: String, open_brace_pos: usize },
    /// Unterminated string literal found during scanning
    UnterminatedString { pos: usize },
    /// Unterminated comment found during scanning
    UnterminatedComment { pos: usize },
    /// Invalid @@target value
    InvalidTarget { value: String, pos: usize },
}

impl std::fmt::Display for SegmentError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SegmentError::UnterminatedSystem {
                name,
                open_brace_pos,
            } => {
                write!(
                    f,
                    "Unterminated @@system block '{}' (opening brace at byte {})",
                    name, open_brace_pos
                )
            }
            SegmentError::UnterminatedString { pos } => {
                write!(f, "Unterminated string literal at byte {}", pos)
            }
            SegmentError::UnterminatedComment { pos } => {
                write!(f, "Unterminated comment at byte {}", pos)
            }
            SegmentError::InvalidTarget { value, pos } => {
                write!(f, "Invalid @@target value '{}' at byte {}", value, pos)
            }
        }
    }
}

// ============================================================================
// Segmenter implementation
// ============================================================================

/// Segment raw source bytes into native code, pragmas, and @@system blocks.
///
/// This is the entry point for Stage 0 of the pipeline. It uses the given
/// `SyntaxSkipper` to avoid false `@@` detection inside strings and comments.
pub fn segment<S: SyntaxSkipper>(skipper: &S, source: &[u8]) -> Result<SourceMap, SegmentError> {
    let mut segments: Vec<Segment> = Vec::new();
    let n = source.len();
    let mut i = 0usize;
    let mut seg_start = 0usize;
    let mut at_sol = true;
    let mut target: Option<TargetLanguage> = None;

    while i < n {
        // At start of line, check for @@ pragma
        if at_sol {
            // Skip leading whitespace
            let line_start = i;
            while i < n && (source[i] == b' ' || source[i] == b'\t') {
                i += 1;
            }

            // Check for @@ pragma
            if i + 1 < n && source[i] == b'@' && source[i + 1] == b'@' {
                // Found pragma — emit any preceding native text
                if seg_start < line_start {
                    segments.push(Segment::Native {
                        span: Span {
                            start: seg_start,
                            end: line_start,
                        },
                    });
                }

                let pragma_start = i;
                let (kind, pragma_value) = identify_pragma(source, i);

                match kind {
                    PragmaKind::Target => {
                        // Parse and validate target language
                        if let Some(ref val) = pragma_value {
                            match TargetLanguage::try_from(val.as_str()) {
                                Ok(lang) => target = Some(lang),
                                Err(_) => {
                                    return Err(SegmentError::InvalidTarget {
                                        value: val.clone(),
                                        pos: pragma_start,
                                    });
                                }
                            }
                        }
                        let line_end = find_line_end(source, pragma_start);
                        segments.push(Segment::Pragma {
                            kind: PragmaKind::Target,
                            span: Span {
                                start: pragma_start,
                                end: line_end,
                            },
                            value: pragma_value,
                        });
                        i = line_end;
                        seg_start = i;
                        at_sol = true;
                        continue;
                    }
                    PragmaKind::Codegen => {
                        // @@codegen { ... } — find closing brace
                        let block_end = find_pragma_block_end(skipper, source, pragma_start)?;
                        segments.push(Segment::Pragma {
                            kind: PragmaKind::Codegen,
                            span: Span {
                                start: pragma_start,
                                end: block_end,
                            },
                            value: pragma_value,
                        });
                        i = block_end;
                        seg_start = i;
                        at_sol = true;
                        continue;
                    }
                    PragmaKind::Persist => {
                        // These can optionally have a block body
                        // Check if there's a { on the same line
                        let line_end_pos = find_line_end(source, pragma_start);
                        let has_brace = source[pragma_start..line_end_pos]
                            .iter()
                            .any(|&b| b == b'{');
                        let end = if has_brace {
                            find_pragma_block_end(skipper, source, pragma_start)?
                        } else {
                            line_end_pos
                        };
                        segments.push(Segment::Pragma {
                            kind: kind.clone(),
                            span: Span {
                                start: pragma_start,
                                end,
                            },
                            value: pragma_value,
                        });
                        i = end;
                        seg_start = i;
                        at_sol = true;
                        continue;
                    }
                    _ if kind == PragmaKind::Other && is_system_pragma(source, i) => {
                        // @@system Name(optional params) { ... }
                        let (system_name, system_visibility, name_end) =
                            extract_system_name(source, i);

                        // After the name we may have an optional parameter list `(...)`.
                        // Skip whitespace, and if `(` is next, capture the span between
                        // the parens (exclusive of the parens themselves) so the pipeline
                        // parser can re-lex it later.
                        let mut after_name = name_end;
                        while after_name < n
                            && (source[after_name] == b' ' || source[after_name] == b'\t')
                        {
                            after_name += 1;
                        }
                        let header_params_span = if after_name < n && source[after_name] == b'(' {
                            // Walk to the matching close paren. Frame param lists are
                            // shallow — depth-tracked plain scan suffices because the
                            // surrounding system body has not started yet.
                            let inner_start = after_name + 1;
                            let mut depth: i32 = 1;
                            let mut k = inner_start;
                            while k < n {
                                let b = source[k];
                                if b == b'(' {
                                    depth += 1;
                                } else if b == b')' {
                                    depth -= 1;
                                    if depth == 0 {
                                        break;
                                    }
                                } else if b == b'\n' {
                                    // Param lists must stay on one line in current grammar;
                                    // bail and let downstream parse errors flag the issue.
                                    break;
                                }
                                k += 1;
                            }
                            if k < n && source[k] == b')' {
                                let span = Span {
                                    start: inner_start,
                                    end: k,
                                };
                                after_name = k + 1;
                                Some(span)
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        // Parse optional base classes: `: Base1, Base2`
                        let mut bases = Vec::new();
                        let mut after_bases = after_name;
                        // Skip whitespace
                        while after_bases < n
                            && (source[after_bases] == b' ' || source[after_bases] == b'\t')
                        {
                            after_bases += 1;
                        }
                        if after_bases < n && source[after_bases] == b':' {
                            after_bases += 1; // skip :
                                              // Parse comma-separated identifiers until {
                            loop {
                                // Skip whitespace
                                while after_bases < n
                                    && (source[after_bases] == b' ' || source[after_bases] == b'\t')
                                {
                                    after_bases += 1;
                                }
                                if after_bases >= n
                                    || source[after_bases] == b'{'
                                    || source[after_bases] == b'\n'
                                {
                                    break;
                                }
                                // Collect identifier (alphanumeric + _ + . for namespaced types)
                                let id_start = after_bases;
                                while after_bases < n
                                    && (source[after_bases].is_ascii_alphanumeric()
                                        || source[after_bases] == b'_'
                                        || source[after_bases] == b'.')
                                {
                                    after_bases += 1;
                                }
                                if after_bases > id_start {
                                    let base_name =
                                        String::from_utf8_lossy(&source[id_start..after_bases])
                                            .to_string();
                                    bases.push(base_name);
                                }
                                // Skip whitespace after identifier
                                while after_bases < n
                                    && (source[after_bases] == b' ' || source[after_bases] == b'\t')
                                {
                                    after_bases += 1;
                                }
                                // Check for comma (more bases) or stop
                                if after_bases < n && source[after_bases] == b',' {
                                    after_bases += 1; // skip comma
                                } else {
                                    break;
                                }
                            }
                        }

                        // Find the opening brace
                        let mut brace_pos = after_bases;
                        while brace_pos < n
                            && source[brace_pos] != b'{'
                            && source[brace_pos] != b'\n'
                        {
                            brace_pos += 1;
                        }

                        if brace_pos >= n || source[brace_pos] != b'{' {
                            return Err(SegmentError::UnterminatedSystem {
                                name: system_name,
                                open_brace_pos: brace_pos,
                            });
                        }

                        // Use BodyCloser to find matching close brace
                        let mut closer = skipper.body_closer();
                        let close_pos = match closer.close_byte(source, brace_pos) {
                            Ok(pos) => pos,
                            Err(_) => {
                                return Err(SegmentError::UnterminatedSystem {
                                    name: system_name,
                                    open_brace_pos: brace_pos,
                                });
                            }
                        };

                        // outer_span: from @@system to after closing }
                        let mut outer_end = close_pos + 1;
                        if outer_end < n && source[outer_end] == b'\n' {
                            outer_end += 1;
                        }

                        // body_span: between { and } (exclusive)
                        let body_start = brace_pos + 1;
                        let body_end = close_pos;

                        segments.push(Segment::System {
                            outer_span: Span {
                                start: pragma_start,
                                end: outer_end,
                            },
                            body_span: Span {
                                start: body_start,
                                end: body_end,
                            },
                            header_params_span,
                            name: system_name,
                            bases,
                            visibility: system_visibility,
                        });

                        i = outer_end;
                        seg_start = i;
                        at_sol = true;
                        continue;
                    }
                    _ => {
                        // Simple pragma (@@run-expect, @@skip-if, @@timeout, etc.)
                        let line_end = find_line_end(source, pragma_start);
                        segments.push(Segment::Pragma {
                            kind,
                            span: Span {
                                start: pragma_start,
                                end: line_end,
                            },
                            value: pragma_value,
                        });
                        i = line_end;
                        seg_start = i;
                        at_sol = true;
                        continue;
                    }
                }
            }

            // Not a pragma — continue scanning as native code
            at_sol = false;
            i = line_start; // Reset to line start for normal scanning
        }

        // Try to skip protected regions (strings/comments)
        if let Some(new_i) = skipper.skip_comment(source, i, n) {
            i = new_i;
            continue;
        }
        if let Some(new_i) = skipper.skip_string(source, i, n) {
            i = new_i;
            continue;
        }

        // Track newlines for SOL detection
        if source[i] == b'\n' {
            at_sol = true;
            i += 1;
            continue;
        }

        i += 1;
    }

    // Emit any remaining native text
    if seg_start < n {
        segments.push(Segment::Native {
            span: Span {
                start: seg_start,
                end: n,
            },
        });
    }

    Ok(SourceMap {
        segments,
        source: source.to_vec(),
        target,
    })
}

// ============================================================================
// Helper functions
// ============================================================================

/// Check if position is at `@@system` (not just any `@@` pragma)
fn is_system_pragma(bytes: &[u8], pos: usize) -> bool {
    let remaining = &bytes[pos..];
    remaining.starts_with(b"@@system")
        && (remaining.len() <= 8 || !remaining[8].is_ascii_alphanumeric())
}

/// Identify the kind and value of a pragma at the given position.
///
/// The position should be at the first `@` of `@@keyword` or `@@[name]`.
///
/// Recognises two surface shapes:
/// - **Bare keyword:** `@@target python_3`, `@@persist`, `@@system Foo {}`.
/// - **Attribute form (RFC-0013):** `@@[name]`, `@@[name(args)]`. Wave 1
///   ships `@@[persist]`; later waves migrate `@@target`, etc.
fn identify_pragma(bytes: &[u8], start: usize) -> (PragmaKind, Option<String>) {
    // RFC-0039 A-half / #373: the surface scan of `@@[name(args?)]` and bare
    // `@@name value` is delegated to the dogfooded `AttributeScannerFsm` (via
    // `scan_attribute`) — the same FSM the parser already uses. This function
    // keeps only the name → `PragmaKind` classification table and the per-kind
    // value shaping; the byte walk (name char-class, paren-depth args, bare-value
    // trim) now lives in one place, the `.frs`. This finally retires the
    // hand-written scanner the `AttributeScannerFsm` was built to replace.
    let span = crate::frame_c::compiler::attribute_scanner::scan_attribute(bytes, start);
    let name = span.name(bytes);

    if span.is_bracket_form {
        // Bracket value is the `(args)` slice verbatim (parens included) — e.g.
        // `@@[persist(domain=[a, b])]` → "(domain=[a, b])" — so downstream
        // consumers see the same argument syntax they already understand.
        let value: Option<String> = span
            .args_with_parens(bytes)
            .map(|b| String::from_utf8_lossy(b).to_string());

        let kind = match name {
            b"persist" => PragmaKind::Persist,
            b"target" => PragmaKind::Target,
            b"main" => PragmaKind::Main,
            // RFC-0015 lifecycle attributes recognized at module level.
            b"create" => PragmaKind::Create,
            b"save" => PragmaKind::Save,
            b"load" => PragmaKind::Load,
            // RFC-0043 system-level attribute.
            b"async" => PragmaKind::Async,
            _ => PragmaKind::Other,
        };

        // For @@[target("python_3")] the downstream code expects
        // value="python_3" (matching the bare-form `@@target python_3`).
        // Strip the surrounding `("...")` to give it the same shape.
        if matches!(kind, PragmaKind::Target) {
            if let Some(raw) = &value {
                let inner = raw.trim_start_matches('(').trim_end_matches(')');
                let inner = inner.trim();
                let inner = inner.trim_start_matches('"').trim_end_matches('"');
                return (kind, Some(inner.to_string()));
            }
        }

        return (kind, value);
    }

    // Bare-keyword form: @@<keyword> <value?> — value is the trimmed rest of
    // line (FSM strips leading ws after the keyword and trailing ws/`\r`).
    let value = span
        .value(bytes)
        .map(|b| String::from_utf8_lossy(b).to_string());

    let kind = match name {
        // RFC-0013 wave 2: bare `@@target lang` migrated to
        // `@@[target("lang")]`. Bare form falls through to Other;
        // pipeline E804 check surfaces a migration error.
        b"target" => PragmaKind::Other,
        b"codegen" => PragmaKind::Codegen,
        // RFC-0022: bare `@@import "path"` — module-scope cross-file
        // dependency. The path string is in `value` with quotes; the
        // pipeline driver strips them before storing in ModuleAst.imports.
        b"import" => PragmaKind::Import,
        b"system" => return (PragmaKind::Other, value), // @@system handled separately
        // RFC-0013 wave 1: bare `@@persist` migrated to `@@[persist]`.
        b"persist" => PragmaKind::Other,
        b"run-expect" => PragmaKind::RunExpect,
        b"skip-if" => PragmaKind::SkipIf,
        b"timeout" => PragmaKind::Timeout,
        _ => PragmaKind::Other,
    };

    (kind, value)
}

/// Extract system name (and optional visibility) from `@@system [private] <Name> ...`
fn extract_system_name(bytes: &[u8], start: usize) -> (String, Option<String>, usize) {
    let n = bytes.len();
    let mut i = start + 8; // Skip "@@system"

    // Skip whitespace
    while i < n && (bytes[i] == b' ' || bytes[i] == b'\t') {
        i += 1;
    }

    // Extract first identifier — could be visibility keyword or name
    let first_start = i;
    while i < n && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
        i += 1;
    }
    let first = String::from_utf8_lossy(&bytes[first_start..i]).to_string();

    // Check if first identifier is a visibility keyword
    if first == "private" || first == "public" {
        let visibility = Some(first);

        // Skip whitespace after visibility
        while i < n && (bytes[i] == b' ' || bytes[i] == b'\t') {
            i += 1;
        }

        // Extract actual system name
        let name_start = i;
        while i < n && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
            i += 1;
        }
        let name = String::from_utf8_lossy(&bytes[name_start..i]).to_string();
        (name, visibility, i)
    } else {
        (first, None, i)
    }
}

/// Find end of line (including the newline character)
fn find_line_end(bytes: &[u8], start: usize) -> usize {
    let n = bytes.len();
    let mut i = start;
    while i < n && bytes[i] != b'\n' {
        i += 1;
    }
    if i < n && bytes[i] == b'\n' {
        i += 1; // Include the newline
    }
    i
}

/// Find end of a pragma block (@@codegen { ... } or @@persist { ... })
fn find_pragma_block_end<S: SyntaxSkipper>(
    skipper: &S,
    bytes: &[u8],
    start: usize,
) -> Result<usize, SegmentError> {
    let n = bytes.len();
    let mut i = start;

    // Find opening brace
    while i < n && bytes[i] != b'{' {
        i += 1;
    }

    if i >= n {
        // No opening brace — treat as simple line pragma
        return Ok(find_line_end(bytes, start));
    }

    // Use BodyCloser to find matching brace
    let mut closer = skipper.body_closer();
    match closer.close_byte(bytes, i) {
        Ok(close_pos) => {
            let mut end = close_pos + 1;
            // Include trailing newline if present
            if end < n && bytes[end] == b'\n' {
                end += 1;
            }
            Ok(end)
        }
        Err(_) => Err(SegmentError::UnterminatedComment { pos: start }),
    }
}

// ============================================================================
// Convenience: get SyntaxSkipper for a TargetLanguage
// ============================================================================

/// Get the appropriate SyntaxSkipper for the given target language
/// and run the segmenter.
///
/// This is the top-level entry point that most callers should use.
pub fn segment_source(source: &[u8], lang: TargetLanguage) -> Result<SourceMap, SegmentError> {
    match lang {
        TargetLanguage::Python3 => {
            use crate::frame_c::compiler::native_region_scanner::python::PythonSkipper;
            segment(&PythonSkipper, source)
        }
        TargetLanguage::TypeScript => {
            use crate::frame_c::compiler::native_region_scanner::typescript::TypeScriptSkipper;
            segment(&TypeScriptSkipper, source)
        }
        TargetLanguage::Rust => {
            use crate::frame_c::compiler::native_region_scanner::rust::RustSkipper;
            segment(&RustSkipper, source)
        }
        TargetLanguage::C => {
            use crate::frame_c::compiler::native_region_scanner::c::CSkipper;
            segment(&CSkipper, source)
        }
        TargetLanguage::Cpp => {
            use crate::frame_c::compiler::native_region_scanner::cpp::CppSkipper;
            segment(&CppSkipper, source)
        }
        TargetLanguage::Java => {
            use crate::frame_c::compiler::native_region_scanner::java::JavaSkipper;
            segment(&JavaSkipper, source)
        }
        TargetLanguage::CSharp => {
            use crate::frame_c::compiler::native_region_scanner::csharp::CSharpSkipper;
            segment(&CSharpSkipper, source)
        }
        TargetLanguage::Go => {
            use crate::frame_c::compiler::native_region_scanner::go::GoSkipper;
            segment(&GoSkipper, source)
        }
        TargetLanguage::JavaScript => {
            use crate::frame_c::compiler::native_region_scanner::javascript::JavaScriptSkipper;
            segment(&JavaScriptSkipper, source)
        }
        TargetLanguage::Swift => {
            use crate::frame_c::compiler::native_region_scanner::swift::SwiftSkipper;
            segment(&SwiftSkipper, source)
        }
        TargetLanguage::Php => {
            use crate::frame_c::compiler::native_region_scanner::php::PhpSkipper;
            segment(&PhpSkipper, source)
        }
        TargetLanguage::Kotlin => {
            use crate::frame_c::compiler::native_region_scanner::kotlin::KotlinSkipper;
            segment(&KotlinSkipper, source)
        }
        TargetLanguage::Ruby => {
            use crate::frame_c::compiler::native_region_scanner::ruby::RubySkipper;
            segment(&RubySkipper, source)
        }
        TargetLanguage::Erlang => {
            use crate::frame_c::compiler::native_region_scanner::erlang::ErlangSkipper;
            segment(&ErlangSkipper, source)
        }
        TargetLanguage::Lua => {
            use crate::frame_c::compiler::native_region_scanner::lua::LuaSkipper;
            segment(&LuaSkipper, source)
        }
        TargetLanguage::Dart => {
            use crate::frame_c::compiler::native_region_scanner::dart::DartSkipper;
            segment(&DartSkipper, source)
        }
        TargetLanguage::GDScript => {
            use crate::frame_c::compiler::native_region_scanner::gdscript::GDScriptSkipper;
            segment(&GDScriptSkipper, source)
        }
        // Graphviz is an output-only target — the source is still written in
        // one of the 17 native-code languages. Look up the source language
        // from the file's `@@target` directive and segment with its skipper.
        // If no directive is present, fall back to Python3 (comment/string
        // semantics are close enough for segmentation of most dynamic langs).
        TargetLanguage::Graphviz => {
            let source_lang = detect_source_language(source).unwrap_or(TargetLanguage::Python3);
            // Graphviz is output-only — if @@target is graphviz, there's no valid source language
            if source_lang == TargetLanguage::Graphviz {
                return Err(SegmentError::InvalidTarget {
                    value: "graphviz".to_string(),
                    pos: 0,
                });
            }
            segment_source(source, source_lang)
        }
    }
}

/// Scan the source for a leading `@@target <lang>` pragma and return the
/// declared source language, if any. Only looks at the prolog region
/// (before any `@@system` block) — pragmas appearing later are not the
/// source-language declaration.
fn detect_source_language(source: &[u8]) -> Option<TargetLanguage> {
    let n = source.len();
    let mut i = 0;
    while i + 1 < n {
        // Stop scanning once we hit @@system; the target pragma must come first.
        if source[i] == b'@' && source[i + 1] == b'@' {
            let rest = &source[i..];
            // RFC-0013 wave 2: `@@[target("lang")]` attribute form.
            if rest.starts_with(b"@@[target(") {
                let after = i + b"@@[target(".len();
                let mut j = after;
                if j < n && (source[j] == b'"' || source[j] == b'\'') {
                    j += 1;
                }
                let val_start = j;
                while j < n && (source[j].is_ascii_alphanumeric() || source[j] == b'_') {
                    j += 1;
                }
                if val_start < j {
                    if let Ok(val) = std::str::from_utf8(&source[val_start..j]) {
                        if let Ok(lang) = TargetLanguage::try_from(val) {
                            return Some(lang);
                        }
                    }
                }
                return None;
            }
            if rest.starts_with(b"@@target") {
                let after = i + b"@@target".len();
                // Skip whitespace, then read an identifier.
                let mut j = after;
                while j < n && (source[j] == b' ' || source[j] == b'\t') {
                    j += 1;
                }
                let val_start = j;
                while j < n && (source[j].is_ascii_alphanumeric() || source[j] == b'_') {
                    j += 1;
                }
                if val_start < j {
                    if let Ok(val) = std::str::from_utf8(&source[val_start..j]) {
                        if let Ok(lang) = TargetLanguage::try_from(val) {
                            return Some(lang);
                        }
                    }
                }
                return None;
            }
            if rest.starts_with(b"@@system") {
                return None;
            }
        }
        i += 1;
    }
    None
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use crate::frame_c::compiler::native_region_scanner::python::PythonSkipper;

    fn segment_py(source: &str) -> SourceMap {
        segment(&PythonSkipper, source.as_bytes()).unwrap()
    }

    #[test]
    fn test_empty_source() {
        let map = segment_py("");
        assert!(map.segments.is_empty());
        assert!(map.target.is_none());
    }

    #[test]
    fn test_native_only() {
        let source = "import os\ndef foo(): pass\n";
        let map = segment_py(source);
        assert_eq!(map.segments.len(), 1);
        assert!(matches!(&map.segments[0], Segment::Native { .. }));
        assert_eq!(
            map.text(&Span {
                start: 0,
                end: source.len()
            }),
            source
        );
    }

    #[test]
    fn test_target_pragma() {
        let source = "@@[target(\"python_3\")]\n";
        let map = segment_py(source);
        assert_eq!(map.segments.len(), 1);
        match &map.segments[0] {
            Segment::Pragma {
                kind: PragmaKind::Target,
                value,
                ..
            } => {
                assert_eq!(value.as_deref(), Some("python_3"));
            }
            other => panic!("Expected Target pragma, got: {:?}", other),
        }
        assert_eq!(map.target, Some(TargetLanguage::Python3));
    }

    #[test]
    fn test_rfc0015_create_pragma_no_arg() {
        let (kind, _) = identify_pragma(b"@@[create]", 0);
        assert_eq!(kind, PragmaKind::Create);
    }

    #[test]
    fn test_rfc0015_create_pragma_with_name() {
        let (kind, value) = identify_pragma(b"@@[create(make)]", 0);
        assert_eq!(kind, PragmaKind::Create);
        assert_eq!(value.as_deref(), Some("(make)"));
    }

    #[test]
    fn test_rfc0015_save_pragma_with_name() {
        let (kind, value) = identify_pragma(b"@@[save(pickle)]", 0);
        assert_eq!(kind, PragmaKind::Save);
        assert_eq!(value.as_deref(), Some("(pickle)"));
    }

    #[test]
    fn test_rfc0015_load_pragma_with_name() {
        let (kind, value) = identify_pragma(b"@@[load(unpickle)]", 0);
        assert_eq!(kind, PragmaKind::Load);
        assert_eq!(value.as_deref(), Some("(unpickle)"));
    }

    // RFC-0039 A-half / #373: `identify_pragma` now delegates its surface scan
    // to the dogfooded `AttributeScannerFsm` (`scan_attribute`). These lock the
    // (PragmaKind, value) parity points the bare/bracket forms must preserve.
    #[test]
    fn identify_pragma_fsm_parity() {
        // Bracket, no args.
        assert_eq!(
            identify_pragma(b"@@[persist]", 0),
            (PragmaKind::Persist, None)
        );
        assert_eq!(identify_pragma(b"@@[main]", 0), (PragmaKind::Main, None));

        // Bracket args verbatim WITH parens, including nested brackets/parens.
        let (k, v) = identify_pragma(b"@@[persist(domain=[a, b])]", 0);
        assert_eq!(k, PragmaKind::Persist);
        assert_eq!(v.as_deref(), Some("(domain=[a, b])"));

        // Target bracket form strips `("...")` → bare inner value.
        let (k, v) = identify_pragma(b"@@[target(\"python_3\")]", 0);
        assert_eq!(k, PragmaKind::Target);
        assert_eq!(v.as_deref(), Some("python_3"));

        // Unknown bracket name → Other, args preserved.
        let (k, v) = identify_pragma(b"@@[whatever(x)]", 0);
        assert_eq!(k, PragmaKind::Other);
        assert_eq!(v.as_deref(), Some("(x)"));

        // Bare forms: classification + trimmed rest-of-line value.
        assert_eq!(
            identify_pragma(b"@@import \"a/b.frm\"\n", 0),
            (PragmaKind::Import, Some("\"a/b.frm\"".to_string()))
        );
        assert_eq!(
            identify_pragma(b"@@run-expect 42  \n", 0),
            (PragmaKind::RunExpect, Some("42".to_string()))
        );
        // Bare value trailing CRLF is trimmed.
        assert_eq!(
            identify_pragma(b"@@timeout 30\r\n", 0),
            (PragmaKind::Timeout, Some("30".to_string()))
        );
        // Bare with no value → None.
        assert_eq!(
            identify_pragma(b"@@codegen\n", 0),
            (PragmaKind::Codegen, None)
        );
        // RFC-0013 migrated bare forms fall through to Other.
        assert_eq!(identify_pragma(b"@@persist\n", 0).0, PragmaKind::Other);
        assert_eq!(
            identify_pragma(b"@@target python_3\n", 0).0,
            PragmaKind::Other
        );
    }

    #[test]
    fn test_rfc0015_pragmas_in_segment_stream() {
        // End-to-end: scan a source with all four lifecycle pragmas
        // and confirm each lands as the right PragmaKind.
        let source = "@@[target(\"python_3\")]\n\
                      @@[persist(JSON)]\n\
                      @@[create(make)]\n\
                      @@[save(pickle)]\n\
                      @@[load(unpickle)]\n\
                      @@system Inner {\n    machine:\n        $A {\n        }\n}\n";
        let map = segment_py(source);
        let kinds: Vec<&PragmaKind> = map
            .segments
            .iter()
            .filter_map(|s| match s {
                Segment::Pragma { kind, .. } => Some(kind),
                _ => None,
            })
            .collect();
        assert!(kinds.contains(&&PragmaKind::Target));
        assert!(kinds.contains(&&PragmaKind::Persist));
        assert!(kinds.contains(&&PragmaKind::Create));
        assert!(kinds.contains(&&PragmaKind::Save));
        assert!(kinds.contains(&&PragmaKind::Load));
    }

    #[test]
    fn test_simple_system() {
        let source =
            "@@[target(\"python_3\")]\n\n@@system Foo {\n    machine:\n        $A {\n        }\n}\n";
        let map = segment_py(source);

        // Should have: Target pragma, Native (blank line), System
        let systems: Vec<_> = map.systems();
        assert_eq!(systems.len(), 1);
        match systems[0] {
            Segment::System { name, .. } => {
                assert_eq!(name, "Foo");
            }
            _ => panic!("Expected System segment"),
        }
    }

    #[test]
    fn test_native_before_and_after_system() {
        let source = "@@[target(\"python_3\")]\nimport os\n\n@@system S {\n    machine:\n        $A { }\n}\n\ndef main():\n    pass\n";
        let map = segment_py(source);

        // Count segment types
        let native_count = map.natives().len();
        let system_count = map.systems().len();
        assert_eq!(system_count, 1);
        assert!(
            native_count >= 2,
            "Expected at least 2 native segments (before and after system), got {}",
            native_count
        );
    }

    #[test]
    fn test_multi_system() {
        let source = "@@[target(\"python_3\")]\n\n@@system A {\n    machine:\n        $S1 { }\n}\n\n@@system B {\n    machine:\n        $S2 { }\n}\n";
        let map = segment_py(source);

        let systems: Vec<_> = map.systems();
        assert_eq!(systems.len(), 2);

        let names: Vec<&str> = systems
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(names, vec!["A", "B"]);
    }

    #[test]
    fn test_system_body_span() {
        let source = "@@system Foo {\n    machine:\n        $A { }\n}\n";
        let map = segment_py(source);

        let systems = map.systems();
        assert_eq!(systems.len(), 1);
        match systems[0] {
            Segment::System {
                body_span, name, ..
            } => {
                assert_eq!(name, "Foo");
                let body = map.text(body_span);
                assert!(
                    body.contains("machine:"),
                    "Body should contain 'machine:', got: '{}'",
                    body
                );
                assert!(
                    !body.contains("@@system"),
                    "Body should not contain '@@system'"
                );
            }
            _ => panic!("Expected System segment"),
        }
    }

    #[test]
    fn test_pragma_in_string_ignored() {
        let source = "x = \"@@system Fake { }\"\n";
        let map = segment_py(source);
        assert_eq!(map.segments.len(), 1);
        assert!(matches!(&map.segments[0], Segment::Native { .. }));
    }

    #[test]
    fn test_pragma_in_comment_ignored() {
        let source = "# @@system Fake { }\n";
        let map = segment_py(source);
        assert_eq!(map.segments.len(), 1);
        assert!(matches!(&map.segments[0], Segment::Native { .. }));
    }

    #[test]
    fn test_run_expect_pragma() {
        let source = "@@[target(\"python_3\")]\n@@run-expect SUCCESS\n@@system S {\n    machine:\n        $A { }\n}\n";
        let map = segment_py(source);

        assert_eq!(map.run_expect(), Some("SUCCESS"));
    }

    #[test]
    fn test_invalid_target_returns_error() {
        let source = "@@[target(\"invalid_lang\")]\n";
        let result = segment(&PythonSkipper, source.as_bytes());
        assert!(result.is_err());
        match result.unwrap_err() {
            SegmentError::InvalidTarget { value, .. } => {
                assert_eq!(value, "invalid_lang");
            }
            other => panic!("Expected InvalidTarget error, got: {:?}", other),
        }
    }

    #[test]
    fn test_segment_source_convenience() {
        let source = b"@@[target(\"python_3\")]\n@@system S {\n    machine:\n        $A { }\n}\n";
        let map = segment_source(source, TargetLanguage::Python3).unwrap();
        assert_eq!(map.systems().len(), 1);
        assert_eq!(map.target, Some(TargetLanguage::Python3));
    }

    // ========================================================================
    // Adversarial tests: language-specific strings containing @@system
    // These verify each language's skipper correctly ignores @@system inside
    // strings/comments in the prolog region.
    // ========================================================================

    #[test]
    fn test_python_triple_quote_hides_system() {
        let source = br#"@@[target("python_3")]
template = """
@@system Fake {
    this should NOT be parsed
}
"""

@@system Real {
    machine:
        $A { }
}
"#;
        let map = segment_source(source, TargetLanguage::Python3).unwrap();
        let systems: Vec<&str> = map
            .systems()
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(
            systems,
            vec!["Real"],
            "Triple-quoted string should hide @@system Fake"
        );
    }

    #[test]
    fn test_php_heredoc_hides_system() {
        let source = br#"<?php
@@[target("php")]

$template = <<<EOT
@@system Fake {
    this should NOT be parsed
}
EOT;

@@system Real {
    machine:
        $A { }
}
"#;
        let map = segment_source(source, TargetLanguage::Php).unwrap();
        let systems: Vec<&str> = map
            .systems()
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(
            systems,
            vec!["Real"],
            "PHP heredoc should hide @@system Fake"
        );
    }

    #[test]
    fn test_kotlin_raw_string_hides_system() {
        let source = br#"@@[target("kotlin")]

val template = """
@@system Fake {
    this should NOT be parsed
}
"""

@@system Real {
    machine:
        $A { }
}
"#;
        let map = segment_source(source, TargetLanguage::Kotlin).unwrap();
        let systems: Vec<&str> = map
            .systems()
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(
            systems,
            vec!["Real"],
            "Kotlin raw string should hide @@system Fake"
        );
    }

    #[test]
    fn test_swift_nested_comment_hides_system() {
        let source = br#"@@[target("swift")]

/* Outer comment
/* Nested comment
@@system Fake {
    this should NOT be parsed
}
*/
Still in outer comment
*/

@@system Real {
    machine:
        $A { }
}
"#;
        let map = segment_source(source, TargetLanguage::Swift).unwrap();
        let systems: Vec<&str> = map
            .systems()
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(
            systems,
            vec!["Real"],
            "Swift nested block comment should hide @@system Fake"
        );
    }

    #[test]
    fn test_ruby_percent_literal_hides_system() {
        let source = br#"@@[target("ruby")]

template = %Q{
@@system Fake {
    this should NOT be parsed
}
}

@@system Real {
    machine:
        $A { }
}
"#;
        let map = segment_source(source, TargetLanguage::Ruby).unwrap();
        let systems: Vec<&str> = map
            .systems()
            .iter()
            .map(|s| match s {
                Segment::System { name, .. } => name.as_str(),
                _ => "",
            })
            .collect();
        assert_eq!(
            systems,
            vec!["Real"],
            "Ruby percent-Q literal should hide @@system Fake"
        );
    }

    #[test]
    fn test_graphviz_returns_error() {
        let source = b"@@[target(\"graphviz\")]\n";
        let result = segment_source(source, TargetLanguage::Graphviz);
        assert!(
            result.is_err(),
            "Graphviz should return error from segmenter"
        );
    }
}
