import { MultiSelect, Select, TextInput } from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import { useDebouncedCallback } from "@mantine/hooks";
import {
  IconCalendarStats,
  IconCoin,
  IconSearch,
  IconTags,
  IconUser,
} from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import {
  ActiveFilterChips,
  FilterDrawer,
  FilterSection,
  FilterTrigger,
  type FilterChip,
} from "@/routes/promo-code-campaigns/_components/FilterDrawer";
import {
  eventTypeLabel,
  type EventAnalyticsFilters,
} from "@/lib/features/event-analytics/types";

/**
 * Filters for Event Analytics, using the promo-code drawer.
 *
 * The controls used to sit inline above the tiles, which pushed the numbers and
 * charts below the fold on a laptop. Moving them into the shared drawer lets the
 * KPI row, charts and tables read as one block, and matches how filtering works
 * on the campaigns and contact-completeness pages.
 *
 * Filters apply live; the drawer has no Apply step. `ActiveFilterChips` keeps
 * the narrowing visible on the page so a filtered view is never mistaken for the
 * whole dataset.
 */

/**
 * Normalise a picker value to the `yyyy-mm-dd` the filters and core both use.
 *
 * Mantine 8's date inputs emit `yyyy-mm-dd` strings, but the repo's other date
 * filters still pass Date objects around, so both are accepted here rather than
 * assuming one and silently producing "Invalid Date". Core widens a date-only
 * `to` bound to end of day.
 */
const toDateParam = (value: unknown): string | null => {
  if (!value) return null;
  if (typeof value === "string") {
    if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) return null;
    return toDateParam(parsed);
  }
  if (value instanceof Date) {
    if (Number.isNaN(value.getTime())) return null;
    const y = value.getFullYear();
    const m = String(value.getMonth() + 1).padStart(2, "0");
    const d = String(value.getDate()).padStart(2, "0");
    return `${y}-${m}-${d}`;
  }
  return null;
};

type FilterBarProps = {
  filters: EventAnalyticsFilters;
  onChange: (next: Partial<EventAnalyticsFilters>) => void;
  onReset: () => void;
  availableEventTypes: string[];
  availableCurrencies: string[];
  availableProperties: string[];
  availableSources: string[];
};

/**
 * Builds the removable chip for each active filter.
 *
 * Every chip clears exactly the one filter it names — `Clear all` is a separate
 * control, so removing "Property: Karma Chakra" must not also drop the date
 * range the user set alongside it.
 */
function useFilterChips(
  filters: EventAnalyticsFilters,
  onChange: (next: Partial<EventAnalyticsFilters>) => void,
): FilterChip[] {
  return useMemo(() => {
    const chips: FilterChip[] = [];

    if (filters.from || filters.to) {
      chips.push({
        key: "dates",
        label: `${filters.from ?? "start"} → ${filters.to ?? "now"}`,
        onRemove: () => onChange({ from: null, to: null }),
      });
    }
    for (const eventType of filters.eventTypes) {
      chips.push({
        key: `type:${eventType}`,
        label: eventTypeLabel(eventType),
        onRemove: () =>
          onChange({
            eventTypes: filters.eventTypes.filter((t) => t !== eventType),
          }),
      });
    }
    if (filters.source) {
      chips.push({
        key: "source",
        label: `Source: ${filters.source}`,
        onRemove: () => onChange({ source: null }),
      });
    }
    if (filters.property) {
      chips.push({
        key: "property",
        label: filters.property,
        onRemove: () => onChange({ property: null }),
      });
    }
    if (filters.currency) {
      chips.push({
        key: "currency",
        label: filters.currency,
        onRemove: () => onChange({ currency: null }),
      });
    }
    if (filters.memberId) {
      chips.push({
        key: "member",
        label: `Member ${filters.memberId}`,
        onRemove: () => onChange({ memberId: null }),
      });
    }
    if (filters.search) {
      chips.push({
        key: "search",
        label: `"${filters.search}"`,
        onRemove: () => onChange({ search: null }),
      });
    }

    return chips;
  }, [filters, onChange]);
}

/**
 * The three pieces of filter UI, ready to place.
 *
 * A hook rather than a component because the pieces belong in three different
 * places — the trigger sits in the page header beside Export, the chips under
 * it, and the drawer is portalled — and they share one open/close state and one
 * chip list. Returning them separately avoids threading that state back up
 * through the page.
 */
