import {
  detectAttachmentPreviewKind,
  fmtAmount,
  fmtBytes,
  fmtDate,
  fmtDateOnly,
  fmtShortDateTime,
  fmtTimeOnly,
  humanizeKey,
  statusLabel,
} from "../../shared/utils.js";

export function RequestWorkspace({
  viewerRole,
  viewerUserId,
  loading,
  trackNumber,
  requestData,
  financeSummary,
  statusRouteNodes,
  statusHistory,
  availableStatuses,
  currentImportantDateAt,
  pendingStatusChangePreset,
  messages,
  attachments,
  messageDraft,
  selectedFiles,
  fileUploading,
  status,
  onMessageChange,
  onSendMessage,
  onFilesSelect,
  onRemoveSelectedFile,
  onClearSelectedFiles,
  onLoadRequestDataTemplates,
  onLoadRequestDataBatch,
  onLoadRequestDataTemplateDetails,
  onSaveRequestDataTemplate,
  onSaveRequestDataBatch,
  onSaveRequestDataValues,
  onUploadRequestAttachment,
  onChangeStatus,
  onConsumePendingStatusChangePreset,
  onLiveProbe,
  onTypingSignal,
  domIds,
  AttachmentPreviewModalComponent,
  StatusLineComponent,
}) {
  const { useEffect, useMemo, useRef, useState } = React;
  const [preview, setPreview] = useState({ open: false, url: "", fileName: "", mimeType: "" });
  const [chatTab, setChatTab] = useState("chat");
  const [dropActive, setDropActive] = useState(false);
  const [financeOpen, setFinanceOpen] = useState(false);
  const [requestDataListOpen, setRequestDataListOpen] = useState(false);
  const [descriptionOpen, setDescriptionOpen] = useState(false);
  const [requestTemplateSuggestOpen, setRequestTemplateSuggestOpen] = useState(false);
  const [catalogFieldSuggestOpen, setCatalogFieldSuggestOpen] = useState(false);
  const [statusChangeModal, setStatusChangeModal] = useState({
    open: false,
    saving: false,
    statusCode: "",
    allowedStatusCodes: null,
    importantDateAt: "",
    comment: "",
    files: [],
    error: "",
  });
  const [draggedRequestRowId, setDraggedRequestRowId] = useState("");
  const [dragOverRequestRowId, setDragOverRequestRowId] = useState("");
  const [dataRequestModal, setDataRequestModal] = useState({
    open: false,
    loading: false,
    saving: false,
    savingTemplate: false,
    messageId: "",
    documentName: "",
    availableDocuments: [],
    templateList: [],
    requestTemplateQuery: "",
    templateName: "",
    selectedRequestTemplateId: "",
    templates: [],
    catalogFieldQuery: "",
    selectedCatalogTemplateId: "",
    rows: [],
    customLabel: "",
    customType: "string",
    templateStatus: "",
    error: "",
  });
  const [clientDataModal, setClientDataModal] = useState({
    open: false,
    loading: false,
    saving: false,
    messageId: "",
    items: [],
    status: "",
    error: "",
  });
  const [composerFocused, setComposerFocused] = useState(false);
  const [typingPeers, setTypingPeers] = useState([]);
  const [liveMode, setLiveMode] = useState("online");
  const fileInputRef = useRef(null);
  const statusChangeFileInputRef = useRef(null);
  const chatListRef = useRef(null);
  const liveCursorRef = useRef("");
  const liveTimerRef = useRef(null);
  const liveInFlightRef = useRef(false);
  const liveFailCountRef = useRef(0);
  const typingHeartbeatRef = useRef(null);
  const typingActiveRef = useRef(false);
  const lastAutoScrollCursorRef = useRef("");
  const idMap = useMemo(
    () => ({
      messagesList: "request-modal-messages",
      filesList: "request-modal-files",
      messageBody: "request-modal-message-body",
      sendButton: "request-modal-message-send",
      fileInput: "request-modal-file-input",
      fileUploadButton: "",
      dataRequestOverlay: "data-request-overlay",
      dataRequestItems: "data-request-items",
      dataRequestStatus: "data-request-status",
      dataRequestSave: "data-request-save",
      ...(domIds || {}),
    }),
    [domIds]
  );
  const requestDataTypeOptions = useMemo(
    () => [
      { value: "string", label: "Строка" },
      { value: "date", label: "Дата" },
      { value: "number", label: "Число" },
      { value: "file", label: "Файл" },
      { value: "text", label: "Текст" },
    ],
    []
  );

  const openPreview = (item) => {
    if (!item?.download_url) return;
    setPreview({
      open: true,
      url: String(item.download_url),
      fileName: String(item.file_name || ""),
      mimeType: String(item.mime_type || ""),
    });
  };

  const closePreview = () => setPreview({ open: false, url: "", fileName: "", mimeType: "" });
  const pendingFiles = Array.isArray(selectedFiles) ? selectedFiles : [];
  const hasPendingFiles = pendingFiles.length > 0;
  const canSubmit = Boolean(String(messageDraft || "").trim() || hasPendingFiles);

  const onInputFiles = (event) => {
    const files = Array.from((event.target && event.target.files) || []);
    if (files.length && typeof onFilesSelect === "function") onFilesSelect(files);
    event.target.value = "";
  };

  const onDropFiles = (event) => {
    event.preventDefault();
    setDropActive(false);
    const files = Array.from((event.dataTransfer && event.dataTransfer.files) || []);
    if (files.length && typeof onFilesSelect === "function") onFilesSelect(files);
  };

  const row = requestData && typeof requestData === "object" ? requestData : null;
  const finance = financeSummary && typeof financeSummary === "object" ? financeSummary : null;
  const viewerRoleCode = String(viewerRole || "").toUpperCase();
  const canRequestData = viewerRoleCode === "LAWYER" || viewerRoleCode === "ADMIN";
  const canFillRequestData = viewerRoleCode === "CLIENT";
  const canSeeRate = viewerRoleCode !== "CLIENT";
  const canSeeCreatedUpdatedInCard = viewerRoleCode !== "CLIENT";
  const showTopicStatusInCard = viewerRoleCode !== "CLIENT";
  const showContactsInCard = viewerRoleCode !== "CLIENT";
  const safeMessages = Array.isArray(messages) ? messages : [];
  const safeAttachments = Array.isArray(attachments) ? attachments : [];
  const safeStatusHistory = Array.isArray(statusHistory) ? statusHistory : [];
  const safeAvailableStatuses = Array.isArray(availableStatuses) ? availableStatuses : [];
  const totalFilesBytes = safeAttachments.reduce((acc, item) => acc + Number(item?.size_bytes || 0), 0);
  const clientLabel = row?.client_name || "-";
  const clientPhone = String(row?.client_phone || "").trim();
  const lawyerLabel = row?.assigned_lawyer_name || row?.assigned_lawyer_id || "Не назначен";
  const lawyerPhone = String(row?.assigned_lawyer_phone || "").trim();
  const clientHasPhone = Boolean(clientPhone);
  const lawyerHasPhone = Boolean(lawyerPhone);
  const messagePlaceholder = canFillRequestData ? "Введите сообщение для юриста" : "Введите сообщение для клиента";

  const selectedRequestTemplateCandidate = useMemo(
    () =>
      (dataRequestModal.templateList || []).find((item) => {
        const query = String(dataRequestModal.requestTemplateQuery || "").trim().toLowerCase();
        if (!query) return false;
        return query === String(item?.name || "").trim().toLowerCase() || query === String(item?.id || "").trim().toLowerCase();
      }) || null,
    [dataRequestModal.requestTemplateQuery, dataRequestModal.templateList]
  );
  const selectedCatalogFieldCandidate = useMemo(
    () =>
      (dataRequestModal.templates || []).find((item) => {
        const query = String(dataRequestModal.catalogFieldQuery || "").trim().toLowerCase();
        if (!query) return false;
        return (
          query === String(item?.label || "").trim().toLowerCase() ||
          query === String(item?.key || "").trim().toLowerCase() ||
          query === String(item?.id || "").trim().toLowerCase()
        );
      }) || null,
    [dataRequestModal.catalogFieldQuery, dataRequestModal.templates]
  );
  const filteredRequestTemplates = useMemo(() => {
    const query = String(dataRequestModal.requestTemplateQuery || "").trim().toLowerCase();
    const rows = Array.isArray(dataRequestModal.templateList) ? dataRequestModal.templateList : [];
    if (!query) return rows.slice(0, 8);
    return rows
      .filter((item) => String(item?.name || "").toLowerCase().includes(query))
      .slice(0, 8);
  }, [dataRequestModal.requestTemplateQuery, dataRequestModal.templateList]);
  const filteredCatalogFields = useMemo(() => {
    const query = String(dataRequestModal.catalogFieldQuery || "").trim().toLowerCase();
    const rows = Array.isArray(dataRequestModal.templates) ? dataRequestModal.templates : [];
    if (!query) return rows.slice(0, 10);
    return rows
      .filter((item) => {
        const label = String(item?.label || "").toLowerCase();
        const key = String(item?.key || "").toLowerCase();
        return label.includes(query) || key.includes(query);
      })
      .slice(0, 10);
  }, [dataRequestModal.catalogFieldQuery, dataRequestModal.templates]);
  const requestTemplateActionMode = selectedRequestTemplateCandidate ? "save" : String(dataRequestModal.requestTemplateQuery || "").trim() ? "create" : "";
  const catalogFieldActionMode = selectedCatalogFieldCandidate ? "add" : String(dataRequestModal.catalogFieldQuery || "").trim() ? "create" : "";
  const requestTemplateBadge = useMemo(() => {
    const query = String(dataRequestModal.requestTemplateQuery || "").trim();
    if (!query) return null;
    const matched = selectedRequestTemplateCandidate;
    if (!matched) return { kind: "create", label: "Новый шаблон" };
    const roleCode = String(viewerRole || "").toUpperCase();
    const actorId = String(viewerUserId || "").trim();
    const ownerId = String(matched.created_by_admin_id || "").trim();
    if (roleCode === "LAWYER" && ownerId && actorId && ownerId !== actorId) {
      return { kind: "readonly", label: "Чужой шаблон" };
    }
    return { kind: "existing", label: "Существующий шаблон" };
  }, [dataRequestModal.requestTemplateQuery, selectedRequestTemplateCandidate, viewerRole, viewerUserId]);
  const canSaveSelectedRequestTemplate = useMemo(() => {
    if (!String(dataRequestModal.requestTemplateQuery || "").trim()) return false;
    if (!requestTemplateBadge) return true;
    return requestTemplateBadge.kind !== "readonly";
  }, [dataRequestModal.requestTemplateQuery, requestTemplateBadge]);
  const attachmentById = useMemo(() => {
    const map = new Map();
    safeAttachments.forEach((item) => {
      const id = String(item?.id || "").trim();
      if (id) map.set(id, item);
    });
    return map;
  }, [safeAttachments]);
  const statusOptions = useMemo(
    () =>
      safeAvailableStatuses
        .filter((item) => item && item.code)
        .map((item) => ({
          code: String(item.code),
          name: String(item.name || item.code),
          groupName: item.status_group_name ? String(item.status_group_name) : "",
          isTerminal: Boolean(item.is_terminal),
        })),
    [safeAvailableStatuses]
  );
  const statusByCode = useMemo(() => new Map(statusOptions.map((item) => [item.code, item])), [statusOptions]);

  const toDateTimeLocalValue = (value) => {
    if (!value) return "";
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return "";
    const pad = (n) => String(n).padStart(2, "0");
    return (
      date.getFullYear() +
      "-" +
      pad(date.getMonth() + 1) +
      "-" +
      pad(date.getDate()) +
      "T" +
      pad(date.getHours()) +
      ":" +
      pad(date.getMinutes())
    );
  };

  const defaultImportantDateLocal = useMemo(() => {
    const source = String(currentImportantDateAt || row?.important_date_at || "").trim();
    if (source) {
      const local = toDateTimeLocalValue(source);
      if (local) return local;
    }
    const next = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
    return toDateTimeLocalValue(next.toISOString());
  }, [currentImportantDateAt, row?.important_date_at]);

  const formatDuration = (seconds) => {
    const total = Number(seconds);
    if (!Number.isFinite(total) || total < 0) return "—";
    const days = Math.floor(total / 86400);
    const hours = Math.floor((total % 86400) / 3600);
    const minutes = Math.floor((total % 3600) / 60);
    if (days > 0) return days + " д " + hours + " ч";
    if (hours > 0) return hours + " ч " + minutes + " мин";
    return Math.max(0, minutes) + " мин";
  };

  const openStatusChangeModal = (preset) => {
    const suggested = Array.isArray(preset?.suggestedStatuses) ? preset.suggestedStatuses.filter(Boolean) : [];
    const currentCode = String(row?.status_code || "").trim();
    const firstSuggested = suggested.find((code) => code && code !== currentCode) || "";
    setStatusChangeModal({
      open: true,
      saving: false,
      statusCode: firstSuggested,
      allowedStatusCodes: suggested.length ? suggested : null,
      importantDateAt: defaultImportantDateLocal,
      comment: "",
      files: [],
      error: "",
    });
  };

  const closeStatusChangeModal = () => {
    setStatusChangeModal((prev) => ({ ...prev, open: false, saving: false, error: "", files: [] }));
  };

  useEffect(() => {
    if (!pendingStatusChangePreset) return;
    openStatusChangeModal(pendingStatusChangePreset);
    if (typeof onConsumePendingStatusChangePreset === "function") onConsumePendingStatusChangePreset();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pendingStatusChangePreset]);
  const requestDataListItems = useMemo(() => {
    const byKey = new Map();
    const messagesChrono = [...safeMessages].sort((a, b) => {
      const at = new Date(a?.created_at || 0).getTime();
      const bt = new Date(b?.created_at || 0).getTime();
      if (at !== bt) return at - bt;
      return String(a?.id || "").localeCompare(String(b?.id || ""), "ru");
    });
    messagesChrono.forEach((msg) => {
      if (String(msg?.message_kind || "") !== "REQUEST_DATA") return;
      const items = Array.isArray(msg?.request_data_items) ? msg.request_data_items : [];
      items.forEach((item, idx) => {
        const key = String(item?.key || item?.id || "item-" + idx);
        if (!key) return;
        byKey.set(key, {
          id: String(item?.id || ""),
          key,
          label: String(item?.label || item?.label_short || key),
          field_type: String(item?.field_type || "string").toLowerCase(),
          value_text: item?.value_text == null ? "" : String(item.value_text),
          is_filled: Boolean(item?.is_filled),
          source_message_id: String(msg?.id || ""),
          source_message_created_at: msg?.created_at || null,
          value_file: item?.value_file || null,
        });
      });
    });
    return Array.from(byKey.values()).sort((a, b) => {
      const aFilled = a.is_filled ? 1 : 0;
      const bFilled = b.is_filled ? 1 : 0;
      if (aFilled !== bFilled) return aFilled - bFilled;
      return String(a.label || a.key).localeCompare(String(b.label || b.key), "ru");
    });
  }, [safeMessages]);
  const attachmentsByMessageId = useMemo(() => {
    const map = new Map();
    safeAttachments.forEach((item) => {
      const messageId = String(item?.message_id || "").trim();
      if (!messageId) return;
      if (!map.has(messageId)) map.set(messageId, []);
      map.get(messageId).push(item);
    });
    return map;
  }, [safeAttachments]);

  const localActivityCursor = useMemo(() => {
    let latestTs = 0;
    const pickLatest = (value) => {
      if (!value) return;
      const ts = new Date(value).getTime();
      if (Number.isFinite(ts) && ts > latestTs) latestTs = ts;
    };
    safeMessages.forEach((item) => {
      pickLatest(item?.updated_at);
      pickLatest(item?.created_at);
    });
    safeAttachments.forEach((item) => {
      pickLatest(item?.updated_at);
      pickLatest(item?.created_at);
    });
    return latestTs > 0 ? new Date(latestTs).toISOString() : "";
  }, [safeAttachments, safeMessages]);

  const typingHintText = useMemo(() => {
    const rows = Array.isArray(typingPeers) ? typingPeers : [];
    if (!rows.length) return "";
    const labels = rows
      .map((item) => String(item?.actor_label || item?.label || "").trim())
      .filter(Boolean);
    if (!labels.length) return "Собеседник печатает...";
    const unique = [];
    labels.forEach((label) => {
      if (!unique.includes(label)) unique.push(label);
    });
    if (unique.length === 1) return unique[0] + " печатает...";
    if (unique.length === 2) return unique[0] + " и " + unique[1] + " печатают...";
    return unique[0] + ", " + unique[1] + " и еще " + String(unique.length - 2) + " печатают...";
  }, [typingPeers]);

  const openAttachmentFromMessage = (item) => {
    if (!item?.download_url) return;
    const kind = detectAttachmentPreviewKind(item.file_name, item.mime_type);
    if (kind === "none") {
      window.open(String(item.download_url), "_blank", "noopener,noreferrer");
      return;
    }
    openPreview(item);
  };

  const downloadAttachment = (item) => {
    const url = String(item?.download_url || "").trim();
    if (!url) return;
    const link = document.createElement("a");
    link.href = url;
    link.target = "_blank";
    link.rel = "noreferrer";
    const fileName = String(item?.file_name || "").trim();
    if (fileName) link.download = fileName;
    document.body.appendChild(link);
    link.click();
    link.remove();
  };

  useEffect(() => {
    liveCursorRef.current = localActivityCursor || "";
  }, [localActivityCursor, row?.id]);

  useEffect(() => {
    if (!row || typeof onLiveProbe !== "function") {
      setTypingPeers([]);
      setLiveMode("online");
      if (liveTimerRef.current) {
        clearTimeout(liveTimerRef.current);
        liveTimerRef.current = null;
      }
      liveInFlightRef.current = false;
      liveFailCountRef.current = 0;
      return undefined;
    }

    let cancelled = false;
    const scheduleNext = (ms) => {
      if (cancelled) return;
      if (liveTimerRef.current) clearTimeout(liveTimerRef.current);
      liveTimerRef.current = setTimeout(runProbe, ms);
    };

    const runProbe = async () => {
      if (cancelled || liveInFlightRef.current) return;
      liveInFlightRef.current = true;
      try {
        const payload = await onLiveProbe({ cursor: liveCursorRef.current });
        const cursor = String(payload?.cursor || "").trim();
        if (cursor) liveCursorRef.current = cursor;
        setTypingPeers(Array.isArray(payload?.typing) ? payload.typing : []);
        liveFailCountRef.current = 0;
        setLiveMode("online");
      } catch (_) {
        liveFailCountRef.current += 1;
        setLiveMode(liveFailCountRef.current >= 3 ? "degraded" : "online");
      } finally {
        liveInFlightRef.current = false;
        const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
        const baseInterval = hidden ? 8000 : 2500;
        const failStep = Math.min(5, Math.max(0, liveFailCountRef.current));
        const backoffInterval = failStep > 0 ? Math.min(30000, baseInterval * Math.pow(2, failStep - 1)) : baseInterval;
        scheduleNext(backoffInterval);
      }
    };

    runProbe();
    return () => {
      cancelled = true;
      if (liveTimerRef.current) {
        clearTimeout(liveTimerRef.current);
        liveTimerRef.current = null;
      }
      liveInFlightRef.current = false;
      liveFailCountRef.current = 0;
      setTypingPeers([]);
      setLiveMode("online");
    };
  }, [onLiveProbe, row, trackNumber]);

  const typingEnabled = Boolean(
    row &&
      typeof onTypingSignal === "function" &&
      !loading &&
      !fileUploading &&
      composerFocused &&
      String(messageDraft || "").trim()
  );

  useEffect(() => {
    if (typeof onTypingSignal !== "function" || !row) {
      if (typingHeartbeatRef.current) {
        clearInterval(typingHeartbeatRef.current);
        typingHeartbeatRef.current = null;
      }
      typingActiveRef.current = false;
      return;
    }
    if (typingEnabled) {
      if (!typingActiveRef.current) {
        typingActiveRef.current = true;
        void onTypingSignal({ typing: true }).catch(() => null);
      }
      if (!typingHeartbeatRef.current) {
        typingHeartbeatRef.current = setInterval(() => {
          void onTypingSignal({ typing: true }).catch(() => null);
        }, 2500);
      }
      return;
    }
    if (typingHeartbeatRef.current) {
      clearInterval(typingHeartbeatRef.current);
      typingHeartbeatRef.current = null;
    }
    if (typingActiveRef.current) {
      typingActiveRef.current = false;
      void onTypingSignal({ typing: false }).catch(() => null);
    }
  }, [onTypingSignal, row, typingEnabled]);

  useEffect(
    () => () => {
      if (typingHeartbeatRef.current) {
        clearInterval(typingHeartbeatRef.current);
        typingHeartbeatRef.current = null;
      }
      if (typingActiveRef.current && typeof onTypingSignal === "function") {
        typingActiveRef.current = false;
        void onTypingSignal({ typing: false }).catch(() => null);
      }
    },
    [onTypingSignal]
  );

  const newDataRequestRow = (source) => {
    const item = source || {};
    const label = String(item.label || "").trim();
    const key = String(item.key || "").trim();
    const fieldTypeRaw = String(item.field_type || item.value_type || "string").trim().toLowerCase();
    const fieldType = ["string", "text", "date", "number", "file"].includes(fieldTypeRaw) ? fieldTypeRaw : "string";
    return {
      localId: "row-" + Math.random().toString(36).slice(2),
      id: item.id ? String(item.id) : "",
      topic_template_id: item.topic_template_id ? String(item.topic_template_id) : item.id ? String(item.id) : "",
      key,
      label: label || "Поле",
      field_type: fieldType,
      document_name: String(item.document_name || "").trim(),
      value_text: item.value_text == null ? "" : String(item.value_text),
      value_file: item.value_file || null,
      is_filled: Boolean(item.is_filled),
    };
  };

  const getRequestDataRowIdentity = (item) => {
    const rowItem = item || {};
    const key = String(rowItem.key || "").trim().toLowerCase();
    if (key) return "key:" + key;
    const tplId = String(rowItem.topic_template_id || rowItem.id || "").trim();
    if (tplId) return "tpl:" + tplId;
    return "label:" + String(rowItem.label || "").trim().toLowerCase();
  };

  const mergeRequestDataRows = (baseRows, incomingRows) => {
    const rows = Array.isArray(baseRows) ? [...baseRows] : [];
    const nextItems = Array.isArray(incomingRows) ? incomingRows : [];
    const seen = new Set(rows.map((rowItem) => getRequestDataRowIdentity(rowItem)));
    nextItems.forEach((rowItem) => {
      const identity = getRequestDataRowIdentity(rowItem);
      if (!identity || seen.has(identity)) return;
      seen.add(identity);
      rows.push(rowItem);
    });
    return rows;
  };

  const openCreateDataRequestModal = async () => {
    if (!canRequestData || typeof onLoadRequestDataTemplates !== "function") return;
    setDataRequestModal((prev) => ({
      ...prev,
      open: true,
      loading: true,
      saving: false,
      savingTemplate: false,
      messageId: "",
      rows: [],
      error: "",
      templateStatus: "",
      requestTemplateQuery: "",
      catalogFieldQuery: "",
      selectedCatalogTemplateId: "",
      selectedRequestTemplateId: "",
      templateName: "",
      documentName: "",
      customLabel: "",
      customType: "string",
    }));
    try {
      const data = await onLoadRequestDataTemplates();
      setDataRequestModal((prev) => ({
        ...prev,
        open: true,
        loading: false,
        templates: Array.isArray(data?.rows) ? data.rows : [],
        templateList: Array.isArray(data?.templates) ? data.templates : [],
        availableDocuments: Array.isArray(data?.documents) ? data.documents : [],
        documentName: "",
        requestTemplateQuery: "",
        catalogFieldQuery: "",
      }));
    } catch (error) {
      setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "Не удалось загрузить шаблоны" }));
    }
  };

  const openEditDataRequestModal = async (messageId) => {
    if (!canRequestData || !messageId) return;
    setDataRequestModal((prev) => ({
      ...prev,
      open: true,
      loading: true,
      saving: false,
      savingTemplate: false,
      messageId: String(messageId),
      rows: [],
      error: "",
      templateStatus: "",
      requestTemplateQuery: "",
      catalogFieldQuery: "",
      selectedCatalogTemplateId: "",
      selectedRequestTemplateId: "",
      templateName: "",
    }));
    try {
      const [batch, templates] = await Promise.all([
        typeof onLoadRequestDataBatch === "function" ? onLoadRequestDataBatch(messageId) : Promise.resolve({ items: [] }),
        typeof onLoadRequestDataTemplates === "function"
          ? onLoadRequestDataTemplates()
          : Promise.resolve({ rows: [], documents: [], templates: [] }),
      ]);
      setDataRequestModal((prev) => ({
        ...prev,
        open: true,
        loading: false,
        messageId: String(messageId),
        rows: Array.isArray(batch?.items) ? batch.items.map(newDataRequestRow) : [],
        documentName: String(batch?.document_name || ""),
        templates: Array.isArray(templates?.rows) ? templates.rows : [],
        templateList: Array.isArray(templates?.templates) ? templates.templates : [],
        availableDocuments: Array.isArray(templates?.documents) ? templates.documents : [],
        requestTemplateQuery: "",
        catalogFieldQuery: "",
      }));
    } catch (error) {
      setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "Не удалось загрузить запрос" }));
    }
  };

  const closeDataRequestModal = () => {
    setDataRequestModal((prev) => ({ ...prev, open: false, error: "", saving: false, savingTemplate: false, templateStatus: "" }));
  };

  const findRequestTemplateByQuery = (queryValue) => {
    const query = String(queryValue || "").trim().toLowerCase();
    if (!query) return null;
    return (
      (dataRequestModal.templateList || []).find((item) => {
        const id = String(item?.id || "").toLowerCase();
        const name = String(item?.name || "").toLowerCase();
        return query === id || query === name;
      }) || null
    );
  };

  const findCatalogFieldByQuery = (queryValue) => {
    const query = String(queryValue || "").trim().toLowerCase();
    if (!query) return null;
    return (
      (dataRequestModal.templates || []).find((item) => {
        const id = String(item?.id || "").toLowerCase();
        const key = String(item?.key || "").toLowerCase();
        const label = String(item?.label || "").toLowerCase();
        return query === id || query === key || query === label;
      }) || null
    );
  };

  const applyRequestTemplateById = async (rawTemplateId, templateNameHint) => {
    if (typeof onLoadRequestDataTemplateDetails !== "function") return;
    const templateId = String(rawTemplateId || "").trim();
    if (!templateId) return;
    setDataRequestModal((prev) => ({ ...prev, loading: true, error: "" }));
    try {
      const data = await onLoadRequestDataTemplateDetails(templateId);
      const incomingRows = (Array.isArray(data?.items) ? data.items : []).map((item) =>
        newDataRequestRow({
          ...item,
          topic_template_id: item.topic_data_template_id || item.topic_template_id || "",
          field_type: item.value_type || item.field_type,
        })
      );
      setDataRequestModal((prev) => ({
        ...prev,
        loading: false,
        rows: mergeRequestDataRows(prev.rows, incomingRows),
        selectedRequestTemplateId: String(data?.template?.id || prev.selectedRequestTemplateId || ""),
        requestTemplateQuery: String(data?.template?.name || templateNameHint || prev.requestTemplateQuery || ""),
        templateStatus: "",
      }));
    } catch (error) {
      setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "Не удалось загрузить шаблон" }));
    }
  };

  const applySelectedRequestTemplate = async () => {
    const selectedByQuery = findRequestTemplateByQuery(dataRequestModal.requestTemplateQuery);
    const templateId = String(selectedByQuery?.id || dataRequestModal.selectedRequestTemplateId || "").trim();
    return applyRequestTemplateById(templateId, selectedByQuery?.name || "");
  };

  const refreshDataRequestCatalog = async () => {
    if (typeof onLoadRequestDataTemplates !== "function") return null;
    const data = await onLoadRequestDataTemplates();
    setDataRequestModal((prev) => ({
      ...prev,
      templates: Array.isArray(data?.rows) ? data.rows : [],
      templateList: Array.isArray(data?.templates) ? data.templates : [],
      availableDocuments: Array.isArray(data?.documents) ? data.documents : [],
      selectedRequestTemplateId: prev.selectedRequestTemplateId && (Array.isArray(data?.templates) ? data.templates : []).some((item) => String(item?.id) === String(prev.selectedRequestTemplateId))
        ? prev.selectedRequestTemplateId
        : "",
    }));
    return data;
  };

  const saveCurrentDataRequestTemplate = async () => {
    if (typeof onSaveRequestDataTemplate !== "function") return;
    const selectedFromQuery = findRequestTemplateByQuery(dataRequestModal.requestTemplateQuery);
    const templateName = String(dataRequestModal.requestTemplateQuery || "").trim();
    const rows = (dataRequestModal.rows || []).filter((row) => String(row.label || "").trim());
    if (!templateName) {
      setDataRequestModal((prev) => ({ ...prev, error: "Укажите название шаблона" }));
      return;
    }
    if (!rows.length) {
      setDataRequestModal((prev) => ({ ...prev, error: "Добавьте хотя бы одно поле для шаблона" }));
      return;
    }
    setDataRequestModal((prev) => ({ ...prev, savingTemplate: true, error: "", templateStatus: "" }));
    try {
      const result = await onSaveRequestDataTemplate({
        template_id: String(selectedFromQuery?.id || dataRequestModal.selectedRequestTemplateId || "").trim() || undefined,
        name: templateName,
        items: rows.map((row) => ({
          topic_data_template_id: row.topic_template_id || undefined,
          key: row.key || undefined,
          label: row.label,
          value_type: row.field_type || "string",
        })),
      });
      const savedRows = (Array.isArray(result?.items) ? result.items : []).map((item) =>
        newDataRequestRow({
          ...item,
          topic_template_id: item.topic_data_template_id || item.topic_template_id || "",
          field_type: item.value_type || item.field_type,
        })
      );
      setDataRequestModal((prev) => ({
        ...prev,
        savingTemplate: false,
        rows: savedRows.length ? savedRows : prev.rows,
        selectedRequestTemplateId: String(result?.template?.id || prev.selectedRequestTemplateId || ""),
        requestTemplateQuery: String(result?.template?.name || templateName),
        templateStatus: "Шаблон сохранен",
      }));
      await refreshDataRequestCatalog();
    } catch (error) {
      setDataRequestModal((prev) => ({ ...prev, savingTemplate: false, error: error.message || "Не удалось сохранить шаблон" }));
    }
  };

  const addSelectedTemplateRow = () => {
    const selectedByQuery = findCatalogFieldByQuery(dataRequestModal.catalogFieldQuery);
    const templateId = String(selectedByQuery?.id || dataRequestModal.selectedCatalogTemplateId || "").trim();
    const template = (dataRequestModal.templates || []).find((item) => String(item.id) === templateId);
    if (!template) {
      const manualLabel = String(dataRequestModal.catalogFieldQuery || "").trim();
      if (!manualLabel) return;
      setDataRequestModal((prev) => ({
        ...prev,
        catalogFieldQuery: "",
        templateStatus: "",
        rows: [...(prev.rows || []), newDataRequestRow({ label: manualLabel, field_type: "string" })],
      }));
      return;
    }
    setDataRequestModal((prev) => {
      const exists = (prev.rows || []).some((row) => String(row.key || "") === String(template.key || ""));
      if (exists) return { ...prev, selectedCatalogTemplateId: "", catalogFieldQuery: "" };
      return {
        ...prev,
        selectedCatalogTemplateId: "",
        catalogFieldQuery: "",
        templateStatus: "",
        rows: [...(prev.rows || []), newDataRequestRow({ ...template, topic_template_id: template.id, field_type: template.value_type })],
      };
    });
  };

  const updateDataRequestRow = (localId, patch) => {
    setDataRequestModal((prev) => ({
      ...prev,
      templateStatus: "",
      rows: (prev.rows || []).map((row) => (row.localId === localId ? { ...row, ...(patch || {}) } : row)),
    }));
  };

  const removeDataRequestRow = (localId) => {
    setDataRequestModal((prev) => ({
      ...prev,
      templateStatus: "",
      rows: (prev.rows || []).filter((row) => row.localId !== localId),
    }));
  };

  const moveDataRequestRow = (localId, delta) => {
    const shift = Number(delta) || 0;
    if (!shift) return;
    setDataRequestModal((prev) => {
      const rows = Array.isArray(prev.rows) ? [...prev.rows] : [];
      const index = rows.findIndex((row) => row.localId === localId);
      if (index < 0) return prev;
      const nextIndex = index + shift;
      if (nextIndex < 0 || nextIndex >= rows.length) return prev;
      const [item] = rows.splice(index, 1);
      rows.splice(nextIndex, 0, item);
      return { ...prev, templateStatus: "", rows };
    });
  };
  const moveDataRequestRowToIndex = (localId, targetIndexRaw) => {
    const targetIndex = Number(targetIndexRaw);
    if (!Number.isInteger(targetIndex)) return;
    setDataRequestModal((prev) => {
      const rows = Array.isArray(prev.rows) ? [...prev.rows] : [];
      const fromIndex = rows.findIndex((rowItem) => rowItem.localId === localId);
      if (fromIndex < 0) return prev;
      const boundedIndex = Math.max(0, Math.min(rows.length - 1, targetIndex));
      if (fromIndex === boundedIndex) return prev;
      const [item] = rows.splice(fromIndex, 1);
      rows.splice(boundedIndex, 0, item);
      return { ...prev, templateStatus: "", rows };
    });
  };

  const submitDataRequestModal = async () => {
    if (typeof onSaveRequestDataBatch !== "function") return;
    const rows = (dataRequestModal.rows || []).filter((row) => String(row.label || "").trim());
    if (!rows.length) {
      setDataRequestModal((prev) => ({ ...prev, error: "Добавьте хотя бы одно поле" }));
      return;
    }
    setDataRequestModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      await onSaveRequestDataBatch({
        message_id: dataRequestModal.messageId || undefined,
        items: rows.map((row) => ({
          id: row.id || undefined,
          topic_template_id: row.topic_template_id || undefined,
          key: row.key || undefined,
          label: row.label,
          field_type: row.field_type || "string",
          document_name: row.document_name || undefined,
        })),
      });
      closeDataRequestModal();
    } catch (error) {
      setDataRequestModal((prev) => ({ ...prev, saving: false, error: error.message || "Не удалось отправить запрос" }));
    }
  };

  const closeClientDataModal = () => {
    setClientDataModal({
      open: false,
      loading: false,
      saving: false,
      messageId: "",
      items: [],
      status: "",
      error: "",
    });
  };

  const openClientDataRequestModal = async (messageId) => {
    if (!canFillRequestData || typeof onLoadRequestDataBatch !== "function" || !messageId) return;
    setClientDataModal({
      open: true,
      loading: true,
      saving: false,
      messageId: String(messageId),
      items: [],
      status: "",
      error: "",
    });
    try {
      const data = await onLoadRequestDataBatch(String(messageId));
      const items = Array.isArray(data?.items)
        ? data.items
            .slice()
            .sort((a, b) => Number(a?.sort_order || 0) - Number(b?.sort_order || 0))
            .map((item, index) => ({
              localId: "client-data-" + String(item?.id || item?.key || index),
              id: String(item?.id || ""),
              key: String(item?.key || ""),
              label: String(item?.label || item?.key || "Поле"),
              field_type: String(item?.field_type || "string").toLowerCase(),
              value_text: item?.value_text == null ? "" : String(item.value_text),
              value_file: item?.value_file || null,
              pendingFile: null,
            }))
        : [];
      setClientDataModal((prev) => ({ ...prev, loading: false, items }));
    } catch (error) {
      setClientDataModal((prev) => ({ ...prev, loading: false, error: error?.message || "Не удалось открыть запрос данных" }));
    }
  };

  const updateClientDataItem = (localId, patch) => {
    setClientDataModal((prev) => ({
      ...prev,
      status: "",
      error: "",
      items: (prev.items || []).map((item) => (item.localId === localId ? { ...item, ...(patch || {}) } : item)),
    }));
  };

  const submitClientDataModal = async (event) => {
    if (event && typeof event.preventDefault === "function") event.preventDefault();
    if (!canFillRequestData || typeof onSaveRequestDataValues !== "function") return;
    const currentMessageId = String(clientDataModal.messageId || "").trim();
    if (!currentMessageId) return;
    setClientDataModal((prev) => ({ ...prev, saving: true, status: "", error: "" }));
    try {
      const payloadItems = [];
      for (const item of clientDataModal.items || []) {
        const fieldType = String(item?.field_type || "string").toLowerCase();
        if (fieldType === "file") {
          let attachmentId = String(item?.value_text || "").trim();
          if (item?.pendingFile) {
            if (typeof onUploadRequestAttachment !== "function") {
              throw new Error("Загрузка файла для поля недоступна");
            }
            const uploadResult = await onUploadRequestAttachment(item.pendingFile, {
              source: "data_request",
              message_id: currentMessageId,
              key: String(item?.key || ""),
            });
            attachmentId = String(
              (uploadResult && (uploadResult.attachment_id || uploadResult.id || uploadResult.value || uploadResult)) || ""
            ).trim();
            if (!attachmentId) throw new Error("Не удалось сохранить файл для поля запроса");
          }
          payloadItems.push({
            id: String(item?.id || ""),
            key: String(item?.key || ""),
            attachment_id: attachmentId || "",
            value_text: attachmentId || "",
          });
          continue;
        }
        payloadItems.push({
          id: String(item?.id || ""),
          key: String(item?.key || ""),
          value_text: String(item?.value_text || ""),
        });
      }
      await onSaveRequestDataValues({
        message_id: currentMessageId,
        items: payloadItems,
      });
      closeClientDataModal();
    } catch (error) {
      setClientDataModal((prev) => ({
        ...prev,
        saving: false,
        error: error?.message || "Не удалось сохранить данные",
      }));
    }
  };

  const handleRequestRowDragStart = (event, rowItem, rowLocked) => {
    if (rowLocked || dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate) {
      event.preventDefault();
      return;
    }
    setDraggedRequestRowId(String(rowItem.localId || ""));
    setDragOverRequestRowId(String(rowItem.localId || ""));
    try {
      event.dataTransfer.effectAllowed = "move";
      event.dataTransfer.setData("text/plain", String(rowItem.localId || ""));
    } catch (_error) {
      // noop for browsers that restrict custom drag payloads
    }
  };

  const handleRequestRowDragEnd = () => {
    setDraggedRequestRowId("");
    setDragOverRequestRowId("");
  };

  const appendStatusChangeFiles = (files) => {
    const list = Array.isArray(files) ? files.filter(Boolean) : [];
    if (!list.length) return;
    setStatusChangeModal((prev) => {
      const existing = Array.isArray(prev.files) ? prev.files : [];
      const next = [...existing];
      list.forEach((file) => {
        const duplicate = next.some(
          (item) =>
            item &&
            item.name === file.name &&
            Number(item.size || 0) === Number(file.size || 0) &&
            Number(item.lastModified || 0) === Number(file.lastModified || 0)
        );
        if (!duplicate) next.push(file);
      });
      return { ...prev, files: next };
    });
  };

  const removeStatusChangeFile = (index) => {
    setStatusChangeModal((prev) => {
      const files = Array.isArray(prev.files) ? [...prev.files] : [];
      files.splice(index, 1);
      return { ...prev, files };
    });
  };

  const submitStatusChange = async (event) => {
    if (event && typeof event.preventDefault === "function") event.preventDefault();
    if (!row?.id || typeof onChangeStatus !== "function") return;
    const nextStatus = String(statusChangeModal.statusCode || "").trim();
    if (!nextStatus) {
      setStatusChangeModal((prev) => ({ ...prev, error: "Выберите новый статус" }));
      return;
    }
    if (nextStatus === String(row?.status_code || "").trim()) {
      setStatusChangeModal((prev) => ({ ...prev, error: "Выберите статус, отличный от текущего" }));
      return;
    }
    setStatusChangeModal((prev) => ({ ...prev, saving: true, error: "" }));
    try {
      const localValue = String(statusChangeModal.importantDateAt || "").trim();
      const importantDateIso = localValue ? new Date(localValue).toISOString() : "";
      await onChangeStatus({
        requestId: String(row.id),
        statusCode: nextStatus,
        importantDateAt: importantDateIso || null,
        comment: statusChangeModal.comment || "",
        files: statusChangeModal.files || [],
      });
      closeStatusChangeModal();
    } catch (error) {
      setStatusChangeModal((prev) => ({ ...prev, saving: false, error: error.message || "Не удалось сменить статус" }));
    }
  };

  const chatTimelineItems = [];
  let previousDate = "";
  const timelineSource = [];
  safeMessages.forEach((item) => {
    timelineSource.push({
      type: "message",
      key: "msg-" + String(item?.id || Math.random()),
      created_at: item?.created_at || null,
      payload: item,
    });
  });
  safeAttachments
    .filter((item) => !String(item?.message_id || "").trim())
    .forEach((item) => {
      timelineSource.push({
        type: "file",
        key: "file-" + String(item?.id || Math.random()),
        created_at: item?.created_at || null,
        payload: item,
      });
    });
  timelineSource.sort((a, b) => {
    const aTime = new Date(a.created_at || 0).getTime();
    const bTime = new Date(b.created_at || 0).getTime();
    if (!Number.isFinite(aTime) && !Number.isFinite(bTime)) return 0;
    if (!Number.isFinite(aTime)) return 1;
    if (!Number.isFinite(bTime)) return -1;
    if (aTime !== bTime) return aTime - bTime;
    return String(a.key).localeCompare(String(b.key), "ru");
  });
  timelineSource.forEach((entry, index) => {
    const dateLabel = fmtDateOnly(entry.created_at);
    const normalizedDate = dateLabel && dateLabel !== "-" ? dateLabel : "Без даты";
    if (normalizedDate !== previousDate) {
      chatTimelineItems.push({ type: "date", key: "date-" + normalizedDate + "-" + index, label: normalizedDate });
      previousDate = normalizedDate;
    }
    chatTimelineItems.push(entry);
  });

  useEffect(() => {
    if (chatTab !== "chat") return;
    const listNode = chatListRef.current;
    if (!listNode) return;
    const cursor = String(localActivityCursor || "");
    if (!cursor || cursor === lastAutoScrollCursorRef.current) return;
    lastAutoScrollCursorRef.current = cursor;
    const raf = window.requestAnimationFrame(() => {
      if (!chatListRef.current) return;
      chatListRef.current.scrollTop = chatListRef.current.scrollHeight;
    });
    return () => window.cancelAnimationFrame(raf);
  }, [chatTab, localActivityCursor]);

  const routeNodes =
    Array.isArray(statusRouteNodes) && statusRouteNodes.length
      ? statusRouteNodes
      : row?.status_code
        ? [{ code: row.status_code, name: statusLabel(row.status_code), state: "current", note: "Текущий этап обработки заявки" }]
        : [];

  const AttachmentPreviewModal = AttachmentPreviewModalComponent;
  const StatusLine = StatusLineComponent;

  const renderRequestDataMessageItems = (payload) => {
    const items = Array.isArray(payload?.request_data_items) ? payload.request_data_items : [];
    const allFilled = Boolean(payload?.request_data_all_filled);
    if (!items.length) return <p className="chat-message-text">Запрос</p>;
    if (allFilled) {
      const fileOnly = items.length === 1 && String(items[0]?.field_type || "").toLowerCase() === "file";
      return <p className="chat-message-text chat-request-data-collapsed">{fileOnly ? "Файл" : "Заполнен"}</p>;
    }
    const visibleItems = items.slice(0, 7);
    const hiddenCount = Math.max(0, items.length - visibleItems.length);
    return (
      <div className="chat-request-data-list">
        {visibleItems.map((item, idx) => (
          <div className={"chat-request-data-item" + (item?.is_filled ? " filled" : "")} key={String(item?.id || idx)}>
            <span className="chat-request-data-index">
              {item?.is_filled ? <span className="chat-request-data-check">✓</span> : null}
              {String(item?.index || idx + 1) + "."}
            </span>
            <span className="chat-request-data-label">{String(item?.label_short || item?.label || "Поле")}</span>
          </div>
        ))}
        {hiddenCount > 0 ? <div className="chat-request-data-more">... еще {hiddenCount}</div> : null}
      </div>
    );
  };

  const formatRequestDataValue = (item) => {
    const type = String(item?.field_type || "string").toLowerCase();
    if (type === "date") {
      const text = String(item?.value_text || "").trim();
      return text ? fmtDateOnly(text) : "Не заполнено";
    }
    if (type === "file") {
      const attachmentId = String(item?.value_text || "").trim();
      const linkedAttachment = attachmentId ? attachmentById.get(attachmentId) : null;
      const fileMeta = item?.value_file || (linkedAttachment ? {
        attachment_id: linkedAttachment.id,
        file_name: linkedAttachment.file_name,
        mime_type: linkedAttachment.mime_type,
        size_bytes: linkedAttachment.size_bytes,
        download_url: linkedAttachment.download_url,
      } : null);
      return fileMeta || null;
    }
    const text = String(item?.value_text || "").trim();
    return text || "Не заполнено";
  };

  const dataRequestProgress = useMemo(() => {
    const rows = Array.isArray(dataRequestModal.rows) ? dataRequestModal.rows : [];
    const total = rows.length;
    const filled = rows.filter((rowItem) => Boolean(rowItem?.is_filled || String(rowItem?.value_text || "").trim())).length;
    return { total, filled };
  }, [dataRequestModal.rows]);

  return (
    <div className="block">
      <div className="request-workspace-layout">
        <div className="request-main-column">
          <div className="block">
            <div className="request-card-head">
              <h3>Карточка</h3>
              <div className="request-card-head-actions">
                {canRequestData ? (
                  <button
                    type="button"
                    className="icon-btn request-card-status-btn"
                    data-tooltip="Сменить статус"
                    aria-label="Сменить статус"
                    onClick={() => openStatusChangeModal()}
                    disabled={loading || !row}
                  >
                    ⇄
                  </button>
                ) : null}
                <button
                  type="button"
                  className="icon-btn request-card-data-btn"
                  data-tooltip="Данные заявки"
                  aria-label="Данные заявки"
                  onClick={() => setRequestDataListOpen(true)}
                  disabled={loading || !row}
                >
                  <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                    <path d="M4 5h16v2H4V5Zm0 6h16v2H4v-2Zm0 6h10v2H4v-2Z" fill="currentColor" />
                  </svg>
                </button>
                <button
                  type="button"
                  className="icon-btn request-card-finance-btn"
                  data-tooltip="Финансы заявки"
                  aria-label="Финансы заявки"
                  onClick={() => setFinanceOpen(true)}
                  disabled={loading || !row}
                >
                  $
                </button>
              </div>
            </div>
            <div className="request-card-head-spacer" aria-hidden="true" />
            {loading ? (
              <p className="muted">Загрузка...</p>
            ) : row ? (
              <>
                <div className="request-card-grid request-card-grid-compact">
                  {showTopicStatusInCard ? (
                    <>
                      <div className="request-field">
                        <span className="request-field-label">Тема</span>
                        <span className="request-field-value">{String(row.topic_name || row.topic_code || "-")}</span>
                      </div>
                      <div className="request-field">
                        <span className="request-field-label">Статус</span>
                        <span className="request-field-value">{statusLabel(row.status_code)}</span>
                      </div>
                    </>
                  ) : null}
                  <div className="request-field request-field-span-2 request-field-description">
                    <div className="request-field-head">
                      <span className="request-field-label">Описание проблемы</span>
                      <button
                        type="button"
                        className="icon-btn request-field-expand-btn"
                        data-tooltip="Развернуть описание"
                        aria-label="Развернуть описание"
                        onClick={() => setDescriptionOpen(true)}
                      >
                        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                          <path
                            d="M4 9V4h5v2H6v3H4zm10-5h6v6h-2V6h-4V4zM4 15h2v3h3v2H4v-5zm14 3v-3h2v5h-5v-2h3z"
                            fill="currentColor"
                          />
                        </svg>
                      </button>
                    </div>
                    <span className="request-field-value">
                      {row.description ? String(row.description) : "Описание не заполнено"}
                    </span>
                  </div>
                  {showContactsInCard ? (
                    <>
                      <div className="request-field">
                        <span className="request-field-label">Клиент</span>
                        <span
                          className={"request-field-value" + (clientHasPhone ? " has-tooltip request-contact-value" : "")}
                          data-tooltip={clientHasPhone ? clientPhone : undefined}
                        >
                          {clientLabel}
                        </span>
                      </div>
                      <div className="request-field">
                        <span className="request-field-label">Юрист</span>
                        <span
                          className={"request-field-value" + (lawyerHasPhone ? " has-tooltip request-contact-value" : "")}
                          data-tooltip={lawyerHasPhone ? lawyerPhone : undefined}
                        >
                          {lawyerLabel}
                        </span>
                      </div>
                    </>
                  ) : null}
                  {canSeeCreatedUpdatedInCard ? (
                    <>
                      <div className="request-field">
                        <span className="request-field-label">Создана</span>
                        <span className="request-field-value">{fmtShortDateTime(row.created_at)}</span>
                      </div>
                      <div className="request-field">
                        <span className="request-field-label">Изменена</span>
                        <span className="request-field-value">{fmtShortDateTime(row.updated_at)}</span>
                      </div>
                    </>
                  ) : null}
                </div>
                <div className="request-status-route">
                  <h4>Маршрут статусов</h4>
                  {routeNodes.length ? (
                    <ol className="request-route-list" id="request-status-route">
                      {routeNodes.map((node, index) => {
                        const state = String(node?.state || "pending");
                        const code = String(node?.code || "").trim();
                        const rawName = String(node?.name || "").trim();
                        const name = rawName && rawName !== code ? rawName : statusLabel(code || rawName);
                        const note = String(node?.note || "").trim();
                        const changedAtSource = String(node?.changed_at || "").trim() || (index === 0 ? String(row?.created_at || "").trim() : "");
                        const changedAt = changedAtSource ? fmtDate(changedAtSource) : "";
                        const className = "route-item " + (state === "current" ? "current" : state === "completed" ? "completed" : "pending");
                        return (
                          <li className={className} key={(node?.code || "node") + "-" + index}>
                            <span className="route-dot" />
                            <div className="route-body">
                              <b>{name}</b>
                              {note ? <p>{note}</p> : null}
                              <div className="muted route-time">Дата статуса: {changedAt || "-"}</div>
                            </div>
                          </li>
                        );
                      })}
                    </ol>
                  ) : (
                    <p className="muted">Маршрут статусов для темы не настроен</p>
                  )}
                </div>
              </>
            ) : (
              <p className="muted">Нет данных по заявке</p>
            )}
          </div>
        </div>

        <div className="block request-chat-block">
          <div className="request-chat-head">
            <h3>Коммуникация</h3>
            <div className="request-chat-tabs" role="tablist" aria-label="Коммуникация">
              <button
                type="button"
                role="tab"
                aria-selected={chatTab === "chat"}
                className={"tab-btn" + (chatTab === "chat" ? " active" : "")}
                onClick={() => setChatTab("chat")}
              >
                Чат
              </button>
              <button
                type="button"
                role="tab"
                aria-selected={chatTab === "files"}
                className={"tab-btn" + (chatTab === "files" ? " active" : "")}
                onClick={() => setChatTab("files")}
              >
                {"Файлы" + (safeAttachments.length ? " (" + safeAttachments.length + ")" : "")}
              </button>
            </div>
          </div>
          <div className="request-chat-live-row" aria-live="polite">
            <span className={"chat-live-dot" + (liveMode === "degraded" ? " degraded" : "")} />
            <span className="request-chat-live-text">{typingHintText || (liveMode === "degraded" ? "Связь нестабильна, включен backoff" : "Онлайн")}</span>
          </div>

          <input
            id={idMap.fileInput}
            ref={fileInputRef}
            type="file"
            multiple
            onChange={onInputFiles}
            disabled={loading || fileUploading}
            style={{ position: "absolute", width: "1px", height: "1px", opacity: 0, pointerEvents: "none" }}
          />

          {chatTab === "chat" ? (
            <>
              <ul className="simple-list request-modal-list request-chat-list" id={idMap.messagesList} ref={chatListRef}>
                {chatTimelineItems.length ? (
                  chatTimelineItems.map((entry) =>
                    entry.type === "date" ? (
                      <li key={entry.key} className="chat-date-divider">
                        <span>{entry.label}</span>
                      </li>
                    ) : entry.type === "file" ? (
                      <li
                        key={entry.key}
                        className={
                          "chat-message " +
                          (String(entry.payload?.responsible || "").toUpperCase().includes("КЛИЕНТ") ? "incoming" : "outgoing")
                        }
                      >
                        <div className="chat-message-author">{String(entry.payload?.responsible || "Система")}</div>
                        <div className="chat-message-bubble">
                          <div className="chat-message-files">
                            <button
                              type="button"
                              className="chat-message-file-chip"
                              onClick={() => openAttachmentFromMessage(entry.payload)}
                              title={String(entry.payload?.file_name || "Файл")}
                            >
                              <span className="chat-message-file-icon" aria-hidden="true">
                                📎
                              </span>
                              <span className="chat-message-file-name">{String(entry.payload?.file_name || "Файл")}</span>
                            </button>
                          </div>
                          <div className="chat-message-time">{fmtTimeOnly(entry.payload?.created_at)}</div>
                        </div>
                      </li>
                    ) : (
                      (() => {
                        const messageKind = String(entry.payload?.message_kind || "");
                        const isRequestDataMessage = messageKind === "REQUEST_DATA";
                        const requestDataInteractive = isRequestDataMessage && (canRequestData || canFillRequestData);
                        const bubbleClass =
                          "chat-message-bubble" +
                          (isRequestDataMessage ? " chat-request-data-bubble" : "") +
                          (entry.payload?.request_data_all_filled ? " all-filled" : "") +
                          (isRequestDataMessage && canFillRequestData ? " request-data-message-btn" : "");
                        const itemClass =
                          "chat-message " +
                          (String(entry.payload?.author_type || "").toUpperCase() === "CLIENT" ? "incoming" : "outgoing") +
                          (isRequestDataMessage && canFillRequestData ? " request-data-item" + (entry.payload?.request_data_all_filled ? " done" : "") : "");
                        return (
                          <li key={entry.key} className={itemClass}>
                        <div className="chat-message-author">{String(entry.payload?.author_name || entry.payload?.author_type || "Система")}</div>
                        <div
                          className={bubbleClass}
                          onClick={
                            requestDataInteractive
                              ? () =>
                                  canRequestData
                                    ? openEditDataRequestModal(String(entry.payload?.id || ""))
                                    : openClientDataRequestModal(String(entry.payload?.id || ""))
                              : undefined
                          }
                          role={requestDataInteractive ? "button" : undefined}
                          tabIndex={requestDataInteractive ? 0 : undefined}
                          onKeyDown={
                            requestDataInteractive
                              ? (event) => {
                                  if (event.key === "Enter" || event.key === " ") {
                                    event.preventDefault();
                                    if (canRequestData) openEditDataRequestModal(String(entry.payload?.id || ""));
                                    else openClientDataRequestModal(String(entry.payload?.id || ""));
                                  }
                                }
                              : undefined
                          }
                        >
                          {String(entry.payload?.message_kind || "") === "REQUEST_DATA" ? (
                            <>
                              <div className="chat-request-data-head">Запрос</div>
                              {renderRequestDataMessageItems(entry.payload)}
                            </>
                          ) : (
                            <p className="chat-message-text">{String(entry.payload?.body || "")}</p>
                          )}
                          {(() => {
                            if (String(entry.payload?.message_kind || "") === "REQUEST_DATA") return null;
                            const messageId = String(entry.payload?.id || "").trim();
                            if (!messageId) return null;
                            const messageFiles = attachmentsByMessageId.get(messageId) || [];
                            if (!messageFiles.length) return null;
                            return (
                              <div className="chat-message-files">
                                {messageFiles.map((file) => (
                                  <button
                                    type="button"
                                    key={String(file.id)}
                                    className="chat-message-file-chip"
                                    onClick={() => openAttachmentFromMessage(file)}
                                    title={String(file.file_name || "Файл")}
                                  >
                                    <span className="chat-message-file-icon" aria-hidden="true">
                                      📎
                                    </span>
                                    <span className="chat-message-file-name">{String(file.file_name || "Файл")}</span>
                                  </button>
                                ))}
                              </div>
                            );
                          })()}
                          <div className="chat-message-time">{fmtTimeOnly(entry.payload?.created_at)}</div>
                        </div>
                          </li>
                        );
                      })()
                    )
                  )
                ) : (
                  <li className="muted">Сообщений нет</li>
                )}
              </ul>
              <form className="stack" onSubmit={onSendMessage}>
                <div
                  className={"field request-chat-composer-dropzone" + (dropActive ? " drag-active" : "")}
                  onDragOver={(event) => {
                    event.preventDefault();
                    setDropActive(true);
                  }}
                  onDragLeave={(event) => {
                    if (event.currentTarget.contains(event.relatedTarget)) return;
                    setDropActive(false);
                  }}
                  onDrop={onDropFiles}
                >
                  <label htmlFor={idMap.messageBody}>Новое сообщение</label>
                  <textarea
                    id={idMap.messageBody}
                    placeholder={messagePlaceholder}
                    value={messageDraft}
                    onChange={onMessageChange}
                    onFocus={() => setComposerFocused(true)}
                    onBlur={() => setComposerFocused(false)}
                    disabled={loading || fileUploading}
                  />
                  <div className="request-drop-hint muted">Перетащите файлы сюда или прикрепите скрепкой</div>
                </div>
                {hasPendingFiles ? (
                  <div className="request-pending-files">
                    {pendingFiles.map((file, index) => (
                      <div className="pending-file-chip" key={(file.name || "file") + "-" + String(file.lastModified || index)}>
                        <span className="pending-file-icon" aria-hidden="true">
                          📎
                        </span>
                        <span className="pending-file-name">{file.name}</span>
                        <button
                          type="button"
                          className="pending-file-remove"
                          aria-label={"Удалить файл " + file.name}
                          onClick={() => onRemoveSelectedFile(index)}
                        >
                          ×
                        </button>
                      </div>
                    ))}
                    <button type="button" className="btn secondary btn-sm" onClick={onClearSelectedFiles}>
                      Очистить вложения
                    </button>
                  </div>
                ) : null}
                <div className="request-chat-composer-actions">
                  {canRequestData ? (
                    <button
                      className="btn secondary btn-sm"
                      type="button"
                      onClick={openCreateDataRequestModal}
                      disabled={loading || fileUploading}
                    >
                      Запросить
                    </button>
                  ) : null}
                  <button
                    className="icon-btn file-action-btn composer-attach-btn"
                    type="button"
                    data-tooltip="Прикрепить файл"
                    aria-label="Прикрепить файл"
                    onClick={() => fileInputRef.current?.click()}
                    disabled={loading || fileUploading}
                  >
                    <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                      <path
                        d="M8.6 13.8 15 7.4a3 3 0 0 1 4.2 4.2l-8.1 8.1a5 5 0 1 1-7.1-7.1l8.6-8.6a1 1 0 0 1 1.4 1.4l-8.6 8.6a3 3 0 1 0 4.2 4.2l8.1-8.1a1 1 0 0 0-1.4-1.4l-6.4 6.4a1 1 0 0 1-1.4-1.4z"
                        fill="currentColor"
                      />
                    </svg>
                  </button>
                  <button
                    className="btn"
                    id={idMap.sendButton}
                    type="submit"
                    disabled={loading || fileUploading || !canSubmit}
                  >
                    Отправить
                  </button>
                </div>
              </form>
            </>
          ) : (
            <div className="request-files-tab">
              <ul className="simple-list request-modal-list" id={idMap.filesList}>
                {safeAttachments.length ? (
                  safeAttachments.map((item) => (
                    <li key={String(item.id)}>
                      <div>{item.file_name || "Файл"}</div>
                      <div className="muted request-modal-item-meta">
                        {String(item.mime_type || "application/octet-stream") + " • " + fmtBytes(item.size_bytes) + " • " + fmtDate(item.created_at)}
                      </div>
                      <div className="request-file-actions">
                        {item.download_url && detectAttachmentPreviewKind(item.file_name, item.mime_type) !== "none" ? (
                          <button
                            className="icon-btn file-action-btn"
                            type="button"
                            data-tooltip="Предпросмотр"
                            onClick={() => openPreview(item)}
                            aria-label="Предпросмотр"
                          >
                            <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                              <path
                                d="M12 5C6.8 5 3 9.2 2 12c1 2.8 4.8 7 10 7s9-4.2 10-7c-1-2.8-4.8-7-10-7zm0 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2.2A1.8 1.8 0 1 0 12 10a1.8 1.8 0 0 0 0 3.8z"
                                fill="currentColor"
                              />
                            </svg>
                          </button>
                        ) : null}
                        {item.download_url ? (
                          <a
                            className="icon-btn file-action-btn request-file-link-icon"
                            data-tooltip="Скачать"
                            aria-label={"Скачать: " + String(item.file_name || "файл")}
                            href={item.download_url}
                            target="_blank"
                            rel="noreferrer"
                          >
                            <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                              <path
                                d="M12 3a1 1 0 0 1 1 1v8.17l2.58-2.58a1 1 0 1 1 1.42 1.42l-4.3 4.3a1 1 0 0 1-1.4 0l-4.3-4.3a1 1 0 0 1 1.42-1.42L11 12.17V4a1 1 0 0 1 1-1zm-7 14a1 1 0 0 1 1 1v1h12v-1a1 1 0 1 1 2 0v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"
                                fill="currentColor"
                              />
                            </svg>
                          </a>
                        ) : null}
                      </div>
                    </li>
                  ))
                ) : (
                  <li className="muted">Файлов пока нет</li>
                )}
              </ul>
              <div className="request-files-tab-actions">
                <span className="muted">
                  {"Сообщений: " + String(safeMessages.length) + " • Общий размер файлов: " + fmtBytes(totalFilesBytes)}
                </span>
              </div>
            </div>
          )}
        </div>
      </div>
      {StatusLine ? <StatusLine status={status} /> : null}
      {AttachmentPreviewModal ? (
        <AttachmentPreviewModal
          open={preview.open}
          title="Предпросмотр файла"
          url={preview.url}
          fileName={preview.fileName}
          mimeType={preview.mimeType}
          onClose={closePreview}
        />
      ) : null}
      <div
        className={"overlay" + (clientDataModal.open ? " open" : "")}
        onClick={closeClientDataModal}
        aria-hidden={clientDataModal.open ? "false" : "true"}
        id={idMap.dataRequestOverlay}
      >
        <div className="modal request-data-summary-modal data-request-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>Запрос данных</h3>
              <p className="muted request-finance-subtitle">
                {row?.track_number ? "Заявка " + String(row.track_number) : "Заполните данные по запросу юриста"}
              </p>
            </div>
            <button className="close" type="button" onClick={closeClientDataModal} aria-label="Закрыть">
              ×
            </button>
          </div>
          <form className="stack" onSubmit={submitClientDataModal}>
            <div className="request-data-summary-list" id={idMap.dataRequestItems}>
              {clientDataModal.loading ? (
                <p className="muted">Загрузка...</p>
              ) : (clientDataModal.items || []).length ? (
                (clientDataModal.items || []).map((item, index) => {
                  const fieldType = String(item?.field_type || "string").toLowerCase();
                  const fileMeta = item?.value_file;
                  return (
                    <div className="request-data-summary-row" key={String(item.localId || index)}>
                      <div className="request-data-summary-label">
                        {String(index + 1) + ". " + String(item?.label || item?.key || "Поле")}
                      </div>
                      <div className="request-data-summary-value">
                        {fieldType === "text" ? (
                          <textarea
                            value={String(item?.value_text || "")}
                            onChange={(event) =>
                              updateClientDataItem(item.localId, { value_text: event.target.value })
                            }
                            rows={3}
                            disabled={clientDataModal.saving || clientDataModal.loading}
                          />
                        ) : fieldType === "date" ? (
                          <input
                            type="date"
                            value={String(item?.value_text || "").slice(0, 10)}
                            onChange={(event) =>
                              updateClientDataItem(item.localId, { value_text: event.target.value })
                            }
                            disabled={clientDataModal.saving || clientDataModal.loading}
                          />
                        ) : fieldType === "number" ? (
                          <input
                            type="number"
                            step="any"
                            value={String(item?.value_text || "")}
                            onChange={(event) =>
                              updateClientDataItem(item.localId, { value_text: event.target.value })
                            }
                            disabled={clientDataModal.saving || clientDataModal.loading}
                          />
                        ) : fieldType === "file" ? (
                          <div className="stack">
                            {fileMeta && fileMeta.download_url ? (
                              <button
                                type="button"
                                className="chat-message-file-chip"
                                onClick={() => openAttachmentFromMessage(fileMeta)}
                              >
                                <span className="chat-message-file-icon" aria-hidden="true">📎</span>
                                <span className="chat-message-file-name">{String(fileMeta.file_name || "Файл")}</span>
                              </button>
                            ) : null}
                            <input
                              type="file"
                              onChange={(event) =>
                                updateClientDataItem(item.localId, {
                                  pendingFile: event.target.files && event.target.files[0] ? event.target.files[0] : null,
                                })
                              }
                              disabled={clientDataModal.saving || clientDataModal.loading}
                            />
                            {item?.pendingFile ? (
                              <span className="muted">{String(item.pendingFile.name || "")}</span>
                            ) : null}
                          </div>
                        ) : (
                          <input
                            type="text"
                            value={String(item?.value_text || "")}
                            onChange={(event) =>
                              updateClientDataItem(item.localId, { value_text: event.target.value })
                            }
                            disabled={clientDataModal.saving || clientDataModal.loading}
                          />
                        )}
                      </div>
                    </div>
                  );
                })
              ) : (
                <p className="muted">Нет полей для заполнения.</p>
              )}
            </div>
            {clientDataModal.error ? <div className="status error">{clientDataModal.error}</div> : null}
            <div className={"request-data-status" + (clientDataModal.status ? " ok" : "")} id={idMap.dataRequestStatus}>
              {clientDataModal.status || ""}
            </div>
            <div className="modal-actions modal-actions-right">
              <button
                type="submit"
                className="btn btn-sm request-data-submit-btn"
                id={idMap.dataRequestSave}
                disabled={clientDataModal.loading || clientDataModal.saving}
              >
                {clientDataModal.saving ? "Сохранение..." : "Сохранить"}
              </button>
            </div>
          </form>
        </div>
      </div>
      <div
        className={"overlay" + (statusChangeModal.open ? " open" : "")}
        onClick={closeStatusChangeModal}
        aria-hidden={statusChangeModal.open ? "false" : "true"}
      >
        <div className="modal request-status-change-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>Смена статуса</h3>
              <p className="muted request-finance-subtitle">
                {row?.track_number ? "Заявка " + String(row.track_number) : "Выберите статус и важную дату"}
              </p>
            </div>
            <button className="close" type="button" onClick={closeStatusChangeModal} aria-label="Закрыть">
              ×
            </button>
          </div>
          <input
            ref={statusChangeFileInputRef}
            type="file"
            multiple
            onChange={(event) => {
              appendStatusChangeFiles(Array.from((event.target && event.target.files) || []));
              event.target.value = "";
            }}
            style={{ position: "absolute", width: "1px", height: "1px", opacity: 0, pointerEvents: "none" }}
          />
          <form className="stack" onSubmit={submitStatusChange}>
            <div className="request-status-change-grid">
              <div className="field">
                <label htmlFor="status-change-next-status">Новый статус</label>
                <select
                  id="status-change-next-status"
                  value={statusChangeModal.statusCode}
                  onChange={(event) => setStatusChangeModal((prev) => ({ ...prev, statusCode: event.target.value, error: "" }))}
                  disabled={statusChangeModal.saving || loading}
                >
                  <option value="">Выберите статус</option>
                  {statusOptions
                    .filter((item) => item.code !== String(row?.status_code || "").trim())
                    .filter((item) =>
                      Array.isArray(statusChangeModal.allowedStatusCodes) && statusChangeModal.allowedStatusCodes.length
                        ? statusChangeModal.allowedStatusCodes.includes(item.code)
                        : true
                    )
                    .map((item) => (
                      <option key={item.code} value={item.code}>
                        {item.name + " (" + item.code + ")" + (item.groupName ? " • " + item.groupName : "")}
                      </option>
                    ))}
                </select>
              </div>
              <div className="field">
                <label htmlFor="status-change-important-date">Важная дата (дедлайн)</label>
                <input
                  id="status-change-important-date"
                  type="datetime-local"
                  value={statusChangeModal.importantDateAt}
                  onChange={(event) => setStatusChangeModal((prev) => ({ ...prev, importantDateAt: event.target.value, error: "" }))}
                  disabled={statusChangeModal.saving || loading}
                />
              </div>
            </div>
            <div className="field">
              <label htmlFor="status-change-comment">Комментарий к смене статуса</label>
              <textarea
                id="status-change-comment"
                placeholder="Комментарий будет добавлен в историю и чат (если указан)"
                value={statusChangeModal.comment}
                onChange={(event) => setStatusChangeModal((prev) => ({ ...prev, comment: event.target.value }))}
                disabled={statusChangeModal.saving || loading}
              />
            </div>
            <div className="request-status-change-files">
              <div className="request-status-change-files-head">
                <b>Вложения</b>
                <button
                  type="button"
                  className="icon-btn file-action-btn"
                  data-tooltip="Прикрепить файлы"
                  onClick={() => statusChangeFileInputRef.current?.click()}
                  disabled={statusChangeModal.saving || loading}
                >
                  <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
                    <path
                      d="M8.6 13.8 15 7.4a3 3 0 0 1 4.2 4.2l-8.1 8.1a5 5 0 1 1-7.1-7.1l8.6-8.6a1 1 0 0 1 1.4 1.4l-8.6 8.6a3 3 0 1 0 4.2 4.2l8.1-8.1a1 1 0 0 0-1.4-1.4l-6.4 6.4a1 1 0 0 1-1.4-1.4z"
                      fill="currentColor"
                    />
                  </svg>
                </button>
              </div>
              {Array.isArray(statusChangeModal.files) && statusChangeModal.files.length ? (
                <div className="request-pending-files">
                  {statusChangeModal.files.map((file, index) => (
                    <div className="pending-file-chip" key={(file.name || "file") + "-" + String(file.lastModified || index)}>
                      <span className="pending-file-icon" aria-hidden="true">📎</span>
                      <span className="pending-file-name">{file.name}</span>
                      <button
                        type="button"
                        className="pending-file-remove"
                        aria-label={"Удалить файл " + file.name}
                        onClick={() => removeStatusChangeFile(index)}
                      >
                        ×
                      </button>
                    </div>
                  ))}
                </div>
              ) : (
                <p className="muted">Файлы не добавлены</p>
              )}
            </div>
            <div className="request-status-history-block">
              <div className="request-status-history-head">
                <b>История статусов</b>
                <span className="muted">{safeStatusHistory.length ? String(safeStatusHistory.length) + " записей" : "Нет записей"}</span>
              </div>
              <ol className="request-route-list request-status-history-list">
                {safeStatusHistory.length ? (
                  safeStatusHistory.map((item, index) => {
                    const statusCode = String(item?.to_status || "");
                    const statusMeta = statusByCode.get(statusCode);
                    const itemClass =
                      "route-item request-status-history-route-item " + (index === 0 ? "current" : "completed");
                    return (
                      <li key={String(item?.id || index)} className={itemClass}>
                        <span className="route-dot" />
                        <div className="route-body">
                          <div className="request-status-history-row">
                            <b>{String(item?.to_status_name || statusMeta?.name || statusLabel(statusCode) || statusCode || "-")}</b>
                            {statusMeta?.isTerminal ? <span className="request-status-history-chip">Терминальный</span> : null}
                          </div>
                          <div className="muted route-time">{fmtShortDateTime(item?.changed_at)}</div>
                          <div className="request-status-history-meta">
                            <span>{"Важная дата: " + fmtShortDateTime(item?.important_date_at)}</span>
                            <span>{"Длительность: " + formatDuration(item?.duration_seconds)}</span>
                          </div>
                          {item?.from_status ? (
                            <div className="request-status-history-meta">
                              <span>{"Из: " + statusLabel(item.from_status)}</span>
                            </div>
                          ) : null}
                          {String(item?.comment || "").trim() ? (
                            <div className="request-status-history-comment">{String(item.comment)}</div>
                          ) : null}
                        </div>
                      </li>
                    );
                  })
                ) : (
                  <li className="muted">История изменений статусов пока пустая</li>
                )}
              </ol>
            </div>
            {statusChangeModal.error ? <div className="status error">{statusChangeModal.error}</div> : null}
            <div className="modal-actions modal-actions-right">
              <button
                type="submit"
                className="btn btn-sm request-data-submit-btn"
                disabled={statusChangeModal.saving || loading}
              >
                {statusChangeModal.saving ? "Сохранение..." : "Отправить"}
              </button>
            </div>
          </form>
        </div>
      </div>
      <div
        className={"overlay" + (financeOpen ? " open" : "")}
        onClick={() => setFinanceOpen(false)}
        aria-hidden={financeOpen ? "false" : "true"}
      >
        <div className="modal request-finance-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>Финансы заявки</h3>
              <p className="muted request-finance-subtitle">
                {row?.track_number ? "Заявка " + String(row.track_number) : "Данные по заявке"}
              </p>
            </div>
            <button className="close" type="button" onClick={() => setFinanceOpen(false)} aria-label="Закрыть">
              ×
            </button>
          </div>
          <div className="request-card-grid request-finance-grid">
            <div className="request-field">
              <span className="request-field-label">Стоимость</span>
              <span className="request-field-value">{fmtAmount(finance?.request_cost ?? row?.request_cost)}</span>
            </div>
            <div className="request-field">
              <span className="request-field-label">Оплачено</span>
              <span className="request-field-value">{fmtAmount(finance?.paid_total)}</span>
            </div>
            <div className="request-field">
              <span className="request-field-label">Дата оплаты</span>
              <span className="request-field-value">{fmtShortDateTime(finance?.last_paid_at ?? row?.paid_at)}</span>
            </div>
            {canSeeRate ? (
              <div className="request-field">
                <span className="request-field-label">Ставка</span>
                <span className="request-field-value">{fmtAmount(finance?.effective_rate ?? row?.effective_rate)}</span>
              </div>
            ) : null}
          </div>
        </div>
      </div>
      <div
        className={"overlay" + (descriptionOpen ? " open" : "")}
        onClick={() => setDescriptionOpen(false)}
        aria-hidden={descriptionOpen ? "false" : "true"}
      >
        <div className="modal request-description-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>{row?.track_number ? "Заявка " + String(row.track_number) : "Заявка"}</h3>
              <div className="request-description-modal-headline">
                <p className="muted request-finance-subtitle">
                  {String(row?.topic_name || row?.topic_code || "Тема не указана")}
                </p>
                <span className="request-description-status-chip">{statusLabel(row?.status_code)}</span>
              </div>
            </div>
            <button className="close" type="button" onClick={() => setDescriptionOpen(false)} aria-label="Закрыть">
              ×
            </button>
          </div>
          <div className="request-description-modal-body">
            <div className="request-description-modal-main">
              <div className="request-description-modal-title">
                <span className="request-field-label">Описание проблемы</span>
              </div>
              <div className="request-description-modal-text">
                {row?.description ? String(row.description) : "Описание не заполнено"}
              </div>
            </div>
            <div className="request-description-modal-meta-wrap">
              <div className="request-description-modal-meta">
                <div className="request-description-meta-item">
                  <span className="request-field-label">Клиент</span>
                  <span
                    className={"request-field-value" + (clientHasPhone ? " has-tooltip request-contact-value" : "")}
                    data-tooltip={clientHasPhone ? clientPhone : undefined}
                  >
                    {clientLabel}
                  </span>
                </div>
                <div className="request-description-meta-item align-right">
                  <span className="request-field-label">Юрист</span>
                  <span
                    className={"request-field-value" + (lawyerHasPhone ? " has-tooltip request-contact-value" : "")}
                    data-tooltip={lawyerHasPhone ? lawyerPhone : undefined}
                  >
                    {lawyerLabel}
                  </span>
                </div>
                <div className="request-description-meta-item">
                  <span className="request-field-label">Создана</span>
                  <span className="request-field-value">{fmtShortDateTime(row?.created_at)}</span>
                </div>
                <div className="request-description-meta-item align-right">
                  <span className="request-field-label">Изменена</span>
                  <span className="request-field-value">{fmtShortDateTime(row?.updated_at)}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div
        className={"overlay" + (dataRequestModal.open ? " open" : "")}
        onClick={closeDataRequestModal}
        aria-hidden={dataRequestModal.open ? "false" : "true"}
      >
        <div className="modal request-data-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>{dataRequestModal.messageId ? "Редактирование запроса данных" : "Запрос дополнительных данных"}</h3>
              <p className="muted request-finance-subtitle">
                {row?.track_number ? "Заявка " + String(row.track_number) : "Выберите поля для запроса"}
              </p>
            </div>
            <button className="close" type="button" onClick={closeDataRequestModal} aria-label="Закрыть">
              ×
            </button>
          </div>
          <div className="stack">
            <div className="request-data-modal-grid">
              <div className="field">
                <label htmlFor="request-data-request-template-select">Шаблон запроса (поиск)</label>
                <div className="request-data-combobox">
                  <input
                    id="request-data-request-template-select"
                    value={dataRequestModal.requestTemplateQuery}
                    onChange={(event) =>
                      setDataRequestModal((prev) => ({
                        ...prev,
                        requestTemplateQuery: event.target.value,
                        selectedRequestTemplateId: "",
                        templateStatus: "",
                        error: "",
                      }))
                    }
                    onFocus={() => setRequestTemplateSuggestOpen(true)}
                    onBlur={() => window.setTimeout(() => setRequestTemplateSuggestOpen(false), 120)}
                    disabled={dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate}
                    placeholder="Введите название шаблона"
                  />
                  {requestTemplateBadge ? (
                    <span className={"request-data-template-badge " + requestTemplateBadge.kind}>{requestTemplateBadge.label}</span>
                  ) : null}
                  {requestTemplateSuggestOpen && filteredRequestTemplates.length ? (
                    <div className="request-data-suggest-list" role="listbox" aria-label="Шаблоны запроса">
                      {filteredRequestTemplates.map((tpl) => (
                        <button
                          key={String(tpl.id)}
                          type="button"
                          className="request-data-suggest-item"
                          onMouseDown={(event) => {
                            event.preventDefault();
                            setDataRequestModal((prev) => ({
                              ...prev,
                              requestTemplateQuery: String(tpl.name || ""),
                              selectedRequestTemplateId: String(tpl.id || ""),
                              error: "",
                              templateStatus: "",
                            }));
                            setRequestTemplateSuggestOpen(false);
                            void applyRequestTemplateById(String(tpl.id || ""), String(tpl.name || ""));
                          }}
                        >
                          <span>{String(tpl.name || "Шаблон")}</span>
                        </button>
                      ))}
                    </div>
                  ) : null}
                </div>
              </div>
              <div className="request-data-modal-actions-inline">
                <button
                  type="button"
                  className="icon-btn"
                  data-tooltip={
                    !canSaveSelectedRequestTemplate
                      ? "Чужой шаблон недоступен для изменения"
                      : requestTemplateActionMode === "save"
                      ? "Перезаписать шаблон"
                      : requestTemplateActionMode === "create"
                        ? "Создать шаблон"
                        : "Введите название шаблона"
                  }
                  onClick={saveCurrentDataRequestTemplate}
                  disabled={
                    !canSaveSelectedRequestTemplate ||
                    dataRequestModal.loading ||
                    dataRequestModal.saving ||
                    dataRequestModal.savingTemplate
                  }
                >
                  {dataRequestModal.savingTemplate ? "…" : requestTemplateActionMode === "create" ? "✚" : "💾"}
                </button>
              </div>
            </div>
            {dataRequestModal.templateStatus ? <div className="status ok">{dataRequestModal.templateStatus}</div> : null}
            {canRequestData && dataRequestModal.messageId ? (
              <div className="request-data-progress-line">
                <span className="request-data-progress-chip">
                  {"Заполнено клиентом: " + String(dataRequestProgress.filled) + " / " + String(dataRequestProgress.total)}
                </span>
              </div>
            ) : null}

            <div className="request-data-modal-grid">
              <div className="field">
                <label htmlFor="request-data-template-select">Поле данных (поиск по справочнику)</label>
                <div className="request-data-combobox">
                  <input
                    id="request-data-template-select"
                    value={dataRequestModal.catalogFieldQuery}
                    onChange={(event) =>
                      setDataRequestModal((prev) => ({
                        ...prev,
                        catalogFieldQuery: event.target.value,
                        selectedCatalogTemplateId: "",
                        templateStatus: "",
                        error: "",
                      }))
                    }
                    onFocus={() => setCatalogFieldSuggestOpen(true)}
                    onBlur={() => window.setTimeout(() => setCatalogFieldSuggestOpen(false), 120)}
                    disabled={dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate}
                    placeholder="Начните вводить наименование поля"
                    autoComplete="off"
                  />
                  {catalogFieldSuggestOpen && filteredCatalogFields.length ? (
                    <div className="request-data-suggest-list" role="listbox" aria-label="Поля данных">
                      {filteredCatalogFields.map((tpl) => (
                        <button
                          key={String(tpl.id)}
                          type="button"
                          className="request-data-suggest-item"
                          onMouseDown={(event) => {
                            event.preventDefault();
                            setDataRequestModal((prev) => ({
                              ...prev,
                              catalogFieldQuery: String(tpl.label || tpl.key || ""),
                              selectedCatalogTemplateId: String(tpl.id || ""),
                              error: "",
                              templateStatus: "",
                            }));
                            setCatalogFieldSuggestOpen(false);
                          }}
                        >
                          <span>{String(tpl.label || tpl.key)}</span>
                          <small>{String(tpl.value_type || "string")}</small>
                        </button>
                      ))}
                    </div>
                  ) : null}
                </div>
              </div>
              <div className="request-data-modal-actions-inline">
                <button
                  type="button"
                  className="icon-btn"
                  data-tooltip={catalogFieldActionMode === "add" ? "Добавить поле из справочника" : "Создать новое поле"}
                  onClick={addSelectedTemplateRow}
                  disabled={
                    !String(dataRequestModal.catalogFieldQuery || "").trim() && !selectedCatalogFieldCandidate ||
                    dataRequestModal.loading ||
                    dataRequestModal.saving ||
                    dataRequestModal.savingTemplate
                  }
                >
                  {catalogFieldActionMode === "add" ? "+" : "✚"}
                </button>
              </div>
            </div>

            <div className="request-data-rows">
              {(dataRequestModal.rows || []).length ? (
                (dataRequestModal.rows || []).map((rowItem, idx) => (
                  <div
                    className={
                      "request-data-row" +
                      (String(draggedRequestRowId) === String(rowItem.localId) ? " dragging" : "") +
                      (String(dragOverRequestRowId) === String(rowItem.localId) && String(draggedRequestRowId) !== String(rowItem.localId) ? " drag-over" : "") +
                      (viewerRoleCode === "LAWYER" && rowItem?.is_filled ? " row-locked" : "")
                    }
                    key={rowItem.localId}
                    onDragOver={(event) => {
                      if (!draggedRequestRowId) return;
                      event.preventDefault();
                      if (viewerRoleCode === "LAWYER" && rowItem?.is_filled) return;
                      setDragOverRequestRowId(String(rowItem.localId || ""));
                    }}
                    onDrop={(event) => {
                      if (!draggedRequestRowId) return;
                      event.preventDefault();
                      if (viewerRoleCode === "LAWYER" && rowItem?.is_filled) return;
                      moveDataRequestRowToIndex(draggedRequestRowId, idx);
                      handleRequestRowDragEnd();
                    }}
                  >
                    <button
                      type="button"
                      className="icon-btn request-data-row-index-handle"
                      data-tooltip={
                        viewerRoleCode === "LAWYER" && rowItem?.is_filled
                          ? "Заполненное поле: перемещение недоступно"
                          : "Перетащите для изменения порядка"
                      }
                      draggable={!(viewerRoleCode === "LAWYER" && rowItem?.is_filled)}
                      onDragStart={(event) => handleRequestRowDragStart(event, rowItem, viewerRoleCode === "LAWYER" && rowItem?.is_filled)}
                      onDragEnd={handleRequestRowDragEnd}
                      disabled={
                        dataRequestModal.loading ||
                        dataRequestModal.saving ||
                        dataRequestModal.savingTemplate ||
                        (viewerRoleCode === "LAWYER" && rowItem?.is_filled)
                      }
                      aria-label={"Порядок поля " + String(idx + 1)}
                    >
                      <span>{idx + 1}</span>
                    </button>
                    <div className="field">
                      <label>Наименование</label>
                      <input
                        value={rowItem.label}
                        onChange={(event) => updateDataRequestRow(rowItem.localId, { label: event.target.value })}
                        disabled={
                          dataRequestModal.loading ||
                          dataRequestModal.saving ||
                          dataRequestModal.savingTemplate ||
                          (viewerRoleCode === "LAWYER" && rowItem?.is_filled)
                        }
                      />
                    </div>
                    <div className="field">
                      <label>Тип</label>
                      <select
                        value={rowItem.field_type || "string"}
                        onChange={(event) => updateDataRequestRow(rowItem.localId, { field_type: event.target.value })}
                        disabled={
                          dataRequestModal.loading ||
                          dataRequestModal.saving ||
                          dataRequestModal.savingTemplate ||
                          (viewerRoleCode === "LAWYER" && rowItem?.is_filled)
                        }
                      >
                        {requestDataTypeOptions.map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div className="request-data-row-controls">
                      <button
                        type="button"
                        className="icon-btn danger request-data-row-action-btn"
                        data-tooltip={
                          viewerRoleCode === "LAWYER" && rowItem?.is_filled
                            ? "Юрист не может удалить заполненное поле"
                            : "Удалить поле"
                        }
                        onClick={() => removeDataRequestRow(rowItem.localId)}
                        disabled={
                          dataRequestModal.loading ||
                          dataRequestModal.saving ||
                          dataRequestModal.savingTemplate ||
                          (viewerRoleCode === "LAWYER" && rowItem?.is_filled)
                        }
                      >
                        ×
                      </button>
                    </div>
                    {canRequestData && (rowItem?.is_filled || String(rowItem?.value_text || "").trim()) ? (
                      <div className="request-data-row-client-value">
                        <span className="request-data-row-client-label">Заполнено клиентом:</span>
                        {String(rowItem?.field_type || "").toLowerCase() === "file" ? (
                          rowItem?.value_file && rowItem.value_file.download_url ? (
                            <button
                              type="button"
                              className="chat-message-file-chip"
                              onClick={() => openAttachmentFromMessage(rowItem.value_file)}
                            >
                              <span className="chat-message-file-icon" aria-hidden="true">📎</span>
                              <span className="chat-message-file-name">{String(rowItem.value_file.file_name || "Файл")}</span>
                            </button>
                          ) : (
                            <span className="muted">Файл добавлен</span>
                          )
                        ) : (
                          <span className="request-data-row-client-text">
                            {String(rowItem?.field_type || "").toLowerCase() === "date"
                              ? fmtDateOnly(rowItem?.value_text)
                              : String(rowItem?.value_text || "").trim().slice(0, 140)}
                          </span>
                        )}
                      </div>
                    ) : null}
                  </div>
                ))
              ) : (
                <div className="muted">Поля для запроса еще не добавлены</div>
              )}
            </div>
          </div>
          {dataRequestModal.error ? <div className="status error">{dataRequestModal.error}</div> : null}
          <div className="modal-actions modal-actions-right">
            <button
              type="button"
              className="btn btn-sm request-data-submit-btn"
              onClick={submitDataRequestModal}
              disabled={dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate}
            >
              {dataRequestModal.saving ? "Сохранение..." : "Отправить"}
            </button>
          </div>
        </div>
      </div>
      <div
        className={"overlay" + (requestDataListOpen ? " open" : "")}
        onClick={() => setRequestDataListOpen(false)}
        aria-hidden={requestDataListOpen ? "false" : "true"}
      >
        <div className="modal request-data-summary-modal" onClick={(event) => event.stopPropagation()}>
          <div className="modal-head">
            <div>
              <h3>Данные заявки</h3>
              <p className="muted request-finance-subtitle">{row?.track_number ? "Заявка " + String(row.track_number) : ""}</p>
            </div>
            <button className="close" type="button" onClick={() => setRequestDataListOpen(false)} aria-label="Закрыть">
              ×
            </button>
          </div>
          <div className="request-data-summary-list">
            {requestDataListItems.length ? (
              requestDataListItems.map((item) => {
                const value = formatRequestDataValue(item);
                const isFile = String(item?.field_type || "").toLowerCase() === "file";
                return (
                  <div className="request-data-summary-row" key={String(item.id || item.key)}>
                    <div className="request-data-summary-label">{String(item.label || humanizeKey(item.key))}</div>
                    <div className="request-data-summary-value">
                      {isFile ? (
                        value && typeof value === "object" ? (
                          <div className="request-data-summary-file">
                            <button type="button" className="chat-message-file-chip" onClick={() => downloadAttachment(value)}>
                              <span className="chat-message-file-icon" aria-hidden="true">📎</span>
                              <span className="chat-message-file-name">{String(value.file_name || "Файл")}</span>
                            </button>
                          </div>
                        ) : (
                          <span className="muted">Не заполнено</span>
                        )
                      ) : String(value || "Не заполнено")}
                    </div>
                  </div>
                );
              })
            ) : (
              <p className="muted">Дополнительные данные по заявке отсутствуют</p>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

export default RequestWorkspace;
