p = 'frontend/src/utils/tipTerms.js'
s = open(p).read()

s = s.replace('''/**
 * `{{stridor|Inspiratory stridor is extrathoracic until proven otherwise}}`
 * — a word in the prose that carries a teaching point behind it.''',
'''/**
 * `{{stridor|Inspiratory stridor is extrathoracic until proven otherwise}}`
 * — a word in the prose that carries a teaching point behind it.
 *
 * Four shapes, all of them the same construct:
 *
 *   {{phrase|explanation}}        the phrase, underlined, opens the card
 *   {{phrase|explanation|kind}}   the same, marked as a fact, a why, a caution
 *   {{|explanation|fact}}         no phrase: a chip at the end of a sentence,
 *                                 for an aside that is not about any one word
 *   {{term}}                      resolved from the shared glossary, so a word
 *                                 used in forty articles is defined once''')

s = s.replace('''// The phrase may not contain a bar or a brace; the tip may not contain braces.
// Both are deliberate: it keeps a stray `{` in a formula from eating the rest
// of a stem.
const TIP = /\\{\\{([^{}|]+)\\|([^{}]+)\\}\\}/g''',
'''// No braces anywhere inside, and no bars within a field: it keeps a stray `{`
// in a formula from eating the rest of a stem, and it makes the three fields
// unambiguous to split.
const TIP = /\\{\\{([^{}|]*)(?:\\|([^{}|]*))?(?:\\|([^{}|]*))?\\}\\}/g

//: What a card can be. Anything else written in the third field is read as
//: `define`, which is what an unmarked tip has always been.
const KINDS = new Set(['define', 'fact', 'why', 'caution'])''')

s = s.replace('''      for (let match = TIP.exec(node.value); match; match = TIP.exec(node.value)) {
        const [whole, phrase, tip] = match''',
'''      for (let match = TIP.exec(node.value); match; match = TIP.exec(node.value)) {
        const [whole, rawPhrase, rawTip, rawKind] = match
        const phrase = rawPhrase ?? ''
        const tip = (rawTip ?? '').trim()
        const kind = KINDS.has((rawKind || '').trim()) ? rawKind.trim() : 'define'
        // `{{}}` is nothing; a glossary reference needs a term to look up.
        if (!phrase.trim() && !tip) continue''')

s = s.replace('''        const phraseAt = at(match.index + 2)
        out.push({
          type: 'tipTerm',
          data: {
            hName: 'span',
            hProperties: { className: 'tip-term', 'data-tip': tip.trim() },
          },
          children: [text(phrase, phraseAt, anchor)],
          position: span(at(match.index), at(match.index + whole.length), anchor),
        })''',
'''        const phraseAt = at(match.index + 2)
        // A tip with no explanation of its own is a glossary reference: the
        // phrase is the term, and the definition is fetched at render time.
        const term = tip ? '' : phrase.trim()
        out.push({
          type: 'tipTerm',
          data: {
            hName: 'span',
            hProperties: {
              className: 'tip-term',
              'data-tip': tip,
              'data-kind': kind,
              'data-term': term,
            },
          },
          // A bare anchor has no words under it — the component draws a chip.
          children: phrase ? [text(phrase, phraseAt, anchor)] : [],
          position: span(at(match.index), at(match.index + whole.length), anchor),
        })''')
open(p, 'w').write(s)
print('written')