export function useEventFilterUI({
  filters,
  onChange,
  onReset,
  availableEventTypes,
  availableCurrencies,
  availableProperties,
  availableSources,
}: FilterBarProps) {
  const [opened, setOpened] = useState(false);
  const chips = useFilterChips(filters, onChange);

  // Text boxes cannot write straight through: every keystroke would re-run a
  // full aggregation. They keep local state and push on a debounce.
  const [search, setSearch] = useState(filters.search ?? "");
  const [memberId, setMemberId] = useState(filters.memberId ?? "");

  // Keep the local boxes honest when filters change from elsewhere — a chip
  // being removed, Reset, the back button, or a drill-in from the member table.
  useEffect(() => setSearch(filters.search ?? ""), [filters.search]);
  useEffect(() => setMemberId(filters.memberId ?? ""), [filters.memberId]);

  const pushSearch = useDebouncedCallback(
    (value: string) => onChange({ search: value || null }),
    400,
  );
  const pushMemberId = useDebouncedCallback(
    (value: string) => onChange({ memberId: value || null }),
    400,
  );

  const eventTypeOptions = useMemo(
    () =>
      availableEventTypes.map((value) => ({ value, label: eventTypeLabel(value) })),
    [availableEventTypes],
  );

  return {
    activeCount: chips.length,
    trigger: (
      <FilterTrigger activeCount={chips.length} onClick={() => setOpened(true)} />
    ),
    chips: <ActiveFilterChips chips={chips} onReset={onReset} />,
    drawer: (
      <FilterDrawer
        opened={opened}
        onClose={() => setOpened(false)}
        activeCount={chips.length}
        onReset={onReset}
      >
        <FilterSection
          icon={<IconCalendarStats size={15} />}
          title="Date range"
          description="Scoped by when the event was recorded."
        >
          <DatePickerInput
            type="range"
            placeholder="All time"
            clearable
            value={[filters.from, filters.to] as any}
            onChange={(range: any) => {
              const [from, to] = (range ?? [null, null]) as [unknown, unknown];
              onChange({ from: toDateParam(from), to: toDateParam(to) });
            }}
          />
        </FilterSection>

        <FilterSection
          icon={<IconTags size={15} />}
          title="Event"
          description="Each event type is its own Firestore collection."
        >
          <MultiSelect
            label="Event types"
            placeholder={filters.eventTypes.length ? undefined : "All events"}
            data={eventTypeOptions}
            value={filters.eventTypes}
            onChange={(value) => onChange({ eventTypes: value })}
            searchable
            clearable
            hidePickedOptions
          />
          <Select
            label="Source"
            placeholder="All sources"
            data={availableSources}
            value={filters.source}
            onChange={(value) => onChange({ source: value })}
            searchable
            clearable
          />
          <Select
            label="Property"
            placeholder="All properties"
            data={availableProperties}
            value={filters.property}
            onChange={(value) => onChange({ property: value })}
            searchable
            clearable
          />
        </FilterSection>

        <FilterSection
          icon={<IconUser size={15} />}
          title="Member"
          description="Narrow to one member, or search across the records."
        >
          <TextInput
            label="Member number"
            placeholder="Exact, e.g. 1278640"
            value={memberId}
            onChange={(e) => {
              setMemberId(e.currentTarget.value);
              pushMemberId(e.currentTarget.value);
            }}
          />
          <TextInput
            label="Search"
            placeholder="Property, experience or offer"
            leftSection={<IconSearch size={16} />}
            value={search}
            onChange={(e) => {
              setSearch(e.currentTarget.value);
              pushSearch(e.currentTarget.value);
            }}
          />
        </FilterSection>

        {/* Only offered when the data actually carries currencies — most events
            have no amount, so an always-visible empty dropdown reads as broken. */}
        {availableCurrencies.length > 0 && (
          <FilterSection
            icon={<IconCoin size={15} />}
            title="Currency"
            description="Revenue is reported per currency, never converted."
            withDivider={false}
          >
            <Select
              label="Currency"
              placeholder="All currencies"
              data={availableCurrencies}
              value={filters.currency}
              onChange={(value) => onChange({ currency: value })}
              clearable
            />
          </FilterSection>
        )}
      </FilterDrawer>
    ),
  };
}
