import {
  Card,
  Grid,
  Group,
  Progress,
  SegmentedControl,
  SimpleGrid,
  Skeleton,
  Stack,
  Text,
  Title,
} from "@mantine/core";
import { useMemo, useState } from "react";
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  Cell,
  LabelList,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import { usePromoTheme } from "@/routes/promo-code-campaigns/_components/appearance";
import {
  BreakdownPanel,
  type Breakdown,
} from "@/routes/promo-code-campaigns/_components/BreakdownPanel";
import styles from "@/routes/promo-code-campaigns/_components/promo.module.css";
import {
  eventTypeLabel,
  memberDisplayName,
  type DayActivity,
  type EventSummary,
  type EventTypeAnalyticsRow,
  type MemberAnalyticsRow,
  type PropertyActivity,
  type SourceActivity,
} from "@/lib/features/event-analytics/types";

/** Booking funnel order; unlisted types are appended after these. */
const FUNNEL_ORDER = ["add_to_cart", "booking_hold_initiated", "purchase"];

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

const fmtInt = (n: number) => n.toLocaleString("en-US");

type TimeMetric = "points" | "events" | "members";

const TIME_METRIC_LABEL: Record<TimeMetric, string> = {
  points: "Points",
  events: "Events",
  members: "Members",
};

function MiniStat({ label, value, hint }: { label: string; value: string; hint?: string }) {
  return (
    <Stack gap={0}>
      <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
        {label}
      </Text>
      <Text fz={22} fw={700} lh={1.2}>
        {value}
      </Text>
      {hint && (
        <Text size="xs" c="dimmed">
          {hint}
        </Text>
      )}
    </Stack>
  );
}

type Props = {
  summary: EventSummary | null;
  eventTypes: EventTypeAnalyticsRow[];
  byDay: DayActivity[];
  byProperty: PropertyActivity[];
  bySource: SourceActivity[];
  /** Highest-scoring members for the leaderboard, already sorted by core. */
  topMembers: MemberAnalyticsRow[];
  loading: boolean;
  /** Drills into one member's page. */
  onSelectMember: (memberId: string) => void;
};

/**
 * Member-wise dashboard.
 *
 * Four panels, each a shape that suits its own question: a compact strip for the
 * funnel, a line for activity over time, and bars for the two rankings.
 *
 * The funnel is counted in members, not events: one member adding to cart six
 * times is six events but one person, and a conversion rate built on events
 * would read far better than reality.
 */
