"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";








var _chunkVYGS24ROcjs = require('../../chunk-VYGS24RO.cjs');
require('../../chunk-VFKQMAUF.cjs');


var _chunkQ52JZP3Pcjs = require('../../chunk-Q52JZP3P.cjs');
























var _chunkQAQ7AROScjs = require('../../chunk-QAQ7AROS.cjs');


var _chunkQLWBBCI5cjs = require('../../chunk-QLWBBCI5.cjs');
require('../../chunk-27APPAJN.cjs');
require('../../chunk-7VMEOFEA.cjs');



var _chunk6JINAOI7cjs = require('../../chunk-6JINAOI7.cjs');
require('../../chunk-PXXS27EE.cjs');



var _chunkUUARMJJEcjs = require('../../chunk-UUARMJJE.cjs');
require('../../chunk-DAAFVOTL.cjs');
require('../../chunk-MEAR2WHS.cjs');
require('../../chunk-XL4V2PYG.cjs');
require('../../chunk-BZFW3FOF.cjs');




var _chunkG7UE6RKVcjs = require('../../chunk-G7UE6RKV.cjs');


var _chunkJMU4RU5Jcjs = require('../../chunk-JMU4RU5J.cjs');



var _chunkN5UOJC3Vcjs = require('../../chunk-N5UOJC3V.cjs');
require('../../chunk-WBR7H6E3.cjs');
require('../../chunk-LGLPNWS6.cjs');
require('../../chunk-FIG2RKZF.cjs');
require('../../chunk-C6ASEPZL.cjs');
require('../../chunk-WZW7C7TF.cjs');
require('../../chunk-XQFFGR6U.cjs');
require('../../chunk-VRHGVLSL.cjs');

// src/components/tickets/ticket-center.tsx
var _react = require('react');
var _reactquery = require('@tanstack/react-query');
_chunkN5UOJC3Vcjs.init_button2.call(void 0, );
var _lucidereact = require('lucide-react');

// src/components/tickets/ticket-open-form.tsx

_chunkN5UOJC3Vcjs.init_button2.call(void 0, );

// src/components/tickets/types.ts
function isOptimistic(t) {
  return t._optimistic === true;
}
var TICKET_TEXT_MAX_CHARS = 5e3;
var TICKET_LIVE_POLL_MS = 8e3;
var TOAST_COPY = {
  open_success: { title: "Ticket opened", description: "We received your message and will follow up shortly." },
  open_mirror_pending: { title: "Ticket opened", description: "Syncing \u2014 your ticket will appear momentarily." },
  close_success: { title: "Ticket closed" },
  reopen_success: { title: "Ticket reopened" },
  comment_success: { title: "Comment added" },
  attach_success: { title: "Files attached" }
  // Failure variants are constructed dynamically from MappedTicketActionError.
};

// src/components/tickets/ticket-open-form.tsx
var _jsxruntime = require('react/jsx-runtime');
var COUNTER_VISIBLE_AT = Math.floor(TICKET_TEXT_MAX_CHARS * 0.8);
function TicketOpenForm({
  onSubmit,
  isSubmitting,
  supportSystemDown
}) {
  const [subject, setSubject] = _react.useState.call(void 0, "");
  const [content, setContent] = _react.useState.call(void 0, "");
  const { attachments, readyAttachments, hasInflightUploads, addFiles, removeAttachment, clear } = _chunkQAQ7AROScjs.useChatAttachments.call(void 0, );
  const trimmedSubject = subject.trim();
  const trimmedContent = content.trim();
  const overCap = content.length > TICKET_TEXT_MAX_CHARS;
  const showCounter = content.length >= COUNTER_VISIBLE_AT;
  const canSubmit = !isSubmitting && !supportSystemDown && !hasInflightUploads && trimmedSubject.length > 0 && trimmedContent.length > 0 && !overCap;
  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!canSubmit) return;
    const ok = await onSubmit({
      subject: trimmedSubject,
      content: trimmedContent,
      attachments: readyAttachments
    });
    if (ok) {
      setSubject("");
      setContent("");
      clear();
    }
  };
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkUUARMJJEcjs.Card, { className: "p-6", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { onSubmit: handleSubmit, className: "flex flex-col md:flex-row gap-6", children: [
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex-1 min-w-0 md:max-w-md", children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "text-2xl font-semibold text-ods-text-primary mb-2", children: "Need Support?" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-ods-text-secondary text-sm", children: "Can't find what you're looking for? Submit a support ticket below \u2014 we'll follow up shortly." }),
      supportSystemDown && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "mt-4 text-sm text-ods-error", children: "Support system temporarily unavailable. Please try again shortly." })
    ] }),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex-1 min-w-0 flex flex-col gap-4", children: [
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          "label",
          {
            htmlFor: "ticket-subject",
            className: "block text-sm font-medium text-ods-text-primary mb-1",
            children: "Ticket Subject"
          }
        ),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunk6JINAOI7cjs.Input,
          {
            id: "ticket-subject",
            type: "text",
            placeholder: "Enter Subject Here",
            value: subject,
            onChange: (e) => setSubject(e.target.value),
            disabled: isSubmitting || supportSystemDown,
            maxLength: 200
          }
        )
      ] }),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          "label",
          {
            htmlFor: "ticket-content",
            className: "block text-sm font-medium text-ods-text-primary mb-1",
            children: "Your Message"
          }
        ),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkQAQ7AROScjs.Textarea,
          {
            id: "ticket-content",
            placeholder: "Describe your issue or question in detail...",
            value: content,
            onChange: (e) => setContent(e.target.value),
            disabled: isSubmitting || supportSystemDown,
            rows: 5,
            className: "resize-none"
          }
        ),
        showCounter && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, 
          "p",
          {
            className: `mt-1 text-xs text-right ${overCap ? "text-ods-error" : "text-ods-text-secondary"}`,
            children: [
              content.length,
              "/",
              TICKET_TEXT_MAX_CHARS
            ]
          }
        )
      ] }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkQAQ7AROScjs.ChatAttachmentChipStrip,
        {
          attachments,
          onRemove: removeAttachment,
          disabled: isSubmitting || supportSystemDown
        }
      ),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between gap-3", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkQAQ7AROScjs.ChatAttachmentAddButton,
          {
            attachmentsEnabled: !supportSystemDown,
            attachmentsCount: attachments.length,
            onAddFiles: addFiles,
            disabled: isSubmitting
          }
        ),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkN5UOJC3Vcjs.Button,
          {
            type: "submit",
            disabled: !canSubmit,
            loading: isSubmitting,
            children: "Open Ticket"
          }
        )
      ] })
    ] })
  ] }) });
}

// src/components/tickets/ticket-row.tsx


// src/components/collapsible.tsx
var _reactcollapsible = require('@radix-ui/react-collapsible'); var CollapsiblePrimitive = _interopRequireWildcard(_reactcollapsible);
var Collapsible = CollapsiblePrimitive.Root;
var CollapsibleContent2 = CollapsiblePrimitive.CollapsibleContent;

// src/components/tickets/ticket-detail-drawer.tsx
_chunkN5UOJC3Vcjs.init_button2.call(void 0, );
var _usesticktobottom = require('use-stick-to-bottom');

// src/components/tickets/hooks/use-ticket-engagements.ts

var LIST_ENGAGEMENTS_ENDPOINT = "/api/chat/agent/list-engagements";
function useTicketEngagements(externalTicketId, enabled = true, refetchInterval = false) {
  const identity = _chunkQAQ7AROScjs.useChatIdentity.call(void 0, );
  const identityKey = _nullishCoalesce(_optionalChain([identity, 'access', _2 => _2.user, 'optionalAccess', _3 => _3.email]), () => ( "anon"));
  const fetchable = enabled && !!externalTicketId && !externalTicketId.startsWith("temp-");
  const queryEnabled = fetchable && identity.authTier !== "anon" && !!_optionalChain([identity, 'access', _4 => _4.user, 'optionalAccess', _5 => _5.email]);
  const query = _reactquery.useQuery.call(void 0, {
    queryKey: ["ticket-engagements", externalTicketId, identityKey],
    enabled: queryEnabled,
    // Caches OFF — same reasoning as `useTicketsList`. The conversation
    // timeline must reflect HubSpot truth on every drawer-open; a stale
    // window risks hiding a freshly-arrived agent reply.
    staleTime: 0,
    gcTime: 0,
    refetchOnMount: "always",
    refetchOnWindowFocus: true,
    // Live conversation: poll while the caller opts in (drawer open). New
    // agent replies + attachments appear within one interval without a
    // manual refresh. `refetchIntervalInBackground` stays false (default)
    // so polling pauses on a hidden tab.
    refetchInterval,
    queryFn: async () => {
      const response = await _chunkQAQ7AROScjs.embedAuthedFetch.call(void 0, LIST_ENGAGEMENTS_ENDPOINT, {
        method: "POST",
        body: JSON.stringify({ ticket_id: externalTicketId })
      });
      if (!response.ok) {
        const text = await response.text().catch(() => "");
        throw new Error(`list-engagements failed: ${response.status} ${text.slice(0, 200)}`);
      }
      const body = await response.json();
      return Array.isArray(body.engagements) ? body.engagements : [];
    }
  });
  return {
    engagements: _nullishCoalesce(query.data, () => ( [])),
    // Loading-state truth that prevents the "body → blink → skeleton → data"
    // double-flash. The bug: `useChatIdentity` starts at anon defaults and
    // resolves async, so on the first render `queryEnabled` is false and the
    // OLD `queryEnabled && query.isLoading` returned FALSE — the panel rendered
    // the ticket body, THEN identity resolved, the query enabled, isLoading
    // flipped true → skeleton appeared (the blink), then data landed.
    //
    // Fix: for a fetchable ticket we are "loading" whenever we don't yet have
    // the timeline to show — that includes the window while identity is still
    // resolving (so we skeleton from the FIRST render, never the body) AND the
    // cold query fetch (`data === undefined`). A background poll keeps
    // `query.data` defined, so it never re-flashes the skeleton. Non-fetchable
    // (optimistic/disabled) or a resolved-anon viewer → not loading.
    isLoading: fetchable && (identity.isLoading || queryEnabled && query.data === void 0),
    isFetching: query.isFetching,
    error: _nullishCoalesce(query.error, () => ( null)),
    refetch: () => {
      void query.refetch();
    }
  };
}

