/**
 * Create / Add-to Purchase Order page.
 *
 * Supports two flows toggled at the top of the page:
 *   - "create"  → pick a supplier and assemble a list of products, then POST
 *                 to /apiv2/po/new/{supplierId}.
 *   - "add"     → enter an existing PO number and assemble a list of
 *                 additional line items, then POST to
 *                 /apiv2/po/add_products/{poId}.
 *
 * Both modes use the same product-add UX (Search/Paste/Upload) and the same
 * line items table. The "add" mode does NOT pull existing items from the
 * target PO — only the newly assembled items are sent to the backend.
 *
 * State model:
 *   - mode               → "create" | "add" (toggled via Tabs)
 *   - supplierId         → controlled string, only used in create mode
 *   - existingPoInput    → controlled string, only used in add mode
 *   - lineItems[]        → working list (PoLineItem; local-only fields qty +
 *                          moqOverride live here). Cleared when mode changes.
 *   - selectedPids: Set  → checkbox state for the bulk-remove flow
 *   - addOpen            → AddProductsDialog visibility
 *   - submitting         → submit button spinner
 *   - confirmation       → null while building; { poId, itemCount, mode }
 *                          after a successful submit.
 *
 * Dedup is enforced server-naive: when AddProductsDialog returns a list of
 * (pid, qty) pairs, we filter out pids already on the working list and show
 * a brief toast indicating how many were skipped. In "add" mode we do NOT
 * dedup against the target PO (we don't fetch its current contents).
 */

import { useCallback, useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";


import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Loader2, Plus } from "lucide-react";
import { toast } from "sonner";
import { SupplierSelector } from "@/components/create-po/SupplierSelector";
import { LineItemsTable } from "@/components/create-po/LineItemsTable";
import { PoFloatingSelectionBar } from "@/components/create-po/PoFloatingSelectionBar";
import { AddProductsDialog } from "@/components/create-po/AddProductsDialog";
import { ConfirmationView } from "@/components/create-po/ConfirmationView";
import { fetchBatchProducts } from "@/components/create-po/resolveIdentifiers";
import type { PoLineItem } from "@/components/create-po/types";
import { computeCostEach } from "@/components/create-po/types";
import { apiClient } from "@/utils/apiClient";
import {
  submitNewPurchaseOrder,
  addProductsToPurchaseOrder,
} from "@/services/apiv2";

type Mode = "create" | "add";

