#!/usr/bin/env python3
"""Merge the per-contribution .bib files into one combined_refs.bib.

BibTeX errors with "Repeated entry" whenever a cited key is defined in more than
one database, which it is 135 times across these 14 files.  It resolves such a
clash by keeping the *first* definition in \bibliography order; this script does
exactly the same, so the rendered bibliography is unchanged while the errors go
away.  The per-contribution .bib files stay the source of truth -- regenerate
with `python3 merge_bibs.py`, do not hand-edit combined_refs.bib.
"""
import os, re, sys, collections

# Paths below are relative to this script, so it works whatever directory the
# caller (VS Code's build recipe, say) happens to run it from.
HERE = os.path.dirname(os.path.abspath(__file__))

FILES = """ectWS SDGlazek-bibliography kievsky/paper1 capitani/capitani_y deltuva/deltuva
madeira/references wu/few_boson_unitarity_Wu/refs mitra/mitra dawid_main Jackura/bibi
Mai/NON-INSPIRE Mai/main vanKolck/vankolck Nicholson/LN""".split()
OUT = 'combined_refs.bib'

def entries(path):
    """Yield (kind, key, raw) per top-level @entry, matching braces."""
    s = open(path, encoding='utf-8', errors='replace').read()
    out, i = [], 0
    for m in re.finditer(r'@(\w+)\s*[{(]\s*([^,\s{}]*)\s*,', s):
        if m.start() < i:
            continue
        j = s.index('{', m.start())
        depth, k = 0, j
        while k < len(s):
            if s[k] == '{':
                depth += 1
            elif s[k] == '}':
                depth -= 1
                if depth == 0:
                    break
            k += 1
        out.append((m.group(1).lower(), m.group(2), s[m.start():k + 1]))
        i = k + 1
    return out

def merge():
    seen, order, strings = {}, [], []
    dups = collections.defaultdict(list)
    for f in FILES:
        for kind, key, raw in entries(f + '.bib'):
            if kind == 'string':
                if raw not in strings:
                    strings.append(raw)
                continue
            if kind in ('preamble', 'comment') or not key:
                continue
            # BibTeX matches cite keys case-insensitively, so Dawid:2023kxu and
            # dawid:2023kxu are one entry to it and clash; dedupe on that basis.
            lk = key.lower()
            if lk in seen:
                dups[lk].append(f + '.bib')
            else:
                seen[lk] = (f + '.bib', raw)
                order.append(lk)

    with open(OUT, 'w', encoding='utf-8') as fh:
        fh.write('%% GENERATED by merge_bibs.py -- do not edit.  Edit the per-contribution\n'
                 '%% .bib files listed in that script and re-run it.\n'
                 '%% First-wins merge, reproducing BibTeX\'s own duplicate resolution.\n\n')
        for s in strings:
            fh.write(s + '\n')
        fh.write('\n')
        for key in order:
            src, raw = seen[key]
            fh.write('%% from %s\n%s\n\n' % (src, raw))

    print('%d unique entries -> %s' % (len(order), OUT))
    print('%d duplicate definitions dropped (%d distinct keys)'
          % (sum(len(v) for v in dups.values()), len(dups)))


if __name__ == '__main__':
    os.chdir(HERE)
    merge()