// src/components/tickets/ticket-linked-delivery-card.tsx

function TicketLinkedDeliveryCard({
  clickup,
  className
}) {
  const item = {
    id: clickup.external_id,
    title: _nullishCoalesce(clickup.title, () => ( "Linked delivery task")),
    description: _nullishCoalesce(clickup.description, () => ( "")),
    status: _nullishCoalesce(clickup.status, () => ( "unknown")),
    statusColor: _nullishCoalesce(clickup.status_color, () => ( "#87909e")),
    taskType: _nullishCoalesce(clickup.task_type, () => ( "Request")),
    customItemId: clickup.custom_item_id,
    listNames: clickup.list_names,
    dateOpened: _nullishCoalesce(clickup.date_opened, () => ( 0)),
    dateUpdated: _nullishCoalesce(_nullishCoalesce(clickup.date_updated, () => ( clickup.date_opened)), () => ( Date.now())),
    dateClosed: clickup.date_closed,
    clickupUrl: _nullishCoalesce(clickup.clickup_url, () => ( ""))
  };
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    "div",
    {
      className: `rounded-md border border-ods-border bg-ods-bg overflow-hidden ${_nullishCoalesce(className, () => ( ""))}`,
      children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkVYGS24ROcjs.DeliveryRow,
        {
          item,
          href: clickup.delivery_href,
          caption: "Linked delivery"
        }
      )
    }
  );
}

// src/components/tickets/ticket-reply-composer.tsx
_chunkN5UOJC3Vcjs.init_button2.call(void 0, );


function TicketReplyComposer({
  ticket,
  busy,
  supportSystemDown,
  onSendMessage,
  onClose
}) {
  const [resolution, setResolution] = _react.useState.call(void 0, "");
  const [closeDialogOpen, setCloseDialogOpen] = _react.useState.call(void 0, false);
  const attachments = _chunkQAQ7AROScjs.useChatAttachments.call(void 0, );
  const ticketRef = { id: ticket.id, external_id: ticket.external_id };
  const hasReadyFiles = attachments.readyAttachments.length > 0;
  const handleSend = _react.useCallback.call(void 0, 
    async (text) => {
      const ref = { id: ticket.id, external_id: ticket.external_id };
      const ok = await onSendMessage(ref, text.trim(), attachments.readyAttachments);
      if (ok) attachments.clear();
      return ok;
    },
    // Depend on the reactive projections, not the whole bag (a fresh object each
    // render). `readyAttachments` is memo-stable; `clear` is callback-stable.
    [
      onSendMessage,
      ticket.id,
      ticket.external_id,
      attachments.readyAttachments,
      attachments.clear
    ]
  );
  const confirmClose = async () => {
    setCloseDialogOpen(false);
    await onClose(ticketRef, resolution.trim() || void 0);
    setResolution("");
  };
  const disabled = busy || supportSystemDown;
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-2", children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkQAQ7AROScjs.ChatAttachmentChipStrip,
      {
        attachments: attachments.attachments,
        onRemove: attachments.removeAttachment,
        disabled,
        size: "compact"
      }
    ),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkQAQ7AROScjs.ChatInput,
      {
        fullWidth: true,
        autoFocus: true,
        placeholder: "Type a reply\u2026",
        sending: busy || attachments.hasInflightUploads,
        disabled: supportSystemDown,
        allowEmptySend: hasReadyFiles,
        maxLength: TICKET_TEXT_MAX_CHARS,
        onSend: handleSend
      }
    ),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 w-full", children: [
      !supportSystemDown && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkQAQ7AROScjs.ChatAttachmentAddButton,
        {
          attachmentsEnabled: true,
          attachmentsCount: attachments.attachments.length,
          onAddFiles: attachments.addFiles,
          disabled,
          size: "compact"
        }
      ),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-1 min-w-0" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkN5UOJC3Vcjs.Button,
        {
          type: "button",
          variant: "transparent",
          size: "small",
          onClick: () => setCloseDialogOpen(true),
          disabled,
          className: "text-ods-text-secondary hover:text-ods-text-primary",
          children: "Close ticket"
        }
      )
    ] }),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.AlertDialog, { open: closeDialogOpen, onOpenChange: setCloseDialogOpen, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.AlertDialogContent, { className: "bg-ods-card border-ods-border", children: [
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.AlertDialogHeader, { children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.AlertDialogTitle, { className: "text-ods-text-primary", children: "Close this ticket?" }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.AlertDialogDescription, { className: "text-ods-text-secondary", children: "Add an optional resolution note below. You can reopen the ticket later if needed." })
      ] }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkQAQ7AROScjs.Textarea,
        {
          value: resolution,
          onChange: (e) => setResolution(e.target.value),
          placeholder: "Resolution (optional)",
          rows: 3,
          maxLength: TICKET_TEXT_MAX_CHARS,
          className: "mt-2"
        }
      ),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.AlertDialogFooter, { children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkQAQ7AROScjs.AlertDialogCancel,
          {
            disabled: busy,
            className: "bg-transparent border-ods-border text-ods-text-primary hover:bg-ods-border",
            children: "Cancel"
          }
        ),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkQAQ7AROScjs.AlertDialogAction,
          {
            onClick: () => void confirmClose(),
            disabled: busy,
            className: "bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover",
            children: "Close ticket"
          }
        )
      ] })
    ] }) })
  ] });
}

// src/components/tickets/ticket-detail-drawer.tsx

