from django import forms
from django.contrib.admin import widgets
from django.forms import (
    BaseInlineFormSet,
    BooleanField,
    Form,
    HiddenInput,
    ModelForm,
    ModelMultipleChoiceField,
    ModelChoiceField,
    ChoiceField,
    CharField,
    ValidationError,
    modelformset_factory,
    CheckboxSelectMultiple,
    SplitDateTimeWidget,
)
from django.utils.html import escape
from django.forms import inlineformset_factory
from django.shortcuts import get_object_or_404
from django_jsonforms.forms import JSONSchemaField
from django_svelte_jsoneditor.widgets import SvelteJSONEditorWidget


from atlas.models import (
    Case,
    CaseCollection,
    CaseDetail,
    CasePrior,
    CidReportAnswer,
    Differential,
    Finding,
    QuestionSchema,
    Resource,
    SelfReview,
    Series,
    SeriesDetail,
    SeriesImage,
    SeriesFinding,
    Condition,
    Structure,
    Subspecialty,
    Presentation,
    PathologicalProcess,
    Procedure,
    UncategorisedDicom,
    UserReportAnswer,
    CaseDisplaySet,
    CaseReviewMessage,
)
from .models import NormalCase

from anatomy.models import Modality

from generic.models import Examination  # , Sign

# from generic.models import Examination, Site, Condition, Sign

from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea, Select
from crispy_forms.layout import Submit, Layout, Fieldset, Field, Div, HTML, Button
from django.urls import reverse
from crispy_forms.bootstrap import Accordion, AccordionGroup

from tinymce.widgets import TinyMCE

from dal import autocomplete

from autocomplete import (
    widgets as htmx_widgets,
    Autocomplete,
    AutocompleteWidget,
    ModelAutocomplete,
    register as autocomplete_register,
)

from loguru import logger


from generic.forms import (
    ExamAuthorFormMixin,
    ExamGroupsFormMixin,
    SplitDateTimeFieldDefaultTime,
    SplitDateTimeFieldDefaultTimeEnd,
)

from crispy_forms.helper import FormHelper

from atlas.helpers import get_cases_available_to_user

from typing import Any

from helpers.cimar import CimarAPI, NotFoundError
from rad.settings import CIMAR_USERNAME, CIMAR_PASSWORD


class ClearableInput(TextInput):
    template_name = "crispy_forms/widgets/clearable_input_single_line.html"


class ClearableTextarea(Textarea):
    template_name = "crispy_forms/widgets/clearable_input.html"


class CaseSelect(Select):
    template_name = "atlas/case_select_widget.html"

    def create_option(self, *args, **kwargs):
        option = super().create_option(*args, **kwargs)

        return option

    def get_context(self, name: str, value, attrs) -> dict[str, Any]:
        context = super().get_context(name, value, attrs)
        return context

    pass


class MoveSelectedSeriesForm(Form):
    destination_case = ModelChoiceField(
        queryset=Case.objects.none(),
        widget=HiddenInput(),
        label="Move selected series to case",
        required=True,
    )

    def __init__(self, *args, **kwargs):
        user = kwargs.pop("user", None)
        source_case = kwargs.pop("source_case", None)
        super().__init__(*args, **kwargs)

        available_cases = get_cases_available_to_user(user)
        if source_case is not None:
            available_cases = available_cases.exclude(pk=source_case.pk)

        self.fields["destination_case"].queryset = available_cases.order_by("pk")


