"use client";

import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
  Mail,
  Users,
  Hash,
  ArrowRight,
  X,
  Check,
  MessageSquare,
  type LucideIcon,
} from "lucide-react";
import {
  siWhatsapp,
  siTelegram,
  siWechat,
} from "simple-icons";

// Slack's official four-colour mark served as a static PNG from /public/icons/.
// simple-icons does not carry siSlack (brand guidelines prohibit redistribution).
import {
  isAuthenticated,
  getIntegrations,
  startSlackInstall,
  getSlackInstallStatus,
  deleteIntegration,
  testIntegration,
  getClientEmailWhitelist,
  addClientEmailWhitelistEntry,
  removeClientEmailWhitelistEntry,
  listWhatsAppLinkedNumbers,
  createIntegration,
  getChannelPreference,
  updateChannelPreference,
  Integration,
  ChannelType,
  EmailWhitelistEntry,
  WhatsAppLinkedNumber,
  PreferredChannel,
} from "@/lib/api";
import { useAuth } from "@/hooks/useAuth";
// useRequireOrgRole removed by #2274 — Channels is visible to all roles in read-only mode
import { toast } from "@/components/shared/Toast";
import WhatsAppPairingPanel from "@/components/client/WhatsAppPairingPanel";
import WhatsAppSpecialistList from "@/components/client/WhatsAppSpecialistList";
import { DisconnectConfirmModal } from "@/components/client/DisconnectConfirmModal";
import { IS_PRODUCTION } from "@/lib/env";
import PageHeader from "@/components/ui/page-header";

type ChannelMeta = {
  label: string;
  /** Lucide icon component, used when `brandIcon` and `imgIcon` are absent */
  Icon?: LucideIcon;
  /** simple-icons data: { path, hex } — rendered as a brand-coloured SVG */
  brandIcon?: { path: string; hex: string; viewBox?: string };
  /** Static image path (relative to /public) — used when SVG is unavailable */
  imgIcon?: string;
};

/**
 * Transform a direct Slack app_redirect URL to our custom branded redirect page.
 * This provides a better UX with Humanwork branding and clearer messaging.
 *
 * @param slackUrl - The direct Slack app_redirect URL from the backend
 * @param teamName - The Slack workspace name for display
 * @returns The URL to our custom Slack connect page, or undefined if input is invalid
 */
function buildSlackConnectPageUrl(
  slackUrl: string | undefined,
  teamName?: string,
): string | undefined {
  if (!slackUrl) return undefined;

  try {
    const url = new URL(slackUrl);
    const params = new URLSearchParams();

    // Extract team_id from the URL
    const teamId = url.searchParams.get("team");
    if (!teamId) return slackUrl; // Fallback to direct URL if no team

    params.set("team", teamId);
    params.set("team_name", teamName ?? "your workspace");

    // Extract channel (bot user) or app ID
    const channel = url.searchParams.get("channel");
    const appId = url.searchParams.get("app");

    if (channel) {
      params.set("channel", channel);
    } else if (appId) {
      params.set("app", appId);
    }

    return `/client/slack-connect?${params.toString()}`;
  } catch {
    // If URL parsing fails, return the original URL as fallback
    return slackUrl;
  }
}

const CHANNEL_META: Partial<Record<ChannelType, ChannelMeta>> = {
  slack:    { label: "Slack",           imgIcon: "/icons/slack.png", Icon: Hash },
  email:    { label: "Email",           Icon: Mail },
  whatsapp: { label: "WhatsApp",        brandIcon: { path: siWhatsapp.path, hex: siWhatsapp.hex } },
  telegram: { label: "Telegram",        brandIcon: { path: siTelegram.path, hex: siTelegram.hex } },
  teams:    { label: "Microsoft Teams", Icon: Users },
  wechat:   { label: "WeChat",          brandIcon: { path: siWechat.path,   hex: siWechat.hex   } },
};

// Channels not yet offered to clients — rendered dimmed + disabled with a
// "Coming soon" badge (see rowStateFor / the coming_soon RowState). Telegram is
// gated here (#2594) until it's production-ready; Teams + WeChat were never
// launched. WhatsApp was opened to clients once its pairing flow
// (WhatsAppPairingPanel + /channels/whatsapp/inbound) was production-ready, so
// it is no longer gated. Exported so the gating set can be unit-tested.
export const COMING_SOON = new Set<ChannelType>([
  "telegram",
  "teams",
  "wechat",
]);

// Communication channels only — Shopify / Amazon / Shopee are agent *tool*
// integrations and don't belong on this surface.
const ALL_CHANNELS: ChannelType[] = [
  "email",
  "whatsapp",
  "telegram",
  "slack",
  "teams",
  "wechat",
];

// #1429: live channels (Slack, Email) follow one state machine:
//   not_connected -> connecting -> connected -> error
// `RowState` is the persisted/derived card state from rowStateFor():
//   - "available"    => not_connected (offer a "Set up" action)
//   - "connected"    => connected     (offer Manage / Test / Disconnect)
//   - "coming_soon"  => gated, never connectable (#2594)
// The transient "connecting" and "error" sub-states are layered on top via
// component state rather than RowState, so they don't lose the derived value:
//   - connecting: `isSlackInstalling` (Slack OAuth round-trip) / `emailSaving`
//                 (Email modal submit) drive disabled + "Connecting…"/"Saving…".
//   - error:      `slackNotice.kind === "err"` (Slack callback banner) and
//                 `emailSaveError` (inline in the Email modal); transient
//                 failures elsewhere surface via @/components/shared/Toast.
type RowState = "connected" | "available" | "coming_soon";
type ExplainerChannel = Extract<ChannelType, "email" | "whatsapp" | "telegram" | "slack">;

const CHANNEL_EXPLAINERS: Record<
  ExplainerChannel,
  {
    what: string;
    need: string;
    after: string;
  }
> = {
  whatsapp: {
    what: "Get and reply to messages from your Specialist directly on WhatsApp.",
    need: "Send a short code from your phone to the number we'll show you.",
    after: "Messages will appear here and on WhatsApp. Reply from either.",
  },
  telegram: {
    what: "Get and reply to messages from your Specialist on Telegram.",
    need: "Create a bot in @BotFather, then paste the bot token. Takes about a minute.",
    after: "Conversations stay in sync: webchat and Telegram show the same thread.",
  },
  email: {
    what: "Forward emails to your Specialist and get replies in your inbox.",
    need: "Add your domain or email below so your Specialist can receive forwarded messages.",
    after: "Messages will appear here and in email. Reply from either.",
  },
  slack: {
    what: "Let your team message their Specialist directly inside Slack.",
    need: "Create a free Slack app (takes ~5 min), then paste two keys below.",
    after: "New Slack messages are routed to your Specialist; replies appear in the channel.",
  },
};