function TicketDetailDrawer({
  ticket,
  busy,
  supportSystemDown,
  onSendMessage,
  onClose,
  onReopen,
  onActionCollapsed,
  replyError,
  onClearReplyError
}) {
  const isClosed = (_nullishCoalesce(ticket.status, () => ( ""))).toUpperCase() === "CLOSED";
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "bg-ods-card border-t border-ods-border px-4 py-4 flex flex-col gap-4", children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, AssignedAgentRow, { assignedOwner: ticket.assignedOwner }),
    ticket.clickup && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TicketLinkedDeliveryCard, { clickup: ticket.clickup }),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-xs font-medium text-ods-text-secondary mb-2 uppercase tracking-wider", children: "Conversation" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TicketTimelinePanel, { ticket })
    ] }),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "border-t border-ods-border pt-4", children: [
      replyError && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        ReplyFailureBanner,
        {
          error: replyError,
          onDismiss: _nullishCoalesce(onClearReplyError, () => ( (() => void 0)))
        }
      ),
      isClosed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        ReopenAction,
        {
          ticketRef: { id: ticket.id, external_id: ticket.external_id },
          busy,
          supportSystemDown,
          onReopen,
          onActionCollapsed
        }
      ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        TicketReplyComposer,
        {
          ticket,
          busy,
          supportSystemDown,
          onSendMessage,
          onClose
        }
      )
    ] })
  ] });
}
var TURN_SEPARATOR_RE = /[\s]{1,16}---[\s]{1,16}/g;
var TICKET_FEED_FRAME = "bg-ods-card border border-ods-border rounded-[6px] overflow-y-auto w-full";
var TICKET_FEED_HEIGHT = "h-[60vh] md:h-[420px]";
var TICKET_FEED_INNER = "flex flex-col gap-4 md:gap-6 px-4 md:px-6 py-4 md:py-6";
var TICKET_FEED_SKELETON_ROWS = 6;
function TicketTimelinePanel({ ticket }) {
  const identity = _chunkQAQ7AROScjs.useChatIdentity.call(void 0, );
  const externalId = isOptimistic(ticket) ? null : ticket.external_id;
  const { engagements, isLoading } = useTicketEngagements(
    externalId,
    !!externalId,
    TICKET_LIVE_POLL_MS
  );
  const { scrollRef, contentRef } = _usesticktobottom.useStickToBottom.call(void 0, { initial: "instant", resize: "smooth" });
  const bodyTurns = ticket.body ? ticket.body.split(TURN_SEPARATOR_RE).map((t) => t.trim()).filter(Boolean) : [];
  const customerEngagementBodies = new Set(
    engagements.filter((e) => e.authorRole === "customer").map((e) => (_nullishCoalesce(e.body, () => ( ""))).trim()).filter(Boolean)
  );
  const suppressBodyTurnZero = bodyTurns.length > 0 && customerEngagementBodies.has(bodyTurns[0]);
  const sessionEmailLower = _nullishCoalesce(_optionalChain([identity, 'access', _6 => _6.user, 'optionalAccess', _7 => _7.email, 'optionalAccess', _8 => _8.trim, 'call', _9 => _9(), 'access', _10 => _10.toLowerCase, 'call', _11 => _11()]), () => ( null));
  const isViewerTheCustomer = !!sessionEmailLower && ticket.customer_emails.some((e) => e.trim().toLowerCase() === sessionEmailLower);
  const viewerName = _optionalChain([identity, 'access', _12 => _12.user, 'optionalAccess', _13 => _13.name, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()]) || null;
  const ticketCustomerName = _optionalChain([ticket, 'access', _16 => _16.customer_name, 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()]) || null;
  const customerName = (isViewerTheCustomer ? viewerName : null) || ticketCustomerName || viewerName || _optionalChain([identity, 'access', _19 => _19.user, 'optionalAccess', _20 => _20.email]) || "You";
  const customerAvatar = isViewerTheCustomer ? _nullishCoalesce(_optionalChain([identity, 'access', _21 => _21.user, 'optionalAccess', _22 => _22.avatarUrl]), () => ( void 0)) : void 0;
  if (isLoading) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `${TICKET_FEED_FRAME} ${TICKET_FEED_HEIGHT}`, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: TICKET_FEED_INNER, children: Array.from({ length: TICKET_FEED_SKELETON_ROWS }, (_, i) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.ChatMessageRowSkeleton, {}, i)) }) });
  }
  if (bodyTurns.length === 0 && engagements.length === 0) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkVYGS24ROcjs.EmptyState,
      {
        type: "generic",
        title: "No conversation yet",
        description: "Reply below to start the thread with the support team.",
        showCTA: false
      }
    );
  }
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: scrollRef, className: `${TICKET_FEED_FRAME} ${TICKET_FEED_HEIGHT}`, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { ref: contentRef, className: TICKET_FEED_INNER, children: [
    bodyTurns.map((turn, i) => {
      if (i === 0 && suppressBodyTurnZero) return null;
      const isResolution = turn.startsWith("[Resolution]");
      const text = isResolution ? turn.replace(/^\[Resolution\]\s*/, "") : turn;
      return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkQAQ7AROScjs.ChatMessageRow,
        {
          displayName: customerName,
          avatarUrl: customerAvatar,
          body: text
        },
        `body-${i}-${turn.slice(0, 24)}`
      );
    }),
    engagements.map((eng) => {
      const isCustomer = eng.authorRole === "customer";
      const isOwnReply = isCustomer && !!eng.authorId && !!_optionalChain([identity, 'access', _23 => _23.user, 'optionalAccess', _24 => _24.email]) && eng.authorId.toLowerCase() === identity.user.email.toLowerCase();
      let author;
      let avatarSrc;
      if (isCustomer && isOwnReply) {
        author = _optionalChain([identity, 'access', _25 => _25.user, 'optionalAccess', _26 => _26.name, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()]) || customerName;
        avatarSrc = _nullishCoalesce(_optionalChain([identity, 'access', _29 => _29.user, 'optionalAccess', _30 => _30.avatarUrl]), () => ( void 0));
      } else if (isCustomer) {
        author = ticketCustomerName || "Customer";
        avatarSrc = void 0;
      } else if (eng.authorName && eng.authorAvatarUrl) {
        author = eng.authorName;
        avatarSrc = eng.authorAvatarUrl;
      } else {
        author = "Support team";
        avatarSrc = void 0;
      }
      const engAttachments = mapEngagementAttachments(eng.attachments);
      return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkQAQ7AROScjs.ChatMessageRow,
        {
          displayName: author,
          avatarUrl: avatarSrc,
          timeLabel: eng.createdAt ? _chunkQAQ7AROScjs.formatRelativeTime.call(void 0, eng.createdAt) : null,
          body: stripAttachmentsPreamble(_nullishCoalesce(eng.body, () => ( ""))),
          footer: engAttachments.length > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mt-2", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQAQ7AROScjs.TicketAttachmentsList, { attachments: engAttachments, size: "compact" }) }) : null
        },
        eng.id
      );
    })
  ] }) });
}
function mapEngagementAttachments(files) {
  return files.map((f) => ({
    id: f.id,
    fileName: _nullishCoalesce(f.name, () => ( `file-${f.id}`)),
    fileSize: f.size ? formatBytes(f.size) : "",
    // Show an inline thumbnail for image attachments (the signed `url` is a
    // viewable URL). Non-images fall back to the file-type icon. SquareAvatar
    // degrades to initials on a broken/expired image URL.
    thumbnailSrc: f.url && (_nullishCoalesce(_optionalChain([f, 'access', _31 => _31.mime, 'optionalAccess', _32 => _32.startsWith, 'call', _33 => _33("image/")]), () => ( false))) ? f.url : void 0,
    onDownload: f.url ? () => window.open(f.url, "_blank", "noopener,noreferrer") : void 0
  }));
}
function formatBytes(size) {
  if (size < 1024) return `${size} B`;
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
  return `${(size / (1024 * 1024)).toFixed(1)} MB`;
}
var ATTACHMENTS_PREAMBLE_RE = /\s*\n\s*Attachments\b[^]*$/i;
function stripAttachmentsPreamble(body) {
  return body.replace(ATTACHMENTS_PREAMBLE_RE, "").trim();
}
function ReopenAction({
  ticketRef,
  busy,
  supportSystemDown,
  onReopen,
  onActionCollapsed
}) {
  const handleReopen = async () => {
    void await onReopen(ticketRef);
  };
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex justify-end", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    _chunkN5UOJC3Vcjs.Button,
    {
      type: "button",
      variant: "outline",
      size: "small",
      onClick: () => void handleReopen(),
      disabled: busy || supportSystemDown,
      loading: busy,
      children: "Reopen"
    }
  ) });
}
function ReplyFailureBanner({
  error,
  onDismiss
}) {
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, 
    "div",
    {
      role: "status",
      "aria-live": "polite",
      className: "mb-3 flex items-start gap-3 rounded-md border border-ods-attention-red-error bg-ods-attention-red-error-secondary px-3 py-2 text-sm text-ods-attention-red-error",
      children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "font-medium leading-snug", children: error.message }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkN5UOJC3Vcjs.Button,
          {
            type: "button",
            variant: "transparent",
            onClick: onDismiss,
            "aria-label": "Dismiss reply failure",
            className: "ml-auto px-2 py-0.5 text-xs font-medium uppercase tracking-wider text-ods-attention-red-error hover:bg-ods-attention-red-error/10 border-transparent",
            children: "Dismiss"
          }
        )
      ]
    }
  );
}
function AssignedAgentRow({
  assignedOwner
}) {
  const trimmedName = _optionalChain([assignedOwner, 'optionalAccess', _34 => _34.name, 'optionalAccess', _35 => _35.trim, 'call', _36 => _36()]) || null;
  const emailFallback = _optionalChain([assignedOwner, 'optionalAccess', _37 => _37.email, 'optionalAccess', _38 => _38.trim, 'call', _39 => _39()]) || null;
  const displayLabel = _nullishCoalesce(trimmedName, () => ( (emailFallback ? emailFallback.split("@")[0] : null)));
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 text-xs", children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-text-secondary uppercase tracking-wider font-medium", children: "Assigned to" }),
    displayLabel ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-1.5 text-ods-text-primary font-medium", children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkUUARMJJEcjs.SquareAvatar,
        {
          size: "sm",
          variant: "round",
          src: _nullishCoalesce(_optionalChain([assignedOwner, 'optionalAccess', _40 => _40.avatarUrl]), () => ( void 0)),
          alt: displayLabel,
          fallback: displayLabel
        }
      ),
      displayLabel
    ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-text-secondary italic", children: "Unassigned" })
  ] });
}

// src/components/tickets/ticket-row.tsx

