"""
camcops_server/tasks/perinatalpoem.py

===============================================================================

    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

"""

import re
from typing import Any, Dict, List, Optional, Tuple, Type

from cardinal_pythonlib.classes import classproperty
from pyramid.renderers import render_to_response
from pyramid.response import Response
from sqlalchemy import Select
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.expression import and_, column, select
from sqlalchemy.sql.sqltypes import UnicodeText

from camcops_server.cc_modules.cc_constants import CssClass

from camcops_server.cc_modules.cc_html import (
    get_yes_no_none,
    subheading_spanning_two_columns,
    tr_qa,
)
from camcops_server.cc_modules.cc_report import (
    DateTimeFilteredReportMixin,
    PercentageSummaryReportMixin,
    Report,
)
from camcops_server.cc_modules.cc_request import CamcopsRequest
from camcops_server.cc_modules.cc_sqla_coltypes import (
    mapped_camcops_column,
    ZERO_TO_ONE_CHECKER,
    ONE_TO_TWO_CHECKER,
    ONE_TO_FIVE_CHECKER,
    ONE_TO_FOUR_CHECKER,
)
from camcops_server.cc_modules.cc_task import get_from_dict, Task
from camcops_server.cc_modules.cc_text import SS
from camcops_server.cc_modules.cc_spreadsheet import SpreadsheetPage


# =============================================================================
# Perinatal-POEM
# =============================================================================