class ConditionForm(ModelForm):
    class Meta:
        model = Condition
        exclude = ["rcr_curriculum"]
        widgets = {
            # Use an AJAX/autocomplete single-select for canonical so users
            # can search for existing canonical Conditions by name.
            "canonical": autocomplete.ModelSelect2(url="atlas:condition-autocomplete"),
            "parent": autocomplete.ModelSelect2Multiple(
                url="atlas:condition-autocomplete"
            ),
            "rcr_curriculum_map": autocomplete.ModelSelect2Multiple(
                url="atlas:condition-autocomplete-rcr-curriculum"
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Crispy helper to render a submit button consistently
        try:
            self.helper = FormHelper()
            self.helper.form_method = "post"
            self.helper.form_tag = True
            # Primary submit
            self.helper.add_input(Submit("submit", "Submit"))
            # Cancel as a button that redirects back to the condition list
            try:
                cancel_url = reverse("atlas:condition_view")
            except Exception:
                cancel_url = "#"
            self.helper.add_input(
                Button(
                    "cancel",
                    "Cancel",
                    css_class="btn btn-secondary ms-2",
                    onclick=f"window.location='{cancel_url}'",
                )
            )
        except Exception:
            # If crispy not available or something goes wrong, don't break form
            pass


class ConditionRelationshipForm(Form):
    parents = ModelMultipleChoiceField(
        required=False,
        queryset=Condition.objects.none(),
        widget=autocomplete.ModelSelect2Multiple(url="atlas:condition-autocomplete"),
    )
    children = ModelMultipleChoiceField(
        required=False,
        queryset=Condition.objects.none(),
        widget=autocomplete.ModelSelect2Multiple(url="atlas:condition-autocomplete"),
    )

    def __init__(self, *args, **kwargs):
        self.instance = kwargs.pop("instance", None)
        super().__init__(*args, **kwargs)

        condition_qs = Condition.objects.all().order_by("name")
        if self.instance is not None and getattr(self.instance, "pk", None):
            condition_qs = condition_qs.exclude(pk=self.instance.pk)

        self.fields["parents"].queryset = condition_qs
        self.fields["children"].queryset = condition_qs

        self.helper = FormHelper()
        self.helper.form_method = "post"
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Fieldset(
                "Condition relationships",
                "parents",
                "children",
            )
        )

    def save(self, commit=True):
        if self.instance is None:
            raise ValueError("ConditionRelationshipForm requires an instance")

        parents = list(self.cleaned_data.get("parents", []))
        children = list(self.cleaned_data.get("children", []))

        self.instance.parent.set(parents)

        current_children = list(self.instance.child.all())
        for child in current_children:
            if child not in children:
                self.instance.child.remove(child)
        for child in children:
            if child not in current_children:
                self.instance.child.add(child)

        return self.instance


class CaseCollectionForm(ModelForm):
    class Meta:
        model = CaseCollection
        exclude = ["cases", "valid_cid_users", "valid_user_users", "author"]

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        if kwargs.get("instance"):
            initial = kwargs.setdefault("initial", {})
        ModelForm.__init__(self, *args, **kwargs)
        super(CaseCollectionForm, self).__init__(*args, **kwargs)

        # Render collection_type as radio buttons and ensure it appears near the top
        if "collection_type" in self.fields:
            # Replace the model field with an explicit ChoiceField that uses RadioSelect
            original = self.fields["collection_type"]
            choices = getattr(original, "choices", ())
            initial = original.initial if hasattr(original, "initial") else None
            required = original.required
            label = original.label
            widget = RadioSelect(attrs={"class": "btn-check"})
            # assign custom template for rendering as button group
            #widget.template_name = "widgets/radioselect_buttons.html"
            self.fields["collection_type"] = forms.ChoiceField(
                choices=choices,
                widget=widget,
                initial=initial,
                required=required,
                label=label,
            )

        if "prerequisites" in self.fields:
            self.fields["prerequisites"].widget = autocomplete.ModelSelect2Multiple(
                url="atlas:casecollection-prerequisite-autocomplete",
                attrs={
                    "data-placeholder": "Search authored collections...",
                    "data-minimum-input-length": 0,
                },
            )
            authored_collections = CaseCollection.objects.filter(author=self.user).distinct().order_by("name")
            if getattr(self.instance, "pk", None):
                authored_collections = authored_collections.exclude(pk=self.instance.pk)
            self.fields["prerequisites"].queryset = authored_collections

        # Identify show_ fields
        show_fields = [f for f in self.fields if f.startswith("show_")]
        show_pre_fields = [f for f in show_fields if f.endswith("_pre")]
        show_post_fields = [f for f in show_fields if f.endswith("_post")]

        self.helper = FormHelper()
        self.helper.form_id = "id-casecollection-form"
        self.helper.form_class = "casecollection-form"
        self.helper.form_method = "post"
        self.helper.form_action = ""
        self.helper.form_tag = True
        # Group cid_user_groups and user_user_groups together if they exist in the form
        user_fields = []
        for f in ["cid_user_groups", "user_user_groups"]:
            if f in self.fields:
                user_fields.append(f)

        # Explicitly define show fields for pre and post
        show_pre_fields = [
            f
            for f in [
                "show_title_pre",
                "show_history_pre",
                "show_description_pre",
                "show_discussion_pre",
                "show_report_pre",
            ]
            if f in self.fields
        ]
        show_post_fields = [
            f
            for f in [
                "show_title_post",
                "show_history_post",
                "show_description_post",
                "show_discussion_post",
                "show_report_post",
                "show_case_link_post",
                "show_findings_post",
                "show_displaysets_post",
                "show_differentials_post",
            ]
            if f in self.fields
        ]

        self.helper.layout = Layout(
            Fieldset(
                "Collection Details",
                "name",
                Field("collection_type", css_class="mb-2"),
                Fieldset(
                    "Dates",
                    "restrict_to_dates",
                    Div(
                        Field(
                            "start_date",
                            wrapper_class="col-md-6",
                            css_class="form-group",
                        ),
                        Field(
                            "end_date", wrapper_class="col-md-6", css_class="form-group"
                        ),
                        css_class="form-row d-flex",
                    ),
                ),
                "active",
                "publish_results",
                "archive",
                "open_access",
                "candidates_only",
                "authors_only",
                "results_supervisor_visible",
                "exam_open_access",
                "markers",
                "exam_mode",
                "self_review",
                "feedback_once_collection_complete",
                "case_query_messaging_enabled",
                "show_results_sharing_information",
                "viewer_mode",
                "question_time_limit",
                "answer_entry_grace_period",
                "prerequisites",
                "pre_survey",
                "pre_survey_mandatory",
                "post_survey",
                "post_survey_mandatory",
                Fieldset(
                    "Custom Start Screen",
                    "start_screen_content",
                    "start_screen_resources",
                ),
                Fieldset(
                    "Custom End Overview",
                    "end_overview_content",
                    "end_overview_resources",
                ),
            ),
            Fieldset("Valid User Groups", *user_fields) if user_fields else None,
            Div(
                Div(
                    Fieldset("Show fields (pre-exam)", *show_pre_fields),
                    css_class="col-md-6",
                    style="float:left;",
                ),
                Div(
                    Fieldset("Show fields (post-exam)", *show_post_fields),
                    css_class="col-md-6",
                    style="float:left;",
                ),
                css_class="row",
                style="display: flex; flex-wrap: wrap;",
            ),
            Submit("submit", "Save Collection", css_class="btn btn-primary"),
        )


class NormalCaseForm(ModelForm):
    # Keep non-model input fields for age entry so users can provide a
    # value + unit (e.g. 6 months) which will be converted to canonical
    # days on save. The model now stores only `age_days`.
    age_value = forms.IntegerField(required=False, min_value=0)
    age_unit = forms.ChoiceField(required=False, choices=NormalCase.AGE_UNIT_CHOICES)

    class Meta:
        model = NormalCase
        fields = ("examination", "modality", "notes")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # If editing an existing NormalCase, prepopulate age_value/age_unit
        try:
            instance = kwargs.get('instance')
        except Exception:
            instance = None
        if instance is None:
            # also support self.instance when ModelForm sets it
            instance = getattr(self, 'instance', None)
        if instance is not None and getattr(instance, 'age_days', None) is not None:
            try:
                val, unit = NormalCase.days_to_value_unit(instance.age_days)
                if val is not None and unit is not None:
                    self.fields['age_value'].initial = val
                    self.fields['age_unit'].initial = unit
            except Exception:
                pass
        self.helper = FormHelper()
        # Do not render a <form> tag from crispy here because the template
        # already provides the surrounding form element (we inject this
        # fragment into a modal that contains the form). Rendering a second
        # <form> would nest forms and cause fields to be outside the
        # submitted form (HTMX would only send the outer form's fields).
        self.helper.form_method = "post"
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Div(
                Field("age_value", wrapper_class="col-md-3"),
                Field("age_unit", wrapper_class="col-md-3"),
                Field("examination", wrapper_class="col-md-3"),
                Field("modality", wrapper_class="col-md-3"),
                css_class="row",
            ),
            Field("notes"),
            # Submit button rendered in modal footer; avoid duplicate button here
        )

    def save(self, commit=True):
        # Respect commit flag. Compute canonical `age_days` from the
        # provided age_value/age_unit and set it on the instance.
        instance = super().save(commit=False)
        age_value = self.cleaned_data.get("age_value")
        age_unit = self.cleaned_data.get("age_unit")
        if age_value is not None and age_unit:
            instance.age_days = NormalCase.to_days(age_value, age_unit)
        else:
            # If the user left fields blank, clear any previous value.
            instance.age_days = None

        if commit:
            instance.save()
            self.save_m2m()

        return instance


