"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
  isAuthenticated,
  getIntegrations,
  getSlackInstallStatus,
  startSlackInstall,
  Integration,
} from "@/lib/api";
import { useAuth } from "@/hooks/useAuth";
import ChannelsTab, {
  ChannelsSkeleton,
} from "@/components/client/settings/ChannelsTab";

// Re-exported for the co-located unit test (coming-soon.test.ts). The gating
// set now lives with the ChannelsTab component it drives.
export { COMING_SOON } from "@/components/client/settings/ChannelsTab";

/**
 * Channels settings page. Owns auth-guarding and fetching the org's channel
 * integrations; the channel grid, agent-tool card, preferred-channel picker,
 * and email-routing UI live in `ChannelsTab` (extracted per Epic #5134).
 */
export default function ChannelsSettingsPage() {
  const router = useRouter();
  const { orgId, ready: authReady } = useAuth();
  const [channels, setChannels] = useState<Integration[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  // Monotonic fetch generation. Every fetch and every optimistic mutation bumps
  // it; a fetch only commits its result if it is still the newest. This is what
  // stops an in-flight refresh (e.g. one kicked off before a disconnect) from
  // resolving afterward and clobbering the newer optimistic state — the single
  // source of truth is this `channels` state, so there is no local overlay to
  // reconcile and a reconnected integration (even with a reused id) shows as
  // soon as an authoritative fetch returns it.
  const genRef = useRef(0);
  // #4203: holds the in-flight Slack warm-up promise so ChannelsTab's "Set
  // up" click can await it before hitting /oauth/start for real. This has to
  // live here, not in ChannelsTab, and fire the moment `orgId` resolves —
  // not gated on `loading` — because ChannelsTab only mounts once the
  // skeleton clears, i.e. the same moment the "Set up" button becomes
  // clickable. Starting the warm-up on ChannelsTab's own mount (as it used
  // to) gave it no head start on a cold container, so the first click raced
  // a cold /oauth/start and silently timed out (#5263 regression).
  const slackWarmupRef = useRef<Promise<void> | null>(null);

  // Fetch the authoritative integration list for this org, discarding the
  // result if a newer fetch or optimistic update superseded it mid-flight.
  const loadChannels = useCallback(async () => {
    if (!orgId) return;
    const gen = ++genRef.current;
    try {
      const list = await getIntegrations(orgId);
      if (gen === genRef.current) setChannels(list);
    } catch (err) {
      if (gen === genRef.current) {
        setError(err instanceof Error ? err.message : "Failed to load channels");
      }
    } finally {
      if (gen === genRef.current) setLoading(false);
    }
  }, [orgId]);

  // Passed to ChannelsTab. Called with an updater to apply a completed local
  // mutation (disconnect / email save) optimistically — this also invalidates
  // any in-flight fetch. Called with no argument to trigger a fresh re-fetch
  // (used by the GitHub card, whose connect/disconnect happens out-of-band).
  const onUpdate = useCallback(
    (optimistic?: (prev: Integration[]) => Integration[]) => {
      if (optimistic) {
        genRef.current++; // supersede any in-flight fetch
        setChannels((prev) => optimistic(prev));
        return;
      }
      void loadChannels();
    },
    [loadChannels],
  );

  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.
    void loadChannels();
  }, [router, authReady, loadChannels]);

  // #2299 / #4203: warm up the Slack OAuth handlers as soon as we know the
  // org, in parallel with `loadChannels` above — not after it. We hit both
  // /status (DI tree + DB connection) and /oauth/start itself (the specific
  // code path, JIT-warm) so the first real click lands on a warm path.
  // /oauth/start is pure computation, so calling it here is side-effect-free:
  // the returned URL is discarded and a fresh one is generated on the real
  // click. Errors are swallowed — this is best-effort only.
  useEffect(() => {
    if (!orgId) return;
    slackWarmupRef.current = Promise.all([
      getSlackInstallStatus(orgId),
      startSlackInstall(orgId),
    ]).then(() => {}).catch(() => {});
  }, [orgId]);

  // Auth resolved but no org on the token — can't fetch anything. Derived (not
  // an effect) so it never sets state during render/commit.
  const orgUnresolved = authReady && !orgId;
  const displayError =
    error ??
    (orgUnresolved
      ? "Could not resolve your organization — please sign in again."
      : null);

  // #2242: no more role-based redirect — members render the page in read-only
  // mode inside ChannelsTab. Skeleton only waits for auth + first data fetch;
  // if the org can't be resolved there is nothing to load, so drop the skeleton.
  const showSkeleton = !authReady || (loading && !orgUnresolved);

  return (
    <div style={{ maxWidth: "100%" }}>

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

      {showSkeleton ? (
        <ChannelsSkeleton />
      ) : orgId ? (
        <ChannelsTab
          orgId={orgId}
          channels={channels}
          onUpdate={onUpdate}
          slackWarmupRef={slackWarmupRef}
        />
      ) : null}
    </div>
  );
}
