"use client";

import { useState, type ReactNode } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { FormField } from "@/components/ui/form-field";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { useI18n, useT } from "@/i18n";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatNumber } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { useFieldErrors } from "@/lib/use-field-errors";
import { bulkUpdateCities } from "@/services/cities";
import type { City, CityBulkUpdateInput, CityEarningChange } from "@/types/city";

interface BulkEditCitiesDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** The selected areas, cities and districts alike. */
  areas: City[];
  currency: string;
  /** Called once the change is saved, so the screen can clear its selection. */
  onSaved: () => void;
}

/**
 * One change to many areas.
 *
 * **Every field starts switched off, and only what is switched on is sent.** A bulk edit that sent the whole form would quietly give fifty
 * areas whatever the empty boxes said; here an area keeps everything nobody chose to change. The server applies each area the way a
 * single edit would, so a city's new rate still carries the districts that follow it.
 */
export function BulkEditCitiesDialog(props: BulkEditCitiesDialogProps) {
  return (
    <Dialog open={props.open} onOpenChange={props.onOpenChange}>
      <DialogContent className="max-w-xl">
        {/* Its own component, so each opening starts with everything switched off. */}
        <BulkEditForm {...props} />
      </DialogContent>
    </Dialog>
  );
}

/** A field that is changed only when its switch is on. */
function Toggled({
  label,
  hint,
  on,
  onToggle,
  disabled,
  children,
}: {
  label: string;
  hint?: string;
  on: boolean;
  onToggle: (on: boolean) => void;
  disabled?: boolean;
  children?: ReactNode;
}) {
  return (
    <div className={disabled ? "rounded-lg border p-4 opacity-60" : "rounded-lg border p-4"}>
      <div className="flex items-start justify-between gap-4">
        <span className="text-sm">
          <span className="font-medium">{label}</span>
          {hint ? (
            <span className="mt-1 block text-xs leading-5 text-muted-foreground">{hint}</span>
          ) : null}
        </span>
        <Switch checked={on} onCheckedChange={onToggle} disabled={disabled} aria-label={label} />
      </div>
      {on && children ? <div className="mt-4">{children}</div> : null}
    </div>
  );
}

