"use client";

import { useEffect, useId, useMemo, useState, type FormEvent, type KeyboardEvent } from "react";
import { CheckCircle2, Loader2, Search, X } from "lucide-react";
import { catalogApi } from "@/lib/api/catalog";
import type { ApiProduct } from "@/lib/api/types";
import type { ChatUnit, EnquiryFormBlock } from "@/lib/chat/types";
import { cn } from "@/lib/utils";
import { useChatBlocks } from "../ChatBlockContext";
import {
  BlockCard,
  Field,
  FormClosedNote,
  FormHeader,
  cancelButtonClass,
  describedBy,
  fieldErrorsFrom,
  inputClass,
  primaryButtonClass,
  useFormDismiss,
} from "./shared";

/** Same list as the Rate Finder; used only if the server sends none. */
const DEFAULT_REQUIRED_BY = [
  "As soon as possible",
  "Within 3 days",
  "Within 1 week",
  "Within 15 days",
  "Within a month",
];

const CITY_MAX = 120;
const NOTE_MAX = 2000;

type Selected = { id: number; name: string };
type Errors = Partial<Record<"product" | "quantity" | "unit" | "city" | "required_by" | "note", string>>;

/** 422 keys (payload names) → form fields. */
const SERVER_FIELD: Record<string, keyof Errors> = {
  commodity_product_id: "product",
  quantity: "quantity",
  unit_id: "unit",
  unit_label: "unit",
  delivery_city: "city",
  required_by: "required_by",
  description: "note",
};

function initialUnitId(units: ChatUnit[], unitId?: number | null, unitLabel?: string | null): string {
  if (unitId && units.some((u) => u.id === unitId)) return String(unitId);
  const label = unitLabel?.trim().toLowerCase();
  if (label) {
    const match = units.find(
      (u) => u.short_name?.toLowerCase() === label || u.name.toLowerCase() === label
    );
    if (match) return String(match.id);
  }
  // No match: leave the placeholder selected rather than silently picking
  // units[0]; the typed label is sent as-is instead (see onSubmit).
  return "";
}

/**
 * Product typeahead: debounced 300 ms and aborted on every keystroke so a slow
 * early response can't repaint the list with stale matches (RateFinderForm
 * pattern).
 */
function useProductSearch(query: string, enabled: boolean) {
  const [results, setResults] = useState<ApiProduct[]>([]);
  const [searching, setSearching] = useState(false);

  useEffect(() => {
    const term = query.trim();
    if (!enabled || !term) {
      setResults([]);
      setSearching(false);
      return;
    }
    const controller = new AbortController();
    setSearching(true);
    const timer = setTimeout(() => {
      catalogApi
        .products({ search: term, per_page: 8 }, { signal: controller.signal })
        .then((r) => {
          if (!controller.signal.aborted) setResults(r.data);
        })
        .catch(() => {
          if (!controller.signal.aborted) setResults([]);
        })
        .finally(() => {
          if (!controller.signal.aborted) setSearching(false);
        });
    }, 300);

    return () => {
      controller.abort();
      clearTimeout(timer);
    };
  }, [query, enabled]);

  return { results, searching };
}

