/**
 * Campaign detail — a right-side Sheet that adapts to campaign status
 * (BUILD-2285). Modelled on GoHighLevel's campaign Summary page.
 *
 *  - sent / sending / paused → **Statistics**: meta + KPI tiles + engagement
 *    chart + delivery health, and a Recipients tab (recipient-level table).
 *    Header actions vary by status (Pause / Resume / Duplicate, Export CSV).
 *  - scheduled → **Schedule info**: nothing has sent yet, so no stats — shows
 *    the schedule, an email preview, and Edit / Cancel / Send-now actions.
 *
 * Rates are numeric percentages; pass `formatRate` to change the default
 * one-decimal "42.1%" rendering.
 */
import { type ReactElement, useMemo, useState } from "react";
import {
  CalendarClock,
  Download,
  Pause,
  Play,
  Send,
  TriangleAlert,
} from "lucide-react";
import type {
  ColumnDef,
  ColumnFiltersState,
  PaginationState,
} from "@tanstack/react-table";
import { Badge } from "./badge";
import { Button } from "./button";
import { DataTable } from "./data-table";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "./sheet";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
import { ToggleGroup, ToggleGroupItem } from "./toggle-group";
import {
  EmailEngagementChart,
  type EmailEngagementPoint,
} from "./email-engagement-chart";
import { EmailStatusBadge, type EmailStatus } from "./email-marketing-primitives";
import {
  EmailStatTile,
  EmailDeliveryHealth,
  type EmailDeliverySegment,
  type EmailOverviewStat,
} from "./email-stat-widgets";

export type CampaignRecipientStatus =
  | "queued"
  | "delivered"
  | "opened"
  | "clicked"
  | "bounced"
  | "complained"
  | "unsubscribed"
  | "failed";

export interface CampaignRecipientRow {
  id: string;
  name: string;
  email: string;
  status: CampaignRecipientStatus;
  timestamp: string;
}

export interface CampaignDetail {
  id: string;
  name: string;
  status: EmailStatus;
  templateName?: string;
  scheduleSummary?: string;
  /** Undefined while the count is still being fetched. */
  recipientCount?: number;
  /** Engagement + delivery rates as percentages (e.g. 42.1). */
  openRate?: number;
  clickRate?: number;
  /** SES-tracked delivery / bounce rates. */
  deliveredRate?: number;
  bounceRate?: number;
  complaintRate?: number;
  unsubscribeRate?: number;
  /** The template's body as it stands now; rendered sandboxed in the preview. */
  htmlContent?: string;
  /** Why the campaign errored; present only for `status: "error"`. */
  errorReason?: string;
  /** Recurring series — gates whether a "sending" campaign can be paused. */
  isRecurring?: boolean;
  /**
   * Whether the campaign has ever completed a run. A recurring campaign sits at `scheduled`
   * between runs, so status alone cannot tell "waiting for its first send" from "waiting for
   * its next one" — and the second has real statistics to show.
   */
  hasSent?: boolean;
}

/**
 * Server-side recipient totals. Supplied by a backend that reduces the roster itself, so the
 * chips and the "all" total describe the whole campaign rather than whatever page is loaded.
 */
export interface CampaignRecipientCounts {
  /** Rows matching EVERY active filter, the status chip included — this is what paging counts. */
  total: number;
  /** Per-status totals for the whole roster, narrowed by search but never by the chip. */
  byStatus: Partial<Record<CampaignRecipientStatus, number>>;
}

/**
 * Server-driven recipient table. Supply it and the roster is filtered, searched and paged by the
 * backend: the component renders exactly the rows it is given and reports intent through the
 * callbacks. Omit it and the table filters and pages the rows it already holds, which is only
 * correct when those rows are the entire roster.
 */
