import type { OutputColumnMetadata } from "@internal/clickhouse";
import { IconSortAscending, IconSortDescending } from "@tabler/icons-react";
import { BarChart, CheckIcon, LineChart, Plus, XIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { cn } from "~/utils/cn";
import {
  type AggregationType,
  type ChartConfiguration,
  type SortDirection,
} from "../metrics/QueryWidget";
import { Paragraph } from "../primitives/Paragraph";
import { Popover, PopoverContent, PopoverTrigger } from "../primitives/Popover";
import SegmentedControl from "../primitives/SegmentedControl";
import { Select, SelectItem } from "../primitives/Select";
import { Switch } from "../primitives/Switch";
import { CHART_COLORS_BY_HUE, getSeriesColor } from "./chartColors";

export const defaultChartConfig: ChartConfiguration = {
  chartType: "bar",
  xAxisColumn: null,
  yAxisColumns: [],
  groupByColumn: null,
  stacked: false,
  sortByColumn: null,
  sortDirection: "asc",
  aggregation: "sum",
  seriesColors: {},
};

interface ChartConfigPanelProps {
  columns: OutputColumnMetadata[];
  config: ChartConfiguration;
  onChange: (config: ChartConfiguration) => void;
  className?: string;
}

// Type detection helpers
function isNumericType(type: string): boolean {
  return (
    type.startsWith("Int") ||
    type.startsWith("UInt") ||
    type.startsWith("Float") ||
    type.startsWith("Decimal") ||
    type.startsWith("Nullable(Int") ||
    type.startsWith("Nullable(UInt") ||
    type.startsWith("Nullable(Float") ||
    type.startsWith("Nullable(Decimal")
  );
}

function isDateTimeType(type: string): boolean {
  return (
    type === "DateTime" ||
    type === "DateTime64" ||
    type === "Date" ||
    type === "Date32" ||
    type.startsWith("DateTime64(") ||
    type.startsWith("Nullable(DateTime") ||
    type.startsWith("Nullable(Date")
  );
}

function isStringType(type: string): boolean {
  return (
    type === "String" ||
    type === "LowCardinality(String)" ||
    type === "Nullable(String)" ||
    type.startsWith("Enum") ||
    type.startsWith("FixedString")
  );
}

export function ChartConfigPanel({ columns, config, onChange, className }: ChartConfigPanelProps) {
  // Categorize columns by type
  const { numericColumns, dateTimeColumns, categoricalColumns, allColumns } = useMemo(() => {
    const numeric: OutputColumnMetadata[] = [];
    const dateTime: OutputColumnMetadata[] = [];
    const categorical: OutputColumnMetadata[] = [];

    for (const col of columns) {
      if (isNumericType(col.type)) {
        numeric.push(col);
      }
      if (isDateTimeType(col.type)) {
        dateTime.push(col);
      }
      if (isStringType(col.type) || isDateTimeType(col.type)) {
        categorical.push(col);
      }
    }

    return {
      numericColumns: numeric,
      dateTimeColumns: dateTime,
      categoricalColumns: categorical,
      allColumns: columns,
    };
  }, [columns]);

  // Create a stable key from column names and types to detect actual changes
  const columnsKey = useMemo(() => columns.map((c) => `${c.name}:${c.type}`).join(","), [columns]);

  // Use refs to access current config/onChange without adding them as dependencies
  const configRef = useRef(config);
  const onChangeRef = useRef(onChange);
  useEffect(() => {
    configRef.current = config;
    onChangeRef.current = onChange;
  });

  // Auto-select defaults when columns change
  useEffect(() => {
    if (columns.length === 0) return;

    const currentConfig = configRef.current;
    let needsUpdate = false;
    const updates: Partial<ChartConfiguration> = {};

    // Auto-select X-axis (prefer datetime, then first categorical)
    if (!currentConfig.xAxisColumn) {
      const defaultX = dateTimeColumns[0] ?? categoricalColumns[0] ?? columns[0];
      if (defaultX) {
        updates.xAxisColumn = defaultX.name;
        needsUpdate = true;
      }
    }

    // Auto-select Y-axis (first numeric column)
    if (currentConfig.yAxisColumns.length === 0 && numericColumns.length > 0) {
      updates.yAxisColumns = [numericColumns[0].name];
      needsUpdate = true;
    }

    // Determine the effective x-axis column (either existing or newly selected)
    const effectiveXAxis = updates.xAxisColumn ?? currentConfig.xAxisColumn;

    // Auto-set sort to x-axis ASC if it's a datetime column and no sort is configured
    if (
      effectiveXAxis &&
      !currentConfig.sortByColumn &&
      dateTimeColumns.some((col) => col.name === effectiveXAxis)
    ) {
      updates.sortByColumn = effectiveXAxis;
      updates.sortDirection = "asc";
      needsUpdate = true;
    }

    if (needsUpdate) {
      onChangeRef.current({ ...currentConfig, ...updates });
    }
    // Only re-run when the actual column structure changes, not on every config change.
    // columnsKey (a string) is stable when columns match, so this won't re-fire
    // unnecessarily when the same query is re-run with identical columns.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [columnsKey]);

  const updateConfig = useCallback(
    (updates: Partial<ChartConfiguration>) => {
      onChange({ ...config, ...updates });
    },
    [config, onChange]
  );

  // X-axis options: prefer datetime and string columns at the top
  const xAxisOptions = useMemo(() => {
    const preferred = [
      ...dateTimeColumns,
      ...categoricalColumns.filter((c) => !isDateTimeType(c.type)),
    ];
    const preferredNames = new Set(preferred.map((c) => c.name));
    const other = allColumns.filter((c) => !preferredNames.has(c.name));

    const options: Array<{ value: string; label: string; type: string }> = [];

    for (const col of preferred) {
      options.push({ value: col.name, label: col.name, type: col.type });
    }
    for (const col of other) {
      options.push({ value: col.name, label: col.name, type: col.type });
    }

    return options;
  }, [allColumns, dateTimeColumns, categoricalColumns]);

  // Y-axis options: numeric columns only
  const yAxisOptions = useMemo(() => {
    return numericColumns.map((col) => ({
      value: col.name,
      label: col.name,
      type: col.type,
    }));
  }, [numericColumns]);

  // Aggregation options
  const aggregationOptions = [
    { value: "sum", label: "Sum" },
    { value: "avg", label: "Average" },
    { value: "count", label: "Count" },
    { value: "min", label: "Min" },
    { value: "max", label: "Max" },
  ];

  // Group by options: categorical columns (excluding selected X axis)
  const groupByOptions = useMemo(() => {
    const options = categoricalColumns
      .filter((col) => col.name !== config.xAxisColumn)
      .map((col) => ({
        value: col.name,
        label: col.name,
        type: col.type,
      }));

    return [{ value: "__none__", label: "None", type: "" }, ...options];
  }, [categoricalColumns, config.xAxisColumn]);

  // Sort by options: all columns
  const sortByOptions = useMemo(() => {
    const options = allColumns.map((col) => ({
      value: col.name,
      label: col.name,
      type: col.type,
    }));

    return [{ value: "__none__", label: "None", type: "" }, ...options];
  }, [allColumns]);

  if (columns.length === 0) {
    return (
      <div className={cn("flex items-center justify-center p-4", className)}>
        <Paragraph variant="small" className="text-text-dimmed">
          Run a query to configure the chart
        </Paragraph>
      </div>
    );
  }

  return (
    <div className={cn("flex flex-col gap-3 p-2", className)}>
      {/* Chart Type */}
      <div className="flex flex-col gap-3">
        <ConfigField label="Type">
          <SegmentedControl
            name="chartType"
            value={config.chartType}
            variant="secondary/small"
            options={[
              {
                label: (
                  <span className="flex items-center gap-1">
                    <BarChart className="size-3" /> Bar
                  </span>
                ),
                value: "bar",
              },
              {
                label: (
                  <span className="flex items-center gap-1">
                    <LineChart className="size-3" /> Line
                  </span>
                ),
                value: "line",
              },
            ]}
            onChange={(value) => updateConfig({ chartType: value as "bar" | "line" })}
          />
        </ConfigField>
      </div>

      <div className="flex flex-col gap-3">
        {/* X-Axis */}
        <ConfigField label="X-Axis">
          <Select
            value={config.xAxisColumn ?? ""}
            setValue={(value) => {
              const updates: Partial<ChartConfiguration> = { xAxisColumn: value || null };
              // Auto-set sort to x-axis ASC if selecting a datetime column
              if (value) {
                const selectedCol = columns.find((c) => c.name === value);
                if (selectedCol && isDateTimeType(selectedCol.type)) {
                  updates.sortByColumn = value;
                  updates.sortDirection = "asc";
                }
              }
              updateConfig(updates);
            }}
            variant="tertiary/small"
            placeholder="Select column"
            items={xAxisOptions}
            dropdownIcon
            className="min-w-[140px]"
          >
            {(items) =>
              items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  <span className="flex items-center gap-2">
                    <span>{item.label}</span>
                    <TypeBadge type={item.type} />
                  </span>
                </SelectItem>
              ))
            }
          </Select>
        </ConfigField>

        {/* Y-Axis / Series */}
        <ConfigField label={config.yAxisColumns.length > 1 ? "Series" : "Y-Axis"}>
          {yAxisOptions.length === 0 ? (
            <span className="text-xs text-text-dimmed">No numeric columns</span>
          ) : (
            <div className="flex flex-col gap-1.5">
              {/* Always show at least one dropdown, even if yAxisColumns is empty */}
              {(config.yAxisColumns.length === 0 ? [""] : config.yAxisColumns).map((col, index) => (
                <div key={index} className="flex items-center gap-1">
                  {col && !config.groupByColumn && (
                    <SeriesColorPicker
                      color={config.seriesColors?.[col] ?? getSeriesColor(index)}
                      onColorChange={(color) => {
                        updateConfig({
                          seriesColors: { ...config.seriesColors, [col]: color },
                        });
                      }}
                    />
                  )}
                  <Select
                    value={col}
                    setValue={(value) => {
                      const newColumns = [...config.yAxisColumns];
                      const updates: Partial<ChartConfiguration> = {};
                      if (value) {
                        // If this is a new slot (empty string), add it
                        if (index >= config.yAxisColumns.length) {
                          newColumns.push(value);
                        } else {
                          // If the column name changed, migrate the color
                          const oldCol = newColumns[index];
                          if (oldCol && oldCol !== value && config.seriesColors?.[oldCol]) {
                            const newSeriesColors = { ...config.seriesColors };
                            newSeriesColors[value] = newSeriesColors[oldCol];
                            delete newSeriesColors[oldCol];
                            updates.seriesColors = newSeriesColors;
                          }
                          newColumns[index] = value;
                        }
                      } else if (index < config.yAxisColumns.length) {
                        newColumns.splice(index, 1);
                      }
                      updateConfig({ ...updates, yAxisColumns: newColumns });
                    }}
                    variant="tertiary/small"
                    placeholder="Select column"
                    items={yAxisOptions.filter(
                      (opt) => opt.value === col || !config.yAxisColumns.includes(opt.value)
                    )}
                    dropdownIcon
                    className="min-w-[140px] flex-1"
                  >
                    {(items) =>
                      items.map((item) => (
                        <SelectItem key={item.value} value={item.value}>
                          <span className="flex items-center gap-2">
                            <span>{item.label}</span>
                            <TypeBadge type={item.type} />
                          </span>
                        </SelectItem>
                      ))
                    }
                  </Select>

                  {index > 0 && (
                    <button
                      type="button"
                      onClick={() => {
                        const removedCol = config.yAxisColumns[index];
                        const newColumns = config.yAxisColumns.filter((_, i) => i !== index);
                        const updates: Partial<ChartConfiguration> = { yAxisColumns: newColumns };
                        // Clean up the color entry for the removed series
                        if (removedCol && config.seriesColors?.[removedCol]) {
                          const newSeriesColors = { ...config.seriesColors };
                          delete newSeriesColors[removedCol];
                          updates.seriesColors = newSeriesColors;
                        }
                        updateConfig(updates);
                      }}
                      className="rounded p-1 text-text-dimmed hover:bg-background-raised hover:text-text-bright"
                      title="Remove series"
                    >
                      <XIcon className="h-3.5 w-3.5" />
                    </button>
                  )}
                </div>
              ))}

              {/* Add another series button - only show when we have at least one series and not grouped */}
              {config.yAxisColumns.length > 0 &&
                config.yAxisColumns.length < yAxisOptions.length &&
                !config.groupByColumn && (
                  <button
                    type="button"
                    onClick={() => {
                      const availableColumns = yAxisOptions.filter(
                        (opt) => !config.yAxisColumns.includes(opt.value)
                      );
                      if (availableColumns.length > 0) {
                        updateConfig({
                          yAxisColumns: [...config.yAxisColumns, availableColumns[0].value],
                        });
                      }
                    }}
                    className="flex items-center gap-1 self-start rounded px-1 py-0.5 text-xs text-text-dimmed hover:bg-background-raised hover:text-text-bright"
                  >
                    <Plus className="h-3 w-3" />
                    Add series
                  </button>
                )}

              {config.groupByColumn && config.yAxisColumns.length === 1 && (
                <span className="text-xxs text-text-dimmed">
                  Remove group by to add multiple series
                </span>
              )}
            </div>
          )}
        </ConfigField>

        {/* Aggregation */}
        <ConfigField label="Aggregation">
          <Select
            value={config.aggregation}
            setValue={(value) => updateConfig({ aggregation: value as AggregationType })}
            variant="tertiary/small"
            items={aggregationOptions}
            dropdownIcon
            className="min-w-[100px]"
          >
            {(items) =>
              items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))
            }
          </Select>
        </ConfigField>

        {/* Group By - disabled when multiple series are selected */}
        <ConfigField label="Group by">
          {config.yAxisColumns.length > 1 ? (
            <span className="text-xs text-text-dimmed">Not available with multiple series</span>
          ) : (
            <Select
              value={config.groupByColumn ?? "__none__"}
              setValue={(value) =>
                updateConfig({ groupByColumn: value === "__none__" ? null : value })
              }
              variant="tertiary/small"
              placeholder="None"
              items={groupByOptions}
              dropdownIcon
              className="min-w-[140px]"
              text={(t) => (t === "__none__" ? "None" : t)}
            >
              {(items) =>
                items.map((item) => (
                  <SelectItem key={item.value} value={item.value}>
                    <span className="flex items-center gap-2">
                      <span>{item.label}</span>
                      {item.type && <TypeBadge type={item.type} />}
                    </span>
                  </SelectItem>
                ))
              }
            </Select>
          )}
        </ConfigField>

        {/* Stacked toggle (when grouped or multiple series) */}
        {(config.groupByColumn || config.yAxisColumns.length > 1) && (
          <ConfigField label={config.groupByColumn ? "Stack groups" : "Stack series"}>
            <Switch
              variant="medium"
              checked={config.stacked}
              onCheckedChange={(checked) => updateConfig({ stacked: checked })}
            />
          </ConfigField>
        )}

        {/* Order By */}
        <ConfigField label="Order by">
          <Select
            value={config.sortByColumn ?? "__none__"}
            setValue={(value) =>
              updateConfig({ sortByColumn: value === "__none__" ? null : value })
            }
            variant="tertiary/small"
            placeholder="None"
            items={sortByOptions}
            dropdownIcon
            className="min-w-[140px]"
            text={(t) => (t === "__none__" ? "None" : t)}
          >
            {(items) =>
              items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  <span className="flex items-center gap-2">
                    <span>{item.label}</span>
                    {item.type && <TypeBadge type={item.type} />}
                  </span>
                </SelectItem>
              ))
            }
          </Select>
        </ConfigField>

        {/* Sort Direction (only when sorting) */}
        {config.sortByColumn && (
          <ConfigField label="Sort direction">
            <SegmentedControl
              name="sortDirection"
              value={config.sortDirection}
              variant="secondary/small"
              options={[
                {
                  label: (
                    <span className="flex items-center gap-1">
                      <IconSortAscending className="size-3" /> Asc
                    </span>
                  ),
                  value: "asc",
                },
                {
                  label: (
                    <span className="flex items-center gap-1">
                      <IconSortDescending className="size-3" /> Desc
                    </span>
                  ),
                  value: "desc",
                },
              ]}
              onChange={(value) => updateConfig({ sortDirection: value as SortDirection })}
            />
          </ConfigField>
        )}
      </div>
    </div>
  );
}

