import re

def load(p):
    raw = open(p, encoding='utf-8', newline='').read()
    return raw.replace('\r\n', '\n'), '\r\n' in raw

def save(p, s, crlf):
    open(p, 'w', encoding='utf-8', newline='').write(s.replace('\n', '\r\n') if crlf else s)

def once(s, old, new):
    assert s.count(old) == 1, (old[:80], s.count(old))
    return s.replace(old, new)

def after_field_in_literals(s, opener, field, make_line, expect):
    """Insert a line after `field` inside every `opener` struct literal."""
    out, pos, n = [], 0, 0
    for m in re.finditer(re.escape(opener), s):
        start = m.end()
        if start < pos or s[max(0, m.start() - 7):m.start()] == 'struct ':
            continue
        fm = re.compile(r'\n([ \t]*)' + re.escape(field) + r'[^\n]*').search(s, start)
        close = s.find('}', start)
        assert fm and fm.start() < close, ('field not inside literal', opener, s[start:start+80])
        indent = fm.group(1)
        out.append(s[pos:fm.end()])
        out.append('\n' + indent + make_line(n))
        pos = fm.end()
        n += 1
    out.append(s[pos:])
    assert n == len(expect), (opener, n, expect)
    return ''.join(out)

# ---- relcache: the field -------------------------------------------------
p = 'crates/spt-daemon/src/relcache.rs'
s, c = load(p)
s = once(s, '''    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub trust_anchor: Option<String>,
}
''', '''    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub trust_anchor: Option<String>,
    /// Where the CURRENT bytes came from (releases#332): `built-in`, `subnet`
    /// or `release`, written on EVERY install and update — unlike
    /// `install_source`, which records the first install only. Absent on a
    /// record written before it existed, and the adapter page then names no
    /// source rather than guessing one.
    // [impl->REQ-DOCS-ADAPTER-PAGE]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub current_source: Option<String>,
}
''')
s = after_field_in_literals(s, 'RetainedAdapter {\n            kind', 'trust_anchor:', lambda i: 'current_source: None,', [1])
save(p, s, c)

# ---- test fixtures that build the sidecar literally -----------------------
for p in ['crates/spt/tests/adapter_peer_e2e.rs', 'crates/spt/tests/source_ladder_e2e.rs']:
    s, c = load(p)
    s = after_field_in_literals(s, 'RetainedAdapter {', 'trust_anchor:', lambda i: 'current_source: None,', [1])
    save(p, s, c)

# ---- cli ------------------------------------------------------------------
p = 'crates/spt/src/cli.rs'
s, c = load(p)
S = 'spt_runtime::surfaces::AdapterSource'
s = once(s, '''struct RetainPending {
    bytes: Vec<u8>,
    signature_hex: Option<String>,
    install_source: Option<String>,
    trust_anchor: Option<String>,
}''', f'''struct RetainPending {{
    bytes: Vec<u8>,
    signature_hex: Option<String>,
    install_source: Option<String>,
    trust_anchor: Option<String>,
    /// Where these bytes came from. Not optional, so every install arm that
    /// retains an archive must say (releases#332).
    // [impl->REQ-DOCS-ADAPTER-PAGE]
    current_source: {S},
}}''')
s = once(s, '''    /// The trust anchor in words, recorded with the retained archive.
    trust_anchor: Option<String>,
}''', f'''    /// The trust anchor in words, recorded with the retained archive.
    trust_anchor: Option<String>,
    /// Where these bytes came from — the bundle, a peer or the release
    /// channel. Not optional, so no candidate is built without one.
    // [impl->REQ-DOCS-ADAPTER-PAGE]
    source: {S},
}}''')
# RetainPending literals, in file order: bundle, release, subnet.
rp = [f'current_source: {S}::BuiltIn,', f'current_source: {S}::Release,', f'current_source: {S}::Subnet,']
s = after_field_in_literals(s, 'RetainPending {\n', 'trust_anchor:', lambda i: rp[i], rp)
# AdapterCandidate literals, in file order: bundle, channel, peer, test.
ac = [f'source: {S}::BuiltIn,', f'source: {S}::Release,', f'source: {S}::Subnet,', f'source: {S}::Release,']
s = after_field_in_literals(s, 'AdapterCandidate {\n', 'trust_anchor:', lambda i: ac[i], ac)
# RetainedAdapter literals: the writer, then the test seed.
ra = ['current_source: Some(current_source.as_str().to_string()),', 'current_source: None,']
s = after_field_in_literals(s, 'spt_daemon::relcache::RetainedAdapter {\n', 'trust_anchor', lambda i: ra[i], ra)
s = once(s, '''    install_source: Option<String>,
    trust_anchor: Option<String>,
) {
    let kind = serde_json::to_value(manifest.adapter.kind)''', f'''    install_source: Option<String>,
    trust_anchor: Option<String>,
    current_source: {S},
) {{
    let kind = serde_json::to_value(manifest.adapter.kind)''')
s = once(s, '''            p.install_source,
            p.trust_anchor,
        );''', '''            p.install_source,
            p.trust_anchor,
            p.current_source,
        );''')
s = once(s, '''                            retained.as_ref().and_then(|k| k.install_source.clone()),
                            cand.trust_anchor.clone(),
                        );''', '''                            retained.as_ref().and_then(|k| k.install_source.clone()),
                            cand.trust_anchor.clone(),
                            cand.source,
                        );''')
save(p, s, c)
print('ok')
