import type { DashboardWidget, DashboardWidgetType, IndicatorAggregation } from "@geolibre/core";
import {
  Button,
  ColorField,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  Input,
  Label,
  Select,
} from "@geolibre/ui";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
  categoricalColumns,
  DEFAULT_HISTOGRAM_BINS,
  MAX_HISTOGRAM_BINS,
  MIN_HISTOGRAM_BINS,
  numericColumns,
  type BarAggregation,
} from "../../lib/attribute-charts";
import { useLayerChartData } from "../../hooks/useLayerChartData";

interface WidgetEditorDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** The widget being edited, or null to create a new one. */
  widget: DashboardWidget | null;
  /** Chartable layers to choose from. */
  layers: { id: string; name: string }[];
  /** Called with the assembled widget when the user saves. */
  onSave: (widget: DashboardWidget) => void;
}

/** Keep a chosen field valid for the active layer: fall back to the first
 * available option when the saved value is not among them. */
function pick(value: string, options: string[]): string {
  return options.includes(value) ? value : (options[0] ?? "");
}

/**
 * Add or edit a dashboard chart widget: pick a layer, a chart type, and the
 * field(s) to plot. The field choices follow the selected layer and the chart
 * type, mirroring the attribute Charts dialog. A new id is minted on save for a
 * new widget; editing preserves the existing id.
 */