export interface CampaignRecipientQuery {
  status: CampaignRecipientStatus | "all";
  onStatusChange: (status: CampaignRecipientStatus | "all") => void;
  search: string;
  onSearchChange: (search: string) => void;
  pagination: PaginationState;
  onPaginationChange: (pagination: PaginationState) => void;
  /** Rows matching the active filters, across every page. */
  rowCount: number;
  isLoading?: boolean;
  /**
   * The campaign has not sent yet, so these rows are who it WOULD reach, resolved live rather
   * than read from a roster. Status filtering is meaningless (everyone is queued) so the chips
   * are hidden; search still applies.
   */
  isAudiencePreview?: boolean;
}

export interface CampaignDetailSheetProps {
  /** The campaign to show; `null` closes the sheet. */
  campaign: CampaignDetail | null;
  onOpenChange: (open: boolean) => void;
  recipients: CampaignRecipientRow[];
  /** Omit for a single-run campaign; the label then shows one number. */
  /** Omit to count the loaded rows instead — correct only when they are the whole roster. */
  recipientCounts?: CampaignRecipientCounts;
  /** Omit for the client-side table; supply for server-driven filtering/search/paging. */
  recipientQuery?: CampaignRecipientQuery;
  engagement: EmailEngagementPoint[];
  deliveryBreakdown: EmailDeliverySegment[];
  /** Edit a scheduled campaign (opens the composer). */
  onEdit?: (id: string) => void;
  /** Pause a sending recurring campaign / resume a paused one. */
  onTogglePause?: (id: string) => void;
  onExport?: (id: string) => void;
  /** Cancel a scheduled campaign before it sends. */
  onCancelSchedule?: (id: string) => void;
  /** Dispatch a scheduled campaign immediately. */
  onSendNow?: (id: string) => void;
  /** Renders every rate value; defaults to one-decimal percent. */
  formatRate?: (value: number) => string;
}

const defaultFormatRate = (value: number): string => `${value.toFixed(1)}%`;

const RECIPIENT_BADGE: Record<
  CampaignRecipientStatus,
  "default" | "secondary" | "destructive" | "outline"
> = {
  queued: "outline",
  clicked: "default",
  opened: "secondary",
  delivered: "outline",
  bounced: "destructive",
  complained: "destructive",
  unsubscribed: "outline",
  failed: "destructive",
};

const RECIPIENT_STATUS_FILTERS: (CampaignRecipientStatus | "all")[] = [
  "all",
  "queued",
  "delivered",
  "opened",
  "clicked",
  "bounced",
  "complained",
  "unsubscribed",
  "failed",
];

/**
 * Summary KPI tiles for a sent campaign — mirrors the metrics AWS SES reports
 * (Deliveries, Opens, Clicks, Bounces, Complaints) plus the app-level
 * Unsubscribes, so the numbers map 1:1 to what the sending stack can track.
 */
function buildTiles(
  campaign: CampaignDetail,
  formatRate: (value: number) => string,
): EmailOverviewStat[] {
  const rate = (value: number | undefined): string =>
    value === undefined ? "—" : formatRate(value);
  return [
    { label: "Recipients", value: formatRecipientCount(campaign.recipientCount) },
    { label: "Delivered", value: rate(campaign.deliveredRate) },
    { label: "Opened", value: rate(campaign.openRate) },
    { label: "Clicked", value: rate(campaign.clickRate) },
    { label: "Bounced", value: rate(campaign.bounceRate) },
    { label: "Complaints", value: rate(campaign.complaintRate) },
    { label: "Unsubscribed", value: rate(campaign.unsubscribeRate) },
  ];
}

/** Shared meta list — label/value rows, used by both detail views so their
 * layout stays identical. */
function MetaList({
  rows,
}: {
  rows: { label: string; value: string }[];
}): ReactElement {
  return (
    <dl className="flex flex-col text-body-small">
      {rows.map((r) => (
        <div
          className="flex justify-between gap-4 border-b border-border py-2"
          key={r.label}
        >
          <dt className="shrink-0 text-muted-foreground">{r.label}</dt>
          <dd className="truncate text-right text-foreground">{r.value}</dd>
        </div>
      ))}
    </dl>
  );
}

