//! M5.3b — BindNoneSubformDoesNotAutoExpand: integration tests.
//!
//! Each test installs a process-global trace sink (Send+Sync) and
//! exercises a synthetic XFA PDF that drives one branch of the rule:
//!
//! - `<bind match="none">` on a repeating named subform with data
//!   records → rule actively prevents expansion (trace fires).
//! - non-repeating occur + `<bind match="none">` → rule applies but
//!   trace stays silent (no observable effect).
//! - no `<bind match="none">` → rule allows expansion (trace silent).

use std::sync::{Arc, Mutex};

use lopdf::{dictionary, Document, Object, Stream};
use pdf_xfa::flatten_xfa_to_pdf;
use xfa_layout_engine::trace::{with_global_sink, RecordingSink, TraceEvent};

fn global_sink_lock() -> &'static Mutex<()> {
    static LOCK: std::sync::OnceLock<Mutex<()>> = std::sync::OnceLock::new();
    LOCK.get_or_init(|| Mutex::new(()))
}

fn build_xfa_pdf(xdp: &str) -> Vec<u8> {
    let mut doc = Document::with_version("1.4");
    let xdp_bytes = xdp.as_bytes().to_vec();
    let xfa_stream = Stream::new(
        dictionary! { "Length" => Object::Integer(xdp_bytes.len() as i64) },
        xdp_bytes,
    );
    let xfa_id = doc.add_object(Object::Stream(xfa_stream));
    let pages_id = doc.new_object_id();
    let content_id = doc.add_object(Object::Stream(Stream::new(
        dictionary! { "Length" => Object::Integer(0_i64) },
        vec![],
    )));
    let page_id = doc.add_object(Object::Dictionary(dictionary! {
        "Type"     => Object::Name(b"Page".to_vec()),
        "Parent"   => Object::Reference(pages_id),
        "MediaBox" => Object::Array(vec![
            Object::Integer(0), Object::Integer(0),
            Object::Integer(612), Object::Integer(792),
        ]),
        "Contents" => Object::Reference(content_id),
    }));
    doc.objects.insert(
        pages_id,
        Object::Dictionary(dictionary! {
            "Type"  => Object::Name(b"Pages".to_vec()),
            "Kids"  => Object::Array(vec![Object::Reference(page_id)]),
            "Count" => Object::Integer(1),
        }),
    );
    let acroform_id = doc.add_object(Object::Dictionary(dictionary! {
        "XFA" => Object::Reference(xfa_id),
    }));
    let catalog_id = doc.add_object(Object::Dictionary(dictionary! {
        "Type"     => Object::Name(b"Catalog".to_vec()),
        "Pages"    => Object::Reference(pages_id),
        "AcroForm" => Object::Reference(acroform_id),
    }));
    doc.trailer.set("Root", Object::Reference(catalog_id));
    let mut out = Vec::new();
    doc.save_to(&mut out).expect("save lopdf");
    out
}

fn flatten_with_sink(pdf: &[u8]) -> Vec<TraceEvent> {
    let sink: Arc<RecordingSink> = Arc::new(RecordingSink::new());
    let _ = with_global_sink(sink.clone() as _, || flatten_xfa_to_pdf(pdf))
        .expect("flatten should succeed");
    sink.events()
}

fn count_reason(events: &[TraceEvent], reason: &str) -> usize {
    events.iter().filter(|e| e.reason.tag() == reason).count()
}

// Positive: repeating subform with <bind match="none"/> and 3 data
// records. The rule must actively prevent expansion. The dataset has
// 3 records that would otherwise materialise 3 instances; the rule
// keeps only 1 (the template instance).
const XDP_BIND_NONE_OCCUR_REPEATING: &str = r#"<?xml version="1.0"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
    <subform name="form1" layout="tb" w="8.5in" h="11in">
      <pageSet>
        <pageArea name="Page1">
          <contentArea x="36pt" y="36pt" w="540pt" h="720pt"/>
          <medium stock="default" short="612pt" long="792pt"/>
        </pageArea>
      </pageSet>
      <subform name="Container" layout="tb" w="540pt">
        <subform name="Slot" layout="tb" w="540pt">
          <occur min="0" max="5"/>
          <bind match="none"/>
          <field name="Label" w="200pt" h="18pt">
            <ui><textEdit/></ui>
            <value><text>static</text></value>
          </field>
        </subform>
      </subform>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <xfa:data>
      <form1>
        <Slot><Label>A</Label></Slot>
        <Slot><Label>B</Label></Slot>
        <Slot><Label>C</Label></Slot>
      </form1>
    </xfa:data>
  </xfa:datasets>