class PerinatalPoem(Task):
    """
    Server implementation of the Perinatal-POEM task.
    """

    __tablename__ = "perinatal_poem"
    shortname = "Perinatal-POEM"
    provides_trackers = False

    # Field names
    FN_QA_RESPONDENT = "qa"
    FN_QB_SERVICE_TYPE = "qb"
    FN_Q1A_MH_FIRST_CONTACT = "q1a"
    FN_Q1B_MH_DISCHARGE = "q1b"
    FN_Q2A_STAFF_DID_NOT_COMMUNICATE = "q2a"
    FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT = "q2b"
    FN_Q2C_HELP_NOT_QUICK_ENOUGH = "q2c"
    FN_Q2D_STAFF_LISTENED = "q2d"
    FN_Q2E_STAFF_DID_NOT_INVOLVE_ME = "q2e"
    FN_Q2F_SERVICE_PROVIDED_INFO = "q2f"
    FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME = "q2g"
    FN_Q2H_STAFF_HELPED_ME_UNDERSTAND = "q2h"
    FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY = "q2i"
    FN_Q2J_STAFF_HELPED_MY_CONFIDENCE = "q2j"
    FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY = "q2k"
    FN_Q2L_I_WOULD_RECOMMEND_SERVICE = "q2l"
    FN_Q3A_UNIT_CLEAN = "q3a"
    FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER = "q3b"
    FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES = "q3c"
    FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY = "q3d"
    FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT = "q3e"
    FN_Q3F_FOOD_NOT_ACCEPTABLE = "q3f"
    FN_GENERAL_COMMENTS = "general_comments"
    FN_FUTURE_PARTICIPATION = "future_participation"
    FN_CONTACT_DETAILS = "contact_details"

    # Response values
    VAL_QA_PATIENT = 1
    VAL_QA_PARTNER_OTHER = 2

    VAL_QB_INPATIENT = 1  # inpatient = MBU = mother and baby unit
    VAL_QB_COMMUNITY = 2

    VAL_Q1_VERY_WELL = 1
    VAL_Q1_WELL = 2
    VAL_Q1_UNWELL = 3
    VAL_Q1_VERY_UNWELL = 4
    VAL_Q1_EXTREMELY_UNWELL = 5
    _MH_KEY = (
        f"({VAL_Q1_VERY_WELL} very well, {VAL_Q1_WELL} well, "
        f"{VAL_Q1_UNWELL} unwell, {VAL_Q1_VERY_UNWELL} very unwell, "
        f"{VAL_Q1_EXTREMELY_UNWELL} extremely unwell)"
    )

    VAL_STRONGLY_AGREE = 1
    VAL_AGREE = 2
    VAL_DISAGREE = 3
    VAL_STRONGLY_DISAGREE = 4
    _AGREE_KEY = (
        f"({VAL_STRONGLY_AGREE} strongly agree, {VAL_AGREE} agree, "
        f"{VAL_DISAGREE} disagree, {VAL_STRONGLY_DISAGREE} strongly disagree)"
    )

    _INPATIENT_ONLY = "[Inpatient services only]"

    YES_INT = 1
    NO_INT = 0

    # -------------------------------------------------------------------------
    # Fields
    # -------------------------------------------------------------------------
    qa: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_TWO_CHECKER,
        comment=(
            f"Question A: Is the respondent the patient ({VAL_QA_PATIENT}) "
            f"or other ({VAL_QA_PARTNER_OTHER})?"
        ),
    )
    qb: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_TWO_CHECKER,
        comment=(
            f"Question B: Was the service type inpatient [mother-and-baby "
            f"unit, MBU] ({VAL_QB_INPATIENT}) or "
            f"community ({VAL_QB_COMMUNITY})?"
        ),
    )

    q1a: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FIVE_CHECKER,
        comment=f"Q1A: mental health at first contact {_MH_KEY}",
    )
    q1b: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FIVE_CHECKER,
        comment=f"Q1B: mental health at discharge {_MH_KEY}",
    )

    q2a: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2a: staff didn't communicate with others {_AGREE_KEY}",
    )
    q2b: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2b: Staff gave right amount of support {_AGREE_KEY}",
    )
    q2c: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2c: Help not quick enough after referral {_AGREE_KEY}",
    )
    q2d: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2d: Staff listened/understood {_AGREE_KEY}",
    )

    q2e: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2e: Staff didn't involve pt enough {_AGREE_KEY}",
    )
    q2f: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2f: Service provided information {_AGREE_KEY}",
    )
    q2g: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2g: Staff not very sensitive to pt {_AGREE_KEY}",
    )
    q2h: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2h: Staff helped understanding of illness {_AGREE_KEY}",
    )

    q2i: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2i: Staff not very sensitive to baby {_AGREE_KEY}",
    )
    q2j: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2j: Staff helped confidence re baby {_AGREE_KEY}",
    )
    q2k: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2k: Service involved others helpfully {_AGREE_KEY}",
    )
    q2l: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q2l: Would recommend service {_AGREE_KEY}",
    )

    q3a: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3a: MBU clean {_AGREE_KEY} {_INPATIENT_ONLY}",
    )
    q3b: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3b: MBU not a good place to recover "
        f"{_AGREE_KEY} {_INPATIENT_ONLY}",
    )
    q3c: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3c: MBU did not provide helpful activities "
        f"{_AGREE_KEY} {_INPATIENT_ONLY}",
    )
    q3d: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3d: MBU a good place for baby to be with pt "
        f"{_AGREE_KEY} {_INPATIENT_ONLY}",
    )
    q3e: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3e: MBU supported contact with family/friends "
        f"{_AGREE_KEY} {_INPATIENT_ONLY}",
    )
    q3f: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ONE_TO_FOUR_CHECKER,
        comment=f"Q3f: Food not acceptable {_AGREE_KEY} {_INPATIENT_ONLY}",
    )

    general_comments: Mapped[Optional[str]] = mapped_column(
        FN_GENERAL_COMMENTS, UnicodeText, comment="General comments"
    )
    future_participation: Mapped[Optional[int]] = mapped_camcops_column(
        permitted_value_checker=ZERO_TO_ONE_CHECKER,
        comment=f"Willing to participate in future studies "
        f"({YES_INT} yes, {NO_INT} no)",
    )
    contact_details: Mapped[Optional[str]] = mapped_column(
        FN_CONTACT_DETAILS, UnicodeText, comment="Contact details"
    )

    # -------------------------------------------------------------------------
    # Fieldname collections
    # -------------------------------------------------------------------------
    REQUIRED_ALWAYS = [
        FN_QA_RESPONDENT,
        FN_QB_SERVICE_TYPE,
        FN_Q1A_MH_FIRST_CONTACT,
        FN_Q1B_MH_DISCHARGE,
        FN_Q2A_STAFF_DID_NOT_COMMUNICATE,
        FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT,
        FN_Q2C_HELP_NOT_QUICK_ENOUGH,
        FN_Q2D_STAFF_LISTENED,
        FN_Q2E_STAFF_DID_NOT_INVOLVE_ME,
        FN_Q2F_SERVICE_PROVIDED_INFO,
        FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME,
        FN_Q2H_STAFF_HELPED_ME_UNDERSTAND,
        FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY,
        FN_Q2J_STAFF_HELPED_MY_CONFIDENCE,
        FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY,
        FN_Q2L_I_WOULD_RECOMMEND_SERVICE,
        # not FN_GENERAL_COMMENTS,
        FN_FUTURE_PARTICIPATION,
        # not FN_CONTACT_DETAILS,
    ]
    REQUIRED_INPATIENT = [
        FN_Q3A_UNIT_CLEAN,
        FN_Q3B_UNIT_NOT_GOOD_PLACE_TO_RECOVER,
        FN_Q3C_UNIT_DID_NOT_PROVIDE_ACTIVITIES,
        FN_Q3D_UNIT_GOOD_PLACE_FOR_BABY,
        FN_Q3E_UNIT_SUPPORTED_FAMILY_FRIENDS_CONTACT,
        FN_Q3F_FOOD_NOT_ACCEPTABLE,
    ]
    Q1_FIELDS = [FN_Q1A_MH_FIRST_CONTACT, FN_Q1B_MH_DISCHARGE]
    Q2_FIELDS = [
        FN_Q2A_STAFF_DID_NOT_COMMUNICATE,
        FN_Q2B_STAFF_GAVE_RIGHT_SUPPORT,
        FN_Q2C_HELP_NOT_QUICK_ENOUGH,
        FN_Q2D_STAFF_LISTENED,
        FN_Q2E_STAFF_DID_NOT_INVOLVE_ME,
        FN_Q2F_SERVICE_PROVIDED_INFO,
        FN_Q2G_STAFF_NOT_SENSITIVE_TO_ME,
        FN_Q2H_STAFF_HELPED_ME_UNDERSTAND,
        FN_Q2I_STAFF_NOT_SENSITIVE_TO_BABY,
        FN_Q2J_STAFF_HELPED_MY_CONFIDENCE,
        FN_Q2K_SERVICE_INVOLVED_OTHERS_HELPFULLY,
        FN_Q2L_I_WOULD_RECOMMEND_SERVICE,
    ]
    Q3_FIELDS = REQUIRED_INPATIENT

    @staticmethod
    def longname(req: "CamcopsRequest") -> str:
        _ = req.gettext
        return _("Perinatal Patient-rated Outcome and Experience Measure")

    def was_inpatient(self) -> bool:
        return self.qb == self.VAL_QB_INPATIENT

    def respondent_not_patient(self) -> bool:
        return self.qa == self.VAL_QA_PARTNER_OTHER

    def offering_participation(self) -> bool:
        return self.future_participation == self.YES_INT

    def is_complete(self) -> bool:
        if self.any_fields_none(self.REQUIRED_ALWAYS):
            return False
        if self.was_inpatient() and self.any_fields_none(
            self.REQUIRED_INPATIENT
        ):
            return False
        if not self.field_contents_valid():
            return False
        return True

    def get_qa_options(self, req: CamcopsRequest) -> List[str]:
        options = [
            self.wxstring(req, f"qa_a{o}")
            for o in range(self.VAL_QA_PATIENT, self.VAL_QA_PARTNER_OTHER + 1)
        ]

        return options

    def get_qb_options(self, req: CamcopsRequest) -> List[str]:
        options = [
            self.wxstring(req, f"qb_a{o}")
            for o in range(self.VAL_QB_INPATIENT, self.VAL_QB_COMMUNITY + 1)
        ]

        return options

    def get_q1_options(self, req: CamcopsRequest) -> List[str]:
        options = [
            self.wxstring(req, f"q1_a{o}")
            for o in range(
                self.VAL_Q1_VERY_WELL, self.VAL_Q1_EXTREMELY_UNWELL + 1
            )
        ]

        return options

    def get_agree_options(self, req: CamcopsRequest) -> List[str]:
        options = [
            self.wxstring(req, f"agreement_a{o}")
            for o in range(
                self.VAL_STRONGLY_AGREE, self.VAL_STRONGLY_DISAGREE + 1
            )
        ]

        return options

    @staticmethod
    def get_yn_options(req: CamcopsRequest) -> List[str]:
        return [req.sstring(SS.NO), req.sstring(SS.YES)]

    def get_task_html(self, req: CamcopsRequest) -> str:
        def loadvalues(
            _dict: Dict[int, str], _first: int, _last: int, _xstringprefix: str
        ) -> None:
            for val in range(_first, _last + 1):
                _dict[val] = (
                    f"{val} — {self.wxstring(req, f'{_xstringprefix}{val}')}"
                )

        respondent_dict = {}  # type: Dict[int, str]
        loadvalues(
            respondent_dict,
            self.VAL_QA_PATIENT,
            self.VAL_QA_PARTNER_OTHER,
            "qa_a",
        )
        service_dict = {}  # type: Dict[int, str]
        loadvalues(
            service_dict, self.VAL_QB_INPATIENT, self.VAL_QB_COMMUNITY, "qb_a"
        )
        mh_dict = {}  # type: Dict[int, str]
        loadvalues(
            mh_dict,
            self.VAL_Q1_VERY_WELL,
            self.VAL_Q1_EXTREMELY_UNWELL,
            "q1_a",
        )
        agree_dict = {}  # type: Dict[int, str]
        loadvalues(
            agree_dict,
            self.VAL_STRONGLY_AGREE,
            self.VAL_STRONGLY_DISAGREE,
            "agreement_a",
        )

        q_a_list = []  # type: List[str]

        def addqa(_fieldname: str, _valuedict: Dict[int, str]) -> None:
            xstringname = _fieldname + "_q"
            q_a_list.append(
                tr_qa(
                    self.xstring(req, xstringname),  # not wxstring
                    get_from_dict(_valuedict, getattr(self, _fieldname)),
                )
            )

        def subheading(_xstringname: str) -> None:
            q_a_list.append(
                subheading_spanning_two_columns(
                    self.wxstring(req, _xstringname)
                )
            )

        # Preamble
        addqa(self.FN_QA_RESPONDENT, respondent_dict)
        addqa(self.FN_QB_SERVICE_TYPE, service_dict)
        # The bulk
        subheading("q1_stem")
        for fieldname in self.Q1_FIELDS:
            addqa(fieldname, mh_dict)
        subheading("q2_stem")
        for fieldname in self.Q2_FIELDS:
            addqa(fieldname, agree_dict)
        if self.was_inpatient():
            subheading("q3_stem")
            for fieldname in self.Q3_FIELDS:
                addqa(fieldname, agree_dict)
        # General
        q_a_list.append(
            subheading_spanning_two_columns(req.sstring(SS.GENERAL))
        )
        q_a_list.append(
            tr_qa(
                self.wxstring(req, "general_comments_q"), self.general_comments
            )
        )
        q_a_list.append(
            tr_qa(
                self.wxstring(req, "participation_q"),
                get_yes_no_none(req, self.future_participation),
            )
        )
        if self.offering_participation():
            q_a_list.append(
                tr_qa(
                    self.wxstring(req, "contact_details_q"),
                    self.contact_details,
                )
            )

        q_a = "\n".join(q_a_list)
        return f"""
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {self.get_is_complete_tr(req)}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Answer</th>
                </tr>
                {q_a}
            </table>
            <div class="{CssClass.FOOTNOTES}">
            </div>
        """

    # No SNOMED codes for Perinatal-POEM.