/** Both detail views show the same meta rows. */
function metaRows(
  campaign: CampaignDetail,
): { label: string; value: string }[] {
  return [
    { label: "Template", value: campaign.templateName ?? "—" },
    { label: "Recipients", value: formatRecipientCount(campaign.recipientCount) },
    { label: "Schedule", value: campaign.scheduleSummary ?? "—" },
  ];
}

const RECIPIENT_COLUMNS: ColumnDef<CampaignRecipientRow>[] = [
  {
    accessorKey: "name",
    header: "Recipient",
    cell: ({ row }) => (
      <div className="min-w-0">
        <p className="truncate text-label-small text-foreground">
          {row.original.name}
        </p>
        <p className="truncate text-caption text-muted-foreground">
          {row.original.email}
        </p>
      </div>
    ),
  },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ row }) => (
      <Badge variant={RECIPIENT_BADGE[row.original.status]}>
        {row.original.status}
      </Badge>
    ),
  },
  {
    accessorKey: "timestamp",
    header: "Last activity",
    cell: ({ row }) => (
      <span className="text-body-small text-muted-foreground">
        {row.original.timestamp}
      </span>
    ),
  },
];

// Scheduled campaigns haven't sent, so the audience shows who *will* receive
// it — name/email + a "Queued" status (no engagement events yet).
const AUDIENCE_COLUMNS: ColumnDef<CampaignRecipientRow>[] = [
  RECIPIENT_COLUMNS[0],
  {
    id: "queued",
    header: "Status",
    cell: () => <Badge variant="outline">Queued</Badge>,
  },
];

/**
 * What was (or will be) sent. Shown for every status: a campaign that has already gone out is
 * the one you most often need to read back, and a recurring series is about to send it again.
 */
function EmailPreview({ campaign }: { campaign: CampaignDetail }): ReactElement {
  return (
    <div className="flex flex-col gap-2">
      <span className="text-overline text-muted-foreground">Preview</span>
      {campaign.htmlContent ? (
        <iframe
          className="h-80 w-full border border-border bg-background"
          sandbox=""
          srcDoc={campaign.htmlContent}
          title={`Preview of ${campaign.name}`}
        />
      ) : (
        <div className="flex h-40 items-center justify-center border border-dashed border-border text-caption text-muted-foreground">
          No content snapshot for this campaign
        </div>
      )}
    </div>
  );
}

/** "…" while the count is still resolving, so the label never claims a campaign reaches nobody. */
function formatRecipientCount(count: number | undefined): string {
  return count === undefined ? "…" : count.toLocaleString();
}

function RecipientsTabLabel({
  campaign,
}: {
  campaign: CampaignDetail;
}): ReactElement {
  return <>Recipients ({formatRecipientCount(campaign.recipientCount)})</>;
}

// ─── Statistics view (sent / sending / paused) ──────────────────────────────