export default function EnquiryForm({ block, formKey }: { block: EnquiryFormBlock; formKey: string }) {
  const { copy, busy, pendingFormKey, isFormSubmitted, sendAction } = useChatBlocks();
  const id = useId();
  const prefill = block.prefill ?? {};
  const units = useMemo(() => (Array.isArray(block.units) ? block.units : []), [block.units]);
  const options = useMemo(() => {
    const fromServer = Array.isArray(block.required_by_options)
      ? block.required_by_options.filter((o): o is string => typeof o === "string" && o.trim() !== "")
      : [];
    return fromServer.length ? fromServer : DEFAULT_REQUIRED_BY;
  }, [block.required_by_options]);

  const [selected, setSelected] = useState<Selected | null>(() =>
    prefill.product && typeof prefill.product.id === "number" ? { id: prefill.product.id, name: prefill.product.name } : null
  );
  const [query, setQuery] = useState(() => (prefill.product ? "" : (prefill.product_query ?? "")));
  const [listOpen, setListOpen] = useState(() => !prefill.product && !!prefill.product_query);
  const [activeIndex, setActiveIndex] = useState(-1);
  const [quantity, setQuantity] = useState(() =>
    typeof prefill.quantity === "number" && prefill.quantity > 0 ? String(prefill.quantity) : ""
  );
  const [unitId, setUnitId] = useState(() => initialUnitId(units, prefill.unit_id, prefill.unit_label));
  const [city, setCity] = useState(() => prefill.delivery_city ?? "");
  const [requiredBy, setRequiredBy] = useState(() =>
    prefill.required_by && options.includes(prefill.required_by) ? prefill.required_by : options[0]
  );
  const [note, setNote] = useState(() => prefill.description ?? "");
  const prefillUnitLabel = typeof prefill.unit_label === "string" ? prefill.unit_label.trim() : "";
  const [errors, setErrors] = useState<Errors>({});
  const [formError, setFormError] = useState<string | null>(null);

  const submitted = isFormSubmitted(formKey);
  const submitting = pendingFormKey === formKey;
  const locked = submitted || busy;
  const { dismissed, canClose, close } = useFormDismiss(formKey);
  const { results, searching } = useProductSearch(query, !selected && !submitted);

  const f = {
    product: `${id}-product`,
    listbox: `${id}-products`,
    quantity: `${id}-quantity`,
    unit: `${id}-unit`,
    city: `${id}-city`,
    requiredBy: `${id}-required-by`,
    note: `${id}-note`,
  };

  if (dismissed && !submitted) return <FormClosedNote />;

  if (submitted) {
    return (
      <BlockCard>
        <p className="flex items-center gap-1.5 font-semibold text-emerald-700">
          <CheckCircle2 size={14} aria-hidden /> {copy.enquirySubmitted}
        </p>
      </BlockCard>
    );
  }

  const choose = (product: ApiProduct) => {
    setSelected({ id: product.id, name: product.name });
    setQuery("");
    setListOpen(false);
    setActiveIndex(-1);
    setErrors((e) => ({ ...e, product: undefined }));
  };

  const onSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
    if (!listOpen || results.length === 0) return;
    if (event.key === "ArrowDown") {
      event.preventDefault();
      setActiveIndex((i) => (i + 1) % results.length);
    } else if (event.key === "ArrowUp") {
      event.preventDefault();
      setActiveIndex((i) => (i <= 0 ? results.length - 1 : i - 1));
    } else if (event.key === "Enter" && activeIndex >= 0) {
      event.preventDefault();
      choose(results[activeIndex]);
    } else if (event.key === "Escape") {
      // Close the list without letting the panel's Esc handler close the chat.
      event.stopPropagation();
      setListOpen(false);
    }
  };

  function validate(): Errors {
    const next: Errors = {};
    if (!selected) next.product = copy.errProduct;
    const qty = Number(quantity);
    if (!quantity.trim() || !Number.isFinite(qty) || qty <= 0) next.quantity = copy.errQuantity;
    const trimmedCity = city.trim();
    if (!trimmedCity || trimmedCity.length > CITY_MAX) next.city = copy.errCity;
    if (!options.includes(requiredBy)) next.required_by = copy.errRequiredBy;
    // Either a unit from the list or the label that came with the prefill. With
    // no unit list at all there is nothing to pick, so the server decides.
    if (units.length > 0 && !unitId && !prefillUnitLabel) next.unit = copy.errUnit;
    return next;
  }

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (locked) return;
    const found = validate();
    setErrors(found);
    setFormError(null);
    if (!selected || Object.keys(found).length) return;

    const unit = units.find((u) => String(u.id) === unitId) ?? null;
    const payload: Record<string, unknown> = {
      commodity_product_id: selected.id,
      quantity: Number(quantity),
      delivery_city: city.trim(),
      required_by: requiredBy,
    };
    if (unit) {
      payload.unit_id = unit.id;
      payload.unit_label = unit.short_name || unit.name;
    } else {
      payload.unit_id = null;
      if (prefillUnitLabel) payload.unit_label = prefillUnitLabel;
    }
    if (note.trim()) payload.description = note.trim().slice(0, NOTE_MAX);

    const error = await sendAction(
      { type: "enquiry.submit", payload },
      { formKey, inlineValidation: true }
    );
    if (error?.kind === "validation") {
      const mapped: Errors = {};
      for (const [field, message] of Object.entries(fieldErrorsFrom(error.errors))) {
        const target = SERVER_FIELD[field];
        if (target && !mapped[target]) mapped[target] = message;
      }
      setErrors(mapped);
      if (!Object.keys(mapped).length) setFormError(error.message);
    }
  }

  const showList = listOpen && !selected && query.trim() !== "";

  return (
    <BlockCard>
      <FormHeader title={copy.enquiryFormTitle} onClose={close} canClose={canClose} />
      <form onSubmit={onSubmit} noValidate className="space-y-2.5">
        <fieldset disabled={locked} className="space-y-2.5">
          <Field id={f.product} label={copy.product} required error={errors.product}>
            {selected ? (
              <div className="flex items-center justify-between gap-2 rounded-lg border border-gray-200 bg-red-50/40 px-3 py-2">
                <span className="truncate text-sm font-medium text-gray-900">{selected.name}</span>
                <button
                  type="button"
                  onClick={() => {
                    setSelected(null);
                    setListOpen(true);
                  }}
                  className="inline-flex shrink-0 items-center gap-0.5 text-[11px] font-semibold text-[#CC1414] hover:underline"
                >
                  <X size={12} aria-hidden /> {copy.changeProduct}
                </button>
              </div>
            ) : (
              <div className="relative">
                <Search
                  size={14}
                  className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400"
                  aria-hidden
                />
                <input
                  id={f.product}
                  type="text"
                  role="combobox"
                  aria-autocomplete="list"
                  aria-expanded={showList}
                  aria-controls={f.listbox}
                  aria-activedescendant={showList && activeIndex >= 0 ? `${f.listbox}-${activeIndex}` : undefined}
                  autoComplete="off"
                  value={query}
                  onChange={(e) => {
                    setQuery(e.target.value);
                    setListOpen(true);
                    setActiveIndex(-1);
                  }}
                  onFocus={() => setListOpen(true)}
                  onBlur={() => setListOpen(false)}
                  onKeyDown={onSearchKeyDown}
                  placeholder={copy.productSearchPlaceholder}
                  className={cn(inputClass, "pl-8")}
                  {...describedBy(f.product, errors.product)}
                />
                {showList ? (
                  <ul
                    id={f.listbox}
                    role="listbox"
                    aria-label={copy.product}
                    className="absolute left-0 right-0 top-full z-10 mt-1 max-h-48 overflow-y-auto rounded-lg bg-white py-1 shadow-lg ring-1 ring-gray-200"
                  >
                    {searching && results.length === 0 ? (
                      <li className="flex items-center gap-1.5 px-3 py-2 text-gray-500" role="presentation">
                        <Loader2 size={12} className="animate-spin" aria-hidden /> {copy.productSearching}
                      </li>
                    ) : results.length === 0 ? (
                      <li className="px-3 py-2 text-gray-500" role="presentation">
                        {copy.productNoResults}
                      </li>
                    ) : (
                      results.map((product, i) => (
                        <li
                          key={product.id}
                          id={`${f.listbox}-${i}`}
                          role="option"
                          aria-selected={i === activeIndex}
                          // mousedown, not click: fires before the input's blur closes the list.
                          onMouseDown={(e) => {
                            e.preventDefault();
                            choose(product);
                          }}
                          className={cn(
                            "cursor-pointer px-3 py-1.5",
                            i === activeIndex ? "bg-red-50 text-[#A50E0E]" : "hover:bg-gray-50"
                          )}
                        >
                          <span className="block truncate text-xs font-medium">{product.name}</span>
                          {product.category?.name ? (
                            <span className="block truncate text-[10px] text-gray-500">{product.category.name}</span>
                          ) : null}
                        </li>
                      ))
                    )}
                  </ul>
                ) : null}
              </div>
            )}
          </Field>

          <div className="grid grid-cols-[1fr_auto] gap-2">
            <Field id={f.quantity} label={copy.quantity} required error={errors.quantity}>
              <input
                id={f.quantity}
                type="number"
                inputMode="decimal"
                min={0}
                step="any"
                value={quantity}
                onChange={(e) => setQuantity(e.target.value)}
                placeholder="20"
                className={inputClass}
                {...describedBy(f.quantity, errors.quantity)}
              />
            </Field>
            {units.length > 0 ? (
              <Field id={f.unit} label={copy.unit} error={errors.unit}>
                <select
                  id={f.unit}
                  value={unitId}
                  onChange={(e) => {
                    setUnitId(e.target.value);
                    setErrors((prev) => ({ ...prev, unit: undefined }));
                  }}
                  className={cn(inputClass, "w-28 px-2")}
                  {...describedBy(f.unit, errors.unit)}
                >
                  {/* Empty value = no unit from the list; shows the prefilled label when there is one. */}
                  <option value="">{prefillUnitLabel || copy.selectUnit}</option>
                  {units.map((u) => (
                    <option key={u.id} value={u.id}>
                      {u.short_name || u.name}
                    </option>
                  ))}
                </select>
              </Field>
            ) : null}
          </div>

          <Field id={f.city} label={copy.deliveryCity} required error={errors.city}>
            <input
              id={f.city}
              type="text"
              autoComplete="address-level2"
              maxLength={CITY_MAX}
              value={city}
              onChange={(e) => setCity(e.target.value)}
              placeholder={copy.cityPlaceholder}
              className={inputClass}
              {...describedBy(f.city, errors.city)}
            />
          </Field>

          <Field id={f.requiredBy} label={copy.requiredBy} required error={errors.required_by}>
            <select
              id={f.requiredBy}
              value={requiredBy}
              onChange={(e) => setRequiredBy(e.target.value)}
              className={inputClass}
              {...describedBy(f.requiredBy, errors.required_by)}
            >
              {options.map((option) => (
                <option key={option} value={option}>
                  {option}
                </option>
              ))}
            </select>
          </Field>

          <Field id={f.note} label={copy.note} optional error={errors.note}>
            <textarea
              id={f.note}
              rows={2}
              maxLength={NOTE_MAX}
              value={note}
              onChange={(e) => setNote(e.target.value)}
              placeholder={copy.notePlaceholder}
              className={cn(inputClass, "resize-none")}
              {...describedBy(f.note, errors.note)}
            />
          </Field>
        </fieldset>

        {formError ? (
          <p role="alert" className="rounded-md bg-red-50 px-2 py-1.5 text-[11px] text-red-700">
            {formError}
          </p>
        ) : null}

        <div className="grid grid-cols-[auto_1fr] gap-2">
          <button type="button" onClick={close} disabled={!canClose} className={cancelButtonClass}>
            {copy.cancel}
          </button>
          <button type="submit" disabled={locked} className={primaryButtonClass}>
            {submitting ? (
              <>
                <Loader2 size={13} className="animate-spin" aria-hidden /> {copy.submitting}
              </>
            ) : (
              copy.submitEnquiry
            )}
          </button>
        </div>
      </form>
    </BlockCard>
  );
}