class FindingForm(ModelForm):
    class Meta:
        model = Finding
        exclude = []
        # Use the canonical FK in forms (if users want to mark this Finding
        # as an alias of another). The old `synonym` M2M has been removed.
        widgets = {
            "canonical": autocomplete.ModelSelect2(
                url="atlas:finding-autocomplete"
            ),
        }


class StructureForm(ModelForm):
    class Meta:
        model = Structure
        exclude = []
        widgets = {
            "canonical": autocomplete.ModelSelect2(
                url="atlas:structure-autocomplete"
            ),
        }


class SubspecialtyForm(ModelForm):
    class Meta:
        model = Subspecialty
        exclude = []


class PresentationForm(ModelForm):
    class Meta:
        model = Presentation
        exclude = []


class PathologicalProcessForm(ModelForm):
    class Meta:
        model = PathologicalProcess
        exclude = []


class ProcedureForm(ModelForm):
    class Meta:
        model = Procedure
        exclude = []
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        try:
            self.helper = FormHelper()
            self.helper.form_method = "post"
            # Prevent crispy from rendering its own <form> tag so our
            # template-level form wrapper contains the submit buttons.
            self.helper.form_tag = False
        except Exception:
            pass


class SeriesFindingForm(ModelForm):
    class Meta:
        model = SeriesFinding
        exclude = [
            "series",
            "annotation_json",
            "viewport_json",
            "current_image_id_index",
            "annotation_json_3d",
            "viewer_state_3d",
        ]

        widgets = {
            "findings": autocomplete.ModelSelect2Multiple(
                url="atlas:finding-autocomplete"
            ),
            "structures": autocomplete.ModelSelect2Multiple(
                url="atlas:structure-autocomplete"
            ),
            "conditions": autocomplete.ModelSelect2Multiple(
                url="atlas:condition-autocomplete"
            ),
        }

    def __init__(self, *args, **kwargs):
        if kwargs.get("series_id"):
            # We get the 'initial' keyword argument or initialize it
            # as a dict if it didn't exist.
            initial = kwargs.setdefault("initial", {})
            # The widget for a ModelMultipleChoiceField expects
            # a list of primary key for the selected data.
            initial["series"] = kwargs.pop("series_id")
        # elif kwargs.get("instance"):
        #    # We get the 'initial' keyword argument or initialize it
        #    # as a dict if it didn't exist.
        #    initial = kwargs.setdefault("initial", {})
        #    # The widget for a ModelMultipleChoiceField expects
        #    # a list of primary key for the selected data.
        #    #initial["structures"] = [t.pk for t in kwargs["structures"].case.all()]
        #    #initial["structures"] = [t.pk for t in kwargs["structures"].case.all()]

        super(SeriesFindingForm, self).__init__(*args, **kwargs)

        ModelForm.__init__(self, *args, **kwargs)