function TicketRow({
  ticket,
  expanded,
  onToggle,
  busy,
  supportSystemDown,
  onSendMessage,
  onClose,
  onReopen,
  onActionCollapsed
}) {
  const optimistic = isOptimistic(ticket);
  const rowRef = _react.useRef.call(void 0, null);
  const handleClick = _react.useCallback.call(void 0, () => {
    onToggle(ticket.id);
    _chunkQAQ7AROScjs.scrollElementIntoView.call(void 0, rowRef.current, {
      adjustTargetY: (raw) => {
        if (!rowRef.current) return raw;
        const expandedDrawer = document.querySelector(
          'div[id^="ticket-drawer-"]'
        );
        if (!(expandedDrawer instanceof HTMLElement)) return raw;
        const drawerRect = expandedDrawer.getBoundingClientRect();
        const myRect = rowRef.current.getBoundingClientRect();
        if (drawerRect.bottom > myRect.top) return raw;
        return raw - drawerRect.height;
      }
    });
  }, [onToggle, ticket.id]);
  const tileData = {
    id: ticket.id,
    title: _nullishCoalesce(ticket.subject, () => ( "(untitled)")),
    ticketNumber: `#${ticket.external_id}`,
    status: _nullishCoalesce(ticket.status, () => ( "OPEN")),
    // Surface the HubSpot pipeline stage label ("New" / "Closed" /
    // "Waiting on contact" / "Waiting on version release") instead of
    // the canonical "Active"/"Resolved" default. The variant + check
    // icon still come from `status` (CLOSED → check; OPEN → no check),
    // so the badge accurately reflects "Closed" with a checkmark.
    statusLabel: _nullishCoalesce(ticket.pipeline_stage_label, () => ( void 0)),
    category: _nullishCoalesce(ticket.customer_company, () => ( void 0)),
    timeAgo: ticket.hubspot_updated_at ? _chunkQAQ7AROScjs.formatRelativeTime.call(void 0, ticket.hubspot_updated_at) : void 0,
    // Linked-work chip: surfaced whenever the ticket has a linked
    // ClickUp task. Uses the linked task's own status so the chip text
    // reads "Working" / "Waiting on version release" / etc. — useful
    // signal pre-expand. Falls back to a generic "Linked work" label
    // when the task exists but its status hasn't synced yet.
    linkedTaskLabel: ticket.clickup ? ticket.clickup.status ? ticket.clickup.status.replace(/\b\w/g, (c) => c.toUpperCase()) : "Linked work" : void 0
  };
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: rowRef, className: "scroll-mt-24", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, 
    Collapsible,
    {
      open: expanded && !optimistic,
      className: "border-b border-ods-border last:border-b-0",
      children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          _chunkQAQ7AROScjs.ChatTicketItem,
          {
            ticket: tileData,
            onClick: optimistic ? void 0 : handleClick,
            "aria-expanded": expanded && !optimistic,
            "aria-controls": `ticket-drawer-${ticket.id}`
          }
        ),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          CollapsibleContent2,
          {
            id: `ticket-drawer-${ticket.id}`,
            className: "overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down",
            children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
              TicketDetailDrawer,
              {
                ticket,
                busy,
                supportSystemDown,
                onSendMessage,
                onClose,
                onReopen,
                onActionCollapsed
              }
            )
          }
        )
      ]
    }
  ) });
}

// src/components/tickets/hooks/use-tickets-list.ts

var FIND_TICKET_ENDPOINT = "/api/chat/agent/find-ticket";
var DEFAULT_PAGE_SIZE = 20;
function useTicketsList(filters) {
  const customerEmail = filters.customerEmail;
  const search = (_nullishCoalesce(filters.search, () => ( ""))).trim();
  const status = (_nullishCoalesce(filters.status, () => ( ""))).trim().toLowerCase();
  const statusFilter = status && status !== "all" ? status : "";
  const page = Math.max(1, Math.floor(_nullishCoalesce(filters.page, () => ( 1))) || 1);
  const pageSize = Math.max(1, Math.min(100, Math.floor(_nullishCoalesce(filters.pageSize, () => ( DEFAULT_PAGE_SIZE))) || DEFAULT_PAGE_SIZE));
  const enabled = !!customerEmail;
  const identityKey = customerEmail || "anon";
  const refetchInterval = _nullishCoalesce(filters.refetchInterval, () => ( false));
  const query = _reactquery.useQuery.call(void 0, {
    queryKey: ["tickets", "self", identityKey, search, statusFilter, page, pageSize],
    enabled,
    // Caches OFF — every mount + every focus + every navigation triggers
    // a fresh fetch. The ticket data is the customer's own list of
    // tickets they expect to be live (sync agents reply, statuses flip,
    // new comments arrive); a stale window of any size is worse than
    // a sub-second refetch.
    staleTime: 0,
    gcTime: 0,
    refetchOnMount: "always",
    refetchOnWindowFocus: true,
    // Live status: poll while the caller opts in (drawer open). Defaults to
    // false. `refetchIntervalInBackground` stays false (the default) so polling
    // pauses on a hidden tab — no wasted requests when the user tabs away.
    refetchInterval,
    queryFn: async () => {
      const body = {
        query: search,
        page,
        pageSize
      };
      if (statusFilter) body.status = statusFilter;
      const response = await _chunkQAQ7AROScjs.embedAuthedFetch.call(void 0, FIND_TICKET_ENDPOINT, {
        method: "POST",
        body: JSON.stringify(body)
      });
      if (!response.ok) {
        const text = await response.text().catch(() => "");
        throw new Error(`find-ticket failed: ${response.status} ${text.slice(0, 200)}`);
      }
      return await response.json();
    }
  });
  const data = query.data;
  const totalCount = _nullishCoalesce(_nullishCoalesce(_optionalChain([data, 'optionalAccess', _41 => _41.totalCount]), () => ( _optionalChain([data, 'optionalAccess', _42 => _42.count]))), () => ( (_nullishCoalesce(_optionalChain([data, 'optionalAccess', _43 => _43.tickets, 'optionalAccess', _44 => _44.length]), () => ( 0)))));
  const echoedPage = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _45 => _45.page]), () => ( page));
  const echoedPageSize = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _46 => _46.pageSize]), () => ( pageSize));
  const totalPages = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _47 => _47.totalPages]), () => ( Math.max(1, Math.ceil(totalCount / echoedPageSize))));
  return {
    tickets: _nullishCoalesce(_optionalChain([data, 'optionalAccess', _48 => _48.tickets]), () => ( [])),
    // Loading-state-truth = `data === undefined`. TanStack v5's
    // `isPending` / `isLoading` flags can be `false` in transient
    // windows where the query is enabled-but-fetch-not-yet-fired
    // OR where stale-data exists from a sibling cache slot — both
    // produced the EmptyState flash on /tickets first load. Treating
    // "no data for THIS query slot yet" as the universal loading
    // signal can't lie:
    //   - Initial render after enabled flips: data === undefined → load
    //   - Background refetch with existing data: data !== undefined → no load
    //   - Filter-change refetch landing on empty results: data?.tickets===[]
    //     + isFetching → bridge skeleton (the `||` branch)
    // Loading-state-truth = `data === undefined`. TanStack v5's
    // `isPending` / `isLoading` flags can be `false` in transient
    // windows where the query is enabled-but-fetch-not-yet-fired
    // OR where stale-data exists from a sibling cache slot. Treating
    // "no data for THIS query slot yet" as the universal loading
    // signal can't lie:
    //   - Initial render after enabled flips: data === undefined → load
    //   - Background refetch with existing data: data !== undefined → no load
    //   - Filter-change refetch landing on empty results: data?.tickets===[]
    //     + isFetching → bridge skeleton (the `||` branch)
    isLoading: enabled && (data === void 0 || query.isFetching && (_nullishCoalesce(_optionalChain([data, 'optionalAccess', _49 => _49.tickets]), () => ( []))).length === 0),
    isFetching: query.isFetching,
    error: _nullishCoalesce(query.error, () => ( null)),
    refetch: () => {
      void query.refetch();
    },
    lastUpdatedAt: query.dataUpdatedAt || null,
    totalCount,
    page: echoedPage,
    pageSize: echoedPageSize,
    totalPages
  };
}

// src/components/tickets/hooks/use-ticket-actions.ts


