import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo, useState } from "react";

import { api } from "@/services/api";
import type { SubmitBookingResponse } from "@/services/bookings.service";
import type { Freight } from "@edr/types";

import { useBookingDocumentSetting } from "./useBookingDocumentSetting";

export type DocumentsValue = Record<string, File | File[] | null>;

/** True when a SmartFileInput value holds at least one file for a key. */
function hasFile(value: File | File[] | null | undefined): boolean {
  if (!value) return false;
  return Array.isArray(value) ? value.length > 0 : true;
}

/**
 * Drives the "update documents and resubmit" flow for a booking that staff
 * returned with `CHANGES_REQUESTED`.
 *
 * The document section mirrors the new-booking step: it's driven by the
 * company's onboarding document setting (TIN, license, ID, passport, …) via
 * SmartFileInput. Fields already present on the booking are treated as on file;
 * any required field with neither an existing file nor a freshly-picked one
 * blocks resubmit.
 *
 * Shared by the booking detail page and the home-page modal.
 */
export function useResubmitFlow(
  booking: Freight.IBooking,
  opts?: { onResubmitted?: () => void },
) {
  const queryClient = useQueryClient();
  const settingQuery = useBookingDocumentSetting();

  // The booking may arrive from the lightweight list endpoint, which omits
  // `files`. Fetch the full record so the already-submitted documents (and the
  // required-field check that depends on them) are accurate everywhere.
  const filesAlreadyLoaded = booking.files !== undefined;
  const detailQuery = useQuery(
    api.bookings.get.queryOptions({
      input: { id: booking.id },
      enabled: !filesAlreadyLoaded,
    }),
  );
  const detailed = detailQuery.data ?? booking;
  const files = detailed.files ?? [];

  // Freshly-selected files keyed by fileKey (SmartFileInput value).
  const [documents, setDocuments] = useState<DocumentsValue>({});
  const [priceChange, setPriceChange] = useState<SubmitBookingResponse | null>(
    null,
  );
  const [validationError, setValidationError] = useState<string>("");
  // Only surface per-field "required" errors once the user has tried to submit.
  const [showErrors, setShowErrors] = useState(false);

  // Codes already attached to the booking from the original submission.
  const existingCodes = useMemo(
    () => new Set(files.map((f) => f.code)),
    [files],
  );

  const fields = settingQuery.data?.fields ?? [];

  // Required fields that have neither an existing file nor a newly-picked one.
  const missingRequiredKeys = useMemo(() => {
    return fields
      .filter((f) => f.isRequired)
      .filter(
        (f) => !existingCodes.has(f.fileKey) && !hasFile(documents[f.fileKey]),
      )
      .map((f) => f.fileKey);
  }, [fields, existingCodes, documents]);

  const hasNewFiles = Object.values(documents).some(hasFile);

  const invalidateLists = () =>
    queryClient.invalidateQueries({ queryKey: api.bookings.list.queryKey() });

  const updateMutation = useMutation({
    mutationFn: (files: DocumentsValue) =>
      api.bookings.update.call({ id: booking.id, dto: {}, documents: files }),
  });

  const submitMutation = useMutation({
    mutationFn: () => api.bookings.submit.call({ id: booking.id }),
    onSuccess: (result) => {
      if (result.priceChanged) {
        setPriceChange(result);
        return;
      }
      finishResubmit();
    },
  });

  const confirmSubmitMutation = useMutation({
    mutationFn: () => api.bookings.confirmSubmit.call({ id: booking.id }),
    onSuccess: () => {
      setPriceChange(null);
      finishResubmit();
    },
  });

  function finishResubmit() {
    queryClient.invalidateQueries({
      queryKey: api.bookings.get.queryKey({ id: booking.id }),
    });
    invalidateLists();
    opts?.onResubmitted?.();
  }

  /**
   * Validate required documents, upload any newly-selected ones, then resubmit
   * the booking for review.
   */
  function resubmit() {
    if (missingRequiredKeys.length > 0) {
      setShowErrors(true);
      setValidationError(
        "Please attach all required documents before resubmitting.",
      );
      return;
    }
    setShowErrors(false);
    setValidationError("");

    const files: DocumentsValue = {};
    for (const [key, value] of Object.entries(documents)) {
      if (hasFile(value)) files[key] = value;
    }

    if (Object.keys(files).length > 0) {
      updateMutation.mutate(files, {
        onSuccess: () => {
          setDocuments({});
          submitMutation.mutate();
        },
      });
    } else {
      submitMutation.mutate();
    }
  }

  const isBusy =
    settingQuery.isLoading ||
    updateMutation.isPending ||
    submitMutation.isPending ||
    confirmSubmitMutation.isPending;

  return {
    booking: detailed,
    /** Documents already attached to the booking from the original submission. */
    files,
    setting: settingQuery.data,
    settingLoading: settingQuery.isLoading || detailQuery.isLoading,
    existingCodes,
    documents,
    setDocuments,
    hasNewFiles,
    missingRequiredKeys,
    /** Per-field errors for SmartFileInput; only set after a failed submit. */
    fieldErrors: showErrors
      ? Object.fromEntries(missingRequiredKeys.map((k) => [k, "Required"]))
      : {},
    canResubmit: missingRequiredKeys.length === 0,
    validationError,
    resubmit,
    isBusy,
    priceChange,
    clearPriceChange: () => setPriceChange(null),
    confirmSubmit: () => confirmSubmitMutation.mutate(),
    confirmSubmitPending: confirmSubmitMutation.isPending,
    mutations: [updateMutation, submitMutation, confirmSubmitMutation] as const,
  };
}

export type ResubmitFlowController = ReturnType<typeof useResubmitFlow>;