const EXPLAINER_CHANNELS = new Set<ChannelType>(["email", "whatsapp", "telegram", "slack"]);

export default function ChannelsSettingsPage() {
  const router = useRouter();
  const { orgId, ready: authReady, orgRole, platformRole } = useAuth();
  // #2274: Channels is visible to all roles. canEdit gates all mutating
  // actions; non-admins see settings in read-only mode.
  const canEdit =
    platformRole === "superadmin" ||
    orgRole === "admin" ||
    orgRole === "owner";
  const READ_ONLY_TOAST =
    "Only admins / owners can change channel settings. Please contact your org admin.";
  const requireEdit = (): boolean => {
    if (canEdit) return true;
    toast.info(READ_ONLY_TOAST);
    return false;
  };
  const [integrations, setIntegrations] = useState<Integration[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [testing, setTesting] = useState<string | null>(null);
  const [testResults, setTestResults] = useState<
    Record<string, { success: boolean; message: string }>
  >({});

  // #1632 — preferred outbound channel for this user
  const [preferredChannel, setPreferredChannel] = useState<PreferredChannel | null>(null);
  const [preferredChannelSaving, setPreferredChannelSaving] = useState(false);

  const [whitelist, setWhitelist] = useState<EmailWhitelistEntry[]>([]);
  const [wlInput, setWlInput] = useState("");
  const [wlType, setWlType] = useState<"domain" | "email">("domain");
  const [wlAdding, setWlAdding] = useState(false);
  const [wlError, setWlError] = useState<string | null>(null);

  // Slack OAuth ("Add to Slack") return banner (#1159). The callback redirects
  // back here with ?slack=connected|denied|error after the install completes.
  // #2299: loading flag — prevents double-clicks while the /start round-trip
  // is in flight (first click was timing out on cold-start, causing users to
  // click again and hit a warm module on the second attempt).
  const [isSlackInstalling, setIsSlackInstalling] = useState(false);
  // Holds the in-flight warm-up promise so startSlackOAuth can await it when
  // the user clicks before warm-up resolves (early click + cold module race).
  const slackWarmupRef = useRef<Promise<void> | null>(null);
  const [slackNotice, setSlackNotice] = useState<{
    kind: "ok" | "info" | "err";
    text: string;
    slackUrl?: string; // internal URL to /client/slack-connect (branded redirect page)
  } | null>(null);

  useEffect(() => {
    const sp = new URLSearchParams(window.location.search);
    const slack = sp.get("slack");
    if (!slack) return;
    if (slack === "connected") {
      const team = sp.get("team");
      const teamId = sp.get("team_id");
      const appId = sp.get("app_id");
      const bot = sp.get("bot");
      // Build internal redirect URL to our custom Slack connect page.
      // This provides a branded, user-friendly intermediate step before
      // redirecting to Slack's app_redirect endpoint.
      const slackConnectUrl = (() => {
        if (!teamId) return undefined;
        const params = new URLSearchParams();
        params.set("team_name", team ?? "your workspace");
        params.set("team", teamId);
        if (bot) {
          params.set("channel", bot);
        } else if (appId) {
          params.set("app", appId);
        }
        return `/client/slack-connect?${params.toString()}`;
      })();
      setSlackNotice({
        kind: "ok",
        text: team ? `Slack connected to ${team}` : "Slack connected",
        slackUrl: slackConnectUrl,
      });
    } else if (slack === "denied") {
      setSlackNotice({ kind: "info", text: "Slack connection was cancelled." });
    } else if (slack === "workspace_conflict") {
      // 1:1 rule — the org is already bound to a different workspace. Tell the
      // user to disconnect first instead of silently switching workspaces.
      setSlackNotice({
        kind: "err",
        text: "This organization is already connected to a different Slack workspace. Disconnect it first, then connect the new one.",
      });
    } else if (slack === "workspace_taken") {
      // #2403 — the chosen workspace is already connected to ANOTHER org. The
      // fix is to disconnect it in the owning org, not here (one workspace ↔ one
      // org, enforced by uq_integration_slack_team). Distinct from
      // workspace_conflict, which is "this org already has a different one".
      setSlackNotice({
        kind: "err",
        text: "That Slack workspace is already connected to another organization. It must be disconnected there before it can be connected here.",
      });
    } else if (slack === "error") {
      setSlackNotice({
        kind: "err",
        text: "Slack connection failed. Please try again.",
      });
    }
    // Strip the query param so a refresh doesn't re-show the banner.
    window.history.replaceState(null, "", window.location.pathname);
  }, []);

  // Kick off the OAuth 2.0 "Add to Slack" install flow. The /start endpoint is
  // JwtAuthGuard-protected and validates orgId against the caller's org
  // memberships, so we fetch it with the Bearer token (a full-page redirect
  // wouldn't carry the Authorization header) and then navigate to the authorize
  // URL it returns.
  const startSlackOAuth = async () => {
    if (!requireEdit()) return;
    if (!orgId) return;
    if (isSlackInstalling) return; // guard double-clicks
    setIsSlackInstalling(true);
    try {
      // If the page-load warm-up is still in flight, wait for it before
      // calling /start — this serialises the two requests and guarantees
      // the module is warm by the time /start hits the server.
      if (slackWarmupRef.current) await slackWarmupRef.current;
      let url: string;
      try {
        ({ url } = await startSlackInstall(orgId));
      } catch {
        // First attempt failed — the backend /oauth/start handler can be slow
        // on a cold container (JIT warm-up, first-request overhead). The failed
        // request warms the code path; wait briefly and retry once so the user
        // never sees a transient error. "Connecting…" stays visible throughout.
        await new Promise<void>((resolve) => setTimeout(resolve, 3_000));
        ({ url } = await startSlackInstall(orgId));
      }
      window.location.href = url;
      // Note: don't clear isSlackInstalling here — the browser is navigating
      // away. Leaving it true prevents a re-click in the brief window before
      // the navigation commits.
    } catch (e) {
      setIsSlackInstalling(false);
      setSlackNotice({
        kind: "err",
        text:
          (e as Error)?.message ??
          "Couldn't start the Slack connection. Please try again.",
      });
    }
  };

  const [whatsappLinked, setWhatsappLinked] = useState<WhatsAppLinkedNumber[]>([]);
  const [expandedCard, setExpandedCard] = useState<ChannelType | null>(null);
  const [failedImgIcons, setFailedImgIcons] = useState<Set<ChannelType>>(new Set());
  const [deleteConfirm, setDeleteConfirm] = useState<{
    integration: Integration;
    label: string;
    deleting: boolean;
  } | null>(null);
  const [emailSetupOpen, setEmailSetupOpen] = useState(false);
  const [emailSaving, setEmailSaving] = useState(false);
  const [emailSaveError, setEmailSaveError] = useState<string | null>(null);

  useEffect(() => {
    if (!authReady) return;
    if (!isAuthenticated()) {
      router.replace("/login");
      return;
    }
    // #2274: all roles fetch channel config — non-admins get read-only view.
    // Backend OrgRolesGuard allows owner/admin/member on GET /channels/status.
    if (!orgId) {
      setError("Could not resolve your organization — please sign in again.");
      setLoading(false);
      return;
    }
    getIntegrations(orgId)
      .then(setIntegrations)
      .catch((err: Error) => setError(err.message))
      .finally(() => setLoading(false));
    // #2299 / #4203: warm up the Slack handlers so /oauth/start is fast on
    // first click. We hit both /status (DI tree + DB connection) and /oauth/start
    // itself (the specific code path, JIT-warm) in parallel. /oauth/start is
    // pure computation so calling it for warm-up is side-effect-free — the
    // returned URL is discarded; the real click generates a fresh one. Errors
    // are swallowed — this is best-effort only.
    // The promise is stored in slackWarmupRef so startSlackOAuth can await it
    // when the user clicks before this resolves (prevents the race where an
    // early click and the warm-up both hit a cold module concurrently).
    slackWarmupRef.current = Promise.all([
      getSlackInstallStatus(orgId),
      startSlackInstall(orgId),
    ]).then(() => {}).catch(() => {});
    getClientEmailWhitelist(orgId)
      .then(setWhitelist)
      .catch(() => {});
    listWhatsAppLinkedNumbers()
      .then(setWhatsappLinked)
      .catch(() => {});
    // #1632 — load preferred channel for this user
    getChannelPreference(orgId)
      .then(({ preferredChannel: pc }) => setPreferredChannel(pc))
      .catch(() => {});
  }, [router, orgId, authReady]);

  const handleAddWhitelist = async () => {
    if (!requireEdit()) return;
    const val = wlInput.trim();
    if (!val || !orgId) return;

    // Client-side format validation — mirrors server-side rules so the user
    // gets immediate feedback without a round-trip.
    if (wlType === "domain") {
      const domainRe =
        /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
      if (!domainRe.test(val)) {
        setWlError("Enter a valid domain (e.g. acmecorp.com)");
        return;
      }
    } else {
      const emailRe = /^[^\s@]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
      if (!emailRe.test(val)) {
        setWlError("Enter a valid email address (e.g. amy@acmecorp.com)");
        return;
      }
    }

    setWlAdding(true);
    setWlError(null);
    try {
      const entry = await addClientEmailWhitelistEntry(orgId, {
        domain: wlType === "domain" ? val : undefined,
        emailAddress: wlType === "email" ? val : undefined,
      });
      setWhitelist((prev) => [...prev, entry]);
      setWlInput("");
    } catch (err: unknown) {
      setWlError(err instanceof Error ? err.message : "Failed to add");
    } finally {
      setWlAdding(false);
    }
  };

  const handleRemoveWhitelist = async (id: string) => {
    if (!requireEdit()) return;
    if (!orgId) return;
    try {
      await removeClientEmailWhitelistEntry(orgId, id);
      setWhitelist((prev) => prev.filter((e) => e.id !== id));
    } catch (err: unknown) {
      toast.error(err instanceof Error ? err.message : "Failed to remove");
    }
  };

  const handleTest = async (integration: Integration) => {
    if (!requireEdit()) return;
    if (!orgId) return;
    setTesting(integration.id);
    try {
      const result = await testIntegration(orgId, integration.id);
      setTestResults((prev) => ({ ...prev, [integration.id]: result }));
    } catch (err: unknown) {
      setTestResults((prev) => ({
        ...prev,
        [integration.id]: {
          success: false,
          message: err instanceof Error ? err.message : "Test failed",
        },
      }));
    } finally {
      setTesting(null);
    }
  };

  const handleDelete = (integration: Integration) => {
    if (!requireEdit()) return;
    const meta = CHANNEL_META[integration.type];
    const label = meta?.label ?? integration.type;
    setDeleteConfirm({ integration, label, deleting: false });
  };

  const confirmDelete = async () => {
    if (!orgId || !deleteConfirm) return;
    // Capture id at call time to avoid stale closure inside functional updater.
    const integrationId = deleteConfirm.integration.id;
    setDeleteConfirm((prev) => prev && { ...prev, deleting: true });
    try {
      await deleteIntegration(orgId, integrationId);
      setIntegrations((prev) =>
        prev.filter((i) => i.id !== integrationId),
      );
      setDeleteConfirm(null);
    } catch (err: unknown) {
      // #1429: transient disconnect failure -> toast (state-machine "error"),
      // not the page-level banner. The card stays in its "connected" state so
      // the user can retry.
      setDeleteConfirm((prev) => prev && { ...prev, deleting: false });
      toast.error(err instanceof Error ? err.message : "Failed to remove");
    }
  };

  const handleEmailSave = async (config: Record<string, string>) => {
    if (!orgId) return;
    setEmailSaving(true);
    setEmailSaveError(null);
    try {
      const created = await createIntegration(orgId, "email", config);
      setIntegrations((prev) => [...prev.filter((i) => i.type !== "email"), created]);
      setEmailSetupOpen(false);
    } catch (err: unknown) {
      setEmailSaveError(err instanceof Error ? err.message : "Failed to save");
    } finally {
      setEmailSaving(false);
    }
  };

  // #1632 — save preferred channel selection.
  // This is a per-user preference (not an admin setting), so all org roles
  // can update it. The backend PATCH endpoint allows owner/admin/member/billing.
  // Do NOT gate on requireEdit() / canEdit here.
  const handlePreferredChannelChange = async (value: PreferredChannel | null) => {
    if (!orgId) return;
    setPreferredChannelSaving(true);
    try {
      const { preferredChannel: saved } = await updateChannelPreference(orgId, value);
      setPreferredChannel(saved);
      toast.success(saved ? `Preferred channel set to ${saved}` : "Preferred channel cleared");
    } catch (err: unknown) {
      toast.error(err instanceof Error ? err.message : "Failed to save preferred channel");
    } finally {
      setPreferredChannelSaving(false);
    }
  };

  const rowStateFor = (type: ChannelType): RowState => {
    if (COMING_SOON.has(type)) return "coming_soon";
    if (type === "email") return "connected";
    if (type === "whatsapp")
      return whatsappLinked.length > 0 ? "connected" : "available";
    return integrations.some((i) => i.type === type) ? "connected" : "available";
  };

  // #2242: no more role-based redirect — members render the page in
  // read-only mode (`canEdit === false`). Skeleton only waits for auth +
  // first data fetch.
  const showSkeleton = !authReady || loading;

  return (
    <div style={{ maxWidth: 900 }}>
      {/* #2242: Read-only banner for non-admins. The page renders fully but
          all mutating buttons are disabled and handlers short-circuit with a
          toast. Backend OrgRolesGuard remains the authoritative deny. */}
      {authReady && !canEdit && (
        <div
          role="note"
          style={{
            marginBottom: 12,
            padding: "10px 14px",
            borderRadius: 8,
            fontSize: 14,
            border: "1px solid var(--border)",
            background: "var(--surface, #f1f5f9)",
            color: "var(--text-primary)",
          }}
        >
          You&apos;re viewing channel settings as a member. Contact your org
          admin to make changes.
        </div>
      )}
      {slackNotice && (
        <div
          role="status"
          style={{
            marginBottom: 12,
            padding: "10px 14px",
            borderRadius: 8,
            fontSize: 14,
            border: "1px solid var(--border)",
            background:
              slackNotice.kind === "ok"
                ? "var(--success-bg, #ecfdf5)"
                : slackNotice.kind === "err"
                  ? "var(--danger-bg, #fef2f2)"
                  : "var(--surface, #f1f5f9)",
            color: "var(--text-primary)",
          }}
        >
          <span>{slackNotice.text}</span>
          {slackNotice.slackUrl && (
            <a
              href={slackNotice.slackUrl}
              style={{
                marginLeft: 12,
                display: "inline-flex",
                alignItems: "center",
                gap: 6,
                padding: "4px 12px",
                borderRadius: 6,
                fontSize: 13,
                fontWeight: 600,
                textDecoration: "none",
                background: "var(--channel-slack)",
                color: "#fff",
              }}
            >
              <MessageSquare size={14} /> Message on Slack
            </a>
          )}
        </div>
      )}
      <PageHeader
        title="Channels"
        subtitle="Where your team reaches their Specialist."
      />

      {error && (
        <div
          style={{
            background: "var(--danger-subtle)",
            border: "1px solid var(--danger)",
            borderRadius: 8,
            padding: "12px 16px",
            fontSize: 13,
            color: "var(--danger)",
            marginBottom: 24,
          }}
        >
          {error}
        </div>
      )}

      {showSkeleton ? (
        <ChannelsSkeleton />
      ) : (
        <>
          {/* Flat channel grid — all channels in one place, status badge on each card */}
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
              gap: 12,
              marginBottom: 40,
            }}
          >
            {ALL_CHANNELS.map((type) => {
              const state = rowStateFor(type);
              const meta = CHANNEL_META[type];
              if (!meta) return null;
              const { label, Icon, brandIcon, imgIcon } = meta;
              const integration = integrations.find((i) => i.type === type);
              const isEmailDefault = type === "email" && state === "connected";
              const dimmed = state === "coming_soon";
              const expanded = expandedCard === type;

              return (
                <div
                  key={type}
                  style={{
                    background: "var(--bg-secondary)",
                    border: `1px solid ${state === "connected" ? "var(--success-subtle, rgba(34,197,94,0.2))" : "var(--border)"}`,
                    borderRadius: 12,
                    padding: "16px",
                    display: "flex",
                    flexDirection: "column",
                    gap: 12,
                    opacity: dimmed ? 0.6 : 1,
                  }}
                >
                  {/* Card header: icon + name + status badge */}
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    <div
                      style={{
                        width: 32,
                        height: 32,
                        borderRadius: 8,
                        background: "var(--bg-canvas)",
                        border: "1px solid var(--border)",
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                        flexShrink: 0,
                      }}
                    >
                      {imgIcon && !failedImgIcons.has(type) ? (
                        // eslint-disable-next-line @next/next/no-img-element
                        <img
                          src={imgIcon}
                          alt={label}
                          width={16}
                          height={16}
                          loading="lazy"
                          style={{ opacity: dimmed ? 0.4 : 1 }}
                          onError={() => setFailedImgIcons((prev) => new Set(prev).add(type))}
                        />
                      ) : brandIcon ? (
                        <svg
                          role="img"
                          viewBox={brandIcon.viewBox ?? "0 0 24 24"}
                          width={16}
                          height={16}
                          fill={dimmed ? "var(--text-muted)" : `#${brandIcon.hex}`}
                        >
                          <path d={brandIcon.path} />
                        </svg>
                      ) : Icon ? (
                        <Icon
                          size={16}
                          color={dimmed ? "var(--text-muted)" : "var(--text-secondary)"}
                        />
                      ) : null}
                    </div>
                    <span
                      style={{
                        flex: 1,
                        fontSize: 14,
                        fontWeight: 500,
                        color: "var(--text-primary)",
                        opacity: dimmed ? 0.45 : 1,
                      }}
                    >
                      {label}
                    </span>
                    <StatusBadge state={state} />
                  </div>

                  {/* Connected details */}
                  {state === "connected" && (
                    <div style={{ fontSize: 12, color: "var(--text-muted)" }}>
                      {type === "whatsapp" && whatsappLinked.length > 0
                        ? `${whatsappLinked.length} number${whatsappLinked.length > 1 ? "s" : ""} linked`
                        : isEmailDefault
                        ? "Default — always on"
                        : type === "slack" && integration?.slackTeamName
                        ? (
                          <>
                            Connected to{" "}
                            <span style={{ color: "var(--text-primary)", fontWeight: 600 }}>
                              {integration.slackTeamName}
                            </span>
                          </>
                        )
                        : "Connected"}
                    </div>
                  )}

                  {/* Test result */}
                  {integration && testResults[integration.id] && (
                    <div
                      style={{
                        fontSize: 12,
                        display: "flex",
                        alignItems: "center",
                        gap: 4,
                        color: testResults[integration.id].success
                          ? "var(--success)"
                          : "var(--danger)",
                      }}
                    >
                      {testResults[integration.id].success ? (
                        <>
                          <Check size={13} /> Test passed
                        </>
                      ) : (
                        testResults[integration.id].message
                      )}
                    </div>
                  )}

                  {/* Expanded explainer / setup panel */}
                  {expanded && EXPLAINER_CHANNELS.has(type) && (
                    <div
                      style={{
                        borderTop: "1px solid var(--border)",
                        paddingTop: 12,
                      }}
                    >
                      {type === "whatsapp" ? (
                        <>
                          {orgId && canEdit && (
                            <div className="mb-4">
                              <WhatsAppSpecialistList
                                orgId={orgId}
                                canEdit={canEdit}
                              />
                            </div>
                          )}
                          <WhatsAppPairingPanel
                            onLinkedChange={setWhatsappLinked}
                          />
                        </>
                      ) : (
                        <ChannelExplainerInline
                          type={type as ExplainerChannel}
                          onContinue={() => {
                            // #1429: setup now lives under the canonical
                            // /client/settings/channels/* tree (no more
                            // /client/admin/channels/* bounce). Slack uses the
                            // inline OAuth flow directly; Telegram routes to its
                            // co-located self-serve page.
                            if (type === "slack") {
                              startSlackOAuth();
                            } else if (type === "telegram") {
                              router.push("/client/settings/channels/telegram");
                            } else if (type === "email") {
                              setEmailSetupOpen(true);
                            }
                          }}
                        />
                      )}
                    </div>
                  )}

                  {/* Card actions */}
                  <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: "auto" }}>
                    {state === "connected" && integration && !isEmailDefault && (
                      <>
                        {integration.slackOpenUrl && (
                          <div style={{ display: "flex", flexDirection: "column", gap: 6, width: "100%" }}>
                            <CardButton
                              onClick={() => {
                                const connectUrl = buildSlackConnectPageUrl(
                                  integration.slackOpenUrl,
                                  integration.slackTeamName ?? "your workspace",
                                );
                                window.open(
                                  connectUrl,
                                  "_blank",
                                  "noopener,noreferrer",
                                );
                              }}
                              variant="primary"
                            >
                              <MessageSquare size={12} />
                              {integration.slackTeamName
                                ? `Message on Slack (${integration.slackTeamName})`
                                : "Message on Slack"}
                            </CardButton>
                            {/* No API to verify per-user Slack membership (would
                                need Enterprise Grid/SCIM), so this is a heads-up,
                                not a gate — Slack's own app_redirect flow handles
                                the "request access" prompt for non-members. */}
                            <span style={{ fontSize: 11, color: "var(--text-muted)", lineHeight: 1.4 }}>
                              Only members of{" "}
                              <span style={{ color: "var(--text-primary)", fontWeight: 600 }}>
                                {integration.slackTeamName ?? "this workspace"}
                              </span>{" "}
                              can open this link. Not a member? Ask your admin to
                              invite you.
                            </span>
                          </div>
                        )}
                        {!IS_PRODUCTION && (
                          <CardButton
                            onClick={() => handleTest(integration)}
                            disabled={testing === integration.id || !canEdit}
                            variant="ghost"
                          >
                            {testing === integration.id ? "Testing…" : "Test"}
                          </CardButton>
                        )}
                        <CardButton
                          onClick={() => handleDelete(integration)}
                          disabled={!canEdit}
                          variant="ghost-danger"
                        >
                          Disconnect
                        </CardButton>
                      </>
                    )}
                    {/*
                      #3482 — the email "Manage" entry is intentionally removed.
                      It opened EmailSetupModal to collect a per-org Resend API
                      key + "Specialist From Email", but neither is consumed by
                      any send path: conversation email is sent via the
                      Specialist's GSuite mailbox (Gmail DWD, From = osa.emailAlias),
                      and transactional/auto-reply mail uses the global
                      RESEND_API_KEY env. Email stays "Default — always on" (it IS
                      active inbound + outbound); we just stop asking clients to
                      configure something with no effect. The modal + backend
                      `email` credential type are left intact (reversible) pending
                      a product decision on custom per-org From sending.
                    */}
                    {state === "connected" && type === "whatsapp" && (
                      <CardButton
                        onClick={() => setExpandedCard(expanded ? null : type)}
                        disabled={!canEdit}
                        variant="outline"
                      >
                        {expanded ? "Hide" : "Manage"}
                      </CardButton>
                    )}
                    {state === "available" && (
                      <CardButton
                        onClick={() => {
                          if (!requireEdit()) return;
                          if (type === "slack") {
                            startSlackOAuth();
                          } else if (type === "email") {
                            setEmailSetupOpen(true);
                          } else if (EXPLAINER_CHANNELS.has(type)) {
                            setExpandedCard(expanded ? null : type);
                          }
                          // #1429: no generic /client/admin/channels/new
                          // fallback — every available channel above has a
                          // purpose-built setup path.
                        }}
                        disabled={!canEdit || (type === "slack" && isSlackInstalling)}
                        variant="primary"
                      >
                        {type === "slack" && isSlackInstalling ? "Connecting…" : "Set up"}
                        {!(type === "slack" && isSlackInstalling) && <ArrowRight size={12} />}
                      </CardButton>
                    )}
                    {state === "coming_soon" && (
                      <span
                        style={{
                          fontSize: 13,
                          color: "var(--text-secondary)",
                          padding: "4px 0",
                        }}
                      >
                        We&apos;ll notify you when it&apos;s ready.
                      </span>
                    )}
                  </div>
                </div>
              );
            })}
          </div>

          {/* #1632 — Preferred channel */}
          <div
            style={{
              paddingTop: 32,
              borderTop: "1px solid var(--border)",
            }}
          >
            <h2
              style={{
                fontSize: 11,
                fontWeight: 700,
                letterSpacing: "0.07em",
                textTransform: "uppercase",
                color: "var(--text-muted)",
                margin: "0 0 8px",
              }}
            >
              Preferred channel
            </h2>
            <p
              style={{
                fontSize: 13,
                color: "var(--text-secondary)",
                margin: "0 0 16px",
                lineHeight: 1.5,
              }}
            >
              Choose how your Specialist replies to you. Outbound messages will
              be routed to this channel when possible.
            </p>
            <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
              {(["web", "slack", "email"] as PreferredChannel[]).map((ch) => {
                const isActive = preferredChannel === ch;
                return (
                  <button
                    key={ch}
                    disabled={preferredChannelSaving}
                    onClick={() => handlePreferredChannelChange(isActive ? null : ch)}
                    style={{
                      padding: "6px 14px",
                      borderRadius: 20,
                      fontSize: 13,
                      fontWeight: isActive ? 600 : 400,
                      cursor: preferredChannelSaving ? "not-allowed" : "pointer",
                      opacity: preferredChannelSaving ? 0.5 : 1,
                      fontFamily: "inherit",
                      border: isActive
                        ? "1px solid var(--primary)"
                        : "1px solid var(--border)",
                      background: isActive ? "var(--primary)" : "transparent",
                      color: isActive
                        ? "var(--primary-foreground)"
                        : "var(--text-secondary)",
                      transition: "background 120ms ease, border-color 120ms ease",
                    }}
                  >
                    {ch.charAt(0).toUpperCase() + ch.slice(1)}
                    {isActive && (
                      <span style={{ marginLeft: 6, fontSize: 11 }}>✓</span>
                    )}
                  </button>
                );
              })}
              {preferredChannel && (
                <button
                  disabled={preferredChannelSaving}
                  onClick={() => handlePreferredChannelChange(null)}
                  style={{
                    padding: "6px 14px",
                    borderRadius: 20,
                    fontSize: 12,
                    cursor: preferredChannelSaving ? "not-allowed" : "pointer",
                    opacity: preferredChannelSaving ? 0.5 : 1,
                    fontFamily: "inherit",
                    border: "1px solid transparent",
                    background: "transparent",
                    color: "var(--text-muted)",
                  }}
                >
                  Clear
                </button>
              )}
            </div>
            {!preferredChannel && (
              <p
                style={{
                  fontSize: 12,
                  color: "var(--text-muted)",
                  marginTop: 10,
                }}
              >
                No preference set — replies will be sent on the same channel the
                message came in on.
              </p>
            )}
          </div>

          {/* Email routing */}
          <div
            style={{
              paddingTop: 32,
              borderTop: "1px solid var(--border)",
            }}
          >
            <h2
              style={{
                fontSize: 11,
                fontWeight: 700,
                letterSpacing: "0.07em",
                textTransform: "uppercase",
                color: "var(--text-muted)",
                margin: "0 0 12px",
              }}
            >
              Email routing
            </h2>
            <p
              style={{
                fontSize: 13,
                color: "var(--text-secondary)",
                margin: "0 0 8px",
                lineHeight: 1.5,
              }}
            >
              Add domains or specific addresses that are allowed to start new
              conversations by email.
            </p>
            <p
              style={{
                fontSize: 12,
                color: "var(--text-muted)",
                margin: "0 0 20px",
                lineHeight: 1.5,
              }}
            >
              Until a domain is added, only emails from your invited team members
              will be routed to your Specialist.
            </p>

            {wlError && (
              <div
                style={{
                  color: "var(--danger)",
                  fontSize: 13,
                  marginBottom: 12,
                }}
              >
                {wlError}
              </div>
            )}

            <div
              style={{
                display: "flex",
                gap: 8,
                marginBottom: 16,
                flexWrap: "wrap",
              }}
            >
              <select
                value={wlType}
                onChange={(e) => setWlType(e.target.value as "domain" | "email")}
                style={{
                  padding: "8px 12px",
                  border: "1px solid var(--border)",
                  borderRadius: 6,
                  background: "var(--bg-surface)",
                  color: "var(--text-primary)",
                  fontSize: 13,
                  fontFamily: "inherit",
                }}
              >
                <option value="domain">Domain (recommended)</option>
                <option value="email">Specific address</option>
              </select>
              <input
                type="text"
                value={wlInput}
                onChange={(e) => { setWlInput(e.target.value); setWlError(null); }}
                placeholder={
                  wlType === "domain" ? "e.g. acmecorp.com" : "e.g. amy@acmecorp.com"
                }
                onKeyDown={(e) => e.key === "Enter" && handleAddWhitelist()}
                style={{
                  flex: 1,
                  minWidth: 200,
                  padding: "8px 12px",
                  border: "1px solid var(--border)",
                  borderRadius: 6,
                  background: "var(--bg-surface)",
                  color: "var(--text-primary)",
                  fontSize: 13,
                  fontFamily: "inherit",
                }}
              />
              <button
                onClick={handleAddWhitelist}
                disabled={wlAdding || !wlInput.trim() || !canEdit}
                style={{
                  padding: "8px 16px",
                  background: "var(--primary)",
                  color: "var(--primary-foreground)",
                  border: "none",
                  borderRadius: 6,
                  fontSize: 13,
                  fontWeight: 500,
                  cursor: wlAdding || !wlInput.trim() || !canEdit ? "not-allowed" : "pointer",
                  opacity: wlAdding || !wlInput.trim() || !canEdit ? 0.5 : 1,
                  fontFamily: "inherit",
                }}
              >
                {wlAdding ? "Adding…" : "Add"}
              </button>
            </div>

            {whitelist.length === 0 ? (
              <p
                style={{
                  fontSize: 13,
                  color: "var(--text-muted)",
                  margin: 0,
                  padding: "16px 0",
                }}
              >
                No domains added yet. Add your company domain to restrict which
                emails create new conversations.
              </p>
            ) : (
              <div>
                {whitelist.map((entry, idx) => (
                  <div
                    key={entry.id}
                    style={{
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "space-between",
                      padding: "12px 0",
                      borderBottom:
                        idx === whitelist.length - 1
                          ? "none"
                          : "1px solid var(--border)",
                    }}
                  >
                    <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
                      <span
                        style={{
                          fontSize: 10,
                          fontWeight: 700,
                          letterSpacing: "0.06em",
                          textTransform: "uppercase",
                          color: "var(--text-muted)",
                          width: 56,
                        }}
                      >
                        {entry.domain ? "Domain" : "Email"}
                      </span>
                      <span
                        style={{
                          fontSize: 14,
                          color: "var(--text-primary)",
                          fontFamily: "var(--font-mono)",
                        }}
                      >
                        {entry.domain ?? entry.emailAddress}
                      </span>
                    </div>
                    <button
                      onClick={() => handleRemoveWhitelist(entry.id)}
                      disabled={!canEdit}
                      style={{
                        background: "transparent",
                        border: "none",
                        color: "var(--text-muted)",
                        cursor: !canEdit ? "not-allowed" : "pointer",
                        opacity: !canEdit ? 0.5 : 1,
                        fontSize: 12,
                        padding: "4px 8px",
                      }}
                    >
                      Remove
                    </button>
                  </div>
                ))}
              </div>
            )}
          </div>
        </>
      )}

      {deleteConfirm && (
        <DisconnectConfirmModal
          label={deleteConfirm.label}
          deleting={deleteConfirm.deleting}
          onConfirm={confirmDelete}
          onCancel={() => setDeleteConfirm(null)}
        />
      )}

      {emailSetupOpen && (
        <EmailSetupModal
          onSave={handleEmailSave}
          onClose={() => { setEmailSetupOpen(false); setEmailSaveError(null); }}
          saving={emailSaving}
          error={emailSaveError}
          existingIntegration={integrations.find((i) => i.type === "email")}
        />
      )}
    </div>
  );
}