var TICKET_ACTION_ENDPOINT = "/api/chat/agent/ticket-action";
var REPLY_BANNER_CODES = /* @__PURE__ */ new Set([
  "HUBSPOT_5XX",
  "HUBSPOT_400_VALIDATION",
  "HUBSPOT_404_THREAD",
  "HUBSPOT_REPLY_UNKNOWN"
]);
var MIRROR_SYNC_BACKOFF_MS = [3e3, 6e3, 12e3];
function useTicketActions(options) {
  const queryClient = _reactquery.useQueryClient.call(void 0, );
  const { prependOptimistic, removeOptimistic, removeTicketFromCache, toast: toast2, onSupportSystemDown } = options;
  const formInFlightRef = _react.useRef.call(void 0, false);
  const [isSubmittingForm, setIsSubmittingForm] = _react.useState.call(void 0, false);
  const busyRowsRef = _react.useRef.call(void 0, /* @__PURE__ */ new Set());
  const [busyRows, setBusyRows] = _react.useState.call(void 0, () => /* @__PURE__ */ new Set());
  const setRowBusy = _react.useCallback.call(void 0, (id, busy) => {
    if (busy) busyRowsRef.current.add(id);
    else busyRowsRef.current.delete(id);
    setBusyRows(new Set(busyRowsRef.current));
  }, []);
  const isRowBusy = _react.useCallback.call(void 0, (id) => busyRows.has(id), [busyRows]);
  const [replyErrorByTicket, setReplyErrorByTicket] = _react.useState.call(void 0, () => /* @__PURE__ */ new Map());
  const setReplyError = _react.useCallback.call(void 0, 
    (externalId, mapped) => {
      setReplyErrorByTicket((prev) => {
        const next = new Map(prev);
        if (mapped) next.set(externalId, mapped);
        else next.delete(externalId);
        return next;
      });
    },
    []
  );
  const replyErrorFor = _react.useCallback.call(void 0, 
    (externalId) => _nullishCoalesce(replyErrorByTicket.get(externalId), () => ( null)),
    [replyErrorByTicket]
  );
  const clearReplyError = _react.useCallback.call(void 0, 
    (externalId) => setReplyError(externalId, null),
    [setReplyError]
  );
  const watcherControllersRef = _react.useRef.call(void 0, /* @__PURE__ */ new Map());
  _react.useEffect.call(void 0, () => {
    return () => {
      for (const controller of watcherControllersRef.current.values()) {
        controller.abort();
      }
      watcherControllersRef.current.clear();
    };
  }, []);
  const queueRef = _react.useRef.call(void 0, Promise.resolve());
  const enqueue = _react.useCallback.call(void 0, (work) => {
    const next = queueRef.current.then(work, work);
    queueRef.current = next.catch(() => void 0);
    return next;
  }, []);
  const executeTicketAction = _react.useCallback.call(void 0, 
    async (toolName, args) => {
      const res = await _chunkQAQ7AROScjs.embedAuthedFetch.call(void 0, TICKET_ACTION_ENDPOINT, {
        method: "POST",
        body: JSON.stringify({ tool_name: toolName, args })
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        const code = resolveErrorCode(body.code, res.status);
        const message = body.error || `${toolName} failed (${res.status})`;
        throw new TicketActionFailure(code, message, res);
      }
      return body;
    },
    []
  );
  const watchMirrorSync = _react.useCallback.call(void 0, 
    (placeholderId, expectedTicketId) => {
      const prior = watcherControllersRef.current.get(placeholderId);
      if (prior) prior.abort();
      const controller = new AbortController();
      watcherControllersRef.current.set(placeholderId, controller);
      const schedule = async () => {
        try {
          for (let i = 0; i < MIRROR_SYNC_BACKOFF_MS.length; i++) {
            if (controller.signal.aborted) return;
            await new Promise((resolve) => {
              const t = setTimeout(resolve, MIRROR_SYNC_BACKOFF_MS[i]);
              controller.signal.addEventListener(
                "abort",
                () => {
                  clearTimeout(t);
                  resolve();
                },
                { once: true }
              );
            });
            if (controller.signal.aborted) return;
            await queryClient.invalidateQueries({ queryKey: ["tickets"] });
            if (expectedTicketId && cacheContainsTicket(queryClient, expectedTicketId)) {
              removeOptimistic(placeholderId);
              return;
            }
          }
          if (!controller.signal.aborted) {
            removeOptimistic(placeholderId);
            toast2({
              title: "Couldn't confirm ticket",
              description: "If the ticket doesn't appear shortly, please contact support.",
              variant: "destructive"
            });
          }
        } finally {
          if (watcherControllersRef.current.get(placeholderId) === controller) {
            watcherControllersRef.current.delete(placeholderId);
          }
        }
      };
      void schedule();
    },
    [queryClient, removeOptimistic, toast2]
  );
  const lastUpdateErrorRef = _react.useRef.call(void 0, null);
  const surfaceError = _react.useCallback.call(void 0, 
    (err, action) => {
      const mapped = mapTicketActionError(err);
      lastUpdateErrorRef.current = mapped;
      if (mapped.supportSystemDown) onSupportSystemDown();
      toast2({
        title: `Could not ${action}`,
        description: mapped.message,
        variant: "destructive"
      });
      return mapped;
    },
    [toast2, onSupportSystemDown]
  );
  const submitTicket = _react.useCallback.call(void 0, 
    async (input) => {
      if (formInFlightRef.current) return false;
      formInFlightRef.current = true;
      setIsSubmittingForm(true);
      const placeholderId = `temp-${cryptoRandomId()}`;
      const placeholder = {
        id: placeholderId,
        external_id: "Pending sync\u2026",
        subject: input.subject.trim(),
        preview: input.content.trim().slice(0, 400),
        body: input.content.trim(),
        status: "OPEN",
        pipeline_stage_label: "New",
        clickup_task_id: null,
        clickup: null,
        priority: null,
        customer_emails: [],
        customer_company: null,
        // Optimistic placeholder has no resolved HubSpot contact yet
        // — the real ticket row replaces this within a couple of
        // seconds via the mirror refetch. Drawer uses live chat
        // identity for own-replies during this window anyway.
        customer_name: null,
        // No assignee until the real ticket lands. Drawer renders
        // "Unassigned" for this brief window.
        assigned_to: null,
        assignedOwner: null,
        hubspot_updated_at: (/* @__PURE__ */ new Date()).toISOString(),
        _optimistic: true
      };
      prependOptimistic(placeholder);
      try {
        return await enqueue(async () => {
          const result = await executeTicketAction("create_ticket", {
            subject: input.subject.trim(),
            content: input.content.trim(),
            ..._optionalChain([input, 'access', _50 => _50.attachments, 'optionalAccess', _51 => _51.length]) ? { attachments: input.attachments } : {}
          });
          if (result.mirror_synced === false) {
            toast2(TOAST_COPY.open_mirror_pending);
            watchMirrorSync(placeholderId, result.ticket_id);
          } else {
            toast2(TOAST_COPY.open_success);
            await queryClient.invalidateQueries({ queryKey: ["tickets"] });
            removeOptimistic(placeholderId);
          }
          return true;
        });
      } catch (err) {
        removeOptimistic(placeholderId);
        surfaceError(err, "open ticket");
        return false;
      } finally {
        formInFlightRef.current = false;
        setIsSubmittingForm(false);
      }
    },
    [
      enqueue,
      executeTicketAction,
      prependOptimistic,
      removeOptimistic,
      queryClient,
      toast2,
      watchMirrorSync,
      surfaceError
    ]
  );
  const updateTicket = _react.useCallback.call(void 0, 
    async (ticket, serverArgs, successCopy, action) => {
      if (busyRowsRef.current.has(ticket.id)) return false;
      setRowBusy(ticket.id, true);
      try {
        return await enqueue(async () => {
          await executeTicketAction("update_ticket", {
            ...serverArgs,
            ticket_id: ticket.external_id
          });
          toast2(successCopy);
          const statusUpdate = _nullishCoalesce(serverArgs.status, () => ( null));
          if (statusUpdate) {
            queryClient.setQueriesData(
              { queryKey: ["tickets"] },
              (prev) => {
                if (!prev || !Array.isArray(prev.tickets)) return prev;
                let mutated = false;
                const nextTickets = prev.tickets.map((t) => {
                  if (t.id !== ticket.id || t.status === statusUpdate) return t;
                  mutated = true;
                  return { ...t, status: statusUpdate };
                });
                return mutated ? { ...prev, tickets: nextTickets } : prev;
              }
            );
          }
          await queryClient.invalidateQueries({ queryKey: ["ticket-engagements"] });
          return true;
        });
      } catch (err) {
        const mapped = surfaceError(err, action);
        if (mapped.removeRowFromCache) {
          removeTicketFromCache(ticket.id);
        }
        return false;
      } finally {
        setRowBusy(ticket.id, false);
      }
    },
    // `busyRowsRef` is read via .current — needs no dep entry. `busyRows`
    // state isn't read inside this callback (only by `isRowBusy` selector
    // outside), so listing it would churn the closure on every flag flip
    // and cascade-recreate addNote/closeTicket/etc.
    [setRowBusy, enqueue, executeTicketAction, queryClient, toast2, surfaceError, removeTicketFromCache]
  );
  const sendMessage = _react.useCallback.call(void 0, 
    async (ticket, text, attachments) => {
      const trimmed = text.trim();
      const hasText = trimmed.length > 0;
      const hasFiles = attachments.length > 0;
      if (!hasText && !hasFiles) return false;
      lastUpdateErrorRef.current = null;
      const ok = await updateTicket(
        ticket,
        {
          ...hasText ? { content_addendum: trimmed } : {},
          ...hasFiles ? { attachments } : {}
        },
        TOAST_COPY.comment_success,
        "send message"
      );
      if (ok) {
        clearReplyError(ticket.external_id);
      } else {
        const mapped = lastUpdateErrorRef.current;
        if (mapped && REPLY_BANNER_CODES.has(mapped.code)) {
          setReplyError(ticket.external_id, mapped);
        }
        lastUpdateErrorRef.current = null;
      }
      return ok;
    },
    [updateTicket, clearReplyError, setReplyError]
  );
  const closeTicket = _react.useCallback.call(void 0, 
    (ticket, resolution) => updateTicket(
      ticket,
      {
        status: "CLOSED",
        ..._optionalChain([resolution, 'optionalAccess', _52 => _52.trim, 'call', _53 => _53()]) ? { resolution: resolution.trim() } : {}
      },
      TOAST_COPY.close_success,
      "close ticket"
    ),
    [updateTicket]
  );
  const reopenTicket = _react.useCallback.call(void 0, 
    (ticket) => updateTicket(ticket, { status: "OPEN" }, TOAST_COPY.reopen_success, "reopen ticket"),
    [updateTicket]
  );
  return _react.useMemo.call(void 0, 
    () => ({
      submitTicket,
      sendMessage,
      closeTicket,
      reopenTicket,
      isSubmittingForm,
      isRowBusy,
      replyErrorFor,
      clearReplyError
    }),
    [
      submitTicket,
      sendMessage,
      closeTicket,
      reopenTicket,
      isSubmittingForm,
      isRowBusy,
      replyErrorFor,
      clearReplyError
    ]
  );
}
var TicketActionFailure = class extends Error {
  constructor(code, message, response) {
    super(message);
    this.code = code;
    this.response = response;
  }
};
function mapTicketActionError(err) {
  if (err instanceof TicketActionFailure) {
    switch (err.code) {
      case "PROPOSAL_NOT_CLAIMABLE":
        return {
          code: err.code,
          message: "This action was already processed.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "TICKET_NOT_FOUND":
        return {
          code: err.code,
          message: "This ticket is no longer available.",
          supportSystemDown: false,
          removeRowFromCache: true
        };
      case "TICKET_OWNERSHIP_DENIED":
        return {
          code: err.code,
          message: "You can only act on tickets you opened.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "HUBSPOT_DISCONNECTED":
        return {
          code: err.code,
          message: "Support system temporarily unavailable.",
          supportSystemDown: true,
          removeRowFromCache: false
        };
      case "RATE_LIMITED": {
        const retryAfterRaw = _optionalChain([err, 'access', _54 => _54.response, 'optionalAccess', _55 => _55.headers, 'access', _56 => _56.get, 'call', _57 => _57("Retry-After")]);
        const retryAfterSeconds = retryAfterRaw ? parseInt(retryAfterRaw, 10) : void 0;
        return {
          code: err.code,
          message: retryAfterSeconds ? `Too many actions. Try again in ${retryAfterSeconds}s.` : "Too many actions. Try again shortly.",
          supportSystemDown: false,
          removeRowFromCache: false,
          ...retryAfterSeconds ? { retryAfterSeconds } : {}
        };
      }
      case "INVALID_TOOL_ARGS":
        return {
          code: err.code,
          message: "Your input was rejected. Please review and try again.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "HUBSPOT_5XX":
        return {
          code: err.code,
          message: "We couldn't reach the support system. Your reply wasn't sent \u2014 please retry in a moment.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "HUBSPOT_400_VALIDATION":
        return {
          code: err.code,
          message: "Your reply was rejected. Please rephrase or remove unsupported content and try again.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "HUBSPOT_404_THREAD":
        return {
          code: err.code,
          message: "This conversation is no longer accepting replies. Open a new ticket to continue.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      case "HUBSPOT_REPLY_UNKNOWN":
        return {
          code: err.code,
          message: "Your reply didn't go through. Please retry.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
      default:
        return {
          code: "UNKNOWN",
          message: err.message || "Something went wrong. Please try again.",
          supportSystemDown: false,
          removeRowFromCache: false
        };
    }
  }
  return {
    code: "UNKNOWN",
    message: err instanceof Error ? err.message : "Something went wrong. Please try again.",
    supportSystemDown: false,
    removeRowFromCache: false
  };
}
function cryptoRandomId() {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
    return crypto.randomUUID();
  }
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}
function cacheContainsTicket(queryClient, expectedTicketId) {
  const entries = queryClient.getQueriesData({
    queryKey: ["tickets"]
  });
  for (const [, data] of entries) {
    if (data && Array.isArray(data.tickets) && data.tickets.some((t) => t.external_id === expectedTicketId)) {
      return true;
    }
  }
  return false;
}
function resolveErrorCode(bodyCode, status) {
  if (bodyCode) return bodyCode;
  if (status === 429) return "RATE_LIMITED";
  if (status === 412) return "HUBSPOT_DISCONNECTED";
  return "UNKNOWN";
}

// src/components/tickets/ticket-center.tsx

function TicketCenter({ toast: toast2 = _chunkQLWBBCI5cjs.toast } = {}) {
  const identity = _chunkQAQ7AROScjs.useChatIdentity.call(void 0, );
  if (identity.isLoading) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TicketCenterSkeleton, {});
  }
  if (identity.authTier === "anon" || !_optionalChain([identity, 'access', _58 => _58.user, 'optionalAccess', _59 => _59.email])) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkVYGS24ROcjs.EmptyState,
      {
        type: "generic",
        title: "Sign in to manage tickets",
        description: "View, open, and follow up on support tickets after signing in.",
        showCTA: false
      }
    );
  }
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    TicketCenterAuthed,
    {
      toast: toast2,
      sessionEmail: identity.user.email
    }
  );
}
function TicketCenterAuthed({
  toast: toast2,
  sessionEmail
}) {
  const queryClient = _reactquery.useQueryClient.call(void 0, );
  const { tickets, isLoading, isFetching, refetch, lastUpdatedAt } = useTicketsList({
    customerEmail: sessionEmail
  });
  const [optimisticTickets, setOptimisticTickets] = _react.useState.call(void 0, []);
  const [expandedTicketId, setExpandedTicketId] = _react.useState.call(void 0, null);
  const [supportSystemDown, setSupportSystemDown] = _react.useState.call(void 0, false);
  const prependOptimistic = _react.useCallback.call(void 0, (placeholder) => {
    setOptimisticTickets((prev) => [placeholder, ...prev]);
  }, []);
  const removeOptimistic = _react.useCallback.call(void 0, (placeholderId) => {
    setOptimisticTickets((prev) => prev.filter((t) => t.id !== placeholderId));
    setExpandedTicketId((prev) => prev === placeholderId ? null : prev);
  }, []);
  const removeTicketFromCache = _react.useCallback.call(void 0, 
    (ticketId) => {
      queryClient.setQueriesData(
        { queryKey: ["tickets"] },
        (prev) => (_nullishCoalesce(prev, () => ( []))).filter((t) => t.id !== ticketId)
      );
      setExpandedTicketId((prev) => prev === ticketId ? null : prev);
    },
    [queryClient]
  );
  const actions = useTicketActions({
    prependOptimistic,
    removeOptimistic,
    removeTicketFromCache,
    toast: toast2,
    onSupportSystemDown: () => setSupportSystemDown(true)
  });
  const toggleRow = _react.useCallback.call(void 0, (id) => {
    setExpandedTicketId((prev) => prev === id ? null : id);
  }, []);
  const merged = [...optimisticTickets, ...tickets];
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-6", children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      TicketOpenForm,
      {
        onSubmit: (input) => actions.submitTicket(input),
        isSubmitting: actions.isSubmittingForm,
        supportSystemDown
      }
    ),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-2", children: [
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between gap-3", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-xs font-medium text-ods-text-secondary uppercase tracking-wider", children: "Your Current Tickets" }),
        /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-3 text-xs text-ods-text-secondary", children: [
          lastUpdatedAt && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { children: [
            "Updated ",
            _chunkQAQ7AROScjs.formatRelativeTime.call(void 0, new Date(lastUpdatedAt))
          ] }),
          /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
            _chunkN5UOJC3Vcjs.Button,
            {
              type: "button",
              variant: "transparent",
              size: "small",
              onClick: refetch,
              disabled: isFetching,
              "aria-label": "Refresh ticket list",
              leftIcon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.RefreshCw, { className: "h-4 w-4" })
            }
          )
        ] })
      ] }),
      isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TicketListSkeleton, {}) : merged.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkUUARMJJEcjs.Card, { className: "p-6", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        _chunkVYGS24ROcjs.EmptyState,
        {
          type: "generic",
          title: "No tickets yet",
          description: "Open one above to start the conversation.",
          showCTA: false
        }
      ) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkUUARMJJEcjs.Card, { className: "overflow-hidden", children: merged.map((ticket) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        TicketRow,
        {
          ticket,
          expanded: expandedTicketId === ticket.id,
          onToggle: toggleRow,
          busy: isOptimistic(ticket) ? false : actions.isRowBusy(ticket.id),
          supportSystemDown,
          onSendMessage: actions.sendMessage,
          onClose: actions.closeTicket,
          onReopen: actions.reopenTicket,
          onActionCollapsed: () => setExpandedTicketId(null)
        },
        ticket.id
      )) })
    ] })
  ] });
}
function TicketCenterSkeleton() {
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-6", children: [
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkUUARMJJEcjs.Card, { className: "p-6", children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-7 w-48 mb-4" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-10 w-full mb-3" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-24 w-full" })
    ] }),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TicketListSkeleton, {})
  ] });
}
function TicketListSkeleton() {
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkUUARMJJEcjs.Card, { className: "overflow-hidden", children: [0, 1, 2].map((i) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "h-20 px-4 flex items-center gap-4 border-b border-ods-border last:border-b-0", children: [
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex-1 flex flex-col gap-2", children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-4 w-2/3" }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-3 w-full" })
    ] }),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-8 w-20" }),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6JINAOI7cjs.Skeleton, { className: "h-8 w-16" })
  ] }, i)) });
}