function StatisticsView({
  campaign,
  recipients,
  recipientCounts,
  recipientQuery,
  engagement,
  deliveryBreakdown,
  formatRate,
}: {
  campaign: CampaignDetail;
  recipients: CampaignRecipientRow[];
  recipientCounts?: CampaignRecipientCounts;
  recipientQuery?: CampaignRecipientQuery;
  engagement: EmailEngagementPoint[];
  deliveryBreakdown: EmailDeliverySegment[];
  formatRate: (value: number) => string;
}): ReactElement {
  const isErrored = campaign.status === "error";
  const tiles = useMemo(
    () => buildTiles(campaign, formatRate),
    [campaign, formatRate],
  );

  const [localStatusFilter, setLocalStatusFilter] = useState<
    CampaignRecipientStatus | "all"
  >("all");
  // Server-driven mode owns the filter so the request and the highlighted chip cannot disagree.
  const statusFilter = recipientQuery?.status ?? localStatusFilter;
  const setStatusFilter =
    recipientQuery?.onStatusChange ?? setLocalStatusFilter;

  // Prefer the server's totals: counting the loaded rows only matches the campaign when they
  // happen to be the entire roster, which stops being true as soon as the fetch is capped.
  const counts = useMemo(() => {
    if (recipientCounts) return recipientCounts.byStatus;
    const c: Partial<Record<CampaignRecipientStatus, number>> = {};
    for (const r of recipients) c[r.status] = (c[r.status] ?? 0) + 1;
    return c;
  }, [recipients, recipientCounts]);
  // Summed, not `total`: that is the count under every active filter, so picking "Complained"
  // made the All chip report the complaints. The per-status counts ignore the chip, so their
  // sum is the roster the chips are offering to filter.
  const allCount = recipientCounts
    ? Object.values(recipientCounts.byStatus).reduce(
        (sum, n) => sum + (n ?? 0),
        0,
      )
    : recipients.length;

  // Rows arrive pre-filtered when the server owns the query; filtering again would drop rows
  // the backend deliberately included.
  const filteredRecipients = recipientQuery
    ? recipients
    : statusFilter === "all"
      ? recipients
      : recipients.filter((r) => r.status === statusFilter);

  return (
    <Tabs
      className="flex min-h-0 flex-1 flex-col gap-0"
      defaultValue="overview"
    >
      <div className="border-b border-border px-6">
        <TabsList className="h-11 gap-4" variant="line">
          <TabsTrigger value="overview">Overview</TabsTrigger>
          {/* An errored campaign never ran, so it has no roster to page through. */}
          {isErrored ? null : (
            <TabsTrigger value="recipients">
              <RecipientsTabLabel campaign={campaign} />
            </TabsTrigger>
          )}
        </TabsList>
      </div>

      <TabsContent
        className="min-h-0 flex-1 overflow-y-auto p-6"
        value="overview"
      >
        <div className="flex flex-col gap-6">
          {isErrored ? (
            <div className="flex items-start gap-3 border border-destructive/40 bg-destructive/5 p-4">
              <TriangleAlert className="mt-0.5 size-5 shrink-0 text-destructive-text" />
              <div className="flex flex-col gap-0.5">
                <span className="text-label-small text-foreground">
                  This campaign couldn&apos;t be sent
                </span>
                <span className="text-caption text-muted-foreground">
                  {campaign.errorReason ??
                    "Its setup is no longer valid, so the send was stopped."}
                </span>
              </div>
            </div>
          ) : null}

          <MetaList rows={metaRows(campaign)} />

          <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
            {tiles.map((stat) => (
              <EmailStatTile key={stat.label} stat={stat} />
            ))}
          </div>

          <div className="flex flex-col gap-4">
            <EmailEngagementChart
              data={engagement}
              height={160}
              title="Engagement"
            />
            <EmailDeliveryHealth segments={deliveryBreakdown} />
          </div>

          <EmailPreview campaign={campaign} />
        </div>
      </TabsContent>

      <TabsContent
        className="min-h-0 flex-1 overflow-y-auto p-6"
        value="recipients"
      >
        <div className="flex flex-col gap-3">
          {recipientQuery?.isAudiencePreview ? (
            // Nothing has sent, so every row is queued and status chips would all read 0 but
            // one. Say who this reaches instead — search still narrows the list.
            <p className="text-body-small text-muted-foreground">
              {allCount.toLocaleString()} client{allCount === 1 ? "" : "s"} will
              receive this campaign when it sends.
            </p>
          ) : (
            <ToggleGroup
              className="flex-wrap justify-start"
              onValueChange={(v) => {
                const next = Array.isArray(v) ? v[0] : v;
                if (next)
                  setStatusFilter(next as CampaignRecipientStatus | "all");
              }}
              type="single"
              value={[statusFilter]}
              variant="outline"
            >
              {RECIPIENT_STATUS_FILTERS.map((s) => (
                <ToggleGroupItem className="gap-1 capitalize" key={s} value={s}>
                  {s === "all" ? "All" : s}
                  <span className="tabular-nums opacity-70">
                    {s === "all" ? allCount : (counts[s] ?? 0)}
                  </span>
                </ToggleGroupItem>
              ))}
            </ToggleGroup>
          )}
          <DataTable
            columns={RECIPIENT_COLUMNS}
            data={filteredRecipients}
            emptyText="No recipients match this filter"
            searchKey="name"
            searchPlaceholder="Search recipients…"
            {...(recipientQuery
              ? {
                  manualPagination: true as const,
                  manualFiltering: true as const,
                  rowCount: recipientQuery.rowCount,
                  pagination: recipientQuery.pagination,
                  onPaginationChange: recipientQuery.onPaginationChange,
                  columnFilters: recipientQuery.search
                    ? [{ id: "name", value: recipientQuery.search }]
                    : [],
                  onColumnFiltersChange: (filters: ColumnFiltersState) => {
                    const next = filters.find((f) => f.id === "name")?.value;
                    recipientQuery.onSearchChange(
                      typeof next === "string" ? next : "",
                    );
                  },
                  loading: recipientQuery.isLoading,
                }
              : {})}
          />
        </div>
      </TabsContent>
    </Tabs>
  );
}