# =============================================================================
# Reports
# =============================================================================


class PerinatalPoemReportTableConfig(object):
    def __init__(
        self,
        heading: str,
        column_headings: List[str],
        fieldnames: List[str],
        min_answer: int = 0,
        xstring_format: str = "{}_q",
    ) -> None:
        self.heading = heading
        self.column_headings = column_headings
        self.fieldnames = fieldnames
        self.min_answer = min_answer
        self.xstring_format = xstring_format


class PerinatalPoemReportTable(object):
    def __init__(
        self,
        req: "CamcopsRequest",
        heading: str,
        column_headings: List[str],
        rows: List[List[str]],
    ) -> None:
        _ = req.gettext
        self.heading = heading

        common_headings = [_("Question"), _("Total responses")]
        self.column_headings = common_headings + column_headings
        self.rows = rows


class PerinatalPoemReport(
    DateTimeFilteredReportMixin, Report, PercentageSummaryReportMixin
):
    """
    Provides a summary of each question, x% of people said each response etc.
    Then a summary of the comments.
    """

    HTML_TAG_RE = re.compile(r"<[^>]+>")

    def __init__(self, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        self.task = PerinatalPoem()  # dummy task, never written to DB

    @classproperty
    def task_class(self) -> Type["Task"]:
        return PerinatalPoem

    # noinspection PyMethodParameters
    @classproperty
    def report_id(cls) -> str:
        return "perinatal_poem"

    @classmethod
    def title(cls, req: "CamcopsRequest") -> str:
        _ = req.gettext
        return _("Perinatal-POEM — Question summaries")

    # noinspection PyMethodParameters
    @classproperty
    def superuser_only(cls) -> bool:
        return False

    def render_html(self, req: "CamcopsRequest") -> Response:
        return render_to_response(
            "perinatal_poem_report.mako",
            dict(
                title=self.title(req),
                report_id=self.report_id,
                start_datetime=self.start_datetime,
                end_datetime=self.end_datetime,
                tables=self._get_html_tables(req),
                comments=self._get_comments(req),
            ),
            request=req,
        )

    def get_spreadsheet_pages(
        self, req: "CamcopsRequest"
    ) -> List[SpreadsheetPage]:
        _ = req.gettext

        pages = []

        for table in self._get_spreadsheet_tables(req):
            pages.append(
                self.get_spreadsheet_page(
                    name=table.heading,
                    column_names=table.column_headings,
                    rows=table.rows,
                )
            )

        pages.append(
            self.get_spreadsheet_page(
                name=_("Comments"),
                column_names=[_("Comment")],
                rows=self._get_comment_rows(req),
            )
        )

        return pages

    def _get_html_tables(
        self, req: "CamcopsRequest"
    ) -> List["PerinatalPoemReportTable"]:

        return [
            self._get_html_table(req, config)
            for config in self._get_table_configs(req)
        ]

    def _get_spreadsheet_tables(
        self, req: "CamcopsRequest"
    ) -> List["PerinatalPoemReportTable"]:

        return [
            self._get_spreadsheet_table(req, config)
            for config in self._get_table_configs(req)
        ]

    def _get_table_configs(
        self, req: "CamcopsRequest"
    ) -> List["PerinatalPoemReportTableConfig"]:
        return [
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "qa_q"),
                column_headings=self.task.get_qa_options(req),
                fieldnames=["qa"],
                min_answer=1,
            ),
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "qb_q"),
                column_headings=self.task.get_qb_options(req),
                fieldnames=["qb"],
                min_answer=1,
            ),
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "q1_stem"),
                column_headings=self.task.get_q1_options(req),
                fieldnames=PerinatalPoem.Q1_FIELDS,
                min_answer=1,
            ),
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "q2_stem"),
                column_headings=self.task.get_agree_options(req),
                fieldnames=PerinatalPoem.Q2_FIELDS,
                min_answer=1,
            ),
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "q3_stem"),
                column_headings=self.task.get_agree_options(req),
                fieldnames=PerinatalPoem.Q3_FIELDS,
                min_answer=1,
            ),
            PerinatalPoemReportTableConfig(
                heading=self.task.xstring(req, "participation_q"),
                column_headings=self.task.get_yn_options(req),
                fieldnames=["future_participation"],
                xstring_format="participation_q",
            ),
        ]

    def _get_html_table(
        self, req: "CamcopsRequest", config: PerinatalPoemReportTableConfig
    ) -> PerinatalPoemReportTable:
        column_dict = {}

        for fieldname in config.fieldnames:
            column_dict[fieldname] = self.task.xstring(
                req, config.xstring_format.format(fieldname)
            )

        rows = self.get_percentage_summaries(
            req,
            column_dict=column_dict,
            num_answers=len(config.column_headings),
            cell_format="{0:.1f}%",
            min_answer=config.min_answer,
        )

        return PerinatalPoemReportTable(
            req,
            heading=config.heading,
            column_headings=config.column_headings,
            rows=rows,
        )

    def _get_spreadsheet_table(
        self, req: "CamcopsRequest", config: PerinatalPoemReportTableConfig
    ) -> PerinatalPoemReportTable:
        column_dict = {}

        for fieldname in config.fieldnames:
            column_dict[fieldname] = self._strip_tags(
                self.task.xstring(req, config.xstring_format.format(fieldname))
            )

        rows = self.get_percentage_summaries(
            req,
            column_dict=column_dict,
            num_answers=len(config.column_headings),
            min_answer=config.min_answer,
        )

        return PerinatalPoemReportTable(
            req,
            heading=config.heading,
            column_headings=config.column_headings,
            rows=rows,
        )

    def _strip_tags(self, text: str) -> str:
        return self.HTML_TAG_RE.sub("", text)

    def _get_comment_rows(self, req: "CamcopsRequest") -> List[Tuple[str]]:
        """
        A list of all the additional comments
        """

        wheres = [column("general_comments").isnot(None)]

        self.add_task_report_filters(wheres)  # type: ignore[arg-type]

        # noinspection PyUnresolvedReferences
        query: Select[Any] = (
            select(column("general_comments"))
            .select_from(self.task.__table__)
            .where(and_(*wheres))
        )

        comment_rows = []

        for result in req.dbsession.execute(query).fetchall():
            comment_rows.append(result)

        return comment_rows

    def _get_comments(self, req: "CamcopsRequest") -> List[str]:
        """
        A list of all the additional comments.
        """
        return [x[0] for x in self._get_comment_rows(req)]