export default function CreatePurchaseOrder() {
  const [mode, setMode] = useState<Mode>("create");
  const [supplierId, setSupplierId] = useState<string | undefined>(undefined);
  const [existingPoInput, setExistingPoInput] = useState<string>("");
  const [lineItems, setLineItems] = useState<PoLineItem[]>([]);
  const [selectedPids, setSelectedPids] = useState<Set<number>>(new Set());
  const [addOpen, setAddOpen] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [hydrating, setHydrating] = useState(false);
  // Supplier of the existing PO in "add" mode (looked up from /api/purchase-orders/:id/supplier).
  // Drives the Notions branch of the cost fallback so the displayed cost matches what the legacy
  // backend will actually stamp onto the line item.
  const [existingPoSupplierId, setExistingPoSupplierId] = useState<number | undefined>(undefined);
  const [confirmation, setConfirmation] = useState<{
    poId: number;
    itemCount: number;
    mode: Mode;
  } | null>(null);

  // ---- Mode toggle ----------------------------------------------------------
  // Switching modes clears the working list and target identifiers — the two
  // flows submit to different endpoints with different keys, so mixing state
  // would just make for confusing UX.
  const handleModeChange = useCallback((next: string) => {
    if (next !== "create" && next !== "add") return;
    setMode(next);
    setSupplierId(undefined);
    setExistingPoInput("");
    setExistingPoSupplierId(undefined);
    setLineItems([]);
    setSelectedPids(new Set());
  }, []);

  // ---- Add products from any tab (Search/Paste/Upload) ----------------------
  const handleAddProducts = useCallback(
    async (items: Array<{ pid: number; qty: number }>) => {
      if (items.length === 0) return;

      // Dedup against existing line items — silently skip duplicates
      const existing = new Set(lineItems.map((i) => i.pid));
      const fresh = items.filter((i) => !existing.has(i.pid));
      const skipped = items.length - fresh.length;

      if (skipped > 0) {
        toast.info(
          skipped === 1
            ? "1 product already on PO"
            : `${skipped} products already on PO`
        );
      }

      if (fresh.length === 0) return;

      setHydrating(true);
      try {
        const qtyByPid = new Map(fresh.map((i) => [i.pid, i.qty]));
        const hydrated = await fetchBatchProducts(
          fresh.map((i) => i.pid),
          qtyByPid,
          // Tell the backend which fallback branch to use for the server-side
          // current_cost_price field. The UI also re-computes locally via
          // computeCostEach() if the supplier changes mid-flow.
          mode === "create"
            ? supplierId
              ? Number(supplierId)
              : undefined
            : existingPoSupplierId
        );
        if (hydrated.length === 0) {
          toast.error("Could not load product details");
          return;
        }
        // Append in the order returned by the backend (which preserves request order)
        setLineItems((prev) => [...prev, ...hydrated]);
        toast.success(
          hydrated.length === 1
            ? "1 product added"
            : `${hydrated.length} products added`
        );
      } catch (e) {
        console.error(e);
        toast.error("Failed to load product details");
      } finally {
        setHydrating(false);
      }
    },
    [lineItems, mode, supplierId, existingPoSupplierId]
  );

  // ---- Row mutation handlers (passed to LineItemsTable) --------------------
  const handleToggleSelect = useCallback((pid: number) => {
    setSelectedPids((prev) => {
      const next = new Set(prev);
      if (next.has(pid)) next.delete(pid);
      else next.add(pid);
      return next;
    });
  }, []);

  const handleToggleSelectAll = useCallback(
    (selectAll: boolean) => {
      if (selectAll) {
        setSelectedPids(new Set(lineItems.map((i) => i.pid)));
      } else {
        setSelectedPids(new Set());
      }
    },
    [lineItems]
  );

  const handleChangeQty = useCallback((pid: number, qty: number) => {
    setLineItems((prev) =>
      prev.map((i) => (i.pid === pid ? { ...i, qty } : i))
    );
  }, []);

  const handleChangeMoqOverride = useCallback(
    (pid: number, moq: number | undefined) => {
      setLineItems((prev) =>
        prev.map((i) => (i.pid === pid ? { ...i, moqOverride: moq } : i))
      );
    },
    []
  );

  const handleRemoveRow = useCallback((pid: number) => {
    setLineItems((prev) => prev.filter((i) => i.pid !== pid));
    setSelectedPids((prev) => {
      const next = new Set(prev);
      next.delete(pid);
      return next;
    });
  }, []);

  const handleBulkRemove = useCallback(() => {
    setLineItems((prev) => prev.filter((i) => !selectedPids.has(i.pid)));
    setSelectedPids(new Set());
    toast.success(
      selectedPids.size === 1
        ? "1 product removed"
        : `${selectedPids.size} products removed`
    );
  }, [selectedPids]);

  const handleClearSelection = useCallback(() => {
    setSelectedPids(new Set());
  }, []);

  // ---- Submit ---------------------------------------------------------------
  const validItems = lineItems
    .filter((i) => i.qty > 0)
    .map((i) => ({ pid: i.pid, qty: i.qty }));

  const parsedPoId = (() => {
    const trimmed = existingPoInput.trim();
    if (!trimmed) return undefined;
    const n = Number(trimmed);
    return Number.isInteger(n) && n > 0 ? n : undefined;
  })();

  const targetReady = mode === "create" ? !!supplierId : parsedPoId !== undefined;

  // Effective supplier driving the cost-each fallback chain.
  // - create mode → the supplier the user just picked
  // - add mode    → the supplier of the existing PO (looked up below)
  const effectiveSupplierId =
    mode === "create"
      ? supplierId
        ? Number(supplierId)
        : undefined
      : existingPoSupplierId;

  // ---- Look up existing PO supplier in "add" mode ---------------------------
  // The cost-each fallback branches on whether the PO is for Notions (supplier 92),
  // so we need to know the target PO's supplier as soon as the user types a valid id.
  // Debounced via parsedPoId so we don't fire a request on every keystroke.
  useEffect(() => {
    if (mode !== "add" || parsedPoId === undefined) {
      setExistingPoSupplierId(undefined);
      return;
    }
    let cancelled = false;
    apiClient
      .get<{ supplierId: number | null }>(
        `/api/purchase-orders/${parsedPoId}/supplier`
      )
      .then((res) => {
        if (cancelled) return;
        const s = res.data?.supplierId;
        setExistingPoSupplierId(typeof s === "number" && s > 0 ? s : undefined);
      })
      .catch(() => {
        if (!cancelled) setExistingPoSupplierId(undefined);
      });
    return () => {
      cancelled = true;
    };
  }, [mode, parsedPoId]);

  const handleSubmit = useCallback(async () => {
    if (mode === "create" && !supplierId) {
      toast.error("Pick a supplier first");
      return;
    }
    if (mode === "add" && parsedPoId === undefined) {
      toast.error("Enter a valid PO number first");
      return;
    }
    if (validItems.length === 0) {
      toast.error("Add at least one product with a positive quantity");
      return;
    }

    setSubmitting(true);
    try {
      if (mode === "create") {
        const res = await submitNewPurchaseOrder({
          supplierId: supplierId!,
          items: validItems,
        });
        if (!res.success || !res.poId) {
          const msg =
            (typeof res.error === "string" && res.error) ||
            res.message ||
            "PO submission failed";
          toast.error(msg);
          return;
        }
        setConfirmation({
          poId: res.poId,
          itemCount: validItems.length,
          mode: "create",
        });
      } else {
        const res = await addProductsToPurchaseOrder({
          poId: parsedPoId!,
          items: validItems,
        });
        if (!res.success) {
          const msg =
            (typeof res.error === "string" && res.error) ||
            res.message ||
            "Failed to add products to PO";
          toast.error(msg);
          return;
        }
        setConfirmation({
          poId: parsedPoId!,
          itemCount: validItems.length,
          mode: "add",
        });
      }
    } catch (e) {
      console.error(e);
      toast.error(e instanceof Error ? e.message : "Submission failed");
    } finally {
      setSubmitting(false);
    }
  }, [mode, supplierId, parsedPoId, validItems]);

  // ---- Reset for "Create another" / "Add more" ------------------------------
  const handleCreateAnother = useCallback(() => {
    setSupplierId(undefined);
    setExistingPoInput("");
    setExistingPoSupplierId(undefined);
    setLineItems([]);
    setSelectedPids(new Set());
    setConfirmation(null);
  }, []);

  // ---- Confirmation view (post-submit) --------------------------------------
  if (confirmation) {
    return (
      <div className="container mx-auto p-6">
        <ConfirmationView
          poId={confirmation.poId}
          itemCount={confirmation.itemCount}
          mode={confirmation.mode}
          onCreateAnother={handleCreateAnother}
        />
      </div>
    );
  }

  // ---- Builder view ---------------------------------------------------------
  const totalQty = lineItems.reduce((sum, i) => sum + (i.qty > 0 ? i.qty : 0), 0);
  // Use the supplier-aware fallback (same chain the legacy PHP backend uses)
  // so the displayed total reflects what will land on the PO.
  const totalCost = lineItems.reduce(
    (sum, i) =>
      sum + (i.qty > 0 ? i.qty * (computeCostEach(i, effectiveSupplierId) ?? 0) : 0),
    0
  );

  const pageTitle =
    mode === "create" ? "Create Purchase Order" : "Add to Purchase Order";
  const targetCardTitle = mode === "create" ? "Supplier" : "Existing PO";
  const submitLabel =
    mode === "create" ? "Create purchase order" : "Add products to PO";

  return (
    <div className="container mx-auto p-6 space-y-4">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-semibold">{pageTitle}</h1>
      </div>

      <Tabs value={mode} onValueChange={handleModeChange}>
        <TabsList>
          <TabsTrigger value="create">Create new PO</TabsTrigger>
          <TabsTrigger value="add">Add to existing PO</TabsTrigger>
        </TabsList>
      </Tabs>

      <Card>
        <CardHeader>
          <CardTitle>{targetCardTitle}</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="max-w-md">
            {mode === "create" ? (
              <SupplierSelector value={supplierId} onChange={setSupplierId} />
            ) : (
              <div className="space-y-2">
                <Input
                  id="existing-po-id"
                  type="text"
                  inputMode="numeric"
                  pattern="[0-9]*"
                  placeholder="PO number"
                  value={existingPoInput}
                  onChange={(e) =>
                    setExistingPoInput(e.target.value.replace(/[^0-9]/g, ""))
                  }
                />
                {existingPoInput.trim() !== "" && parsedPoId === undefined && (
                  <p className="text-sm text-destructive">
                    Enter a valid positive PO number.
                  </p>
                )}
              </div>
            )}
          </div>
        </CardContent>
      </Card>

      <Card>
        <CardHeader className="flex flex-row items-center justify-between">
          <CardTitle>
            Line Items
            {lineItems.length > 0 && (
              <span className="ml-2 text-sm font-normal text-muted-foreground">
                ({lineItems.length} {lineItems.length === 1 ? "product" : "products"} ·{" "}
                {totalQty.toLocaleString()} units · $
                {totalCost.toLocaleString(undefined, {
                  minimumFractionDigits: 2,
                  maximumFractionDigits: 2,
                })}
                )
              </span>
            )}
          </CardTitle>
          <Button onClick={() => setAddOpen(true)} disabled={hydrating}>
            {hydrating ? (
              <>
                <Loader2 className="h-4 w-4 mr-2 animate-spin" />
                Loading…
              </>
            ) : (
              <>
                <Plus className="h-4 w-4 mr-2" />
                Add products
              </>
            )}
          </Button>
        </CardHeader>
        <CardContent>
          <LineItemsTable
            items={lineItems}
            selectedPids={selectedPids}
            supplierId={effectiveSupplierId}
            onToggleSelect={handleToggleSelect}
            onToggleSelectAll={handleToggleSelectAll}
            onChangeQty={handleChangeQty}
            onChangeMoqOverride={handleChangeMoqOverride}
            onRemove={handleRemoveRow}
          />
        </CardContent>
      </Card>

      <div className="flex justify-end">
        <Button
          size="lg"
          onClick={handleSubmit}
          disabled={submitting || lineItems.length === 0 || !targetReady}
        >
          {submitting ? (
            <>
              <Loader2 className="h-4 w-4 mr-2 animate-spin" />
              Submitting…
            </>
          ) : (
            <>{submitLabel}</>
          )}
        </Button>
      </div>

      <AddProductsDialog
        open={addOpen}
        onOpenChange={setAddOpen}
        existingPids={new Set(lineItems.map((i) => i.pid))}
        onAdd={(result) => {
          void handleAddProducts(result.items);
        }}
      />

      <PoFloatingSelectionBar
        selectedCount={selectedPids.size}
        onClear={handleClearSelection}
        onRemove={handleBulkRemove}
      />
    </div>
  );
}