// ─── Schedule view (scheduled) ──────────────────────────────────────────────

function ScheduleView({
  campaign,
  recipients,
  onEdit,
  onCancelSchedule,
  onSendNow,
}: {
  campaign: CampaignDetail;
  recipients: CampaignRecipientRow[];
  onEdit?: (id: string) => void;
  onCancelSchedule?: (id: string) => void;
  onSendNow?: (id: string) => void;
}): ReactElement {
  return (
    <div className="flex min-h-0 flex-1 flex-col">
      <Tabs
        className="flex min-h-0 flex-1 flex-col gap-0"
        defaultValue="overview"
      >
        <div className="border-b border-border px-6">
          <TabsList className="h-11 gap-4" variant="line">
            <TabsTrigger value="overview">Overview</TabsTrigger>
            <TabsTrigger value="recipients">
              <RecipientsTabLabel campaign={campaign} />
            </TabsTrigger>
          </TabsList>
        </div>

        <TabsContent
          className="min-h-0 flex-1 overflow-y-auto p-6"
          value="overview"
        >
          <div className="flex flex-col gap-6">
            <div className="flex items-start gap-3 border border-border bg-muted/40 p-4">
              <CalendarClock className="mt-0.5 size-5 shrink-0 text-muted-foreground" />
              <div className="flex flex-col gap-0.5">
                <span className="text-label-small text-foreground">
                  {campaign.scheduleSummary ?? "Scheduled"}
                </span>
                <span className="text-caption text-muted-foreground">
                  This campaign hasn&apos;t sent yet — delivery and engagement stats
                  will appear here once it goes out.
                </span>
              </div>
            </div>

            <MetaList rows={metaRows(campaign)} />

            <EmailPreview campaign={campaign} />
          </div>
        </TabsContent>

        <TabsContent
          className="min-h-0 flex-1 overflow-y-auto p-6"
          value="recipients"
        >
          <div className="flex flex-col gap-3">
            <p className="text-caption text-muted-foreground">
              {formatRecipientCount(campaign.recipientCount)} clients will
              receive this campaign when it sends.
            </p>
            <DataTable
              columns={AUDIENCE_COLUMNS}
              data={recipients}
              emptyText="No recipients"
              searchKey="name"
              searchPlaceholder="Search recipients…"
            />
          </div>
        </TabsContent>
      </Tabs>

      <div className="flex shrink-0 items-center justify-between gap-2 border-t border-border p-4">
        <Button
          className="text-destructive"
          onClick={() => onCancelSchedule?.(campaign.id)}
          size="sm"
          variant="ghost"
        >
          Cancel schedule
        </Button>
        <div className="flex gap-2">
          <Button
            className="gap-1.5"
            onClick={() => onSendNow?.(campaign.id)}
            size="sm"
            variant="outline"
          >
            <Send className="size-3.5" />
            Send now
          </Button>
          <Button onClick={() => onEdit?.(campaign.id)} size="sm">
            Edit
          </Button>
        </div>
      </div>
    </div>
  );
}