</xdp:xdp>"#;

#[test]
fn bind_none_rule_fires_when_repeating_subform_is_bind_none() {
    let _guard = global_sink_lock().lock().unwrap_or_else(|e| e.into_inner());
    let pdf = build_xfa_pdf(XDP_BIND_NONE_OCCUR_REPEATING);
    let events = flatten_with_sink(&pdf);

    assert!(
        count_reason(&events, "bind_none_subform_expansion_skipped") >= 1,
        "rule must fire when bind=none short-circuits a repeating subform; events={events:?}"
    );
    // The clamp rule must NOT fire because expansion was short-circuited
    // before the clamp could run.
    assert_eq!(
        count_reason(&events, "data_count_clamped_by_occur_max"),
        0,
        "occur-clamp rule must not run when bind=none short-circuits expansion"
    );
}

// Counterexample: non-repeating occur (max=1) + bind=none. The rule
// returns Blocked structurally, but the trace stays silent because
// the caller would have skipped expansion anyway (non-repeating).
const XDP_BIND_NONE_NON_REPEATING_OCCUR: &str = r#"<?xml version="1.0"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
    <subform name="form1" layout="tb" w="8.5in" h="11in">
      <pageSet>
        <pageArea name="Page1">
          <contentArea x="36pt" y="36pt" w="540pt" h="720pt"/>
          <medium stock="default" short="612pt" long="792pt"/>
        </pageArea>
      </pageSet>
      <subform name="Container" layout="tb" w="540pt">
        <subform name="Once" layout="tb" w="540pt">
          <occur min="1" max="1"/>
          <bind match="none"/>
          <field name="Label" w="200pt" h="18pt">
            <ui><textEdit/></ui>
            <value><text>only</text></value>
          </field>
        </subform>
      </subform>
    </subform>
  </template>
</xdp:xdp>"#;

#[test]
fn bind_none_rule_silent_when_occur_not_repeating() {
    let _guard = global_sink_lock().lock().unwrap_or_else(|e| e.into_inner());
    let pdf = build_xfa_pdf(XDP_BIND_NONE_NON_REPEATING_OCCUR);
    let events = flatten_with_sink(&pdf);

    assert_eq!(
        count_reason(&events, "bind_none_subform_expansion_skipped"),
        0,
        "rule must stay silent when occur is non-repeating (would-not-have-expanded anyway)"
    );
}

// Counterexample: repeating subform without bind=none. The rule
// returns Allowed and stays silent. The occur clamp rule may still
// fire — that is a separate decision.
const XDP_REPEATING_NO_BIND_NONE: &str = r#"<?xml version="1.0"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
    <subform name="form1" layout="tb" w="8.5in" h="11in">
      <pageSet>
        <pageArea name="Page1">
          <contentArea x="36pt" y="36pt" w="540pt" h="720pt"/>
          <medium stock="default" short="612pt" long="792pt"/>
        </pageArea>
      </pageSet>
      <subform name="Container" layout="tb" w="540pt">
        <subform name="Item" layout="tb" w="540pt">
          <occur min="0" max="-1"/>
          <field name="Name" w="200pt" h="18pt">
            <ui><textEdit/></ui>
          </field>
        </subform>
      </subform>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <xfa:data>
      <form1>
        <Item><Name>x</Name></Item>
        <Item><Name>y</Name></Item>
      </form1>
    </xfa:data>
  </xfa:datasets>
</xdp:xdp>"#;

#[test]
fn bind_none_rule_silent_when_bind_is_not_none() {
    let _guard = global_sink_lock().lock().unwrap_or_else(|e| e.into_inner());
    let pdf = build_xfa_pdf(XDP_REPEATING_NO_BIND_NONE);
    let events = flatten_with_sink(&pdf);

    assert_eq!(
        count_reason(&events, "bind_none_subform_expansion_skipped"),
        0,
        "rule must stay silent when bind is not none"
    );
}