class SeriesForm(ModelForm):
    class Media:
        # Django also includes a few javascript files necessary
        # for the operation of this form element. You need to
        # include <script src="/admin/jsi18n"></script>
        # in the template.
        css = {
            "all": ["css/widgets.css"],
        }
        # Adding this javascript is crucial
        js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"]

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop(
            "user"
        )  # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
        if kwargs.get("instance"):
            # We get the 'initial' keyword argument or initialize it
            # as a dict if it didn't exist.
            initial = kwargs.setdefault("initial", {})
            # The widget for a ModelMultipleChoiceField expects
            # a list of primary key for the selected data.
            initial["case"] = [t.pk for t in kwargs["instance"].case.all()]

        super(SeriesForm, self).__init__(*args, **kwargs)
        # self.fields["examination"] = ModelChoiceField(
        #    queryset=Examination.objects.all(),
        #    widget=FilteredSelectMultiple(verbose_name="Examination", is_stacked=False),
        # )

        self.fields["modality"] = ModelChoiceField(
            required=True,
            queryset=Modality.objects.all(),
        )

        ModelForm.__init__(self, *args, **kwargs)

        if self.user.groups.filter(name="atlas_editor").exists():
            case_queryset = Case.objects.all()
        else:
            case_queryset = Case.objects.filter(
                author__id=self.user.id
            )  # | Case.objects.filter(open_access=True)

        self.fields["case"] = ModelMultipleChoiceField(
            required=False,
            queryset=case_queryset,
            widget=FilteredSelectMultiple(verbose_name="Case", is_stacked=False),
        )

    def save(self, commit=True):
        # Get the unsaved Long instance
        instance = ModelForm.save(self, False)

        # Prepare a 'save_m2m' method for the form,
        old_save_m2m = self.save_m2m

        def save_m2m():
            old_save_m2m()
            instance.case.clear()
            for case in self.cleaned_data["case"]:
                # We need the id here
                instance.case.add(case.pk)

        self.save_m2m = save_m2m

        # Do we need to save all changes now?
        # if commit:
        instance.save()
        self.save_m2m()

        # instance.anonymise_images()

        return instance

    class Meta:
        model = Series
        exclude = ["author", "series_instance_uid"]

        widgets = {
            "examination": autocomplete.ModelSelect2(
                url="generic:examination-autocomplete"
            ),
            "description": Textarea(attrs={"maxlength": 1000, "rows": 5, "cols": 80}),
        }

    # def save(self, commit=True):
    #    # Get the unsaved Case instance
    #    instance = ModelForm.save(self, False)

    #    # Prepare a 'save_m2m' method for the form,
    #    old_save_m2m = self.save_m2m

    #    def save_m2m():
    #        old_save_m2m()
    #        # instance.exams.clear()
    #        # for exam in self.cleaned_data["exams"]:
    #        #    # We need the id here
    #        #    instance.exams.add(exam.pk)

    #    self.save_m2m = save_m2m

    #    # Do we need to save all changes now?
    #    # if commit:
    #    instance.save()
    #    self.save_m2m()

    #    return instance


class CaseForm(ModelForm):
    class Media:
        # Django also includes a few javascript files necessary
        # for the operation of this form element. You need to
        # include <script src="/admin/jsi18n"></script>
        # in the template.
        # history = forms.CharField(
        #    widget=ClearableTextarea(attrs={"maxlength": 1000, "rows": 5, "cols": 80}),
        #    required=False,
        # )

        css = {
            "all": ["css/widgets.css"],
        }
        # Adding this javascript is crucial
        js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"]

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop(
            "user"
        )  # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole

        # This is populated if we create the case from an exam/collection
        self.collection = None
        initial = kwargs.setdefault("initial", {})
        if initial is not None and "exams" in initial:
            collections = initial.pop("exams")
            logger.debug(collections)

            self.collection = get_object_or_404(CaseCollection, pk=collections[0])

        if kwargs.get("instance"):
            # We get the 'initial' keyword argument or initialize it
            # as a dict if it didn't exist.
            initial = kwargs.setdefault("initial", {})
            # The widget for a ModelMultipleChoiceField expects
            # a list of primary key for the selected data.
            # initial["exams"] = [t.pk for t in kwargs["instance"].exams.all()]

        ModelForm.__init__(self, *args, **kwargs)

        super(CaseForm, self).__init__(*args, **kwargs)
        # self.fields["series"].queryset =

        if self.user.groups.filter(name="atlas_editor").exists():
            case_queryset = Case.objects.all().order_by("pk").prefetch_related()
        else:
            case_queryset = get_cases_available_to_user(self.user)
        self.fields["previous_case"].queryset = case_queryset

        self.helper = FormHelper()
        self.helper.form_id = "id-case-form"
        self.helper.form_class = "case-form"
        self.helper.form_method = "post"
        self.helper.form_action = "submit"
        self.helper.form_tag = False

        self.helper.layout = Layout(
            Fieldset(
                "Case details",
                "title",
                "subspecialty",
                "description",
                "history",
                "presentation",
                "discussion",
                "report",
                "procedures",
                "condition",
                "pathological_process",
                "open_access",
                "previous_case",
                "diagnostic_certainty",
                #cimar_uuid",
                "study_date",
            ),
        )

    def get_queryset(self, request):
        return (
            super()
            .get_queryset(request)
            .prefetch_related(
                "condition", "subspecialty", "pathological_process", "series"
            )
        )

    class Meta:
        model = Case
        # fields = ['due_back']
        # fields = '__all__'
        fields = [
            # "examination",
            # "site",
            "title",
            "subspecialty",
            "description",
            "history",
            "presentation",
            "discussion",
            "procedures",
            "condition",
            "pathological_process",
            "report",
            "open_access",
            "previous_case",
            "diagnostic_certainty",
            #cimar_uuid",
            "study_date",
        ]
        # fields = ['question', 'findings', 'subspecialty', 'references']
        widgets = {
            # "normal": RadioSelect(
            # choices=[(True, 'Yes'),
            #     (False, 'No')])
            # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
            # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
            "title": ClearableInput(attrs={"rows": 1}),
            "description": ClearableTextarea(attrs={"cols": 80, "rows": 5}),
            "history": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
            "discussion": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
            "report": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}),
            "condition": autocomplete.ModelSelect2Multiple(
                url="atlas:condition-autocomplete"
            ),
            "presentation": autocomplete.ModelSelect2Multiple(
                url="atlas:presentation-autocomplete"
            ),
            "subspecialty": CheckboxSelectMultiple(),
            "pathological_process": CheckboxSelectMultiple(),
            "previous_case": CaseSelect(),
            "procedures": autocomplete.ModelSelect2Multiple(
                url="atlas:procedure-autocomplete"
            ),
        }

    def clean_cimar_uuid(self):
        cimar_uuid = self.cleaned_data["cimar_uuid"]
        if cimar_uuid:
            with CimarAPI(username=CIMAR_USERNAME, password=CIMAR_PASSWORD) as api:
                try:
                    study = api.get_study_by_uuid(cimar_uuid)

                except NotFoundError:
                    raise ValidationError("Study not found in CIMAR")

        return cimar_uuid

    def save(self, commit=True):
        # Get the unsaved Case instance
        instance = ModelForm.save(self, False)

        # Prepare a 'save_m2m' method for the form,
        old_save_m2m = self.save_m2m

        def save_m2m():
            old_save_m2m()
            # instance.exams.clear()
            # for exam in self.cleaned_data["exams"]:
            #    # We need the id here
            #    instance.exams.add(exam.pk)

        self.save_m2m = save_m2m

        # Do we need to save all changes now?
        if commit:
            instance.save()
            self.save_m2m()

            logger.debug(f"{self.collection=}")

            if self.collection is not None:
                logger.debug(f"{self.collection=}")
                case_no = self.collection.cases.count() + 1
                logger.debug(f"{case_no=}")
                logger.debug(f"{instance=}")

                # Create through model
                # cd = CaseDetail(case=instance, collection=exam, sort_order=case_no)
                self.collection.cases.add(
                    instance, through_defaults={"sort_order": case_no}
                )

        return instance


