"use client";

import Link from "next/link";
import { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { RotateCcw, TriangleAlert, UserMinus } from "lucide-react";
import { toast } from "sonner";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { FormField } from "@/components/ui/form-field";
import { Textarea } from "@/components/ui/input";
import { useCan } from "@/features/auth/use-can";
import { useT } from "@/i18n";
import { applyFieldErrors, friendlyErrorMessage } from "@/lib/errors";
import { queryKeys } from "@/lib/query-keys";
import { continueWithoutSupplier, recollectFromSupplier } from "@/services/orders";
import type { OrderCollectionLeg } from "@/types/order";

const REASON_MAX = 255;

const reasonSchema = z.object({
  reason: z.string().trim().min(1, "errors.required").max(REASON_MAX, "errors.generic"),
});

type ReasonValues = z.infer<typeof reasonSchema>;

/**
 * What the office does about a collection that failed.
 *
 * A captain who could not get the goods used to leave the order stuck for good: the delivery waits on every collection of its order. The office
 * now has two ways out, and this is where they are, on the supplier that failed: send another captain (a replacement collection is opened for the
 * same supplier and items) or carry on without that supplier (its items are called off and the delivery is priced again).
 */
export function FailedCollectionPanel({ leg, deliveryUuid }: { leg: OrderCollectionLeg; deliveryUuid: string }) {
  const t = useT();
  const { can } = useCan();
  const queryClient = useQueryClient();
  const [carryOn, setCarryOn] = useState(false);

  const refresh = () => {
    void queryClient.invalidateQueries({ queryKey: queryKeys.orders.detail(deliveryUuid) });
    void queryClient.invalidateQueries({ queryKey: ["orders"] });
  };

  const recollect = useMutation({
    mutationFn: () => recollectFromSupplier(leg.uuid),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("orders.recover.recollected"));
      refresh();
    },
    onError: (error) => toast.error(friendlyErrorMessage(error, t)),
  });

  return (
    <div className="mt-3 rounded-lg border border-destructive/40 bg-destructive-soft/30 p-4">
      <p className="flex items-start gap-2 text-sm font-medium text-destructive">
        <TriangleAlert className="mt-0.5 h-4 w-4 shrink-0" aria-hidden />
        <span>
          {t("orders.recover.title")}
          {leg.failed_reason ? <span className="ms-1 font-normal text-foreground">{leg.failed_reason}</span> : null}
        </span>
      </p>

      <div className="mt-3 flex flex-wrap gap-2">
        {can("orders.assign") ? (
          <div className="max-w-xs">
            <Button type="button" size="sm" loading={recollect.isPending} disabled={recollect.isPending} onClick={() => recollect.mutate()}>
              <RotateCcw className="h-4 w-4" aria-hidden />
              {recollect.isPending ? t("common.saving") : t("orders.recover.recollect")}
            </Button>
            <p className="mt-1 text-xs text-muted-foreground">{t("orders.recover.recollectHint")}</p>
          </div>
        ) : null}
        {can("orders.cancel") ? (
          <div className="max-w-xs">
            <Button type="button" size="sm" variant="outline" onClick={() => setCarryOn(true)}>
              <UserMinus className="h-4 w-4" aria-hidden />
              {t("orders.recover.continue")}
            </Button>
            <p className="mt-1 text-xs text-muted-foreground">{t("orders.recover.continueHint")}</p>
          </div>
        ) : null}
      </div>

      <Dialog open={carryOn} onOpenChange={setCarryOn}>
        <DialogContent>
          {/* Its own component, so every open starts with an empty reason. */}
          <ContinueWithoutForm leg={leg} deliveryUuid={deliveryUuid} onClose={() => setCarryOn(false)} onDone={refresh} />
        </DialogContent>
      </Dialog>
    </div>
  );
}

function ContinueWithoutForm({ leg, deliveryUuid, onClose, onDone }: { leg: OrderCollectionLeg; deliveryUuid: string; onClose: () => void; onDone: () => void }) {
  const t = useT();
  const form = useForm<ReasonValues>({ resolver: zodResolver(reasonSchema), defaultValues: { reason: "" } });

  const carryOn = useMutation({
    mutationFn: (values: ReasonValues) => continueWithoutSupplier(leg.uuid, values.reason.trim()),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("orders.recover.continued"));
      onDone();
      onClose();
    },
    onError: (error) => {
      // A missing or too long reason belongs under the box; "nothing would be left to deliver" is not a field, and stays a toast.
      const shown = applyFieldErrors(error, (field, message) => form.setError(field as never, { message }), { only: ["reason"] });
      if (shown === 0) toast.error(friendlyErrorMessage(error, t));
    },
  });

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("orders.recover.dialogTitle", { number: leg.order_number })}</DialogTitle>
        <DialogDescription>{t("orders.recover.dialogBody")}</DialogDescription>
      </DialogHeader>

      <form
        className="space-y-4"
        onSubmit={form.handleSubmit((values) => {
          if (carryOn.isPending) return;
          carryOn.mutate(values);
        })}
        data-delivery={deliveryUuid}
      >
        <FormField label={t("orders.recover.reasonLabel")} htmlFor="carry-on-reason" required error={form.formState.errors.reason?.message} hint={t("orders.recover.reasonHint")}>
          <Textarea id="carry-on-reason" rows={3} maxLength={REASON_MAX} {...form.register("reason")} />
        </FormField>
        <DialogFooter>
          <Button type="button" variant="outline" onClick={onClose}>
            {t("common.back")}
          </Button>
          <Button type="submit" variant="destructive" loading={carryOn.isPending} disabled={carryOn.isPending}>
            {carryOn.isPending ? t("common.saving") : t("orders.recover.confirm")}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}

/** A failed collection the office already dealt with: history, one line, with where it went. */
export function ResolvedCollectionNote({ leg }: { leg: OrderCollectionLeg }) {
  const t = useT();

  return (
    <p className="mt-3 text-xs text-muted-foreground">
      {leg.replaced_by ? (
        <>
          {t("orders.recover.resolvedReplaced")}{" "}
          <Link href={`/orders/${leg.replaced_by.uuid}`} dir="ltr" className="font-medium text-primary hover:underline">
            {leg.replaced_by.order_number}
          </Link>
        </>
      ) : (
        t("orders.recover.resolvedWithout")
      )}
      {leg.failed_reason ? <span> · {leg.failed_reason}</span> : null}
    </p>
  );
}