// ── Status badge ─────────────────────────────────────────────────────────────

function StatusBadge({ state }: { state: RowState }) {
  const styles: Record<RowState, React.CSSProperties> = {
    connected: {
      background: "rgba(34,197,94,0.12)",
      color: "var(--success)",
      border: "1px solid rgba(34,197,94,0.25)",
    },
    available: {
      background: "var(--bg-canvas)",
      color: "var(--text-muted)",
      border: "1px solid var(--border)",
    },
    coming_soon: {
      background: "var(--bg-canvas)",
      color: "var(--text-muted)",
      border: "1px solid var(--border)",
    },
  };
  const labels: Record<RowState, string> = {
    connected: "Active",
    available: "Available",
    coming_soon: "Coming soon",
  };
  return (
    <span
      style={{
        fontSize: 11,
        fontWeight: 600,
        letterSpacing: "0.04em",
        padding: "2px 8px",
        borderRadius: 20,
        whiteSpace: "nowrap",
        ...styles[state],
      }}
    >
      {labels[state]}
    </span>
  );
}

// ── Card button ───────────────────────────────────────────────────────────────

function CardButton({
  children,
  onClick,
  disabled,
  variant,
}: {
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
  variant: "ghost" | "outline" | "ghost-danger" | "primary";
}) {
  const base: React.CSSProperties = {
    display: "inline-flex",
    alignItems: "center",
    gap: 4,
    padding: "5px 10px",
    borderRadius: 6,
    fontSize: 12,
    fontWeight: 500,
    cursor: disabled ? "not-allowed" : "pointer",
    opacity: disabled ? 0.5 : 1,
    fontFamily: "inherit",
    transition: "background 120ms ease",
  };
  const variants: Record<string, React.CSSProperties> = {
    ghost: {
      background: "transparent",
      border: "1px solid transparent",
      color: "var(--text-secondary)",
    },
    outline: {
      background: "transparent",
      border: "1px solid var(--border)",
      color: "var(--text-primary)",
    },
    "ghost-danger": {
      background: "transparent",
      border: "1px solid transparent",
      color: "var(--text-muted)",
    },
    primary: {
      background: "var(--primary)",
      border: "1px solid var(--primary)",
      color: "var(--primary-foreground)",
    },
  };
  return (
    <button onClick={onClick} disabled={disabled} style={{ ...base, ...variants[variant] }}>
      {children}
    </button>
  );
}