export default function MemberDashboard({
  summary,
  eventTypes,
  byDay,
  byProperty,
  bySource,
  topMembers,
  loading,
  onSelectMember,
}: Props) {
  const { series } = usePromoTheme();
  const [timeMetric, setTimeMetric] = useState<TimeMetric>("points");

  const funnel = useMemo(
    () =>
      [...eventTypes].sort((a, b) => funnelRank(a.eventType) - funnelRank(b.eventType)),
    [eventTypes],
  );

  const colors = useMemo(
    () => series(Math.max(3, funnel.length)),
    [funnel.length, series],
  );

  const leaderboard = useMemo(
    () =>
      topMembers.slice(0, 8).map((m) => ({
        name: memberDisplayName(m),
        memberId: m.memberId,
        points: m.points,
        events: m.events,
      })),
    [topMembers],
  );

  const properties = useMemo(() => byProperty.slice(0, 8), [byProperty]);

  /*
   * Splits where a pie is actually correct.
   *
   * Every one of these assigns each event to exactly one slice, so the ring
   * genuinely divides the whole. The member funnel is deliberately absent: a
   * member counts at every step they reached, so those slices would overlap and
   * a ring would report more people than exist.
   */
  const breakdowns = useMemo<Breakdown[]>(() => {
    const palette = series(
      Math.max(3, funnel.length, byProperty.length, bySource.length),
    );
    const slice = (name: string, value: number, i: number) => ({
      name,
      id: name,
      value,
      color: palette[i % palette.length].from,
      colorTo: palette[i % palette.length].to,
    });

    const out: Breakdown[] = [];

    if (funnel.length) {
      out.push({
        key: "events-by-type",
        label: "Events",
        unit: "event",
        centerLabel: "Events",
        slices: funnel.map((row, i) => slice(eventTypeLabel(row.eventType), row.events, i)),
      });
      out.push({
        key: "points-by-type",
        label: "Points",
        unit: "point",
        centerLabel: "Points",
        slices: funnel.map((row, i) => slice(eventTypeLabel(row.eventType), row.points, i)),
      });
    }
    if (byProperty.length) {
      out.push({
        key: "by-property",
        label: "Property",
        unit: "event",
        centerLabel: "Events",
        slices: byProperty.map((row, i) => slice(row.property, row.events, i)),
      });
    }
    if (bySource.length) {
      out.push({
        key: "by-source",
        label: "Source",
        unit: "event",
        centerLabel: "Events",
        slices: bySource.map((row, i) => slice(row.source, row.events, i)),
      });
    }
    return out;
  }, [funnel, byProperty, bySource, series]);

  if (loading && !summary) {
    return (
      <Grid gutter="md">
        {[0, 1, 2, 3].map((i) => (
          <Grid.Col key={i} span={{ base: 12, lg: 6 }}>
            <Skeleton h={260} radius="md" />
          </Grid.Col>
        ))}
      </Grid>
    );
  }

  const members = summary?.uniqueMembers ?? 0;
  const totalEvents = summary?.totalEvents ?? 0;
  const totalPoints = summary?.totalPoints ?? 0;

  // The widest step is the funnel's entry point, so it is what later steps are
  // measured against — not the first listed step, which a filter may exclude.
  const entryStep = funnel.reduce<EventTypeAnalyticsRow | null>(
    (widest, row) => (!widest || row.uniqueMembers > widest.uniqueMembers ? row : widest),
    null,
  );
  const finalStep = funnel.length ? funnel[funnel.length - 1] : null;

  return (
    <Grid gutter="md">
      <Grid.Col span={{ base: 12, lg: 6 }}>
        <Card className={styles.sectionCard} p="md" h="100%">
          <Stack gap="sm">
            <Stack gap={0}>
              <Title order={5}>Member funnel</Title>
              <Text size="xs" c="dimmed">
                Distinct members who reached each step, not event counts.
              </Text>
            </Stack>

            <SimpleGrid cols={{ base: 2, sm: 3 }} spacing="md">
              <MiniStat label="Members" value={fmtInt(members)} />
              <MiniStat
                label="Events / member"
                value={members ? (totalEvents / members).toFixed(1) : "—"}
                hint={`${fmtInt(totalEvents)} events`}
              />
              <MiniStat
                label="Points / member"
                value={
                  members ? Math.round(totalPoints / members).toLocaleString("en-US") : "—"
                }
                hint={`${fmtInt(totalPoints)} total`}
              />
            </SimpleGrid>

            {funnel.length === 0 ? (
              <Text c="dimmed" size="sm">
                No events in this range.
              </Text>
            ) : (
              /*
               * A strip per step rather than a chart.
               *
               * Steps overlap — a member counts at every step they reached — so
               * a pie would wrongly imply they divide the population, and a bar
               * chart at equal counts is three identical bars with an axis. The
               * strip carries count and conversion inline and reads the same at
               * two members as at two thousand.
               */
              <Stack gap="xs">
                {funnel.map((row, i) => {
                  const base = entryStep?.uniqueMembers ?? 0;
                  const share = base ? (row.uniqueMembers / base) * 100 : 0;
                  const prev = i > 0 ? funnel[i - 1] : null;
                  const stepPct =
                    prev && prev.uniqueMembers
                      ? (row.uniqueMembers / prev.uniqueMembers) * 100
                      : null;
                  return (
                    <Group key={row.eventType} gap="sm" wrap="nowrap" align="center">
                      <Text size="sm" w={140} lineClamp={1} fw={600}>
                        {eventTypeLabel(row.eventType)}
                      </Text>
                      <Progress
                        value={share}
                        size="lg"
                        radius="sm"
                        color={colors[i]?.from ?? "teal"}
                        style={{ flex: 1 }}
                      />
                      <Text size="sm" fw={700} w={48} ta="right" className={styles.num}>
                        {fmtInt(row.uniqueMembers)}
                      </Text>
                      <Text size="xs" c="dimmed" w={58} ta="right">
                        {stepPct === null ? "" : `→ ${stepPct.toFixed(1)}%`}
                      </Text>
                    </Group>
                  );
                })}

                {entryStep && finalStep && entryStep !== finalStep && (
                  <Group justify="flex-end" gap="xs" pt={2}>
                    <Text size="xs" c="dimmed" tt="uppercase" fw={600}>
                      End-to-end
                    </Text>
                    <Text size="sm" fw={700}>
                      {entryStep.uniqueMembers
                        ? ((finalStep.uniqueMembers / entryStep.uniqueMembers) * 100).toFixed(1)
                        : "0.0"}
                      %
                    </Text>
                  </Group>
                )}
              </Stack>
            )}
          </Stack>
        </Card>
      </Grid.Col>

      <Grid.Col span={{ base: 12, lg: 6 }}>
        <Card className={styles.sectionCard} p="md" h="100%">
          <Stack gap="sm">
            <Group justify="space-between" align="center" wrap="wrap">
              <Stack gap={0}>
                <Title order={5}>Activity over time</Title>
                <Text size="xs" c="dimmed">
                  {byDay.length} day{byDay.length === 1 ? "" : "s"} with activity
                </Text>
              </Stack>
              {/* Three unrelated magnitudes, so one at a time rather than three
                  series sharing an axis that flattens the smallest. */}
              <SegmentedControl
                size="xs"
                value={timeMetric}
                onChange={(value) => setTimeMetric(value as TimeMetric)}
                data={[
                  { value: "points", label: "Points" },
                  { value: "events", label: "Events" },
                  { value: "members", label: "Members" },
                ]}
              />
            </Group>

            {byDay.length === 0 ? (
              <Text c="dimmed" size="sm">
                No dated events in this range.
              </Text>
            ) : (
              <ResponsiveContainer width="100%" height={240}>
                <AreaChart data={byDay} margin={{ left: 4, right: 12, top: 8 }}>
                  <defs>
                    <linearGradient id="memberActivityFill" x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0%" stopColor={colors[0].from} stopOpacity={0.45} />
                      <stop offset="100%" stopColor={colors[0].to} stopOpacity={0.04} />
                    </linearGradient>
                  </defs>
                  <CartesianGrid strokeDasharray="3 3" vertical={false} opacity={0.4} />
                  <XAxis
                    dataKey="day"
                    fontSize={11}
                    tickLine={false}
                    /* Day and month only: a full ISO date per tick overlaps at
                       any range wider than a fortnight. */
                    tickFormatter={(day: string) => day.slice(5)}
                    minTickGap={24}
                  />
                  <YAxis fontSize={11} tickLine={false} allowDecimals={false} width={40} />
                  <Tooltip
                    formatter={(value) => [
                      Number(value).toLocaleString("en-US"),
                      TIME_METRIC_LABEL[timeMetric],
                    ]}
                  />
                  <Area
                    type="monotone"
                    dataKey={timeMetric}
                    stroke={colors[0].from}
                    strokeWidth={2}
                    fill="url(#memberActivityFill)"
                  />
                </AreaChart>
              </ResponsiveContainer>
            )}
          </Stack>
        </Card>
      </Grid.Col>

      <Grid.Col span={{ base: 12, lg: 4 }}>
        <Card className={styles.sectionCard} p="md" h="100%">
          <Stack gap="sm">
            <Stack gap={0}>
              <Title order={5}>Share</Title>
              <Text size="xs" c="dimmed">
                Splits where each event belongs to exactly one slice.
              </Text>
            </Stack>
            {breakdowns.length === 0 ? (
              <Text c="dimmed" size="sm">
                Nothing to break down in this range.
              </Text>
            ) : (
              <BreakdownPanel breakdowns={breakdowns} loading={loading} height={190} />
            )}
          </Stack>
        </Card>
      </Grid.Col>

      <Grid.Col span={{ base: 12, lg: 4 }}>
        <Card className={styles.sectionCard} p="md" h="100%">
          <Stack gap="sm">
            <Stack gap={0}>
              <Title order={5}>Most active members</Title>
              <Text size="xs" c="dimmed">
                By points earned. Click a bar to open that member.
              </Text>
            </Stack>

            {leaderboard.length === 0 ? (
              <Text c="dimmed" size="sm">
                No member activity in this range.
              </Text>
            ) : (
              <ResponsiveContainer
                width="100%"
                height={Math.max(180, leaderboard.length * 38)}
              >
                <BarChart data={leaderboard} layout="vertical" margin={{ left: 8, right: 34 }}>
                  <CartesianGrid strokeDasharray="3 3" horizontal={false} opacity={0.4} />
                  <XAxis type="number" allowDecimals={false} fontSize={12} />
                  <YAxis
                    type="category"
                    dataKey="name"
                    width={150}
                    fontSize={11}
                    tickLine={false}
                  />
                  <Tooltip
                    cursor={{ fillOpacity: 0.06 }}
                    formatter={(value, _name, item) => [
                      `${Number(value).toLocaleString("en-US")} points · ${
                        (item?.payload as { events?: number })?.events ?? 0
                      } events`,
                      "Activity",
                    ]}
                  />
                  <Bar dataKey="points" radius={[0, 6, 6, 0]} maxBarSize={22}>
                    {leaderboard.map((row, i) => (
                      <Cell
                        key={row.memberId}
                        fill={colors[i % colors.length]?.from ?? "#c2a154"}
                        cursor="pointer"
                        onClick={() => onSelectMember(row.memberId)}
                      />
                    ))}
                    <LabelList
                      dataKey="points"
                      position="right"
                      fontSize={11}
                      formatter={(v) => Number(v).toLocaleString("en-US")}
                    />
                  </Bar>
                </BarChart>
              </ResponsiveContainer>
            )}
          </Stack>
        </Card>
      </Grid.Col>

      <Grid.Col span={{ base: 12, lg: 4 }}>
        <Card className={styles.sectionCard} p="md" h="100%">
          <Stack gap="sm">
            <Stack gap={0}>
              <Title order={5}>By property</Title>
              <Text size="xs" c="dimmed">
                Distinct members per property, across every event type.
              </Text>
            </Stack>

            {properties.length === 0 ? (
              <Text c="dimmed" size="sm">
                No property recorded on these events.
              </Text>
            ) : (
              <ResponsiveContainer
                width="100%"
                height={Math.max(180, properties.length * 38)}
              >
                <BarChart data={properties} layout="vertical" margin={{ left: 8, right: 34 }}>
                  <CartesianGrid strokeDasharray="3 3" horizontal={false} opacity={0.4} />
                  <XAxis type="number" allowDecimals={false} fontSize={12} />
                  <YAxis
                    type="category"
                    dataKey="property"
                    width={150}
                    fontSize={11}
                    tickLine={false}
                  />
                  <Tooltip
                    cursor={{ fillOpacity: 0.06 }}
                    formatter={(value, _name, item) => [
                      `${Number(value).toLocaleString("en-US")} members · ${
                        (item?.payload as { events?: number })?.events ?? 0
                      } events`,
                      "Property",
                    ]}
                  />
                  <Bar dataKey="members" radius={[0, 6, 6, 0]} maxBarSize={22}>
                    {properties.map((row, i) => (
                      <Cell
                        key={row.property}
                        fill={colors[(i + 1) % colors.length]?.from ?? "#876328"}
                      />
                    ))}
                    <LabelList
                      dataKey="members"
                      position="right"
                      fontSize={11}
                      formatter={(v) => Number(v).toLocaleString("en-US")}
                    />
                  </Bar>
                </BarChart>
              </ResponsiveContainer>
            )}
          </Stack>
        </Card>
      </Grid.Col>
    </Grid>
  );
}