function BulkEditForm({ onOpenChange, areas, currency, onSaved }: BulkEditCitiesDialogProps) {
  const t = useT();
  const { locale } = useI18n();
  const queryClient = useQueryClient();

  const cityCount = areas.filter((area) => !area.is_district).length;
  const districtCount = areas.length - cityCount;
  const onlyDistricts = cityCount === 0;
  const ownDistrictsAtStake = areas
    .filter((area) => !area.is_district)
    .reduce((total, city) => total + (city.districts_with_own_earning ?? 0), 0);

  const [changeEarning, setChangeEarning] = useState(false);
  const [mode, setMode] = useState<CityEarningChange>("set");
  const [earning, setEarning] = useState("");

  // Held as a percentage, sent as a fraction: the same edge the single-area dialog converts at.
  const [changeShare, setChangeShare] = useState(false);
  const [share, setShare] = useState("100");

  const [inherit, setInherit] = useState(false);

  const [changeRadius, setChangeRadius] = useState(false);
  const [radius, setRadius] = useState("1000");

  const [changeActive, setChangeActive] = useState(false);
  const [active, setActive] = useState(true);

  const [overwriteChildren, setOverwriteChildren] = useState(false);

  const fields = useFieldErrors({
    only: ["uuids", "earning_mode", "delivery_earning", "freelance_share", "radius_m", "is_active", "inherit"],
  });

  const numeric = (value: string) => (value.trim() === "" ? Number.NaN : Number(value));

  // Following the city takes the figure and the share from it, so the two cannot be typed alongside it.
  const earningOn = changeEarning && !inherit;
  const shareOn = changeShare && !inherit;

  const earningValid = (() => {
    const value = numeric(earning);
    if (Number.isNaN(value)) return false;
    if (mode === "set") return value >= 0;
    if (mode === "percent") return value >= -100 && value <= 1000;
    return true;
  })();

  const nothingChosen = !earningOn && !shareOn && !inherit && !changeRadius && !changeActive;

  const invalid =
    nothingChosen ||
    (earningOn && !earningValid) ||
    (shareOn && (Number.isNaN(numeric(share)) || numeric(share) < 0 || numeric(share) > 100)) ||
    (changeRadius && (Number.isNaN(numeric(radius)) || numeric(radius) < 100 || numeric(radius) > 100000));

  const rateMoves = earningOn || shareOn;
  const askOverwrite = rateMoves && ownDistrictsAtStake > 0;

  const save = useMutation({
    mutationFn: () => {
      const input: CityBulkUpdateInput = { uuids: areas.map((area) => area.uuid) };

      if (inherit) input.inherit = true;
      if (earningOn) {
        input.earning_mode = mode;
        input.delivery_earning = numeric(earning);
      }
      if (shareOn) input.freelance_share = numeric(share) / 100;
      if (changeRadius) input.radius_m = numeric(radius);
      if (changeActive) input.is_active = active;
      if (askOverwrite) input.overwrite_own_children = overwriteChildren;

      return bulkUpdateCities(input);
    },
    onMutate: fields.reset,
    onSuccess: (response) => {
      const result = response.Model;
      const extras = [
        result && result.followed > 0
          ? t("cities.bulk.resultFollowed", { count: formatNumber(result.followed, locale) })
          : null,
        result && result.overwritten > 0
          ? t("cities.bulk.resultOverwritten", { count: formatNumber(result.overwritten, locale) })
          : null,
        result && result.kept > 0
          ? t("cities.bulk.resultKept", { count: formatNumber(result.kept, locale) })
          : null,
        result && result.not_on_map > 0
          ? t("cities.bulk.resultNotOnMap", { count: formatNumber(result.not_on_map, locale) })
          : null,
      ].filter((line): line is string => line !== null);

      toast.success(
        t("cities.bulk.saved", { count: formatNumber(result?.updated ?? areas.length, locale) }),
        extras.length > 0 ? { description: extras.join(" ") } : undefined,
      );

      onSaved();
      onOpenChange(false);
      void queryClient.invalidateQueries({ queryKey: queryKeys.cities.all });
    },
    onError: (err) => {
      if (!fields.capture(err)) toast.error(friendlyErrorMessage(err, t));
    },
  });

  const earningUnit = mode === "percent" ? "%" : currency;

  return (
    <>
      <DialogHeader>
        <DialogTitle>
          {t("cities.bulk.title", { count: formatNumber(areas.length, locale) })}
        </DialogTitle>
        <DialogDescription>
          {t("cities.bulk.body", {
            // Only what was actually ticked: "0 cities and 2 districts" reads like a mistake.
            what: [
              cityCount > 0 ? t("cities.bulk.countCities", { count: formatNumber(cityCount, locale) }) : null,
              districtCount > 0
                ? t("cities.bulk.countDistricts", { count: formatNumber(districtCount, locale) })
                : null,
            ]
              .filter((part): part is string => part !== null)
              .join(t("cities.bulk.and")),
          })}
        </DialogDescription>
      </DialogHeader>

      <form
        className="space-y-3"
        onSubmit={(event) => {
          event.preventDefault();
          if (invalid || save.isPending) return;
          save.mutate();
        }}
      >
        {/* Only districts have a city to follow; with a city selected the choice is not offered at all. */}
        {onlyDistricts ? (
          <Toggled
            label={t("cities.inherit")}
            hint={t("cities.bulk.inheritHint")}
            on={inherit}
            onToggle={setInherit}
          />
        ) : null}

        <Toggled
          label={t("cities.earning")}
          hint={t("cities.bulk.earningHint")}
          on={earningOn}
          onToggle={setChangeEarning}
          disabled={inherit}
        >
          <div className="grid items-start gap-3 sm:grid-cols-2">
            <FormField label={t("cities.bulk.howToChange")} htmlFor="bulk-earning-mode" error={fields.get("earning_mode")}>
              <Select value={mode} onValueChange={(value) => setMode(value as CityEarningChange)}>
                <SelectTrigger id="bulk-earning-mode">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="set">{t("cities.bulk.mode.set")}</SelectItem>
                  <SelectItem value="add">{t("cities.bulk.mode.add")}</SelectItem>
                  <SelectItem value="percent">{t("cities.bulk.mode.percent")}</SelectItem>
                </SelectContent>
              </Select>
            </FormField>

            <FormField
              label={mode === "set" ? t("cities.bulk.newFigure") : t("cities.bulk.changeBy")}
              htmlFor="bulk-earning"
              required
              error={fields.get("delivery_earning")}
              hint={mode === "set" ? undefined : t("cities.bulk.negativeLowers")}
            >
              <div className="flex items-center gap-2">
                <Input
                  id="bulk-earning"
                  type="number"
                  step="any"
                  min={mode === "set" ? "0" : mode === "percent" ? "-100" : undefined}
                  max={mode === "percent" ? "1000" : undefined}
                  className="numeric"
                  value={earning}
                  onChange={(event) => setEarning(event.target.value)}
                  aria-invalid={fields.get("delivery_earning") !== undefined}
                  autoFocus
                />
                <span className="w-10 shrink-0 text-sm text-muted-foreground" dir="ltr">
                  {earningUnit}
                </span>
              </div>
            </FormField>
          </div>
        </Toggled>

        <Toggled
          label={t("cities.freelanceShare")}
          hint={t("cities.bulk.shareHint")}
          on={shareOn}
          onToggle={setChangeShare}
          disabled={inherit}
        >
          <FormField label={t("cities.freelanceShare")} htmlFor="bulk-share" required error={fields.get("freelance_share")}>
            <div className="flex items-center gap-2 sm:w-1/2">
              <Input
                id="bulk-share"
                type="number"
                min="0"
                max="100"
                step="any"
                className="numeric"
                value={share}
                onChange={(event) => setShare(event.target.value)}
                aria-invalid={fields.get("freelance_share") !== undefined}
              />
              <span className="w-10 shrink-0 text-sm text-muted-foreground" dir="ltr">
                %
              </span>
            </div>
          </FormField>
        </Toggled>

        {/*
          The overwrite question, asked only when a selected city's rate is moving and it has districts priced separately — the same
          question the single edit asks, with the count summed over the selection.
        */}
        {askOverwrite ? (
          <label className="flex items-start gap-3 rounded-lg border border-warning/40 bg-warning/5 p-4">
            <Checkbox
              checked={overwriteChildren}
              onCheckedChange={(checked) => setOverwriteChildren(checked === true)}
              aria-label={t("cities.overwriteChildren")}
              className="mt-0.5"
            />
            <span className="text-sm">
              <span className="flex items-center gap-1.5 font-medium text-warning">
                <AlertTriangle className="h-3.5 w-3.5" aria-hidden />
                {t("cities.overwriteChildren")}
              </span>
              <span className="mt-1 block text-xs leading-5 text-muted-foreground">
                {t("cities.bulk.overwriteHint", { count: formatNumber(ownDistrictsAtStake, locale) })}
              </span>
            </span>
          </label>
        ) : null}

        <Toggled
          label={t("cities.radius")}
          hint={t("cities.bulk.radiusHint")}
          on={changeRadius}
          onToggle={setChangeRadius}
        >
          <FormField label={t("cities.radius")} htmlFor="bulk-radius" required error={fields.get("radius_m")}>
            <div className="flex items-center gap-2 sm:w-1/2">
              <Input
                id="bulk-radius"
                type="number"
                min="100"
                max="100000"
                step="1"
                className="numeric"
                value={radius}
                onChange={(event) => setRadius(event.target.value)}
                aria-invalid={fields.get("radius_m") !== undefined}
              />
              <span className="w-10 shrink-0 text-sm text-muted-foreground" dir="ltr">
                m
              </span>
            </div>
          </FormField>
        </Toggled>

        <Toggled
          label={t("cities.bulk.state")}
          hint={t("cities.activeHint")}
          on={changeActive}
          onToggle={setChangeActive}
        >
          <div className="flex items-center justify-between gap-4">
            <span className="text-sm">{active ? t("cities.bulk.switchOn") : t("cities.bulk.switchOff")}</span>
            <Switch checked={active} onCheckedChange={setActive} aria-label={t("cities.active")} />
          </div>
        </Toggled>

        {fields.get("uuids") ? <p className="text-sm text-destructive">{fields.get("uuids")}</p> : null}

        <DialogFooter className="pt-2">
          <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
            {t("common.cancel")}
          </Button>
          <Button type="submit" loading={save.isPending} disabled={invalid || save.isPending}>
            {save.isPending
              ? t("common.saving")
              : t("cities.bulk.apply", { count: formatNumber(areas.length, locale) })}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}
