/**
 * Email campaign table (BUILD-2285 / BUILD-2283): one column factory used by both the
 * Email Marketing **Overview** tab ("Recent campaigns") and the **Campaigns** tab (full
 * list, with toolbar) so the two stay column-consistent — GHL-style: Campaign · Status ·
 * Recipients · Delivered · Opened · Clicked · Bounced · Schedule, plus an optional
 * row-actions ("…") column when action handlers are supplied.
 *
 * Rates are numeric percentages; pass `formatRate` to change the default one-decimal
 * "42.1%" rendering.
 */
import { type ReactElement, useMemo, useState } from "react";
import { MoreHorizontal, Plus, Search, Send } from "lucide-react";
import type { ColumnDef, PaginationState } from "@tanstack/react-table";
import { Button } from "./button";
import { DataTable } from "./data-table";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "./dropdown-menu";
import {
  Empty,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "./empty";
import { InfoTooltip } from "./info-tooltip";
import { Input } from "./input";
import { ToggleGroup, ToggleGroupItem } from "./toggle-group";
import { EmailStatusBadge, type EmailStatus } from "./email-marketing-primitives";

/** One campaign row — shared by the Overview recent list and the Campaigns tab. */
export interface EmailCampaignRow {
  id: string;
  name: string;
  status: EmailStatus;
  templateName?: string;
  /** Staff who created it — its audience is whatever contacts that person can see. */
  createdByName?: string;
  scheduleSummary?: string;
  /** Undefined while the count is still being fetched — the cell shows a pending marker. */
  recipientCount?: number;
  /** Engagement + delivery rates as percentages (e.g. 42.1) — present once sending started. */
  openRate?: number;
  clickRate?: number;
  /** SES-tracked delivery / bounce rates (once sending has started). */
  deliveredRate?: number;
  bounceRate?: number;
  /** Why the campaign errored — shown beside the badge; only set for `status: "error"`. */
  errorReason?: string;
  /** Recurring campaigns only — surfaced under Schedule. */
  isRecurring?: boolean;
  lastSentLabel?: string;
  nextSendLabel?: string;
}

export type CampaignRateFormatter = (value: number) => string;

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

export interface CampaignColumnHandlers {
  /** Row title click → open the campaign (status-aware in the parent). */
  onOpenCampaign?: (id: string) => void;
  /** Row "…" actions — supplying any of these adds the actions column. */
  onEditCampaign?: (id: string) => void;
  /** Pause a running campaign / resume a paused one (label flips by status). */
  onTogglePause?: (id: string) => void;
  onDeleteCampaign?: (id: string) => void;
  /** Renders every rate column; defaults to one-decimal percent. */
  formatRate?: CampaignRateFormatter;
}

function CampaignActionsCell({
  row,
  onEditCampaign,
  onTogglePause,
  onDeleteCampaign,
}: { row: EmailCampaignRow } & CampaignColumnHandlers): ReactElement {
  // An errored campaign's setup is broken beyond editing — deleting it is the only move left.
  const isInert = row.status === "error";
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <Button
            aria-label={`Actions for ${row.name}`}
            className="size-8"
            size="icon"
            variant="ghost"
          >
            <MoreHorizontal className="size-4" />
          </Button>
        }
      />
      <DropdownMenuContent>
        {onEditCampaign && !isInert ? <DropdownMenuItem onClick={() => { onEditCampaign(row.id); }}>
            Edit
          </DropdownMenuItem> : null}
        {/* Pause/Resume only applies to campaigns that are still in flight —
            a scheduled send, a recurring series, or an already-paused one.
            An immediate one-off send can't be paused. */}
        {onTogglePause &&
          !isInert &&
          (row.status === "scheduled" ||
            row.status === "paused" ||
            (row.status === "sending" && row.isRecurring)) ? <DropdownMenuItem onClick={() => { onTogglePause(row.id); }}>
              {row.status === "paused" ? "Resume" : "Pause"}
            </DropdownMenuItem> : null}
        {onDeleteCampaign ? <>
            <DropdownMenuSeparator />
            <DropdownMenuItem
              onClick={() => { onDeleteCampaign(row.id); }}
              variant="destructive"
            >
              Delete
            </DropdownMenuItem>
          </> : null}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

/**
 * Build the campaign table columns. Always returns the base GHL columns; if any
 * row-action handler is passed, appends a trailing "…" actions column.
 */
export function buildCampaignColumns(
  handlers: CampaignColumnHandlers = {},
): ColumnDef<EmailCampaignRow>[] {
  const {
    onOpenCampaign,
    onEditCampaign,
    onTogglePause,
    onDeleteCampaign,
    formatRate = defaultFormatRate,
  } = handlers;

  const rate = (value: number | undefined): string =>
    value === undefined ? "—" : formatRate(value);

  const columns: ColumnDef<EmailCampaignRow>[] = [
    {
      accessorKey: "name",
      header: () => (
        <span className="inline-flex items-center gap-1">
          Campaign
          <InfoTooltip text="A campaign sends to the contacts its creator can see, so who created it decides who it reaches. Two people running the same campaign over the same list can reach different clients." />
        </span>
      ),
      cell: ({ row }) => (
        <button
          className="min-w-0 max-w-[260px] text-left"
          onClick={() => onOpenCampaign?.(row.original.id)}
          type="button"
        >
          <p className="truncate text-label-small text-foreground hover:text-primary">
            {row.original.name}
          </p>
          {row.original.templateName || row.original.createdByName ? (
            <p className="truncate text-caption text-muted-foreground">
              {[row.original.templateName, row.original.createdByName]
                .filter(Boolean)
                .join(" · ")}
            </p>
          ) : null}
        </button>
      ),
    },
    {
      accessorKey: "status",
      header: "Status",
      cell: ({ row }) => (
        <span className="inline-flex items-center gap-1">
          <EmailStatusBadge status={row.original.status} />
          {row.original.status === "error" && row.original.errorReason ? (
            // An errored campaign cannot be opened, so the reason has to be reachable here.
            <InfoTooltip text={row.original.errorReason} />
          ) : null}
        </span>
      ),
    },
    {
      accessorKey: "recipientCount",
      header: () => (
        <span className="inline-flex items-center gap-1">
          Recipients
          <InfoTooltip text="Who the most recent send went to. For a campaign that has not sent yet, who it would reach right now." />
        </span>
      ),
      cell: ({ row }) => {
        const count = row.original.recipientCount;
        return (
          <span className="text-body-small tabular-nums text-muted-foreground">
            {count === undefined ? "…" : count.toLocaleString()}
          </span>
        );
      },
    },
    {
      id: "deliveredRate",
      header: "Delivered",
      cell: ({ row }) => (
        <span className="text-body-small tabular-nums text-muted-foreground">
          {rate(row.original.deliveredRate)}
        </span>
      ),
    },
    {
      id: "openRate",
      header: "Opened",
      cell: ({ row }) => (
        <span className="text-body-small tabular-nums text-muted-foreground">
          {rate(row.original.openRate)}
        </span>
      ),
    },
    {
      id: "clickRate",
      header: "Clicked",
      cell: ({ row }) => (
        <span className="text-body-small tabular-nums text-muted-foreground">
          {rate(row.original.clickRate)}
        </span>
      ),
    },
    {
      id: "bounceRate",
      header: "Bounced",
      cell: ({ row }) => {
        const v = row.original.bounceRate;
        return (
          <span
            className={`text-body-small tabular-nums ${
              v !== undefined ? "text-destructive-text" : "text-muted-foreground"
            }`}
          >
            {rate(v)}
          </span>
        );
      },
    },
    {
      accessorKey: "scheduleSummary",
      header: "Schedule",
      cell: ({ row }) => {
        const { scheduleSummary, isRecurring, lastSentLabel, nextSendLabel } =
          row.original;
        return (
          <div className="min-w-0">
            <p className="truncate text-body-small text-muted-foreground">
              {scheduleSummary ?? "—"}
            </p>
            {isRecurring && (lastSentLabel || nextSendLabel) ? <p className="truncate text-caption text-muted-foreground/80">
                Last sent {lastSentLabel ?? "—"} · Next {nextSendLabel ?? "—"}
              </p> : null}
          </div>
        );
      },
    },
  ];

  const hasActions =
    onEditCampaign || onTogglePause || onDeleteCampaign;

  if (hasActions) {
    columns.push({
      id: "actions",
      header: "",
      cell: ({ row }) => (
        <div className="flex justify-end">
          <CampaignActionsCell
            onDeleteCampaign={onDeleteCampaign}
            onEditCampaign={onEditCampaign}
            onTogglePause={onTogglePause}
            row={row.original}
          />
        </div>
      ),
    });
  }

  return columns;
}

/** Statuses a campaign can filter by (drip-only `active`/`ended` excluded). */
export type CampaignStatusFilter = Extract<
  EmailStatus,
  "scheduled" | "sending" | "sent" | "paused" | "error"
>;

const STATUS_FILTERS: (CampaignStatusFilter | "all")[] = [
  "all",
  "scheduled",
  "sending",
  "sent",
  "paused",
  "error",
];

/**
 * Server-driven campaign table. Supply it and the component renders exactly the rows it is
 * given, reporting status/search/page intent through callbacks; omit it and the table filters
 * and pages the rows it holds, which is only correct when those rows are every campaign.
 */
export interface EmailCampaignTableQuery {
  status: CampaignStatusFilter | "all";
  onStatusChange: (status: CampaignStatusFilter | "all") => void;
  search: string;
  onSearchChange: (search: string) => void;
  pagination: PaginationState;
  onPaginationChange: (pagination: PaginationState) => void;
  /** Campaigns matching the active filters, across every page. */
  rowCount: number;
  /** Per-status totals for the whole set; omit to count the loaded rows. */
  statusCounts?: Partial<Record<EmailStatus, number>>;
  isLoading?: boolean;
}

export interface EmailCampaignTableProps extends CampaignColumnHandlers {
  campaigns: EmailCampaignRow[];
  onNewCampaign?: () => void;
  className?: string;
  /** Omit for the client-side table; supply for server-driven filtering/search/paging. */
  query?: EmailCampaignTableQuery;
}

export function EmailCampaignTable({
  campaigns,
  onNewCampaign,
  className,
  query: serverQuery,
  ...handlers
}: EmailCampaignTableProps): ReactElement {
  const [localQuery, setLocalQuery] = useState("");
  const [localStatus, setLocalStatus] = useState<CampaignStatusFilter | "all">(
    "all",
  );
  // Server-driven mode owns search and status so the request and the controls cannot disagree.
  const query = serverQuery?.search ?? localQuery;
  const setQuery = serverQuery?.onSearchChange ?? setLocalQuery;
  const status = serverQuery?.status ?? localStatus;
  const setStatus = serverQuery?.onStatusChange ?? setLocalStatus;

  const counts = useMemo(() => {
    if (serverQuery?.statusCounts) return serverQuery.statusCounts;
    const c: Partial<Record<EmailStatus, number>> = {};
    for (const campaign of campaigns) {
      c[campaign.status] = (c[campaign.status] ?? 0) + 1;
    }
    return c;
  }, [campaigns, serverQuery?.statusCounts]);

  // Summed rather than `rowCount`: that counts the rows under every active filter, the status
  // chip included, so the All chip would shrink to whichever status was selected.
  const allCount = useMemo(
    () =>
      serverQuery?.statusCounts
        ? Object.values(serverQuery.statusCounts).reduce(
            (sum, n) => sum + (n ?? 0),
            0,
          )
        : (serverQuery?.rowCount ?? campaigns.length),
    [campaigns.length, serverQuery?.rowCount, serverQuery?.statusCounts],
  );

  // Rows arrive pre-filtered when the server owns the query; filtering again would drop rows
  // the backend deliberately included.
  const filtered = useMemo(() => {
    if (serverQuery) return campaigns;
    const q = query.trim().toLowerCase();
    return campaigns.filter((c) => {
      const matchesStatus = status === "all" || c.status === status;
      const matchesQuery =
        !q ||
        c.name.toLowerCase().includes(q) ||
        (c.templateName?.toLowerCase().includes(q) ?? false);
      return matchesStatus && matchesQuery;
    });
  }, [campaigns, query, status, serverQuery]);

  const columns = useMemo(() => buildCampaignColumns(handlers), [handlers]);

  return (
    <div className={`flex flex-col gap-4${className ? ` ${className}` : ""}`}>
      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <div className="flex flex-1 flex-wrap items-center gap-2">
          <div className="relative">
            <Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
            <Input
              className="h-8 w-56 pl-8"
              onChange={(e) => { setQuery(e.target.value); }}
              placeholder="Search campaigns…"
              value={query}
            />
          </div>
          <ToggleGroup
            className="flex-wrap justify-start"
            onValueChange={(v) => {
              const next = Array.isArray(v) ? v[0] : v;
              if (next) setStatus(next as CampaignStatusFilter | "all");
            }}
            type="single"
            value={[status]}
            variant="outline"
          >
            {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>
        </div>

        {onNewCampaign ? <Button className="gap-1.5" onClick={onNewCampaign}>
            <Plus className="size-4" />
            New Campaign
          </Button> : null}
      </div>

      {campaigns.length === 0 ? (
        <Empty>
          <EmptyHeader>
            <EmptyMedia variant="icon">
              <Send />
            </EmptyMedia>
            <EmptyTitle>No campaigns yet</EmptyTitle>
            <EmptyDescription>
              Send your first templated campaign to a group of clients.
            </EmptyDescription>
          </EmptyHeader>
        </Empty>
      ) : (
        <DataTable
          columns={columns}
          data={filtered}
          emptyText="No campaigns match your filters"
          {...(serverQuery
            ? {
                manualPagination: true as const,
                manualFiltering: true as const,
                rowCount: serverQuery.rowCount,
                pagination: serverQuery.pagination,
                onPaginationChange: serverQuery.onPaginationChange,
                loading: serverQuery.isLoading,
              }
            : {})}
        />
      )}
    </div>
  );
}

export default EmailCampaignTable;
