import {
  Anchor,
  Badge,
  Card,
  Center,
  Checkbox,
  Group,
  Loader,
  Progress,
  SegmentedControl,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
  UnstyledButton,
} from "@mantine/core";
import { IconSearch, IconX } from "@tabler/icons-react";
import { useMemo, useState } from "react";
import {
  Bar,
  BarChart,
  CartesianGrid,
  Cell,
  LabelList,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import { usePromoTheme } from "@/routes/promo-code-campaigns/_components/appearance";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import { formatRevenue } from "@/lib/export/eventAnalyticsSheets";
import {
  eventTypeLabel,
  type EventTypeAnalyticsResponse,
} from "@/lib/features/event-analytics/types";

/** Chart bars beyond this are unreadable; the table below still lists them all. */
const MAX_CHART_BARS = 12;

/**
 * The booking funnel, in order.
 *
 * These are the collections the producer writes today. Charting them in this
 * order rather than by volume is what makes the drop-off between steps legible;
 * any collection not listed here is appended after them, so a new event type
 * still appears without a code change.
 */
const FUNNEL_ORDER = ["add_to_cart", "booking_hold_initiated", "purchase"];

/** What the chart plots. All three are counts, so one axis serves them all. */
type Metric = "events" | "uniqueMembers" | "points";

const METRIC_LABEL: Record<Metric, string> = {
  events: "Events",
  uniqueMembers: "Members",
  points: "Points",
};

const funnelRank = (eventType: string) => {
  const i = FUNNEL_ORDER.indexOf(eventType);
  return i === -1 ? FUNNEL_ORDER.length : i;
};

type Props = {
  data: EventTypeAnalyticsResponse | null;
  loading: boolean;
  activeEventTypes: string[];
  onToggleEventType: (eventType: string) => void;
  /** Hides the bar chart and donut, matching the module-wide charts toggle. */
  showCharts: boolean;
  /** Event types ticked for a per-event export sheet. */
  selected: string[];
  onSelectedChange: (next: string[]) => void;
  /**
   * Opens the event's own page.
   *
   * "How many members reached this step" is only half the question — the other
   * half is who they were, which is a table of member details rather than
   * anything that fits in this row.
   */
  onOpenEvent: (eventType: string) => void;
};

/**
 * Event-wise analytics.
 *
 * Laid out like the member-wise tab on purpose — search and selection above, one
 * full-width table below, charts optional — so moving between the two tabs does
 * not mean relearning where anything is.
 */
export default function EventAnalytics({
  data,
  loading,
  activeEventTypes,
  onToggleEventType,
  showCharts,
  selected,
  onSelectedChange,
  onOpenEvent,
}: Props) {
  const { series } = usePromoTheme();
  const [search, setSearch] = useState("");
  const [metric, setMetric] = useState<Metric>("events");

  // Funnel order, not volume order — a funnel sorted by size hides the drop-off
  // it exists to show.
  const ordered = useMemo(
    () =>
      [...(data?.rows ?? [])].sort(
        (a, b) => funnelRank(a.eventType) - funnelRank(b.eventType),
      ),
    [data?.rows],
  );

  /*
   * One colour per event type, assigned in funnel order.
   *
   * Fixed to the type rather than to the row's position, so a type keeps the
   * same colour in the bars, the donut and the table badge, and does not change
   * colour when a filter removes a step above it.
   */
  const colorByType = useMemo(() => {
    const palette = series(Math.max(1, ordered.length));
    return new Map(ordered.map((row, i) => [row.eventType, palette[i]]));
  }, [ordered, series]);

  /*
   * Searched locally: the event-type list is a handful of rows that already
   * arrived in full, so a round trip per keystroke would buy nothing.
   */
  const visible = useMemo(() => {
    const needle = search.trim().toLowerCase();
    if (!needle) return ordered;
    return ordered.filter((row) => row.eventType.toLowerCase().includes(needle));
  }, [ordered, search]);

  const selectedSet = new Set(selected);
  const visibleIds = visible.map((r) => r.eventType);
  const allVisibleSelected =
    visibleIds.length > 0 && visibleIds.every((id) => selectedSet.has(id));
  const someVisibleSelected = visibleIds.some((id) => selectedSet.has(id));

  const toggleRow = (eventType: string) =>
    onSelectedChange(
      selectedSet.has(eventType)
        ? selected.filter((id) => id !== eventType)
        : [...selected, eventType],
    );

  const toggleAll = () =>
    onSelectedChange(
      allVisibleSelected
        ? selected.filter((id) => !visibleIds.includes(id))
        : [...new Set([...selected, ...visibleIds])],
    );

  const toolbar = (
    <Group justify="space-between" align="center" wrap="wrap" gap="sm">
      <TextInput
        placeholder="Search by event type"
        leftSection={<IconSearch size={16} />}
        w={280}
        value={search}
        onChange={(e) => setSearch(e.currentTarget.value)}
      />
      {selected.length > 0 && (
        <Group gap="xs">
          <Text size="sm" c="dimmed">
            {selected.length} selected for export
          </Text>
          <UnstyledButton
            onClick={() => onSelectedChange([])}
            aria-label="Clear selection"
          >
            <Group gap={2}>
              <IconX size={13} />
              <Text size="sm">Clear</Text>
            </Group>
          </UnstyledButton>
        </Group>
      )}
    </Group>
  );

  if (loading && !data) {
    return (
      <Center py="xl">
        <Loader size="sm" />
      </Center>
    );
  }

  if (!data || ordered.length === 0) {
    return (
      <Stack gap="sm">
        {toolbar}
        <Card className={styles.sectionCard} p="xl">
          <Text c="dimmed" ta="center">
            No events for these filters.
          </Text>
        </Card>
      </Stack>
    );
  }

  const chartRows = ordered.slice(0, MAX_CHART_BARS).map((r) => ({
    eventType: eventTypeLabel(r.eventType),
    rawType: r.eventType,
    events: r.events,
    uniqueMembers: r.uniqueMembers,
    points: r.points,
  }));
  const hiddenFromChart = ordered.length - chartRows.length;

  /*
   * Step-to-step conversion, as a percentage of the step before it.
   *
   * Only meaningful for the known funnel steps and only when they are all in
   * view: with an event-type filter applied, the "previous step" may simply not
   * have been read, and a ratio against a step that was never loaded would be
   * nonsense rather than a low conversion rate.
   */
  const funnelSteps = ordered.filter(
    (r) => funnelRank(r.eventType) < FUNNEL_ORDER.length,
  );
  const showConversion = activeEventTypes.length === 0 && funnelSteps.length > 1;

  return (
    <Stack gap="md">
      {toolbar}

      {showCharts && (
        <Card className={styles.sectionCard} p="md">
          <Stack gap="sm">
            <Group justify="space-between" align="center" wrap="wrap">
              <Stack gap={0}>
                <Title order={5}>Events by type</Title>
                {hiddenFromChart > 0 ? (
                  // Say what the chart leaves out rather than let it imply the
                  // top 12 are everything.
                  <Text size="xs" c="dimmed">
                    Top {chartRows.length} of {ordered.length} shown — all types
                    are listed in the table.
                  </Text>
                ) : (
                  <Text size="xs" c="dimmed">
                    In funnel order, so the drop-off between steps is visible.
                  </Text>
                )}
              </Stack>
              {/*
               * One chart with a metric switch, rather than a bar chart beside
               * a donut showing the same numbers twice. The donut also could not
               * carry members: a member appears at several steps, so the steps
               * overlap and do not divide the population the way a ring implies.
               */}
              <SegmentedControl
                size="xs"
                value={metric}
                onChange={(value) => setMetric(value as Metric)}
                data={[
                  { value: "events", label: "Events" },
                  { value: "uniqueMembers", label: "Members" },
                  { value: "points", label: "Points" },
                ]}
              />
            </Group>

            <ResponsiveContainer
              width="100%"
              height={Math.max(180, chartRows.length * 52)}
            >
              <BarChart
                data={chartRows}
                layout="vertical"
                margin={{ left: 12, right: 32 }}
              >
                <CartesianGrid strokeDasharray="3 3" horizontal={false} opacity={0.4} />
                <XAxis type="number" allowDecimals={false} fontSize={12} />
                <YAxis
                  type="category"
                  dataKey="eventType"
                  width={160}
                  fontSize={12}
                  tickLine={false}
                />
                <Tooltip
                  cursor={{ fillOpacity: 0.06 }}
                  formatter={(value) => [
                    Number(value).toLocaleString("en-US"),
                    METRIC_LABEL[metric],
                  ]}
                />
                <Bar dataKey={metric} radius={[0, 6, 6, 0]} maxBarSize={30}>
                  {/* Per-cell fill so a bar matches its table badge. */}
                  {chartRows.map((row) => (
                    <Cell
                      key={row.rawType}
                      fill={colorByType.get(row.rawType)?.from ?? "#c2a154"}
                      cursor="pointer"
                      onClick={() => onOpenEvent(row.rawType)}
                    />
                  ))}
                  <LabelList
                    dataKey={metric}
                    position="right"
                    fontSize={12}
                    formatter={(v) => Number(v).toLocaleString("en-US")}
                  />
                </Bar>
              </BarChart>
            </ResponsiveContainer>

            {showConversion && (
              <Group gap="lg" wrap="wrap">
                {funnelSteps.slice(1).map((step, i) => {
                  const prev = funnelSteps[i];
                  const from = prev[metric];
                  const pct = from ? Number(((step[metric] / from) * 100).toFixed(1)) : 0;
                  return (
                    <Stack key={step.eventType} gap={0}>
                      <Text size="xs" c="dimmed">
                        {eventTypeLabel(prev.eventType)} →{" "}
                        {eventTypeLabel(step.eventType)}
                      </Text>
                      <Text size="sm" fw={700} className={styles.num}>
                        {pct}%
                      </Text>
                    </Stack>
                  );
                })}
              </Group>
            )}
          </Stack>
        </Card>
      )}

      <Card className={styles.tableCard} p={0}>
        <Table.ScrollContainer minWidth={720}>
          <Table highlightOnHover verticalSpacing="sm" horizontalSpacing="md">
            <Table.Thead className={styles.tableHead}>
              <Table.Tr>
                <Table.Th w={40}>
                  <Checkbox
                    size="xs"
                    aria-label="Select every event type shown"
                    checked={allVisibleSelected}
                    indeterminate={someVisibleSelected && !allVisibleSelected}
                    onChange={toggleAll}
                  />
                </Table.Th>
                <Table.Th>Event type</Table.Th>
                <Table.Th ta="right">Events</Table.Th>
                <Table.Th ta="right">Members</Table.Th>
                <Table.Th w={130} />
                <Table.Th w={150}>Share</Table.Th>
                <Table.Th ta="right">Points</Table.Th>
                <Table.Th ta="right">Revenue</Table.Th>
              </Table.Tr>
            </Table.Thead>
            <Table.Tbody>
              {visible.map((row) => {
                const active = activeEventTypes.includes(row.eventType);
                const color = colorByType.get(row.eventType);
                return (
                  <Table.Tr
                    key={row.eventType}
                    bg={
                      selectedSet.has(row.eventType)
                        ? "var(--mantine-primary-color-light)"
                        : undefined
                    }
                  >
                    <Table.Td>
                      <Checkbox
                        size="xs"
                        aria-label={`Select event type ${row.eventType}`}
                        checked={selectedSet.has(row.eventType)}
                        onChange={() => toggleRow(row.eventType)}
                      />
                    </Table.Td>
                    <Table.Td>
                      {/* The badge toggles this type into the page filter; the
                          link at the end of the row opens its own page. Two
                          different intentions, so two different controls. */}
                      <Badge
                        variant={active ? "filled" : "light"}
                        style={{
                          cursor: "pointer",
                          // Carries the series colour so the row, the bar and
                          // the slice are recognisably one thing.
                          backgroundColor: active
                            ? color?.from
                            : `color-mix(in srgb, ${color?.from ?? "#c2a154"} 14%, transparent)`,
                          color: active ? "#fff" : undefined,
                        }}
                        onClick={() => onToggleEventType(row.eventType)}
                      >
                        {eventTypeLabel(row.eventType)}
                      </Badge>
                    </Table.Td>
                    <Table.Td ta="right" className={styles.num}>
                      {row.events.toLocaleString("en-US")}
                    </Table.Td>
                    <Table.Td ta="right" className={styles.num}>
                      {row.uniqueMembers.toLocaleString("en-US")}
                    </Table.Td>
                    <Table.Td>
                      <Anchor
                        component="button"
                        type="button"
                        size="sm"
                        onClick={() => onOpenEvent(row.eventType)}
                      >
                        View members →
                      </Anchor>
                    </Table.Td>
                    <Table.Td>
                      <Stack gap={2}>
                        <Progress
                          value={row.sharePct}
                          size="sm"
                          color={color?.from ?? "teal"}
                        />
                        <Text size="xs" c="dimmed">
                          {row.sharePct}%
                        </Text>
                      </Stack>
                    </Table.Td>
                    <Table.Td ta="right" className={styles.num}>
                      {row.points.toLocaleString("en-US")}
                    </Table.Td>
                    <Table.Td ta="right">
                      <Text size="sm">{formatRevenue(row.revenueByCurrency)}</Text>
                    </Table.Td>
                  </Table.Tr>
                );
              })}
              {visible.length === 0 && (
                <Table.Tr>
                  <Table.Td colSpan={8}>
                    <Text c="dimmed" ta="center" py="md">
                      No event type matches "{search}".
                    </Text>
                  </Table.Td>
                </Table.Tr>
              )}
            </Table.Tbody>
          </Table>
        </Table.ScrollContainer>
      </Card>
    </Stack>
  );
}