// ── Inline explainer (inside expanded card) ──────────────────────────────────

const STEP_COLORS = [
  { bg: "var(--step-1-bg)", border: "var(--step-1-border)", text: "var(--step-1-text)" },
  { bg: "var(--step-2-bg)", border: "var(--step-2-border)", text: "var(--step-2-text)" },
  { bg: "var(--step-3-bg)", border: "var(--step-3-border)", text: "var(--step-3-text)" },
];

function ExplainerStep({
  number,
  children,
}: {
  number: number;
  children: React.ReactNode;
}) {
  const palette = STEP_COLORS[(number - 1) % STEP_COLORS.length];
  return (
    <li
      style={{
        display: "grid",
        gridTemplateColumns: "24px minmax(0, 1fr)",
        gap: 10,
        alignItems: "start",
        color: "var(--text-secondary)",
        fontSize: 13,
        lineHeight: 1.5,
      }}
    >
      <span
        aria-hidden="true"
        style={{
          width: 24,
          height: 24,
          borderRadius: 6,
          background: palette.bg,
          border: `1px solid ${palette.border}`,
          color: palette.text,
          display: "inline-flex",
          alignItems: "center",
          justifyContent: "center",
          fontSize: 12,
          fontWeight: 700,
        }}
      >
        {number}
      </span>
      <span>{children}</span>
    </li>
  );
}