CaseDifferentialFormSet = inlineformset_factory(
    Case,
    Differential,
    exclude=[],
    can_delete=True,
    extra=1,
    max_num=10,
    widgets={
        "condition": autocomplete.ModelSelect2(url="atlas:condition-autocomplete"),
        "text": Textarea(attrs={"maxlength": 1000, "rows": 3, "cols": 80}),
    },
)


class ResourceForm(ModelForm):
    class Meta:
        model = Resource
        exclude = ["author", "created_date"]

        widgets = {
            "description": Textarea(attrs={"cols": 80, "rows": 5}),
            "url": TextInput(attrs={"cols": 80, "rows": 1}),
        }


class CaseResourceForm(ModelForm):
    def __init__(self, *args, user, **kwargs):
        super(CaseResourceForm, self).__init__(*args, **kwargs)

        if not user.groups.filter(name="atlas_editor").exists():
            queryset = Resource.objects.filter(author__id=user.id)
        else:
            queryset = Resource.objects.all()

        self.fields["resource"] = ModelChoiceField(
            required=False,
            queryset=queryset,
            # widget=Select(verbose_name="Series", is_stacked=False),
        )

        self.fields["pre_review"] = BooleanField(required=False)


# class CaseSeriesForm(ModelForm):
#
#    def __init__(self, *args, user, **kwargs):
#        super(CaseSeriesForm, self).__init__(*args, **kwargs)
#
#        if not user.groups.filter(name="atlas_editor").exists():
#            queryset = Series.objects.filter(author__id=user.id)
#        else:
#            queryset = Series.objects.all()
#
#        # Optimize the queryset for the ModelChoiceField
#        queryset = queryset.select_related(
#            "modality"
#        ).prefetch_related(
#            "examination", "plane", "contrast", "images"
#        )
#
#        self.fields["series"] = ModelChoiceField(
#            required=False,
#            queryset=queryset,
#            # widget=Select(verbose_name="Series", is_stacked=False),
#        )


class CaseCollectionCaseForm(ModelForm):
    def __init__(self, *args, user, collection=None, **kwargs):
        super(CaseCollectionCaseForm, self).__init__(*args, **kwargs)

        queryset = get_cases_available_to_user(user, collection=collection)

        self.fields["case"] = ModelChoiceField(
            required=False,
            queryset=queryset,
            # widget=Select(verbose_name="Series", is_stacked=False),
        )


# SeriesFormSet = inlineformset_factory(
#    Case,
#    Series.case.through,
#    form=CaseSeriesForm,
#    exclude=[],
#    can_delete=True,
#    extra=0,
#    max_num=10,
# )

CaseCollectionCaseFormSet = inlineformset_factory(
    CaseCollection,
    Case.casecollection_set.through,
    form=CaseCollectionCaseForm,
    exclude=[
        "question_schema",
        "question_answers",
        "series_visibility_config",
        "learner_comment",
        "learner_review_comment",
    ],
    can_delete=True,
    extra=0,
    max_num=50,
)

CaseResourceFormSet = inlineformset_factory(
    Case,
    Resource.case_set.through,
    form=CaseResourceForm,
    exclude=[],
    can_delete=True,
    extra=1,
    max_num=10,
)

SeriesImageFormSet = inlineformset_factory(
    Series,
    SeriesImage,
    exclude=[
        "dicom_tags_ohif",
        "replaced",
        "image_md5_hash",
        "image_blake3_hash",
        "is_dicom",
    ],
    can_delete=True,
    extra=0,
    max_num=2000,
)


class JsonAnswerForm(Form):
    json_answer = JSONSchemaField(
        schema={},
        # options = 'schema/options.json'
        options="schema/options.json",
    )

    class Meta:
        fields = ["json_answer"]

    def __init__(self, *args, question_schema=None, **kwargs):
        super(JsonAnswerForm, self).__init__(*args, **kwargs)
        if question_schema is not None:
            self.fields["json_answer"] = JSONSchemaField(
                schema=question_schema, options="schema/options.json"
            )
            self.fields["json_answer"].label = False


class BaseReportAnswerForm(ModelForm):
    class Meta:
        fields = ["answer"]

        widgets = {
            # "normal": RadioSelect(
            # choices=[(True, 'Yes'),
            #     (False, 'No')])
            # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
            # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
            "answer": Textarea(
                attrs={"cols": 100, "rows": 5, "placeholder": "Report text"}
            ),
        }

        # help_texts = {
        #    "answer": "Write your answer in here."
        # }

    def __init__(self, *args, casedetail, **kwargs):
        super(BaseReportAnswerForm, self).__init__(*args, **kwargs)
        self.fields["answer"].required = False