// src/components/tickets/help-center-list.tsx


_chunkVYGS24ROcjs.init_unified_pagination.call(void 0, );

// src/components/tickets/help-center-card.tsx


var STICKY_HEADER_OFFSET_PX = 96;
function HelpCenterCard({
  ticket,
  expanded,
  onToggle,
  busy,
  supportSystemDown,
  onSendMessage,
  onClose,
  onReopen,
  onActionCollapsed,
  replyError,
  onClearReplyError
}) {
  const optimistic = isOptimistic(ticket);
  const rawStatus = (_nullishCoalesce(ticket.status, () => ( "OPEN"))).toUpperCase();
  const priority = (_nullishCoalesce(ticket.priority, () => ( ""))).toUpperCase();
  const relativeUpdated = ticket.hubspot_updated_at ? _chunkQAQ7AROScjs.formatRelativeTime.call(void 0, ticket.hubspot_updated_at) : "recently";
  const title = (ticket.subject || "").trim() || "(untitled)";
  const subtitle = `UPDATED ${relativeUpdated}, #${ticket.external_id || "\u2014"}${ticket.pipeline_stage_label ? `, ${ticket.pipeline_stage_label}` : ""}`;
  const description = _nullishCoalesce(_nullishCoalesce(ticket.preview, () => ( ticket.body)), () => ( ""));
  const isExpandable = !optimistic;
  const isExpanded = expanded && isExpandable;
  const rowRef = _react.useRef.call(void 0, null);
  const handleClick = _react.useCallback.call(void 0, () => {
    onToggle(ticket.id);
  }, [onToggle, ticket.id]);
  _react.useEffect.call(void 0, () => {
    if (!isExpanded) return;
    const raf = requestAnimationFrame(() => {
      _chunkQAQ7AROScjs.scrollElementIntoView.call(void 0, rowRef.current, {
        headerOffset: STICKY_HEADER_OFFSET_PX
      });
    });
    return () => cancelAnimationFrame(raf);
  }, [isExpanded]);
  const rightBadges = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkJMU4RU5Jcjs.StatusBadge,
      {
        text: rawStatus,
        colorScheme: _chunkQAQ7AROScjs.getStatusColorScheme.call(void 0, rawStatus),
        variant: "card",
        className: "border border-ods-border"
      }
    ),
    priority && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkJMU4RU5Jcjs.StatusBadge,
      {
        text: priority,
        colorScheme: mapPriorityScheme(priority),
        variant: "card",
        className: "border border-ods-border"
      }
    )
  ] });
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, 
    "div",
    {
      ref: rowRef,
      style: { scrollMarginTop: STICKY_HEADER_OFFSET_PX },
      className: `border-b border-ods-border last:border-b-0 ${optimistic ? "opacity-60" : ""}`,
      "aria-busy": optimistic || void 0,
      children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          "button",
          {
            type: "button",
            onClick: isExpandable ? handleClick : void 0,
            disabled: !isExpandable,
            "aria-expanded": isExpandable ? isExpanded : void 0,
            "aria-controls": isExpanded ? `help-center-drawer-${ticket.id}` : void 0,
            className: "w-full text-left p-[12px] md:p-[16px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-inset disabled:cursor-default",
            children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
              _chunkVYGS24ROcjs.DevCardRowContent,
              {
                title,
                subtitle,
                description,
                emptyDescription: "No description provided",
                rightBadges
              }
            )
          }
        ),
        isExpanded && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { id: `help-center-drawer-${ticket.id}`, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
          TicketDetailDrawer,
          {
            ticket,
            busy,
            supportSystemDown,
            onSendMessage,
            onClose,
            onReopen,
            onActionCollapsed,
            replyError,
            onClearReplyError
          }
        ) })
      ]
    }
  );
}
function mapPriorityScheme(priority) {
  if (priority === "HIGH" || priority === "URGENT") return "error";
  if (priority === "MEDIUM") return "warning";
  return "default";
}