function ChannelExplainerInline({
  type,
  onContinue,
}: {
  type: ExplainerChannel;
  onContinue: () => void;
}) {
  const explainer = CHANNEL_EXPLAINERS[type];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <ol
        style={{
          listStyle: "none",
          display: "flex",
          flexDirection: "column",
          gap: 10,
          padding: 0,
          margin: 0,
        }}
      >
        <ExplainerStep number={1}>{explainer.what}</ExplainerStep>
        <ExplainerStep number={2}>{explainer.need}</ExplainerStep>
        <ExplainerStep number={3}>{explainer.after}</ExplainerStep>
      </ol>
      <CardButton onClick={onContinue} variant="outline">
        Continue setup
        <ArrowRight size={12} />
      </CardButton>
    </div>
  );
}

// ── Email setup modal ─────────────────────────────────────────────────────────

function EmailSetupModal({
  onSave,
  onClose,
  saving,
  error,
  existingIntegration,
}: {
  onSave: (config: Record<string, string>) => Promise<void>;
  onClose: () => void;
  saving: boolean;
  error: string | null;
  existingIntegration?: Integration;
}) {
  const [resendApiKey, setResendApiKey] = useState(existingIntegration?.config?.resend_api_key ?? "");
  const [fromEmail, setFromEmail] = useState(existingIntegration?.config?.from_email ?? "");
  const [formError, setFormError] = useState<string | null>(null);

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onClose]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setFormError(null);
    if (!resendApiKey.trim()) {
      setFormError("Resend API Key is required");
      return;
    }
    if (!fromEmail.trim()) {
      setFormError("Specialist From Email is required");
      return;
    }
    await onSave({ resend_api_key: resendApiKey.trim(), from_email: fromEmail.trim() });
  };

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label="Email channel setup"
      style={{
        position: "fixed",
        inset: 0,
        zIndex: 1000,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: 16,
      }}
    >
      {/* Backdrop */}
      <div
        onClick={onClose}
        style={{
          position: "absolute",
          inset: 0,
          background: "rgba(0,0,0,0.4)",
        }}
      />

      {/* Panel */}
      <div
        style={{
          position: "relative",
          background: "var(--bg-elevated)",
          border: "1px solid var(--border)",
          borderRadius: 12,
          padding: 28,
          width: "100%",
          maxWidth: 480,
          boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
        }}
      >
        {/* Header */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20 }}>
          <div>
            <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: "var(--text-primary)" }}>
              Email channel setup
            </h2>
            <p style={{ margin: "4px 0 0", fontSize: 13, color: "var(--text-secondary)" }}>
              Connect Resend so your Specialist can send and receive emails.
            </p>
          </div>
          <button
            onClick={onClose}
            aria-label="Close"
            style={{
              background: "transparent",
              border: "none",
              cursor: "pointer",
              color: "var(--text-muted)",
              padding: 4,
              borderRadius: 6,
              display: "flex",
              alignItems: "center",
            }}
          >
            <X size={18} />
          </button>
        </div>

        {/* Resend explainer */}
        <div
          style={{
            background: "var(--bg-secondary)",
            border: "1px solid var(--border)",
            borderRadius: 8,
            padding: "12px 14px",
            marginBottom: 20,
            fontSize: 13,
            color: "var(--text-secondary)",
            lineHeight: 1.6,
          }}
        >
          <p style={{ margin: "0 0 6px", fontWeight: 500, color: "var(--text-primary)" }}>
            What is a Resend API Key?
          </p>
          <p style={{ margin: "0 0 6px" }}>
            Resend is the email delivery service that powers your Specialist&apos;s
            email replies. You&apos;ll need a free Resend account and an API key to
            connect it here.
          </p>
          <p style={{ margin: 0 }}>
            <a
              href="https://resend.com/api-keys"
              target="_blank"
              rel="noopener noreferrer"
              style={{ color: "var(--primary)", textDecoration: "none", fontWeight: 500 }}
            >
              Create or find your API key on the Resend dashboard →
            </a>
          </p>
        </div>

        <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          {/* Resend API Key */}
          <div>
            <label
              htmlFor="email-resend-api-key"
              style={{ display: "block", fontSize: 13, fontWeight: 500, color: "var(--text-primary)", marginBottom: 6 }}
            >
              Resend API Key <span style={{ color: "var(--danger)" }}>*</span>
            </label>
            <input
              id="email-resend-api-key"
              type="password"
              value={resendApiKey}
              onChange={(e) => setResendApiKey(e.target.value)}
              placeholder="re_xxxxx..."
              autoComplete="off"
              style={{
                width: "100%",
                padding: "8px 12px",
                border: "1px solid var(--border)",
                borderRadius: 6,
                background: "var(--bg-surface)",
                color: "var(--text-primary)",
                fontSize: 13,
                fontFamily: "var(--font-mono)",
                boxSizing: "border-box",
              }}
            />
          </div>

          {/* From email */}
          <div>
            <label
              htmlFor="email-from-email"
              style={{ display: "block", fontSize: 13, fontWeight: 500, color: "var(--text-primary)", marginBottom: 6 }}
            >
              Specialist From Email <span style={{ color: "var(--danger)" }}>*</span>
            </label>
            <p style={{ margin: "0 0 6px", fontSize: 12, color: "var(--text-secondary)" }}>
              The email address replies will be sent from (e.g. your Specialist&apos;s address).
            </p>
            <input
              id="email-from-email"
              type="email"
              value={fromEmail}
              onChange={(e) => setFromEmail(e.target.value)}
              placeholder="specialist@yourdomain.com"
              autoComplete="off"
              style={{
                width: "100%",
                padding: "8px 12px",
                border: "1px solid var(--border)",
                borderRadius: 6,
                background: "var(--bg-surface)",
                color: "var(--text-primary)",
                fontSize: 13,
                fontFamily: "var(--font-mono)",
                boxSizing: "border-box",
              }}
            />
          </div>

          {(formError ?? error) && (
            <div
              style={{
                padding: "8px 12px",
                borderRadius: 6,
                fontSize: 13,
                color: "var(--danger)",
                background: "var(--danger-bg, #fef2f2)",
                border: "1px solid rgba(239,68,68,0.2)",
              }}
            >
              {formError ?? error}
            </div>
          )}

          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 4 }}>
            <button
              type="button"
              onClick={onClose}
              disabled={saving}
              style={{
                padding: "8px 16px",
                background: "transparent",
                border: "1px solid var(--border)",
                borderRadius: 6,
                fontSize: 13,
                fontWeight: 500,
                color: "var(--text-primary)",
                cursor: saving ? "not-allowed" : "pointer",
                opacity: saving ? 0.5 : 1,
                fontFamily: "inherit",
              }}
            >
              Cancel
            </button>
            <button
              type="submit"
              disabled={saving}
              style={{
                padding: "8px 16px",
                background: "var(--primary)",
                border: "1px solid var(--primary)",
                borderRadius: 6,
                fontSize: 13,
                fontWeight: 500,
                color: "var(--primary-foreground)",
                cursor: saving ? "not-allowed" : "pointer",
                opacity: saving ? 0.6 : 1,
                fontFamily: "inherit",
              }}
            >
              {saving ? "Saving…" : existingIntegration ? "Update" : "Connect"}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

function ChannelsSkeleton() {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
        gap: 12,
      }}
    >
      {[0, 1, 2, 3, 4, 5].map((i) => (
        <div
          key={i}
          style={{
            background: "var(--bg-secondary)",
            border: "1px solid var(--border)",
            borderRadius: 12,
            padding: "16px",
            height: 100,
            display: "flex",
            gap: 10,
            alignItems: "flex-start",
          }}
        >
          <div
            style={{
              width: 32,
              height: 32,
              borderRadius: 8,
              background: "var(--bg-canvas)",
              flexShrink: 0,
            }}
          />
          <div style={{ flex: 1 }}>
            <div
              style={{
                width: 100,
                height: 12,
                borderRadius: 4,
                background: "var(--bg-canvas)",
                marginBottom: 8,
              }}
            />
            <div
              style={{
                width: 60,
                height: 10,
                borderRadius: 4,
                background: "var(--bg-canvas)",
              }}
            />
          </div>
        </div>
      ))}
    </div>
  );
}