class BaseQuestionAnswerForm(ModelForm):
    json_answer = JSONSchemaField(schema={}, options="schema/options.json")

    class Meta:
        fields = ["json_answer"]
        labels = {"json_answer": ""}

        # help_texts = {
        #    "answer": "Write your answer in here."
        # }

    def __init__(self, *args, casedetail, **kwargs):
        super(BaseQuestionAnswerForm, self).__init__(*args, **kwargs)
        # self.fields["json_answer"].schema = casedetail.question_schema
        if casedetail.question_schema is not None:
            self.fields["json_answer"] = JSONSchemaField(
                schema=casedetail.question_schema, options="schema/options.json"
            )
            self.fields["json_answer"].label = ""


class CidReportAnswerForm(BaseReportAnswerForm):
    class Meta(BaseReportAnswerForm.Meta):
        model = CidReportAnswer


class UserReportAnswerForm(BaseReportAnswerForm):
    class Meta(BaseReportAnswerForm.Meta):
        model = UserReportAnswer


class CidQuestionAnswerForm(BaseQuestionAnswerForm):
    class Meta(BaseQuestionAnswerForm.Meta):
        model = CidReportAnswer


class UserQuestionAnswerForm(BaseQuestionAnswerForm):
    class Meta(BaseQuestionAnswerForm.Meta):
        model = UserReportAnswer


class CidReportAnswerMarkForm(ModelForm):
    class Meta:
        model = CidReportAnswer
        fields = ["score", "feedback"]

        widgets = {
            # "normal": RadioSelect(
            # choices=[(True, 'Yes'),
            #     (False, 'No')])
            # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
            # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
            "feedback": Textarea(attrs={"cols": 80, "rows": 4}),
        }

    def __init__(self, *args, **kwargs):
        super(CidReportAnswerMarkForm, self).__init__(*args, **kwargs)
        self.fields["score"].required = False


# class AddCollectionToCaseForm(Form):
#
#    def __init__(self, *args, user, **kwargs):
#        super(AddCollectionToCaseForm, self).__init__(*args, **kwargs)
#
#        if not user.groups.filter(name="atlas_editor").exists():
#            queryset = CaseCollection.objects.filter(author__id=user.id)
#        else:
#            queryset = CaseCollection.objects.all()
#
#        self.valid_collections = queryset
#        self.case = get_object_or_404(Case, pk=pk)
#
#        self.fields["casecollection"] = ModelMultipleChoiceField(
#            required=False,
#            queryset=queryset,
#            # widget=Select(verbose_name="Series", is_stacked=False),
#        )
#
#    def save(self, *args, **kwargs):
#        print("SAVE", args, kwargs)
#        instance = super(AddCollectionToCaseForm, self).save(*args, **kwargs)
#
#        # Here we save the modified project selection back into the database
#        instance.casecollection_set.set(self.cleaned_data['casecollection'])
#
#        print(f"{instance=}")
#
#        return instance
#
#    def clean_casecollection(self):
#        collection_set = self.cleaned_data["casecollection"]
#
#        print(f"{collection_set=}")
#
#        for col in collection_set:
#            if col not in self.valid_collections:
#                raise ValidationError("Invalid collection")
#
#        return collection_set


class AddCollectionToCaseForm(Form):
    def __init__(self, *args, user, **kwargs):
        super(AddCollectionToCaseForm, self).__init__(*args, **kwargs)

        if not user.groups.filter(name="atlas_editor").exists():
            queryset = CaseCollection.objects.filter(author__id=user.id)
        else:
            queryset = CaseCollection.objects.all()

        self.valid_collections = queryset

        self.fields["casecollection"] = ModelMultipleChoiceField(
            required=False,
            queryset=queryset,
            # widget=Select(verbose_name="Series", is_stacked=False),
        )

    def save(self, *args, **kwargs):
        instance = super(AddCollectionToCaseForm, self).save(*args, **kwargs)

        # Here we save the modified project selection back into the database
        instance.casecollection_set.set(self.cleaned_data["casecollection"])

        return instance

    def clean_casecollection(self):
        collection_set = self.cleaned_data["casecollection"]

        for col in collection_set:
            if col not in self.valid_collections:
                raise ValidationError("Invalid collection")

        return collection_set


class SelfReviewForm(ModelForm):
    SCORE_CHOICES = [
        (1, "1 - Poor"),
        (2, "2 - Limited"),
        (3, "3 - Adequate"),
        (4, "4 - Good"),
        (5, "5 - Excellent"),
    ]

    class Meta:
        model = SelfReview
        fields = ["user_exam", "case", "comments", "findings", "interpretation"]
        widgets = {"user_exam": HiddenInput(), "case": HiddenInput()}

    def __init__(self, *args, **kwargs):
        if "user_exam_id" in kwargs:
            user_exam_id = kwargs.pop("user_exam_id")
        if "case_id" in kwargs:
            case_id = kwargs.pop("case_id")

        ModelForm.__init__(self, *args, **kwargs)
        super(SelfReviewForm, self).__init__(*args, **kwargs)

        # Render scoring fields as explicit 1-5 selector choices for cleaner UX.
        self.fields["findings"] = forms.TypedChoiceField(
            choices=self.SCORE_CHOICES,
            coerce=int,
            empty_value=None,
            required=False,
            widget=RadioSelect,
            label="Findings",
            help_text="How well did you identify key findings?",
        )
        self.fields["interpretation"] = forms.TypedChoiceField(
            choices=self.SCORE_CHOICES,
            coerce=int,
            empty_value=None,
            required=False,
            widget=RadioSelect,
            label="Interpretation",
            help_text="How strong was your overall interpretation?",
        )

        self.fields["comments"].widget.attrs.update(
            {
                "rows": 5,
                "placeholder": "What went well? What would you do differently next time?",
            }
        )

        self.helper = FormHelper()
        self.helper.form_method = "post"
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Field("user_exam"),
            Field("case"),
            Field("comments"),
            Field("findings"),
            Field("interpretation"),
        )


