"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useMutation, useQueryClient } from "@tanstack/react-query";
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 {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useT } from "@/i18n";
import { applyFieldErrors, friendlyErrorMessage } from "@/lib/errors";
import { queryKeys } from "@/lib/query-keys";
import { resolveOrderItemNote } from "@/services/orders";
import type { ItemNoteResolution } from "@/types/order";

// The server's own limit; saying it here keeps the refusal on this side of the wire.
const NOTE_MAX = 255;

const RESOLUTIONS = ["alternative_agreed", "line_cancelled", "shortage_accepted", "no_action_needed"] as const;

const schema = z
  .object({
    resolution: z.enum(RESOLUTIONS),
    note: z.string().trim().max(NOTE_MAX, "errors.generic"),
  })
  // "The customer chose something else" without saying what is not an answer anybody can act on at the
  // counter or at the door, so the words are required for that one outcome and optional for the rest.
  .refine((values) => values.resolution !== "alternative_agreed" || values.note.length > 0, {
    path: ["note"],
    message: "errors.required",
  });

type Values = z.infer<typeof schema>;

/** The line being answered, and the order it actually sits on. */
export interface NoteTarget {
  /**
   * The order that owns the line.
   *
   * Not always the order on screen: a wrapped order keeps its lines twice, and the captain's note is written on
   * the **collection leg's** copy, because that is the one carried to the counter. Answering it on the delivery's
   * copy would answer a line nobody flagged.
   */
  orderUuid: string;
  uuid: string;
  name: string;
  /** The captain's own words, shown back so the answer is written against what was actually said. */
  captainNote: string | null;
  /** The kind of problem the captain picked from the list, already translated. */
  reasonLabel: string | null;
  /** Whether the line is already called off, which is what `line_cancelled` needs to be true. */
  cancelled: boolean;
}

interface Props {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** Null while the dialog is closed. */
  item: NoteTarget | null;
}

/**
 * Close a product note: what the office settled with the customer about a product the captain flagged.
 *
 * The captain says at the counter what is wrong — a colour nobody has, poor quality, a crushed box. Somebody
 * here rings the customer, and what the customer said has to land back on the product: otherwise the only
 * record of the call is in the head of whoever made it, and the next person to open the order rings them again.
 *
 * **It changes nothing about the basket.** Cancelling a line and accepting a shortage move money and have their
 * own buttons; this writes down an answer, which is why it sits on `orders.update` rather than `orders.cancel`.
 */
export function ResolveNoteDialog(props: Props) {
  return (
    <Dialog open={props.open} onOpenChange={props.onOpenChange}>
      <DialogContent>
        {/* Its own component, so every open starts empty rather than with last time's answer. */}
        {props.item ? <ResolveNoteForm {...props} item={props.item} /> : null}
      </DialogContent>
    </Dialog>
  );
}

function ResolveNoteForm({ onOpenChange, item }: Props & { item: NoteTarget }) {
  const t = useT();
  const queryClient = useQueryClient();
  const form = useForm<Values>({
    resolver: zodResolver(schema),
    defaultValues: { resolution: "alternative_agreed", note: "" },
  });

  // `useWatch` rather than `form.watch()`: the subscription is this one field's, and a watch() call here
  // re-renders the dialog on every keystroke in the note box to answer a question about the select.
  const resolution = useWatch({ control: form.control, name: "resolution" });

  const resolve = useMutation({
    mutationFn: (values: Values) =>
      resolveOrderItemNote(item.orderUuid, item.uuid, values.resolution, values.note.trim() || null),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("orders.items.note.done"));
      onOpenChange(false);
      // The line on screen, and the order's open-issue count that was holding this note.
      void queryClient.invalidateQueries({ queryKey: queryKeys.orders.detail(item.orderUuid) });
      void queryClient.invalidateQueries({ queryKey: ["orders"] });
    },
    onError: (error) => {
      // A refused `note` belongs under the box. Everything else the server refuses — the note already
      // answered, `line_cancelled` on a live line — is about the order, not the field, and stays a toast.
      const shown = applyFieldErrors(error, (field, message) => form.setError(field as never, { message }), {
        only: ["resolution", "note"],
      });
      if (shown === 0) toast.error(friendlyErrorMessage(error, t));
    },
  });

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("orders.items.note.title", { item: item.name })}</DialogTitle>
        <DialogDescription>{t("orders.items.note.body")}</DialogDescription>
      </DialogHeader>

      {/* What the captain actually said, in front of whoever is answering it: the answer is to this, not to a line number. */}
      {item.captainNote || item.reasonLabel ? (
        <p className="rounded-md border bg-muted/40 p-3 text-sm text-foreground">
          <span className="text-muted-foreground">{t("orders.items.captainNote")}: </span>
          {item.reasonLabel ? <span className="font-medium">{item.reasonLabel}</span> : null}
          {item.reasonLabel && item.captainNote ? " · " : null}
          {item.captainNote}
        </p>
      ) : null}

      <form
        className="space-y-5"
        onSubmit={form.handleSubmit((values) => {
          if (resolve.isPending) return;
          resolve.mutate(values);
        })}
      >
        <FormField
          label={t("orders.items.note.resolution")}
          htmlFor="resolve-note-resolution"
          required
          error={form.formState.errors.resolution?.message}
        >
          <Controller
            control={form.control}
            name="resolution"
            render={({ field }) => (
              <Select value={field.value} onValueChange={field.onChange}>
                <SelectTrigger id="resolve-note-resolution">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {RESOLUTIONS.map((value) => (
                    <SelectItem
                      key={value}
                      value={value}
                      // Only on a line already called off: the office cancels it first, then records that as what
                      // was agreed. Offering it on a live line invites a record the order itself contradicts.
                      disabled={value === "line_cancelled" && !item.cancelled}
                    >
                      {t(`orders.items.note.resolutions.${value}` as never)}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            )}
          />
        </FormField>

        <FormField
          label={t("orders.items.note.note")}
          htmlFor="resolve-note-note"
          required={resolution === "alternative_agreed"}
          error={form.formState.errors.note?.message}
          hint={t("orders.items.note.noteHint")}
        >
          <Textarea
            id="resolve-note-note"
            rows={3}
            maxLength={NOTE_MAX}
            placeholder={t("orders.items.note.notePlaceholder")}
            {...form.register("note")}
          />
        </FormField>

        <DialogFooter>
          <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
            {t("common.back")}
          </Button>
          <Button type="submit" loading={resolve.isPending} disabled={resolve.isPending}>
            {resolve.isPending ? t("common.saving") : t("orders.items.note.confirm")}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}

export type { ItemNoteResolution };
