def extract_paragraphs(children, case_id_to_alto_ids, blocks_by_id):
    """
        Extract paragraph and footnote structures from a list of CaseMETS paragraphs and footnotes as PyQuery elements.

        Example:
        >>> doc = PyQuery("<opinion><author id='1'>text</author><footnote id='footnote_1_1'><p id='2'>footnote text</p></footnote></opinion>")
        >>> case_id_to_alto_ids = {'1': [1], 'footnote_1_1': [1], '2': [1]}
        >>> blocks_by_id = {1: {}}
        >>> paragraphs, footnotes, paragraph_els = extract_paragraphs(doc.children().items(), case_id_to_alto_ids, blocks_by_id)
        >>> assert paragraphs == [{'id': '1', 'class': 'author', 'block_ids':[1]}]
        >>> assert footnotes == [{'id': 'footnote_1_1', 'paragraphs': [{'block_ids': [1], 'id': '2', 'class': 'p'}]}]
        >>> assert paragraph_els == [doc('author'), doc('p')]
    """
    paragraphs = []
    footnotes = []
    paragraph_els = []
    for el in children:
        if el[0].tag == 'footnote':
            # extract footnote tag and call self recursively to get footnote paragraphs
            footnote = {'id': el.attr.id}
            if el.attr.label:
                footnote['label'] = el.attr.label
            if el.attr.redact:
                footnote['redacted'] = el.attr.redact == 'true'
            if el.attr.orphan:
                footnote['orphan'] = el.attr.orphan
            footnote_pars, _, footnote_par_els = extract_paragraphs(el.children().items(), case_id_to_alto_ids, blocks_by_id)

            # casemets <footnote redact='true'> flag is not reliably set. we also set it if all paragraphs are redacted.
            if all(par.get('redacted', False) for par in footnote_pars):
                footnote['redacted'] = True

            footnote['paragraphs'] = footnote_pars
            paragraph_els.extend(footnote_par_els)
            footnotes.append(footnote)
        else:
            # extract other tags as paragraphs
            par = {
                'id': el.attr.id,
                'class': el[0].tag,
                'block_ids': case_id_to_alto_ids[el.attr.id],
            }
            if all(blocks_by_id[id].get('redacted', False) for id in par['block_ids']):
                par['redacted'] = True
            paragraphs.append(par)
            paragraph_els.append(el)
    return paragraphs, footnotes, paragraph_els