class CaseReviewMessageForm(ModelForm):
    class Meta:
        model = CaseReviewMessage
        fields = ["message", "requires_acknowledgement"]

    def __init__(self, *args, allow_ack=False, message_placeholder=None, **kwargs):
        super(CaseReviewMessageForm, self).__init__(*args, **kwargs)

        self.fields["message"].widget.attrs.update(
            {
                "rows": 3,
                "placeholder": message_placeholder or "Write a message...",
                "class": "form-control",
            }
        )

        self.fields["requires_acknowledgement"].widget.attrs.update(
            {
                "class": "form-check-input",
            }
        )

        if not allow_ack:
            self.fields["requires_acknowledgement"].widget = HiddenInput()
            self.fields["requires_acknowledgement"].required = False

        self.helper = FormHelper()
        self.helper.form_method = "post"
        self.helper.form_tag = False


class UncategorisedDicomForm(ModelForm):
    class Meta:
        model = UncategorisedDicom
        fields = ["image"]  # , "user"]


# UncategorisedDicomFormset = formset_factory(UncategorisedDicomForm)

# class ConditionAutocompleteFormModel(forms.ModelForm):
#    class Meta:
#        model = Condition
#        fields = ["name"]
#        widgets = {
#            'members': widgets.Autocomplete(
#                name='members',
#                options=dict(multiselect=True, model=Condition)
#            )
#        }


class ConditionAutocomplete(ModelAutocomplete):
    model = Condition
    search_attrs = ["name"]

    @classmethod
    def get_items_from_keys(cls, keys, context_obj=None):
        """Return items for given keys, ignoring empty strings.

        The autocomplete frontend sometimes sends empty string keys which
        causes the ORM to raise ValueError when filtering numeric PK fields.
        Filter out falsy/blank keys before querying. If no valid keys remain
        return an empty queryset.
        """
        # normalize and remove empty/blank values
        clean_keys = [k for k in keys if k is not None and str(k).strip() != ""]
        if not clean_keys:
            return cls.model.objects.none()

        # try to convert numeric keys to ints where possible
        parsed_keys = []
        for k in clean_keys:
            try:
                parsed_keys.append(int(k))
            except Exception:
                parsed_keys.append(k)

        qs = cls.model.objects.filter(pk__in=parsed_keys)

        # Return a list of dicts matching the autocomplete core expectations
        # (items subscriptable by keys like 'key' / 'label').
        return [
            {"key": obj.pk, "label": getattr(obj, "name", str(obj)), "value": str(obj)}
            for obj in qs
        ]


class ConditionAutocompleteForm(Form):
    condition = ModelChoiceField(
        queryset=Condition.objects.all(),
        widget=AutocompleteWidget(
            ac_class=ConditionAutocomplete  # , options=dict(model=Condition)
        ),
    )


autocomplete_register(ConditionAutocomplete)


class CaseCollectionAuthorForm(ExamAuthorFormMixin):
    class Meta(ExamAuthorFormMixin.Meta):
        model = CaseCollection


class CaseAuthorForm(ExamAuthorFormMixin):
    class Meta(ExamAuthorFormMixin.Meta):
        model = Case


class SeriesAuthorForm(ExamAuthorFormMixin):
    class Meta(ExamAuthorFormMixin.Meta):
        model = Series


class ExamGroupsForm(ExamGroupsFormMixin):
    class Meta(ExamGroupsFormMixin.Meta):
        model = CaseCollection


class AnswerJSONForm(Form):
    json = JSONSchemaField(schema="schema/schema.json", options="schema/options.json")


class SvelteJSONEditorWidgetOverride(SvelteJSONEditorWidget):
    template_name = "atlas/svelte_jsoneditor_widget_override.html"


class CaseQuestionForm(ModelForm):
    class Meta:
        model = CaseDetail
        fields = ["question_schema", "question_answers"]  # , "user"]

        widgets = {
            "question_schema": SvelteJSONEditorWidgetOverride(),
            "question_answers": SvelteJSONEditorWidgetOverride(),
        }