function ConfigField({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div className="flex flex-col gap-1">
      {label && <span className="text-xs text-text-bright">{label}</span>}
      {children}
    </div>
  );
}

function SeriesColorPicker({
  color,
  onColorChange,
}: {
  color: string;
  onColorChange: (color: string) => void;
}) {
  const [open, setOpen] = useState(false);

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button
          type="button"
          className="shrink-0 rounded p-0.5 hover:bg-background-raised"
          title="Change series color"
        >
          <span
            className="block h-4 w-4 rounded-full border border-white/30"
            style={{ backgroundColor: color }}
          />
        </button>
      </PopoverTrigger>
      <PopoverContent align="start" className="w-auto p-2">
        <div className="grid grid-cols-6 gap-1.5">
          {CHART_COLORS_BY_HUE.map((c) => (
            <button
              key={c}
              type="button"
              onClick={() => {
                onColorChange(c);
                setOpen(false);
              }}
              className="group/swatch flex h-6 w-6 items-center justify-center rounded-full border border-white/30"
              style={{ backgroundColor: c }}
              title={c}
            >
              {c === color && <CheckIcon className="h-3.5 w-3.5 text-white drop-shadow-md" />}
            </button>
          ))}
        </div>
      </PopoverContent>
    </Popover>
  );
}

function TypeBadge({ type }: { type: string }) {
  // Simplify type for display
  let displayType = type;
  if (type.startsWith("Nullable(")) {
    displayType = type.slice(9, -1) + "?";
  }
  if (type.startsWith("LowCardinality(")) {
    displayType = type.slice(15, -1);
  }

  // Shorten long type names
  if (displayType.length > 12) {
    displayType = displayType.slice(0, 10) + "…";
  }

  return (
    <span className="rounded bg-background-hover px-1 py-0.5 font-mono text-xxs text-text-dimmed">
      {displayType}
    </span>
  );
}