export function WidgetEditorDialog({
  open,
  onOpenChange,
  widget,
  layers,
  onSave,
}: WidgetEditorDialogProps) {
  const { t } = useTranslation();
  const [layerId, setLayerId] = useState("");
  const [type, setType] = useState<DashboardWidgetType>("histogram");
  const [field, setField] = useState("");
  const [xField, setXField] = useState("");
  const [yField, setYField] = useState("");
  const [bins, setBins] = useState(DEFAULT_HISTOGRAM_BINS);
  const [category, setCategory] = useState("");
  const [aggregation, setAggregation] = useState<BarAggregation>("count");
  const [valueField, setValueField] = useState("");
  const [title, setTitle] = useState("");
  // "indicator" widget fields (issue #1381).
  const [indicatorAggregation, setIndicatorAggregation] = useState<IndicatorAggregation>("count");
  const [prefix, setPrefix] = useState("");
  const [suffix, setSuffix] = useState("");
  // "selector" widget fields (issue #1381).
  const [multiple, setMultiple] = useState(false);
  // "list" widget fields (issue #1381).
  const [listFields, setListFields] = useState<string[]>([]);
  const [sortBy, setSortBy] = useState("");
  const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
  const [limit, setLimit] = useState(20);
  // "" means no custom color: fall back to the theme primary / palette.
  const [color, setColor] = useState("");

  // Seed the form when it opens, from the edited widget or sensible defaults.
  useEffect(() => {
    if (!open) return;
    setLayerId(widget?.layerId ?? layers[0]?.id ?? "");
    setType(widget?.type ?? "histogram");
    setField(widget?.field ?? "");
    setXField(widget?.xField ?? "");
    setYField(widget?.yField ?? "");
    setBins(widget?.bins ?? DEFAULT_HISTOGRAM_BINS);
    setCategory(widget?.category ?? "");
    setAggregation(widget?.aggregation ?? "count");
    setValueField(widget?.valueField ?? "");
    setTitle(widget?.title ?? "");
    setColor(widget?.color ?? "");
    setIndicatorAggregation(widget?.indicatorAggregation ?? "count");
    setPrefix(widget?.prefix ?? "");
    setSuffix(widget?.suffix ?? "");
    setMultiple(widget?.multiple ?? false);
    setListFields(widget?.listFields ?? []);
    setSortBy(widget?.sortBy ?? "");
    setSortDir(widget?.sortDir ?? "desc");
    setLimit(widget?.limit ?? 20);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, widget]);

  const data = useLayerChartData(layerId);
  const numericCols = useMemo(() => numericColumns(data.rows, data.columns), [data]);
  const categoryCols = useMemo(() => categoricalColumns(data.rows, data.columns), [data]);
  const hasNumeric = numericCols.length > 0;
  const hasCategory = categoryCols.length > 0;
  const hasChartable = hasNumeric || hasCategory;

  // A widget can only be saved when its chart type has the fields it needs in
  // the chosen layer: bar/pie need a category (and a numeric field too when they
  // sum/average rather than count); scatter needs two numeric fields so x and y
  // aren't forced to the same column; the rest need one numeric field.
  const isCategorical = type === "bar" || type === "pie";
  const isIndicator = type === "indicator";
  const isSelector = type === "selector";
  const isList = type === "list";
  const canSave =
    layerId !== "" &&
    (isIndicator
      ? indicatorAggregation === "count" || hasNumeric
      : isSelector
        ? hasCategory
        : isList
          ? hasChartable
          : isCategorical
            ? hasCategory && (aggregation === "count" || hasNumeric)
            : type === "scatter"
              ? numericCols.length >= 2
              : hasNumeric);
  const save = () => {
    if (!canSave) return;
    const next: DashboardWidget = {
      id: widget?.id ?? crypto.randomUUID(),
      layerId,
      type,
    };
    const trimmedTitle = title.trim();
    if (trimmedTitle) next.title = trimmedTitle;
    if (color) next.color = color;
    if (type === "histogram" || type === "line" || type === "box") {
      next.field = pick(field, numericCols);
    }
    // Clicking Save can skip the bins input's onBlur, so guard the cleared/0
    // sentinel here rather than persisting an invalid bin count.
    if (type === "histogram") {
      next.bins = Math.max(MIN_HISTOGRAM_BINS, bins || MIN_HISTOGRAM_BINS);
    }
    if (type === "scatter") {
      next.xField = pick(xField, numericCols);
      next.yField = pick(yField, numericCols);
    }
    if (type === "bar" || type === "pie") {
      next.category = pick(category, categoryCols);
      // A pie shows parts of a whole, so it only counts or sums (no average).
      const agg = type === "pie" && aggregation === "mean" ? "sum" : aggregation;
      next.aggregation = agg;
      if (agg !== "count") next.valueField = pick(valueField, numericCols);
    }
    if (type === "indicator") {
      next.indicatorAggregation = indicatorAggregation;
      if (indicatorAggregation !== "count") {
        next.field = pick(field, numericCols);
      }
      // Preserve whitespace: prefix/suffix may have intentional spaces (" ha").
      if (prefix) next.prefix = prefix;
      if (suffix) next.suffix = suffix;
    }
    if (type === "selector") {
      next.category = pick(category, categoryCols);
      if (multiple) next.multiple = true;
    }
    if (type === "list") {
      // Ensure at least one column is selected; default to all available.
      const allCols = [...numericCols, ...categoryCols];
      const cols = listFields.filter((f) => allCols.includes(f));
      next.listFields = cols.length > 0 ? cols : allCols.slice(0, 3);
      if (sortBy) next.sortBy = sortBy;
      if (sortDir !== "desc") next.sortDir = sortDir;
      if (limit !== 20) next.limit = limit;
    }
    onSave(next);
    onOpenChange(false);
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-lg">
        <DialogHeader>
          <DialogTitle>
            {widget ? t("dashboard.editor.editTitle") : t("dashboard.editor.addTitle")}
          </DialogTitle>
          <DialogDescription>{t("dashboard.editor.description")}</DialogDescription>
        </DialogHeader>

        {layers.length === 0 ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            {t("dashboard.editor.noLayers")}
          </p>
        ) : (
          <div className="grid gap-3 py-1">
            <div className="grid gap-1.5">
              <Label htmlFor="widget-layer">{t("dashboard.editor.layer")}</Label>
              <Select
                id="widget-layer"
                value={layerId}
                onChange={(event) => setLayerId(event.target.value)}
              >
                {layers.map((layer) => (
                  <option key={layer.id} value={layer.id}>
                    {layer.name}
                  </option>
                ))}
              </Select>
            </div>

            {/* A layer with no numeric or categorical columns still supports a
                count indicator, so keep the type selector visible and let the
                per-option `disabled` flags gate the field-dependent types. */}
            {!hasChartable && (
              <p className="text-sm text-muted-foreground">{t("dashboard.editor.noFields")}</p>
            )}

            <div className="flex flex-wrap items-end gap-3">
              <div className="grid gap-1.5">
                <Label htmlFor="widget-type">{t("dashboard.editor.chartType")}</Label>
                <Select
                  id="widget-type"
                  className="w-36"
                  value={type}
                  onChange={(event) => {
                    const nextType = event.target.value as DashboardWidgetType;
                    setType(nextType);
                    // Pie has no "average"; drop a carried-over mean so the
                    // select doesn't show a stale value with no matching option.
                    if (nextType === "pie" && aggregation === "mean") {
                      setAggregation("count");
                    }
                  }}
                >
                  <option value="histogram" disabled={!hasNumeric}>
                    {t("dashboard.chartType.histogram")}
                  </option>
                  <option value="scatter" disabled={!hasNumeric}>
                    {t("dashboard.chartType.scatter")}
                  </option>
                  <option value="bar" disabled={!hasCategory}>
                    {t("dashboard.chartType.bar")}
                  </option>
                  <option value="line" disabled={!hasNumeric}>
                    {t("dashboard.chartType.line")}
                  </option>
                  <option value="box" disabled={!hasNumeric}>
                    {t("dashboard.chartType.box")}
                  </option>
                  <option value="pie" disabled={!hasCategory}>
                    {t("dashboard.chartType.pie")}
                  </option>
                  <option value="indicator">{t("dashboard.chartType.indicator")}</option>
                  <option value="selector" disabled={!hasCategory}>
                    {t("dashboard.chartType.selector")}
                  </option>
                  <option value="list" disabled={!hasChartable}>
                    {t("dashboard.chartType.list")}
                  </option>
                </Select>
              </div>

              {(type === "histogram" || type === "line" || type === "box") && (
                <FieldSelect
                  id="widget-field"
                  label={t("dashboard.editor.field")}
                  value={pick(field, numericCols)}
                  options={numericCols}
                  onChange={setField}
                />
              )}

              {type === "histogram" && (
                <div className="grid gap-1.5">
                  <Label htmlFor="widget-bins">{t("dashboard.editor.bins")}</Label>
                  <Input
                    id="widget-bins"
                    type="number"
                    className="w-24"
                    min={MIN_HISTOGRAM_BINS}
                    max={MAX_HISTOGRAM_BINS}
                    value={bins === 0 ? "" : bins}
                    onChange={(event) => {
                      const raw = event.target.value;
                      if (raw === "") {
                        setBins(0);
                        return;
                      }
                      const value = Number(raw);
                      if (Number.isFinite(value)) {
                        setBins(
                          Math.max(
                            MIN_HISTOGRAM_BINS,
                            Math.min(MAX_HISTOGRAM_BINS, Math.trunc(value)),
                          ),
                        );
                      }
                    }}
                    onBlur={() => {
                      if (bins < MIN_HISTOGRAM_BINS) setBins(MIN_HISTOGRAM_BINS);
                    }}
                  />
                </div>
              )}

              {type === "scatter" && (
                <>
                  <FieldSelect
                    id="widget-x"
                    label={t("dashboard.editor.xAxis")}
                    value={pick(xField, numericCols)}
                    options={numericCols}
                    onChange={setXField}
                  />
                  <FieldSelect
                    id="widget-y"
                    label={t("dashboard.editor.yAxis")}
                    value={pick(yField, numericCols)}
                    options={numericCols}
                    onChange={setYField}
                  />
                </>
              )}

              {(type === "bar" || type === "pie") && (
                <>
                  <FieldSelect
                    id="widget-category"
                    label={t("dashboard.editor.category")}
                    value={pick(category, categoryCols)}
                    options={categoryCols}
                    onChange={setCategory}
                  />
                  <div className="grid gap-1.5">
                    <Label htmlFor="widget-agg">{t("dashboard.editor.aggregate")}</Label>
                    <Select
                      id="widget-agg"
                      className="w-32"
                      value={aggregation}
                      onChange={(event) => setAggregation(event.target.value as BarAggregation)}
                    >
                      <option value="count">{t("dashboard.aggregate.count")}</option>
                      <option value="sum" disabled={!hasNumeric}>
                        {t("dashboard.aggregate.sum")}
                      </option>
                      {/* Averaging parts of a whole is meaningless for a pie. */}
                      {type !== "pie" && (
                        <option value="mean" disabled={!hasNumeric}>
                          {t("dashboard.aggregate.mean")}
                        </option>
                      )}
                    </Select>
                  </div>
                  {aggregation !== "count" && (
                    <FieldSelect
                      id="widget-value"
                      label={t("dashboard.editor.value")}
                      value={pick(valueField, numericCols)}
                      options={numericCols}
                      onChange={setValueField}
                    />
                  )}
                </>
              )}

              {type === "selector" && (
                <>
                  <FieldSelect
                    id="widget-selector-category"
                    label={t("dashboard.editor.category")}
                    value={pick(category, categoryCols)}
                    options={categoryCols}
                    onChange={setCategory}
                  />
                  <label className="flex items-center gap-2 text-sm">
                    <input
                      type="checkbox"
                      checked={multiple}
                      onChange={(event) => setMultiple(event.target.checked)}
                    />
                    {t("dashboard.editor.multiSelect")}
                  </label>
                </>
              )}

              {type === "list" && (
                <>
                  <div className="grid gap-1.5">
                    <Label>{t("dashboard.editor.listColumns")}</Label>
                    <div className="flex flex-wrap gap-1.5">
                      {[...numericCols, ...categoryCols].map((col) => {
                        const checked = listFields.includes(col);
                        return (
                          <label
                            key={col}
                            className="flex items-center gap-1 rounded border border-border px-2 py-0.5 text-xs"
                          >
                            <input
                              type="checkbox"
                              checked={checked}
                              onChange={(event) => {
                                if (event.target.checked) {
                                  setListFields((prev) => [...prev, col]);
                                } else {
                                  setListFields((prev) => prev.filter((f) => f !== col));
                                }
                              }}
                            />
                            {col}
                          </label>
                        );
                      })}
                    </div>
                  </div>
                  <div className="flex flex-wrap items-end gap-3">
                    <FieldSelect
                      id="widget-list-sort"
                      label={t("dashboard.editor.sortBy")}
                      value={sortBy}
                      options={["", ...numericCols, ...categoryCols]}
                      onChange={setSortBy}
                    />
                    <div className="grid gap-1.5">
                      <Label htmlFor="widget-list-sortdir">{t("dashboard.editor.sortDir")}</Label>
                      <Select
                        id="widget-list-sortdir"
                        className="w-28"
                        value={sortDir}
                        onChange={(event) => setSortDir(event.target.value as "asc" | "desc")}
                      >
                        <option value="asc">{t("dashboard.editor.ascending")}</option>
                        <option value="desc">{t("dashboard.editor.descending")}</option>
                      </Select>
                    </div>
                    <div className="grid gap-1.5">
                      <Label htmlFor="widget-list-limit">{t("dashboard.editor.limit")}</Label>
                      <Input
                        id="widget-list-limit"
                        type="number"
                        className="w-20"
                        min={1}
                        max={500}
                        value={limit}
                        onChange={(event) => {
                          const v = Number(event.target.value);
                          if (Number.isFinite(v)) setLimit(Math.max(1, Math.trunc(v)));
                        }}
                      />
                    </div>
                  </div>
                </>
              )}

              {type === "indicator" && (
                <>
                  <div className="grid gap-1.5">
                    <Label htmlFor="widget-indicator-agg">{t("dashboard.editor.aggregate")}</Label>
                    <Select
                      id="widget-indicator-agg"
                      className="w-32"
                      value={indicatorAggregation}
                      onChange={(event) =>
                        setIndicatorAggregation(event.target.value as IndicatorAggregation)
                      }
                    >
                      <option value="count">{t("dashboard.indicatorAggregation.count")}</option>
                      <option value="sum" disabled={!hasNumeric}>
                        {t("dashboard.indicatorAggregation.sum")}
                      </option>
                      <option value="mean" disabled={!hasNumeric}>
                        {t("dashboard.indicatorAggregation.mean")}
                      </option>
                      <option value="min" disabled={!hasNumeric}>
                        {t("dashboard.indicatorAggregation.min")}
                      </option>
                      <option value="max" disabled={!hasNumeric}>
                        {t("dashboard.indicatorAggregation.max")}
                      </option>
                      <option value="median" disabled={!hasNumeric}>
                        {t("dashboard.indicatorAggregation.median")}
                      </option>
                    </Select>
                  </div>
                  {indicatorAggregation !== "count" && (
                    <FieldSelect
                      id="widget-indicator-field"
                      label={t("dashboard.editor.field")}
                      value={pick(field, numericCols)}
                      options={numericCols}
                      onChange={setField}
                    />
                  )}
                  <div className="flex gap-2">
                    <div className="grid gap-1.5">
                      <Label htmlFor="widget-prefix">{t("dashboard.editor.prefix")}</Label>
                      <Input
                        id="widget-prefix"
                        className="w-20"
                        value={prefix}
                        placeholder="€"
                        onChange={(event) => setPrefix(event.target.value)}
                      />
                    </div>
                    <div className="grid gap-1.5">
                      <Label htmlFor="widget-suffix">{t("dashboard.editor.suffix")}</Label>
                      <Input
                        id="widget-suffix"
                        className="w-20"
                        value={suffix}
                        placeholder=" ha"
                        onChange={(event) => setSuffix(event.target.value)}
                      />
                    </div>
                  </div>
                </>
              )}
            </div>

            <div className="grid gap-1.5">
              <Label htmlFor="widget-title">{t("dashboard.editor.titleLabel")}</Label>
              <Input
                id="widget-title"
                value={title}
                placeholder={t("dashboard.editor.titlePlaceholder")}
                onChange={(event) => setTitle(event.target.value)}
              />
            </div>

            <div className="grid gap-1.5">
              <Label htmlFor="widget-color">{t("dashboard.editor.color")}</Label>
              <div className="flex items-center gap-2">
                <ColorField
                  id="widget-color"
                  eyedropperLabel={t("common.pickColorFromScreen")}
                  fill={false}
                  className="h-8 w-12 cursor-pointer p-0.5"
                  buttonClassName="h-8 w-8"
                  // Native color inputs need a concrete value; show a neutral
                  // swatch while no custom color is set.
                  value={color || "#3fb1ce"}
                  onChange={(next) => setColor(next)}
                />
                {color ? (
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-8 px-2 text-xs"
                    onClick={() => setColor("")}
                  >
                    {t("dashboard.editor.colorReset")}
                  </Button>
                ) : (
                  <span className="text-xs text-muted-foreground">
                    {t("dashboard.editor.colorDefault")}
                  </span>
                )}
              </div>
            </div>
          </div>
        )}

        <div className="flex items-center justify-end gap-2">
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            {t("dashboard.editor.cancel")}
          </Button>
          <Button onClick={save} disabled={!canSave}>
            {t("dashboard.editor.save")}
          </Button>
        </div>
      </DialogContent>
    </Dialog>
  );
}

function FieldSelect({
  id,
  label,
  value,
  options,
  onChange,
}: {
  id: string;
  label: string;
  value: string;
  options: string[];
  onChange: (next: string) => void;
}) {
  return (
    <div className="grid gap-1.5">
      <Label htmlFor={id}>{label}</Label>
      <Select
        id={id}
        className="w-44"
        value={value}
        onChange={(event) => onChange(event.target.value)}
      >
        {options.map((col) => (
          <option key={col} value={col}>
            {col}
          </option>
        ))}
      </Select>
    </div>
  );
}