// ─── Root sheet ─────────────────────────────────────────────────────────────

function headerActions(
  campaign: CampaignDetail,
  handlers: Pick<
    CampaignDetailSheetProps,
    "onTogglePause" | "onExport"
  >,
): ReactElement {
  const { status, isRecurring } = campaign;
  const { onTogglePause, onExport } = handlers;
  const exportBtn = (
    <Button
      className="gap-1.5"
      onClick={() => onExport?.(campaign.id)}
      size="sm"
      variant="outline"
    >
      <Download className="size-3.5" />
      Export CSV
    </Button>
  );
  // A campaign can only be paused while it's still in flight: a recurring
  // series that's sending, or an already-paused one. An immediate one-off
  // send in progress can't be paused (nothing to hold back).
  if (status === "sending") {
    return (
      <>
        {isRecurring ? <Button
            className="gap-1.5"
            onClick={() => onTogglePause?.(campaign.id)}
            size="sm"
            variant="outline"
          >
            <Pause className="size-3.5" />
            Pause
          </Button> : null}
        {exportBtn}
      </>
    );
  }
  if (status === "paused") {
    return (
      <>
        <Button
          className="gap-1.5"
          onClick={() => onTogglePause?.(campaign.id)}
          size="sm"
          variant="outline"
        >
          <Play className="size-3.5" />
          Resume
        </Button>
        {exportBtn}
      </>
    );
  }
  if (status === "error") {
    return <></>;
  }
  return (
    <>
      {exportBtn}
    </>
  );
}

export function CampaignDetailSheet({
  campaign,
  onOpenChange,
  recipients,
  recipientCounts,
  recipientQuery,
  engagement,
  deliveryBreakdown,
  onEdit,
  onTogglePause,
  onExport,
  onCancelSchedule,
  onSendNow,
  formatRate = defaultFormatRate,
}: CampaignDetailSheetProps): ReactElement {
  const isAwaitingFirstSend = campaign?.status === "scheduled" && !campaign.hasSent;

  return (
    <Sheet onOpenChange={onOpenChange} open={Boolean(campaign)}>
      <SheetContent className="w-[min(760px,94vw)] max-w-none p-0" side="right">
        {campaign ? <div className="flex h-full flex-col">
            <SheetHeader className="shrink-0 gap-3 border-b border-border px-6 py-4">
              <div className="flex items-center gap-3">
                <SheetTitle className="min-w-0 truncate">
                  {campaign.name}
                </SheetTitle>
                <EmailStatusBadge status={campaign.status} />
              </div>
              {!isAwaitingFirstSend && (
                <div className="flex flex-wrap gap-2">
                  {headerActions(campaign, {
                    onTogglePause,
                    onExport,
                  })}
                </div>
              )}
            </SheetHeader>

            {isAwaitingFirstSend ? (
              <ScheduleView
                campaign={campaign}
                onCancelSchedule={onCancelSchedule}
                onEdit={onEdit}
                onSendNow={onSendNow}
                recipients={recipients}
              />
            ) : (
              <StatisticsView
                campaign={campaign}
                deliveryBreakdown={deliveryBreakdown}
                engagement={engagement}
                formatRate={formatRate}
                recipientCounts={recipientCounts}
                recipientQuery={recipientQuery}
                recipients={recipients}
              />
            )}
          </div> : null}
      </SheetContent>
    </Sheet>
  );
}

export default CampaignDetailSheet;
