from __future__ import annotations

import copy
import json
from typing import Any, Optional, Sequence

from django.http import Http404, HttpResponse, HttpResponseBadRequest, QueryDict
from django.template.loader import render_to_string
from django.utils.functional import Promise
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
from django.utils import timezone
from django.utils.formats import date_format
from django.forms.forms import NON_FIELD_ERRORS

from powercrud.templatetags import powercrud as powercrud_tags
from powercrud.logging import get_logger
from .config_mixin import resolve_config

log = get_logger(__name__)


class InlineEditingMixin:
    """Handle HTMX endpoints for inline row editing."""

    inline_action: str | None = None

    def dispatch(self, request, *args, **kwargs):  # pragma: no cover - thin wrapper
        inline_action = getattr(self, "inline_action", None)
        if inline_action == "inline_row":
            return self._dispatch_inline_row(request, *args, **kwargs)
        if inline_action == "inline_dependency":
            return self._dispatch_inline_dependency(request, *args, **kwargs)
        return super().dispatch(request, *args, **kwargs)

    # ------------------------------------------------------------------
    # Inline row handling
    # ------------------------------------------------------------------
    def _dispatch_inline_row(self, request, *args, **kwargs):
        if not self.get_inline_editing():
            raise Http404("Inline editing disabled")

        self.kwargs = kwargs
        self.request = request
        obj = self.get_object()

        should_render_display = request.GET.get("inline_display") or request.POST.get(
            "inline_display"
        )

        auth_state = self._evaluate_inline_state(obj, request)
        if auth_state["status"] != "ok":
            return self._build_inline_guard_response(obj, auth_state)

        if should_render_display:
            html = self._render_inline_row_display(obj)
            return HttpResponse(html)

        if request.method == "POST":
            lock_state = self._evaluate_inline_state(obj, request)
            if lock_state["status"] != "ok":
                return self._build_inline_guard_response(obj, lock_state)

            form = self.build_inline_form(
                instance=obj, data=request.POST, files=request.FILES
            )
            self._prepare_inline_number_widgets(form)
            self._prepare_inline_preservation(form, request.POST)
            self._preserve_inline_raw_data(form, request.POST)
            self._restore_inline_preserved_dataset(form)
            if form.is_valid():
                post_save_state = self._evaluate_inline_state(obj, request)
                if post_save_state["status"] != "ok":
                    return self._build_inline_guard_response(obj, post_save_state)

                self.object = form.save()
                row_html = self._render_inline_row_display(self.object)
                response = HttpResponse(row_html)
                response["HX-Trigger"] = json.dumps(
                    {"inline-row-saved": {"pk": self.object.pk}}
                )
                return response

            # Log validation context to help downstream debugging.
            posted_fields = [
                key for key in request.POST.keys() if key != "csrfmiddlewaretoken"
            ]
            try:
                error_details = form.errors.get_json_data()
            except Exception:
                error_details = form.errors
            log.error(
                "Inline save failed for pk %s (row id %s). Errors=%s | posted_fields=%s | inline_edit_fields=%s",
                getattr(obj, "pk", None),
                getattr(self, "get_inline_row_id", lambda _: None)(obj)
                if callable(getattr(self, "get_inline_row_id", None))
                else None,
                error_details,
                posted_fields,
                resolve_config(self).inline_edit_fields,
            )

            error_summary = self._get_inline_form_error_summary(form)
            html = self._render_inline_row_form(
                obj, form=form, error_summary=error_summary
            )
            response = HttpResponse(html)
            row_id = None
            row_id_getter = getattr(self, "get_inline_row_id", None)
            if callable(row_id_getter):
                try:
                    row_id = row_id_getter(obj)
                except Exception:
                    row_id = None
            response["HX-Trigger"] = json.dumps(
                {
                    "inline-row-error": {
                        "pk": obj.pk,
                        "row_id": row_id,
                        "message": error_summary
                        or str(_("Inline save failed. Fix the errors and try again.")),
                    }
                }
            )
            return response

        html = self._render_inline_row_form(obj, form=None)
        return HttpResponse(html)

    # ------------------------------------------------------------------
    # Dependency handling
    # ------------------------------------------------------------------
    def _dispatch_inline_dependency(self, request, *args, **kwargs):
        if not self.get_inline_editing():
            raise Http404("Inline editing disabled")
        cfg = resolve_config(self)

        field = request.POST.get("field")
        if not field:
            return HttpResponseBadRequest("Missing field parameter")

        pk_url_kwarg = getattr(self, "pk_url_kwarg", "pk")
        pk = request.POST.get("pk") or kwargs.get(pk_url_kwarg)
        if not pk:
            resolver_match = getattr(request, "resolver_match", None)
            resolver_kwargs = getattr(resolver_match, "kwargs", {}) if resolver_match else {}
            pk = resolver_kwargs.get(pk_url_kwarg)
        self.kwargs = kwargs
        self.request = request

        obj = None
        if pk:
            self.kwargs[pk_url_kwarg] = pk
            try:
                obj = self.get_object()
            except Http404:
                obj = None

        form = self.build_inline_form(
            instance=obj, data=request.POST, files=request.FILES
        )
        if field not in form.fields:
            return HttpResponseBadRequest("Invalid field")

        field_dependencies = self.get_inline_field_dependencies()
        field_dependency = field_dependencies.get(field)
        field_dependency_context = dict(field_dependency) if field_dependency else None
        inline_context = self.get_inline_context()
        dependency_endpoint_name = (
            field_dependency_context.get("endpoint_name")
            if field_dependency_context
            else self.get_inline_dependency_endpoint_name()
        )
        resolved_dependency_endpoint_url = None
        if dependency_endpoint_name:
            reverse_kwargs = {pk_url_kwarg: obj.pk} if obj and getattr(obj, "pk", None) else None
            resolved_dependency_endpoint_url = self.safe_reverse(
                dependency_endpoint_name,
                kwargs=reverse_kwargs,
            ) or self.safe_reverse(dependency_endpoint_name)
        if field_dependency_context is not None:
            field_dependency_context["endpoint_url"] = (
                resolved_dependency_endpoint_url
                or field_dependency_context.get("endpoint_url")
            )
        widget_html = render_to_string(
            f"{cfg.templates_path}/partial/inline_field.html",
            {
                "field": form[field],
                "field_name": field,
                "field_dependency": field_dependency_context,
                "dependency_endpoint_url": (
                    resolved_dependency_endpoint_url
                    or inline_context.get("dependency_endpoint_url")
                ),
            },
            request=request,
        )
        return HttpResponse(widget_html)

    # ------------------------------------------------------------------
    # Required-field preservation
    # ------------------------------------------------------------------
    def build_inline_form(self, *, instance, data=None, files=None):
        """Construct the inline form and prime preservation metadata.

        Args:
            instance: Model instance being edited inline.
            data: POST payload when handling a save.
            files: Uploaded files (unused for inline rows today).

        Returns:
            forms.ModelForm: Inline form wired with any required-field preservation
            metadata so subsequent POSTs can be rehydrated.
        """
        form = super().build_inline_form(instance=instance, data=data, files=files)
        self._prepare_inline_preservation(form, data)
        return form

    def get_inline_preserve_required_fields(self) -> bool:
        """Flag controlling whether PowerCRUD auto-preserves missing required inputs."""
        return bool(resolve_config(self).inline_preserve_required_fields)

    def _prepare_inline_preservation(self, form, data=None):
        """Record which fields need preservation and hydrate initial POST clones.

        Args:
            form: Inline ModelForm instance.
            data: POST data to seed the preserved QueryDict (may be None on GET).
        """
        if not self.get_inline_preserve_required_fields() or not form:
            return

        ready = getattr(form, "_inline_preservation_ready", False)
        preserved = getattr(form, "_inline_preserved_fields", None)
        if not ready or preserved is None:
            preserved = self._configure_inline_preserved_fields(form)
            form._inline_preservation_ready = True

        if (
            preserved
            and data is not None
            and not getattr(form, "_inline_preserved_data", None)
        ):
            dataset = self._build_inline_preserved_dataset(form, data)
            if dataset is not None:
                form._inline_preserved_data = dataset

    def _configure_inline_preserved_fields(self, form) -> list[str]:
        """Determine which required fields must be silently re-posted.

        Args:
            form: Inline ModelForm instance.

        Returns:
            list[str]: Field names that will be cloned into hidden inputs.
        """
        if not form:
            return []

        inline_fields = set(self.get_inline_edit_fields())
        if not inline_fields:
            form._inline_preserved_fields = []
            return []
        preserved: list[str] = []
        fields = getattr(form, "fields", {})
        for name, field in fields.items():
            if not getattr(field, "required", False):
                continue
            if inline_fields and name in inline_fields:
                continue
            widget = getattr(field, "widget", None)
            if widget and getattr(widget, "is_hidden", False):
                continue
            preserved.append(name)

        form._inline_preserved_fields = preserved
        return preserved

    def _build_inline_preserved_dataset(self, form, data):
        """Create a mutable QueryDict containing preserved field values.

        Args:
            form: Inline ModelForm instance.
            data: Original POST payload.

        Returns:
            QueryDict | None: Cloned data including preserved required fields, or
            None if cloning failed.
        """
        preserved = getattr(form, "_inline_preserved_fields", None)
        if not preserved or not data:
            return None

        mutable = self._clone_inline_post_data(data)
        if mutable is None:
            return None

        for name in preserved:
            field = form.fields.get(name)
            if not field:
                continue
            values = self._format_inline_preserved_values(form, field, name)
            key = form.add_prefix(name) if hasattr(form, "add_prefix") else name
            mutable.setlist(key, values)

        return mutable

    def _restore_inline_preserved_dataset(self, form):
        """Swap the inline form's data with the preserved QueryDict, if any."""
        if not self.get_inline_preserve_required_fields():
            return
        dataset = getattr(form, "_inline_preserved_data", None)
        if dataset is not None:
            form.data = dataset

    def _format_inline_preserved_values(self, form, field, name) -> list[str]:
        """Serialize initial field values into POST-friendly strings."""
        try:
            raw_value = form.initial_for_field(field, name)
        except Exception:
            instance = getattr(form, "instance", None)
            raw_value = getattr(instance, name, None) if instance is not None else None

        try:
            prepared = field.prepare_value(raw_value)
        except Exception:
            prepared = raw_value

        if prepared is None:
            return []
        if isinstance(prepared, (list, tuple, set)):
            return ["" if value is None else str(value) for value in prepared]
        if isinstance(prepared, QueryDict):
            combined: list[str] = []
            for key in prepared.keys():
                combined.extend(prepared.getlist(key))
            return combined
        return ["" if prepared is None else str(prepared)]

    def _clone_inline_post_data(self, data):
        """Return a mutable copy of the current POST data (if QueryDict)."""
        if isinstance(data, QueryDict):
            clone = data.copy()
            clone._mutable = True
            return clone
        return None

    # ------------------------------------------------------------------
    # Rendering helpers
    # ------------------------------------------------------------------
    def _render_inline_row_form(
        self, obj, form=None, error_summary: str | None = None
    ) -> str:
        cfg = resolve_config(self)
        row_payload = self._build_inline_row_payload(obj)
        inline_form = form or self.build_inline_form(instance=obj)
        self._prepare_inline_number_widgets(inline_form)
        inline_hidden_fields = self._get_inline_hidden_bound_fields(
            inline_form, row_payload
        )
        summary = error_summary
        if summary is None:
            summary = self._get_inline_form_error_summary(inline_form)
        context = {
            "row": row_payload,
            "form": inline_form,
            "inline_hidden_fields": inline_hidden_fields,
            "inline_config": self.get_inline_context(),
            "inline_save_url": self._get_inline_row_url(obj),
            "inline_cancel_url": self._get_inline_row_url(obj),
            "enable_bulk_edit": self.get_bulk_edit_enabled(),
            "selected_ids": self._get_selected_ids(),
            "list_view_url": self._get_list_url(),
            "action_button_classes": self.get_action_button_classes(),
        }
        return render_to_string(
            f"{cfg.templates_path}/partial/list.html#inline_row_form",
            context,
            request=self.request,
        )

    def _get_inline_visible_field_names(
        self, row_payload: dict[str, Any], form
    ) -> set[str]:
        """
        Return form field names rendered visibly as inline widgets in the row.
        """
        if not form:
            return set()

        visible_fields: set[str] = set()
        for cell in row_payload.get("cells", []):
            field_name = cell.get("name")
            if not field_name or field_name not in form.fields:
                continue
            if cell.get("is_inline_editable"):
                visible_fields.add(field_name)
        return visible_fields

    def _get_inline_hidden_bound_fields(self, form, row_payload: dict[str, Any]):
        """
        Return bound fields that should be reposted as hidden inputs on inline save.

        These are the fields expected by the full edit form that are not rendered
        visibly as inline widgets in the current row.
        """
        if not form:
            return []

        visible_fields = self._get_inline_visible_field_names(row_payload, form)
        hidden_fields = []
        for field_name, field in form.fields.items():
            if field_name in visible_fields:
                continue
            if getattr(field, "disabled", False):
                continue
            widget = getattr(field, "widget", None)
            if widget and getattr(widget, "input_type", None) == "file":
                continue
            hidden_fields.append(form[field_name])
        return hidden_fields

    def _render_inline_row_display(self, obj) -> str:
        cfg = resolve_config(self)
        row_payload = self._build_inline_row_payload(obj)
        context = {
            "row": row_payload,
            "inline_config": self.get_inline_context(),
            "enable_bulk_edit": self.get_bulk_edit_enabled(),
            "selected_ids": self._get_selected_ids(),
            "list_view_url": self._get_list_url(),
        }
        return render_to_string(
            f"{cfg.templates_path}/partial/list.html#inline_row_display",
            context,
            request=self.request,
        )

    def _resolve_inline_field_list(self, source: Sequence[str] | None) -> list[str]:
        if not source:
            return []
        all_editable = set(self._get_all_editable_fields())
        return [field for field in source if field in all_editable]

    def _get_inline_form_field_names(self) -> list[str]:
        """
        Return the set of field names exposed by the view's form.
        """
        try:
            form_class = self.get_form_class()
        except Exception:
            return []

        base_fields = getattr(form_class, "base_fields", None)
        if base_fields:
            return list(base_fields.keys())

        try:
            form = form_class()
        except Exception:
            return []
        fields = getattr(form, "fields", None)
        if fields:
            return list(fields.keys())
        return []

    def _filter_inline_fields_by_form(self, fields: list[str]) -> list[str]:
        if not fields:
            return []
        form_field_names = set(self._get_inline_form_field_names())
        if not form_field_names:
            return fields

        filtered = [field for field in fields if field in form_field_names]

        return filtered

    def get_inline_edit_fields(self) -> list[str]:
        """
        Return the list of fields that should be editable inline.
        Inline editing is disabled until `inline_edit_fields` is set explicitly,
        except for the temporary legacy compatibility fallback from
        `inline_edit_enabled=True` to the resolved form fields.
        """
        cfg = resolve_config(self)
        config = cfg.inline_edit_fields
        legacy_declared = getattr(self, "_has_legacy_inline_edit_enabled", None)
        legacy_enabled = getattr(self, "_legacy_inline_editing_enabled", None)
        legacy_uses_form_fields = getattr(self, "_legacy_inline_edit_uses_form_fields", None)

        if callable(legacy_declared) and legacy_declared():
            if callable(legacy_enabled) and not legacy_enabled():
                return []
            if callable(legacy_uses_form_fields) and legacy_uses_form_fields(config):
                return self._filter_inline_fields_by_form(
                    self._resolve_inline_field_list(cfg.form_fields)
                )

        if config is None or config == []:
            return []

        if config == "__all__":
            return self._filter_inline_fields_by_form(self._get_all_editable_fields())

        if config == "__fields__":
            return self._filter_inline_fields_by_form(
                self._resolve_inline_field_list(cfg.fields)
            )

        return self._filter_inline_fields_by_form(
            self._resolve_inline_field_list(config)
        )

    def _resolve_inline_endpoint(self, endpoint_name: str | None) -> str | None:
        """
        Convert a named endpoint into a URL if possible.
        """
        if not endpoint_name:
            return None
        resolver = getattr(self, "safe_reverse", None)
        if not callable(resolver):
            return None
        try:
            return resolver(endpoint_name)
        except Exception:
            return None

    def get_inline_field_dependencies(self) -> dict[str, dict[str, Any]]:
        """
        Return dependency metadata for inline fields, derived from
        `field_queryset_dependencies`.
        """
        inline_fields = set(self.get_inline_edit_fields())
        endpoint_getter = getattr(self, "get_inline_dependency_endpoint_name", None)
        default_endpoint_name = endpoint_getter() if callable(endpoint_getter) else None
        default_endpoint_url = self._resolve_inline_endpoint(default_endpoint_name)

        dependencies: dict[str, dict[str, Any]] = {}
        queryset_dependency_getter = getattr(self, "get_field_queryset_dependencies", None)
        if callable(queryset_dependency_getter):
            for field, meta in queryset_dependency_getter(
                available_fields=inline_fields,
                warn_on_unavailable=False,
            ).items():
                if not meta.get("depends_on"):
                    continue
                dependencies[field] = {"depends_on": list(meta.get("depends_on", []))}

        resolved: dict[str, dict[str, Any]] = {}
        for field, meta in dependencies.items():
            if not isinstance(meta, dict):
                continue
            if inline_fields and field not in inline_fields:
                log.warning(
                    "Inline dependency for '%s' ignored because the field is not inline-editable on %s",
                    field,
                    self.__class__.__name__,
                )
                continue
            entry = dict(meta)
            endpoint_name = entry.get("endpoint_name") or default_endpoint_name
            entry["endpoint_name"] = endpoint_name
            entry["endpoint_url"] = (
                self._resolve_inline_endpoint(endpoint_name)
                or entry.get("endpoint_url")
                or default_endpoint_url
            )

            depends_on = entry.get("depends_on") or []
            valid_parents = [
                parent
                for parent in depends_on
                if not inline_fields or parent in inline_fields
            ]
            missing_parents = sorted(set(depends_on) - set(valid_parents))
            if missing_parents:
                log.warning(
                    "Inline dependency for '%s' references non-inline parent fields %s on %s",
                    field,
                    missing_parents,
                    self.__class__.__name__,
                )
            entry["depends_on"] = valid_parents
            if not valid_parents:
                log.warning(
                    "Inline dependency for '%s' ignored because it has no valid parent fields on %s",
                    field,
                    self.__class__.__name__,
                )
                continue
            resolved[field] = entry
        return resolved

    def can_inline_edit(self, obj, request) -> bool:
        """
        Determine whether the provided object can be edited inline for this request.
        """
        if not self.get_inline_editing():
            return False

        if obj is None:
            return False

        if self.is_inline_row_locked(obj):
            return False

        cfg = resolve_config(self)
        perm = cfg.inline_edit_requires_perm
        user = getattr(request, "user", None)

        if perm:
            if not user or not user.has_perm(perm):
                return False

        allowed_callable = cfg.inline_edit_allowed
        if callable(allowed_callable):
            return bool(allowed_callable(obj, request))

        return True

    def is_inline_row_locked(self, obj) -> bool:
        """
        Check whether the provided object is currently locked by an async conflict.
        """
        if obj is None:
            return False

        pk = getattr(obj, "pk", None)
        if pk in (None, ""):
            return False

        conflict_enabled = getattr(self, "get_conflict_checking_enabled", None)
        if not callable(conflict_enabled) or not conflict_enabled():
            return False

        checker = getattr(self, "_check_single_record_conflict", None)
        if not callable(checker):
            return False

        try:
            return bool(checker(pk))
        except Exception:
            return False

    def _build_inline_row_payload(self, obj) -> dict[str, Any]:
        object_list_context = powercrud_tags.object_list(
            {
                "request": self.request,
                "inline_edit": self.get_inline_context(),
                "use_htmx": self.get_use_htmx(),
                "original_target": self.get_original_target(),
                "htmx_target": self.get_htmx_target(),
                "selected_ids": self._get_selected_ids(),
            },
            [obj],
            self,
        )
        return object_list_context["object_list"][0]

    def _get_inline_row_url(self, obj) -> str:
        endpoint = self.get_inline_row_endpoint_name()
        return self.safe_reverse(endpoint, kwargs={"pk": obj.pk})

    def _get_selected_ids(self):
        if hasattr(self, "get_selected_ids_from_session") and self.request:
            ids = self.get_selected_ids_from_session(self.request)
            return [str(pk) for pk in ids]
        return []

    def _get_list_url(self) -> str:
        if self.namespace:
            list_url_name = f"{self.namespace}:{self.url_base}-list"
        else:
            list_url_name = f"{self.url_base}-list"
        return self.safe_reverse(list_url_name) or ""

    # ------------------------------------------------------------------
    # Inline guard helpers
    # ------------------------------------------------------------------
    def _evaluate_inline_state(self, obj, request) -> dict[str, Any]:
        """
        Return a dict describing whether inline editing is allowed.
        """
        if obj is None:
            return {
                "status": "forbidden",
                "message": _("Inline editing unavailable for this row."),
            }

        locker = getattr(self, "is_inline_row_locked", None)
        if callable(locker) and locker(obj):
            return {
                "status": "locked",
                "message": _("Inline editing blocked – record is locked."),
                "lock": self._get_inline_lock_metadata(obj),
            }

        checker = getattr(self, "can_inline_edit", None)
        if callable(checker) and not checker(obj, request):
            return {
                "status": "forbidden",
                "message": _("Inline editing not permitted for this row."),
            }

        return {"status": "ok"}

    def _build_inline_guard_response(self, obj, state: dict[str, Any]) -> HttpResponse:
        """
        Construct an HTMX-friendly response when inline editing is blocked.
        """
        message = state.get("message") or _("Inline editing unavailable.")
        trigger = {
            "locked": "inline-row-locked",
            "forbidden": "inline-row-forbidden",
        }.get(state.get("status"), "inline-row-error")

        payload = {
            "pk": getattr(obj, "pk", None),
            "message": message,
            "refresh": self._get_inline_refresh_payload(obj),
        }
        if state.get("lock"):
            payload["lock"] = state["lock"]

        # Render display row so the UI falls back to read-only state
        html = self._render_inline_row_display(obj)
        status_code = 423 if state.get("status") == "locked" else 403
        response = HttpResponse(html, status=status_code)
        response["HX-Trigger"] = json.dumps(
            {trigger: self._normalise_inline_trigger_payload(payload)}
        )
        return response

    def _normalise_inline_trigger_payload(self, payload: Any) -> Any:
        """
        Convert lazy translation proxies and nested structures into JSON-safe data.
        """
        if isinstance(payload, dict):
            return {
                force_str(key) if not isinstance(key, str) else key: (
                    self._normalise_inline_trigger_payload(value)
                )
                for key, value in payload.items()
            }
        if isinstance(payload, list):
            return [self._normalise_inline_trigger_payload(value) for value in payload]
        if isinstance(payload, tuple):
            return [self._normalise_inline_trigger_payload(value) for value in payload]
        if isinstance(payload, Promise):
            return force_str(payload)
        return payload

    def get_inline_lock_details(self, obj) -> dict[str, Any]:
        """
        Public helper so templates can retrieve lock metadata for a row.
        """
        return self._get_inline_lock_metadata(obj)

    def _get_inline_lock_metadata(self, obj) -> dict[str, Any]:
        metadata: dict[str, Any] = {}
        if not obj or not getattr(self, "model", None):
            return metadata

        get_manager = getattr(self, "get_async_manager", None)
        if not callable(get_manager):
            return metadata

        try:
            manager = get_manager()
        except Exception:
            return metadata

        cache = getattr(manager, "cache", None)
        prefix = getattr(manager, "conflict_model_prefix", None)
        if not cache or not prefix:
            return metadata

        model_label = f"{self.model._meta.app_label}.{self.model._meta.model_name}"
        lock_key = f"{prefix}{model_label}:{obj.pk}"
        task_name = cache.get(lock_key)
        if not task_name:
            return metadata

        metadata["task"] = str(task_name)
        metadata["lock_key"] = lock_key

        record = self._lookup_async_record(manager, task_name)
        if record is not None:
            metadata["user"] = self._extract_record_field(
                manager, record, "user", "user_label"
            )
            metadata["status"] = getattr(record, "status", None)
            metadata["message"] = getattr(record, "message", None)
            created_iso, created_display = self._serialize_datetime(
                getattr(record, "created_at", None)
            )
            updated_iso, updated_display = self._serialize_datetime(
                getattr(record, "updated_at", None)
            )
            metadata["created_at"] = created_iso
            metadata["created_at_display"] = created_display
            metadata["updated_at"] = updated_iso
            metadata["updated_at_display"] = updated_display

        metadata["label"] = self._format_lock_label(metadata)
        return metadata

    def _lookup_async_record(self, manager, task_name: str) -> Optional[Any]:
        record_model = getattr(manager, "_record_model", None)
        field_getter = getattr(manager, "_field", None)
        if not record_model or not callable(field_getter):
            return None

        task_field = field_getter("task_name", "task_name")
        if not task_field:
            return None

        try:
            return record_model.objects.filter(**{task_field: task_name}).first()
        except Exception:
            return None

    def _extract_record_field(self, manager, record, logical_name: str, default: str):
        field_getter = getattr(manager, "_field", None)
        field_name = (
            field_getter(logical_name, default) if callable(field_getter) else default
        )
        if not field_name:
            return None
        return getattr(record, field_name, None)

    def _serialize_datetime(self, value):
        if not value:
            return (None, None)
        try:
            aware = timezone.localtime(value) if timezone.is_aware(value) else value
        except Exception:
            aware = value
        try:
            display = date_format(aware, "DATETIME_FORMAT")
        except Exception:
            display = str(aware)
        try:
            iso_value = aware.isoformat()
        except Exception:
            iso_value = str(aware)
        return (iso_value, display)

    def _format_lock_label(self, metadata: dict[str, Any]) -> str:
        user = metadata.get("user")
        timestamp = metadata.get("created_at_display") or metadata.get(
            "updated_at_display"
        )
        if user and timestamp:
            return _("Locked by %(user)s at %(timestamp)s") % {
                "user": user,
                "timestamp": timestamp,
            }
        if user:
            return _("Locked by %(user)s") % {"user": user}
        if timestamp:
            return _("Lock acquired at %(timestamp)s") % {"timestamp": timestamp}
        return _("Inline editing blocked – record is locked.")

    def _get_inline_refresh_payload(self, obj) -> dict[str, Any]:
        if not obj:
            return {}
        row_id = None
        row_id_getter = getattr(self, "get_inline_row_id", None)
        if callable(row_id_getter):
            try:
                row_id = row_id_getter(obj)
            except Exception:
                row_id = None
        return {
            "pk": getattr(obj, "pk", None),
            "row_id": row_id,
            "url": self._get_inline_row_url(obj) if getattr(obj, "pk", None) else None,
        }

    def _preserve_inline_raw_data(self, form, data):
        if not form or not data:
            return
        try:
            form.data = data
        except Exception:
            pass

    def _prepare_inline_number_widgets(self, form):
        if not form:
            return
        for field in form.fields.values():
            widget = getattr(field, "widget", None)
            if not widget:
                continue
            if getattr(widget, "input_type", None) != "number":
                continue
            try:
                cloned = copy.deepcopy(widget)
            except Exception:
                cloned = widget
            cloned.input_type = "text"
            attrs = dict(getattr(cloned, "attrs", {}))
            attrs.setdefault("inputmode", "numeric")
            attrs.setdefault("data-inline-number", "true")
            cloned.attrs = attrs
            field.widget = cloned

    def _get_inline_form_error_summary(self, form) -> str:
        if not form or not getattr(form, "errors", None):
            return ""
        try:
            non_field = form.non_field_errors()
        except Exception:
            non_field = []
        if non_field:
            return str(non_field[0])
        errors = getattr(form, "errors", {})
        for field, messages in errors.items():
            if field == NON_FIELD_ERRORS:
                continue
            if messages:
                return str(messages[0])
        return ""