class CaseDetailForm(ModelForm):
    class Meta:
        model = CaseDetail
        fields = [
            "redact_history",
            "override_history",
            "learner_comment",
            "learner_review_comment",
            "question_time_limit_override",
            "answer_entry_grace_period_override",
        ]

    def __init__(self, *args, case_history: str = None, **kwargs):
        """
        case_history: optional explicit history text to show (falls back to instance.case.history)
        """
        super().__init__(*args, **kwargs)

        # Determine original history text (safe-escaped for HTML)
        if case_history is None:
            try:
                case_history = self.instance.case.history if self.instance and getattr(self.instance, "case", None) else ""
            except Exception:
                case_history = ""
        case_history_html = escape(case_history or "No history available.")

        # Ensure override_history has a stable id we can reference from the inline script
        override_id = self.fields["override_history"].widget.attrs.get("id", "id_override_history")
        self.fields["override_history"].widget.attrs["id"] = override_id
        # Optionally make it a textarea style appearance if not already
        self.fields["override_history"].widget.attrs.setdefault("rows", 6)
        self.fields["override_history"].widget.attrs.setdefault("class", "form-control")

        if "question_time_limit_override" in self.fields:
            self.fields["question_time_limit_override"].help_text = (
                "Leave blank to use the collection default answer time limit."
            )

        if "answer_entry_grace_period_override" in self.fields:
            self.fields["answer_entry_grace_period_override"].help_text = (
                "Leave blank to use the collection default answer-only grace period."
            )

        # Build crispy helper/layout embedding the original history and buttons tied to override_history
        self.helper = FormHelper()
        self.helper.form_tag = False

        # Inline HTML block with buttons and a script that copies/clears the override field.
        # The script references the explicit override_id above.
        history_block = f"""
        <details class="mb-3">
          <summary class="h6" style="cursor:pointer;"><i class="bi bi-info-circle"></i> Show original case history</summary>
          <div class="mt-2 p-2 bg-dark text-light border rounded">
            <label class="form-label fw-bold">Original history (read-only)</label>
            <div id="original-history" style="white-space: pre-wrap;">{case_history_html}</div>
            <div class="form-text text-secondary">Use the buttons below to copy this into the override field or clear the override.</div>
            <div class="mt-2">
              <button type="button" class="btn btn-sm btn-outline-light" id="use-original-history">Copy original into override</button>
              <button type="button" class="btn btn-sm btn-outline-danger ms-2" id="clear-override-history">Clear override</button>
            </div>
          </div>
        </details>
        <script>
        document.addEventListener('DOMContentLoaded', function() {{
          var copyBtn = document.getElementById('use-original-history');
          var clearBtn = document.getElementById('clear-override-history');
          var original = '{case_history_html.replace("'", "\\'").replace("\\n", "\\\\n")}';
          var overrideField = document.getElementById('{override_id}');
          function setOverride(val) {{ 
            if(!overrideField) return; overrideField.value = val; overrideField.dispatchEvent(new Event('input',{{bubbles:true}})); }}
          if(copyBtn) copyBtn.addEventListener('click', function(e){{ setOverride(original); }});
          if(clearBtn) clearBtn.addEventListener('click', function(e){{ setOverride(''); }});
        }});
        </script>
        """

        # Compose layout: history block then the override field and redact checkbox
        self.helper.layout = Layout(
            HTML(history_block),
            Field("override_history"),
            Field("redact_history"),
            Fieldset(
                "Learner Comments",
                Field("learner_comment"),
                Field("learner_review_comment"),
            ),
            Fieldset(
                "Case Timing Overrides",
                Field("question_time_limit_override"),
                Field("answer_entry_grace_period_override"),
            ),
        )



class QuestionSchemaForm(ModelForm):
    class Meta:
        model = QuestionSchema
        fields = ["name", "description", "schema"]

        widgets = {
            "schema": SvelteJSONEditorWidgetOverride(),
        }


# class ModelPriorCaseForm(ModelForm):
#    class Meta:
#        model = CasePrior
#        fields = ["case_detail", "prior_case", "relation_text", "prior_visibility"]


class PriorCaseForm(Form):
    relation = CharField(max_length=255, required=True)

    prior_visibility = CharField(max_length=2, required=True)


class CaseSeriesForm(forms.ModelForm):
    def __init__(self, *args, queryset, user=None, is_atlas_editor=False, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["series"].queryset = queryset
        self.fields["series"].label_from_instance = lambda obj: f"Series {obj.pk}: {obj.description or ''} ({obj.modality or ''})"

    class Meta:
        model = SeriesDetail  # through model for Case.series
        
        fields = ["series", "sort_order", "feedback"]
        widgets = {
            "series": autocomplete.ModelSelect2(
                url="atlas:series-autocomplete",
                attrs={
                    "data-placeholder": "Search by Series ID or description...",
                    "data-minimum-input-length": 0,
                }
            )
        }


class CaseUpdateSeriesForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = "id-case-form"
        self.helper.form_class = "case-form"
        self.helper.form_method = "post"
        self.helper.form_action = "submit"
        self.helper.form_tag = False
        self.helper.layout = Layout()

    class Meta:
        model = Case
        fields = []  # No fields, just for compatibility with UpdateView


class BaseSeriesFormSet(BaseInlineFormSet):
    def __init__(self, *args, queryset=None, **kwargs):
        self.series_queryset = queryset
        super().__init__(*args, **kwargs)

    def get_form_kwargs(self, index):
        kwargs = super().get_form_kwargs(index)
        kwargs["queryset"] = self.series_queryset
        return kwargs


SeriesFormSet = inlineformset_factory(
    Case,
    SeriesDetail,
    form=CaseSeriesForm,
    formset=BaseSeriesFormSet,
    extra=1,
    can_delete=True,
)


class CaseDisplaySetForm(forms.ModelForm):
    class Meta:
        model = CaseDisplaySet
        fields = [
            "name",
            "description",
            "findings",
            "structures",
            "conditions",
        ]
        widgets = {
            "name": forms.TextInput(
                attrs={"class": "form-control", "placeholder": "Display Set Name"}
            ),
            "description": forms.Textarea(
                attrs={"class": "form-control", "rows": 3, "placeholder": "Description"}
            ),
            "findings": autocomplete.ModelSelect2Multiple(
                url="atlas:finding-autocomplete"
            ),
            "structures": autocomplete.ModelSelect2Multiple(
                url="atlas:structure-autocomplete"
            ),
            "conditions": autocomplete.ModelSelect2Multiple(
                url="atlas:condition-autocomplete"
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Optionally, you can filter queryset for findings/structures/conditions here if needed


class CaseCollectionUpdateCaseForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        self.collection = kwargs.pop("collection", None)
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = "id-case-form"
        self.helper.form_class = "case-form"
        self.helper.form_method = "post"
        self.helper.form_action = "submit"
        self.helper.form_tag = False
        self.helper.layout = Layout()

    class Meta:
        model = CaseCollection
        fields = []  # No fields, just for compatibility with UpdateView