// src/components/tickets/help-center-create-form.tsx


var SUBJECT_MAX_CHARS = 200;
function HelpCenterCreateFormSkeleton() {
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "h-full flex flex-col border border-ods-border rounded-2xl md:rounded-3xl p-6 md:p-8 lg:p-10", children: [
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mb-6 md:mb-8", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-10 w-72 bg-ods-border rounded animate-pulse mb-3 md:mb-4" }) }),
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col flex-grow space-y-4 md:space-y-6", children: [
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", "aria-hidden": true }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", "aria-hidden": true }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", "aria-hidden": true }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { type: "hidden", "aria-hidden": true }),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-[27px] w-20 bg-ods-border rounded animate-pulse mb-1" }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-12 w-full bg-ods-border rounded animate-pulse" })
      ] }),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col flex-grow", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-[27px] w-32 bg-ods-border rounded animate-pulse mb-1" }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-24 w-full bg-ods-border rounded animate-pulse flex-grow" })
      ] }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-7 w-7 bg-ods-border rounded animate-pulse shrink-0" }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-4 w-40 bg-ods-border rounded animate-pulse" })
      ] }) }),
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col md:flex-row gap-4 md:gap-6 items-center justify-end w-full pt-2 mt-auto", children: [
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-4 w-72 bg-ods-border rounded animate-pulse" }),
        /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-12 w-32 bg-ods-border rounded animate-pulse" })
      ] })
    ] })
  ] });
}
function HelpCenterCreateForm({
  actions,
  sessionName,
  sessionEmail,
  supportSystemDown = false
}) {
  const [subject, setSubject] = _react.useState.call(void 0, "");
  const [subjectError, setSubjectError] = _react.useState.call(void 0, null);
  const subjectField = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col", children: [
    /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQAQ7AROScjs.Label, { htmlFor: "help-center-subject", children: [
      "Subject",
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-accent", children: "*" })
    ] }),
    /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunk6JINAOI7cjs.Input,
      {
        id: "help-center-subject",
        type: "text",
        value: subject,
        onChange: (e) => {
          setSubject(e.target.value);
          if (subjectError) setSubjectError(null);
        },
        placeholder: "Briefly describe what's going on",
        maxLength: SUBJECT_MAX_CHARS,
        "aria-invalid": !!subjectError,
        "aria-describedby": subjectError ? "help-center-subject-error" : void 0,
        disabled: supportSystemDown,
        className: "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12"
      }
    ),
    subjectError && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      "span",
      {
        id: "help-center-subject-error",
        className: "text-ods-error text-xs font-['DM_Sans'] mt-1",
        children: subjectError
      }
    )
  ] });
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    _chunkQ52JZP3Pcjs.ContactForm,
    {
      title: "Open a new ticket",
      footerText: "The support team typically responds within one business day.",
      hideFields: ["name", "email", "companySize", "referralSource", "helpCategory"],
      defaultValues: {
        name: sessionName,
        email: sessionEmail,
        helpCategory: "Support Request"
      },
      extraTopField: subjectField,
      submitLabel: "Open ticket",
      attachmentsEnabled: true,
      onCustomSubmit: async (data, attachments) => {
        const trimmedSubject = subject.trim();
        if (!trimmedSubject) {
          setSubjectError("Subject is required");
          throw new Error("SUBJECT_REQUIRED");
        }
        setSubjectError(null);
        const ok = await actions.submitTicket({
          subject: trimmedSubject,
          content: data.message,
          attachments: attachments.length > 0 ? attachments : void 0
        });
        if (ok) {
          setSubject("");
        } else {
          throw new Error("TICKET_SUBMIT_FAILED");
        }
      }
    }
  );
}

// src/components/tickets/help-center-list.tsx

function HelpCenterList({ toast: toast2 = _chunkQLWBBCI5cjs.toast } = {}) {
  const identity = _chunkQAQ7AROScjs.useChatIdentity.call(void 0, );
  const searchParams = _chunkG7UE6RKVcjs.useSearchParams.call(void 0, );
  const router = _chunkG7UE6RKVcjs.useRouter.call(void 0, );
  const pathname = _chunkG7UE6RKVcjs.usePathname.call(void 0, );
  const search = searchParams.get("search") || "";
  const status = searchParams.get("status") || "all";
  const ticketParam = searchParams.get("ticket") || "";
  const rawPage = Number(searchParams.get("page"));
  const page = Number.isFinite(rawPage) && rawPage > 0 ? Math.floor(rawPage) : 1;
  if (identity.isLoading) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevSectionPage, { sectionKey: "tickets", preControls: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, HelpCenterCreateFormSkeleton, {}), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevCardRowSkeletonList, {}) });
  }
  if (identity.authTier === "anon" || !_optionalChain([identity, 'access', _60 => _60.user, 'optionalAccess', _61 => _61.email])) {
    return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevSectionPage, { sectionKey: "tickets", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkVYGS24ROcjs.EmptyState,
      {
        type: "generic",
        title: "Sign in to manage tickets",
        description: "View, open, and follow up on support tickets after signing in.",
        showCTA: false
      }
    ) });
  }
  const sessionName = [_optionalChain([identity, 'access', _62 => _62.user, 'optionalAccess', _63 => _63.firstName]), _optionalChain([identity, 'access', _64 => _64.user, 'optionalAccess', _65 => _65.lastName])].filter(Boolean).join(" ").trim() || _optionalChain([identity, 'access', _66 => _66.user, 'optionalAccess', _67 => _67.email, 'optionalAccess', _68 => _68.split, 'call', _69 => _69("@"), 'access', _70 => _70[0]]) || "Customer";
  const sessionEmail = identity.user.email;
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    HelpCenterListAuthed,
    {
      search,
      status,
      page,
      ticketParam,
      searchParams,
      router,
      pathname,
      toast: toast2,
      sessionName,
      sessionEmail
    }
  );
}
function HelpCenterListAuthed({
  search,
  status,
  page,
  ticketParam,
  searchParams,
  router,
  pathname,
  toast: toast2,
  sessionName,
  sessionEmail
}) {
  const queryClient = _reactquery.useQueryClient.call(void 0, );
  const [optimisticTickets, setOptimisticTickets] = _react.useState.call(void 0, []);
  const [supportSystemDown, setSupportSystemDown] = _react.useState.call(void 0, false);
  const setOpenTicket = _react.useCallback.call(void 0, 
    (externalId) => {
      const params = new URLSearchParams(searchParams.toString());
      if (externalId) params.set("ticket", externalId);
      else params.delete("ticket");
      const qs = params.toString();
      router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
    },
    [searchParams, router, pathname]
  );
  const { tickets, isLoading, isFetching, error, refetch, totalPages } = useTicketsList({
    // `sessionEmail` is drilled in from the parent — see the same
    // pattern + race-cause rationale documented in
    // `HelpCenterCreateForm.sessionName/sessionEmail`. Calling
    // `useChatIdentity` inside `useTicketsList` would race the
    // parent's already-resolved identity and produce an empty-state
    // flash on first render.
    customerEmail: sessionEmail,
    search,
    status,
    page,
    // Live status: while a drawer is open, poll so an out-of-band HubSpot
    // status change (e.g. agent closes the ticket) flips the badge +
    // open/reopen affordance within one interval. Idle (no drawer) → no poll.
    // `ticketParam` (the open ticket's external_id) is the open signal.
    refetchInterval: ticketParam ? TICKET_LIVE_POLL_MS : false
  });
  const expandedTicketId = ticketParam && _optionalChain([tickets, 'access', _71 => _71.find, 'call', _72 => _72((t) => t.external_id === ticketParam), 'optionalAccess', _73 => _73.id]) || null;
  const prependOptimistic = _react.useCallback.call(void 0, (placeholder) => {
    setOptimisticTickets((prev) => [placeholder, ...prev]);
  }, []);
  const removeOptimistic = _react.useCallback.call(void 0, (placeholderId) => {
    setOptimisticTickets((prev) => prev.filter((t) => t.id !== placeholderId));
  }, []);
  const removeTicketFromCache = _react.useCallback.call(void 0, 
    (ticketId) => {
      queryClient.setQueriesData(
        { queryKey: ["tickets"] },
        (prev) => {
          if (!prev || !Array.isArray(prev.tickets)) return prev;
          const nextTickets = prev.tickets.filter((t) => t.id !== ticketId);
          if (nextTickets.length === prev.tickets.length) return prev;
          return { ...prev, tickets: nextTickets };
        }
      );
    },
    [queryClient]
  );
  const actions = useTicketActions({
    prependOptimistic,
    removeOptimistic,
    removeTicketFromCache,
    toast: toast2,
    onSupportSystemDown: () => setSupportSystemDown(true)
  });
  const toggleRow = _react.useCallback.call(void 0, 
    (id) => {
      const t = tickets.find((x) => x.id === id);
      if (!_optionalChain([t, 'optionalAccess', _74 => _74.external_id])) return;
      setOpenTicket(t.external_id === ticketParam ? null : t.external_id);
    },
    [tickets, ticketParam, setOpenTicket]
  );
  const merged = [...optimisticTickets, ...tickets];
  const hasActiveFilters = search !== "" || status !== "" && status !== "all";
  const hasResults = merged.length > 0;
  const form = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
    HelpCenterCreateForm,
    {
      actions,
      sessionName,
      sessionEmail,
      supportSystemDown
    }
  );
  const body = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "w-full flex flex-col gap-[40px]", children: [
    error && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "bg-ods-card border border-ods-border rounded-[6px] p-[40px] text-center w-full flex flex-col items-center gap-3", children: [
      /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "text-ods-error text-base", children: [
        "Couldn\u2019t load your tickets. ",
        error.message
      ] }),
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkN5UOJC3Vcjs.Button, { type: "button", variant: "accent", onClick: () => refetch(), children: "Retry" })
    ] }),
    !error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full", children: isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevCardRowSkeletonList, {}) : !hasResults && isFetching ? (
      // Bridge state — background refetch in flight and the
      // optimistic placeholder was just removed by the mutation
      // callback. Without this branch "No tickets yet" would flash
      // for ~50ms between `removeOptimistic` and the server
      // response landing.
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevCardRowSkeletonList, { rows: 1 })
    ) : !hasResults ? hasActiveFilters ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkVYGS24ROcjs.EmptyState,
      {
        type: "search",
        title: "No tickets found",
        description: "No tickets match your current filters. Try clearing them or broadening your search.",
        showCTA: true,
        ctaText: "Reset filters",
        onCtaClick: () => {
          const params = new URLSearchParams(searchParams.toString());
          params.delete("search");
          params.delete("status");
          router.replace(`${pathname}?${params.toString()}`, { scroll: false });
        }
      }
    ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
      _chunkVYGS24ROcjs.EmptyState,
      {
        type: "generic",
        title: "No tickets yet",
        description: "Open one above to start the conversation with the support team.",
        showCTA: false
      }
    ) : (
      // `overflow-clip` (NOT `overflow-hidden`) — both visually
      // clip the rounded corners, but `hidden` makes the element
      // a "scroll container" per CSSOM spec, which causes
      // `scrollIntoView` calls inside (`<HelpCenterCard>` click
      // handlers) to try scrolling THIS div (can't, overflow
      // hidden) instead of bubbling up to the window. `clip`
      // keeps the visual clip but NOT the scroll-container
      // status, so click-to-scroll actually moves the page.
      /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bg-ods-card border border-ods-border rounded-[6px] overflow-clip w-full", children: merged.map((ticket) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, 
        HelpCenterCard,
        {
          ticket,
          expanded: expandedTicketId === ticket.id,
          onToggle: toggleRow,
          busy: isOptimistic(ticket) ? false : actions.isRowBusy(ticket.id),
          supportSystemDown,
          onSendMessage: actions.sendMessage,
          onClose: actions.closeTicket,
          onReopen: actions.reopenTicket,
          onActionCollapsed: () => setOpenTicket(null),
          replyError: actions.replyErrorFor(ticket.external_id),
          onClearReplyError: () => actions.clearReplyError(ticket.external_id)
        },
        ticket.id
      )) })
    ) }),
    !error && totalPages > 1 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.UnifiedPagination, { currentPage: page, totalPages })
  ] });
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVYGS24ROcjs.DevSectionPage, { sectionKey: "tickets", preControls: form, children: body });
}

















exports.HelpCenterCard = HelpCenterCard; exports.HelpCenterCreateForm = HelpCenterCreateForm; exports.HelpCenterCreateFormSkeleton = HelpCenterCreateFormSkeleton; exports.HelpCenterList = HelpCenterList; exports.TICKET_TOAST_COPY = TOAST_COPY; exports.TicketCenter = TicketCenter; exports.TicketDetailDrawer = TicketDetailDrawer; exports.TicketLinkedDeliveryCard = TicketLinkedDeliveryCard; exports.TicketOpenForm = TicketOpenForm; exports.TicketReplyComposer = TicketReplyComposer; exports.TicketRow = TicketRow; exports.isOptimistic = isOptimistic; exports.mapTicketActionError = mapTicketActionError; exports.useTicketActions = useTicketActions; exports.useTicketEngagements = useTicketEngagements; exports.useTicketsList = useTicketsList;
//# sourceMappingURL=index.cjs.map