"use client";

import * as React from "react";

import { useChanged } from "@/hooks/use-changed";

import { useRouter } from "next/navigation";
import { ExternalLink, FileSearch, Loader2, TriangleAlert, X } from "lucide-react";

import { notifyTutorialAction } from "@/components/customer-tutorial/tutorial-actions";
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { cn } from "@/lib/utils";
import { buildCustomerSidebarPanelsFromNavigation, type CustomerNavigationPayload } from "@/navigation/sidebar/dynamic-sidebar";
import type { NavMainItem } from "@/navigation/sidebar/frontend-items";
import type { ConsolidatedHolding } from "@/app/customer/[tenant]/reports/consolidated-holdings-report/_components/consolidated-holdings-report/schema";
import { formatMoneyExact } from "@/lib/format/numbers";
import {
  ConsolidatedIndividualDialog,
  type ConsolidatedIndividualTarget,
} from "@/app/customer/[tenant]/reports/consolidated-holdings-report/_components/consolidated-holdings-report/consolidated-individual-dialog";

import {
  applyCompletion,
  buildHoldingFacets,
  describeQuery,
  highlightTerms,
  removeQueryToken,
  splitHighlight,
  suggestCompletions,
  type Suggestion,
} from "./holdings-search-assist";
import { setChrHoldingsSearchHandoff } from "./holdings-search-handoff";
import {
  type CollapsedHoldingHit,
  clearCachedHoldings,
  collapsedSubtitle,
  collapseHoldingsByIsin,
  consolidatedReportHref,
  countCollapsedByAssetClass,
  getCachedHoldings,
  hasConsolidatedHoldingsAccess,
  holdingMatchesQuery,
  holdingsListErrorMessage,
  HOLDINGS_SEARCH_LOAD_TIMEOUT_MS,
  HOLDINGS_SEARCH_MAX_RESULTS,
  parseHoldingsListResponse,
  setCachedHoldings,
  unpopulatedFilterKeys,
} from "./holdings-search-lib";
import { errorMessageOr } from "@/lib/toast-api-error";

const FILTER_KEY_LABELS: Record<string, string> = {
  country: "Country",
  sector: "Sector",
  industry: "Industry",
};

type HoldingsSearchDialogContentProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  tenant: string;
  navigation?: CustomerNavigationPayload | null;
  allowedRoutes?: readonly string[];
};

function Highlighted({ text, terms }: { text: string; terms: readonly string[] }) {
  return (
    <>
      {splitHighlight(text, terms).map((segment, i) =>
        segment.match ? (
          <mark key={i} className="rounded-[3px] bg-primary/15 text-foreground">
            {segment.text}
          </mark>
        ) : (
          <React.Fragment key={i}>{segment.text}</React.Fragment>
        ),
      )}
    </>
  );
}

function flattenNavUrls(
  tenant: string,
  navigation: CustomerNavigationPayload | null | undefined,
  allowedRoutes?: readonly string[],
): string[] {
  const panels = buildCustomerSidebarPanelsFromNavigation(tenant, navigation, allowedRoutes);
  const urls: string[] = [];

  const walk = (item: NavMainItem) => {
    if (item.subItems?.length) {
      for (const sub of item.subItems) {
        if (!sub.comingSoon) urls.push(sub.url);
      }
      return;
    }
    if (!item.comingSoon) urls.push(item.url);
  };

  for (const panel of panels) {
    for (const group of panel.groups) {
      for (const item of group.items) walk(item);
    }
  }
  return urls;
}

export function HoldingsSearchDialogContent({
  open,
  onOpenChange,
  tenant,
  navigation = null,
  allowedRoutes,
}: HoldingsSearchDialogContentProps) {
  const [query, setQuery] = React.useState("");
  const [classFilter, setClassFilter] = React.useState<string | null>(null);
  const [allHoldings, setAllHoldings] = React.useState<ConsolidatedHolding[]>([]);
  const [assetClasses, setAssetClasses] = React.useState<string[]>([]);
  const [loading, setLoading] = React.useState(false);
  const [loadError, setLoadError] = React.useState<string | null>(null);
  const [reloadNonce, setReloadNonce] = React.useState(0);
  const [individualOpen, setIndividualOpen] = React.useState(false);
  const [individualTarget, setIndividualTarget] =
    React.useState<ConsolidatedIndividualTarget | null>(null);
  const router = useRouter();
  const inputRef = React.useRef<HTMLInputElement>(null);
  const debouncedQuery = useDebouncedValue(query, 200);

  const canConsolidated = React.useMemo(
    () => hasConsolidatedHoldingsAccess(flattenNavUrls(tenant, navigation, allowedRoutes)),
    [tenant, navigation, allowedRoutes],
  );

  React.useEffect(() => {
    if (!open) {
      // Closing cancels the load; the effect also handles cached/uncached fetches below (fetch-in-effect).
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setLoading(false);
      return;
    }
    if (!canConsolidated) {
      setLoading(false);
      setLoadError(null);
      return;
    }

    const cached = getCachedHoldings(tenant);
    if (cached) {
      setAllHoldings(cached.holdings);
      setAssetClasses(cached.assetClasses);
      setLoadError(null);
      setLoading(false);
      return;
    }

    let cancelled = false;
    const controller = new AbortController();
    const timeoutId = window.setTimeout(() => controller.abort(), HOLDINGS_SEARCH_LOAD_TIMEOUT_MS);

    setLoading(true);
    setLoadError(null);

    void (async () => {
      try {
        const response = await fetch(
          `/customer/${encodeURIComponent(tenant)}/pdf/consolidated/list`,
          {
            method: "GET",
            credentials: "same-origin",
            headers: { Accept: "application/json" },
            cache: "no-store",
            signal: controller.signal,
          },
        );
        const data = (await response.json().catch(() => null)) as {
          status?: string;
          message?: string;
        } | null;

        if (cancelled) return;

        if (!response.ok || !data || data.status === "error") {
          throw new Error(holdingsListErrorMessage(response, data));
        }

        const parsed = parseHoldingsListResponse(data);
        setCachedHoldings(tenant, parsed.holdings, parsed.assetClasses);
        setAllHoldings(parsed.holdings);
        setAssetClasses(parsed.assetClasses);
        setLoadError(null);
      } catch (error) {
        if (cancelled) return;
        setAllHoldings([]);
        setAssetClasses([]);
        if (controller.signal.aborted) {
          setLoadError("Holdings took too long to load. Try again.");
        } else {
          setLoadError(errorMessageOr(error, "Failed to load holdings."));
        }
      } finally {
        window.clearTimeout(timeoutId);
        if (!cancelled) setLoading(false);
      }
    })();

    return () => {
      cancelled = true;
      window.clearTimeout(timeoutId);
      controller.abort();
      setLoading(false);
    };
  }, [open, canConsolidated, tenant, reloadNonce]);

  const trimmed = debouncedQuery.trim();

  if (useChanged(trimmed)) setClassFilter(null);

  const matchedHoldings = React.useMemo(() => {
    if (!trimmed) return [];
    return allHoldings.filter((h) => {
      if (classFilter && h.assetClass !== classFilter) return false;
      return holdingMatchesQuery(h, trimmed);
    });
  }, [allHoldings, trimmed, classFilter]);

  const collapsedHits = React.useMemo(
    () => collapseHoldingsByIsin(matchedHoldings, trimmed),
    [matchedHoldings, trimmed],
  );

  const classCounts = React.useMemo(
    () => (trimmed ? countCollapsedByAssetClass(allHoldings, trimmed) : new Map<string, number>()),
    [allHoldings, trimmed],
  );

  const totalUnique = React.useMemo(
    () => Array.from(classCounts.values()).reduce((sum, n) => sum + n, 0),
    [classCounts],
  );

  const filterChips = React.useMemo(() => {
    const source = assetClasses.length > 0 ? assetClasses : Array.from(classCounts.keys());
    return source.filter(
      (assetClass) => (classCounts.get(assetClass) ?? 0) > 0 || classFilter === assetClass,
    );
  }, [assetClasses, classCounts, classFilter]);

  const missingFilterFields = React.useMemo(
    () =>
      trimmed && allHoldings.length > 0
        ? unpopulatedFilterKeys(allHoldings, trimmed).map((key) => FILTER_KEY_LABELS[key] ?? key)
        : [],
    [allHoldings, trimmed],
  );

  const facets = React.useMemo(() => buildHoldingFacets(allHoldings), [allHoldings]);
  // Completion follows the live text so it feels instant; pills follow the debounced query
  // so they stay in step with the results below them.
  const completion = React.useMemo(
    () => (allHoldings.length > 0 ? suggestCompletions(query, facets) : null),
    [allHoldings.length, query, facets],
  );
  const pillGroups = React.useMemo(() => describeQuery(trimmed), [trimmed]);
  const terms = React.useMemo(() => highlightTerms(trimmed), [trimmed]);

  const acceptSuggestion = (suggestion: Suggestion) => {
    if (!completion) return;
    setQuery(applyCompletion(query, completion, suggestion));
    inputRef.current?.focus();
  };

  const removePill = (group: number, index: number) => {
    setQuery(removeQueryToken(trimmed, group, index));
    inputRef.current?.focus();
  };

  const visibleHits = collapsedHits.slice(0, HOLDINGS_SEARCH_MAX_RESULTS);

  const resultSections = React.useMemo(() => {
    if (classFilter) return [{ heading: classFilter, hits: visibleHits }];
    const map = new Map<string, CollapsedHoldingHit[]>();
    for (const hit of visibleHits) {
      const list = map.get(hit.assetClass) ?? [];
      list.push(hit);
      map.set(hit.assetClass, list);
    }
    return Array.from(map.entries()).map(([heading, hits]) => ({ heading, hits }));
  }, [visibleHits, classFilter]);

  const handleOpenChange = (value: boolean) => {
    onOpenChange(value);
    if (!value) {
      setQuery("");
      setClassFilter(null);
      setIndividualOpen(false);
      setIndividualTarget(null);
    }
  };

  const openReport = (assetClass: string | null) => {
    const q = query.trim();
    if (!q || !canConsolidated) return;
    setChrHoldingsSearchHandoff({ q, assetClass });
    handleOpenChange(false);
    notifyTutorialAction("holdings-search-opened");
    router.push(consolidatedReportHref(tenant));
  };

  const openHolding = (hit: CollapsedHoldingHit) => {
    const holding = hit.primary;
    setIndividualTarget({
      isin: holding.ticker,
      assetType: holding.assetTypeTitle || holding.assetType,
      assetClass: holding.fType || holding.assetClass,
      name: holding.name || hit.name,
    });
    setIndividualOpen(true);
    notifyTutorialAction("holdings-search-opened");
  };

  const showResults = canConsolidated && !loading && !loadError && Boolean(trimmed);

  return (
    <>
      <CommandDialog
        open={open}
        onOpenChange={handleOpenChange}
        title="Holdings search"
        description="Paste an ISIN or type a name to see matching holdings"
        className="top-1/2 -translate-y-1/2 sm:max-w-xl"
      >
        <Command shouldFilter={false} className="rounded-xl" data-tutorial="holdings-search-dialog">
          <div className="border-b px-3 pb-3 pt-3">
            <p className="mb-2 px-0.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
              Holdings search
            </p>
            <CommandInput
              ref={inputRef}
              placeholder="Search name or ISIN, or filter — e.g. country:India"
              value={query}
              onValueChange={setQuery}
              onKeyDown={(event) => {
                if (event.key === "Tab" && !event.shiftKey && completion) {
                  event.preventDefault();
                  acceptSuggestion(completion.items[0]);
                }
              }}
            />
            {completion ? (
              <div className="mt-2 flex items-center gap-1.5 overflow-x-auto pb-0.5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
                <span className="shrink-0 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70">
                  {completion.kind === "key" ? "Filter by" : "Values"}
                </span>
                {completion.items.map((item, i) => (
                  <button
                    key={item.insert}
                    type="button"
                    // Keep focus in the input so typing carries on after a click.
                    onMouseDown={(event) => event.preventDefault()}
                    onClick={() => acceptSuggestion(item)}
                    className={cn(
                      "inline-flex shrink-0 items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs transition-colors hover:border-primary/50 hover:bg-accent hover:text-foreground",
                      i === 0 ? "border-primary/40 bg-accent/60 text-foreground" : "border-border/80 bg-muted/40 text-muted-foreground",
                      completion.kind === "key" && "font-mono",
                    )}
                  >
                    {item.label}
                    {item.hint ? (
                      <span className="tabular-nums text-[10px] text-muted-foreground">{item.hint}</span>
                    ) : null}
                    {i === 0 ? (
                      <kbd className="rounded border bg-background px-1 font-mono text-[9px] text-muted-foreground">tab</kbd>
                    ) : null}
                  </button>
                ))}
              </div>
            ) : null}
          </div>

          {showResults && pillGroups.length > 0 ? (
            <div className="flex flex-wrap items-center gap-x-1.5 gap-y-1.5 border-b px-3 py-2">
              {pillGroups.map((group, groupIndex) => (
                <React.Fragment key={groupIndex}>
                  {groupIndex > 0 ? (
                    <span className="px-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">or</span>
                  ) : null}
                  {group.map((pill, i) => (
                    <React.Fragment key={`${pill.group}:${pill.index}`}>
                      {i > 0 ? <span className="text-[10px] text-muted-foreground/70">and</span> : null}
                      <span
                        title={
                          pill.unknown
                            ? `"${pill.label}" is not a filter${pill.hint ? ` — did you mean ${pill.hint}?` : ""}`
                            : undefined
                        }
                        className={cn(
                          "inline-flex items-center gap-1 rounded-full border py-0.5 pl-2.5 pr-1 text-xs",
                          pill.unknown
                            ? "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-400"
                            : "border-border bg-muted/50",
                        )}
                      >
                        {pill.unknown ? <TriangleAlert className="size-3" /> : null}
                        <span className={pill.unknown ? undefined : "text-muted-foreground"}>{pill.label}</span>
                        {pill.unknown ? (
                          <span>{pill.hint ? `isn’t a filter — try ${pill.hint}` : "isn’t a filter"}</span>
                        ) : (
                          <span className="font-medium text-foreground">
                            {pill.value}
                            {pill.resolved ? <span className="font-normal text-muted-foreground"> → {pill.resolved}</span> : null}
                          </span>
                        )}
                        <button
                          type="button"
                          aria-label={`Remove ${pill.label} filter`}
                          onClick={() => removePill(pill.group, pill.index)}
                          className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-foreground/10 hover:text-foreground"
                        >
                          <X className="size-3" />
                        </button>
                      </span>
                    </React.Fragment>
                  ))}
                </React.Fragment>
              ))}
            </div>
          ) : null}

          {showResults && filterChips.length > 0 ? (
            <div className="border-b px-3 py-2.5">
              <div className="flex gap-1.5 overflow-x-auto pb-0.5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
                <button
                  type="button"
                  onClick={() => setClassFilter(null)}
                  className={cn(
                    "inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors",
                    classFilter === null
                      ? "border-foreground/15 bg-foreground text-background"
                      : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
                  )}
                >
                  All
                  <span className={cn("tabular-nums", classFilter === null ? "text-background/70" : "text-muted-foreground")}>
                    {totalUnique}
                  </span>
                </button>
                {filterChips.map((assetClass) => {
                  const count = classCounts.get(assetClass) ?? 0;
                  const active = classFilter === assetClass;
                  return (
                    <button
                      key={assetClass}
                      type="button"
                      onClick={() => setClassFilter(active ? null : assetClass)}
                      className={cn(
                        "inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors",
                        active
                          ? "border-foreground/15 bg-foreground text-background"
                          : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
                      )}
                    >
                      {assetClass}
                      <span className={cn("tabular-nums", active ? "text-background/70" : "text-muted-foreground")}>
                        {count}
                      </span>
                    </button>
                  );
                })}
              </div>
            </div>
          ) : null}

          <CommandList className="max-h-[min(28rem,55vh)]">
            {!canConsolidated ? (
              <div className="flex flex-col items-center gap-2 px-6 py-10 text-center">
                <FileSearch className="size-5 text-muted-foreground" />
                <p className="text-sm text-muted-foreground">
                  Consolidated Holdings is not available for this account
                </p>
              </div>
            ) : null}

            {canConsolidated && loading ? (
              <div className="flex flex-col items-center gap-3 px-6 py-12 text-center">
                <Loader2 className="size-5 animate-spin text-muted-foreground" />
                <div>
                  <p className="text-sm font-medium">Loading holdings</p>
                  <p className="mt-1 text-xs text-muted-foreground">
                    First open can take a few seconds
                  </p>
                </div>
              </div>
            ) : null}

            {canConsolidated && loadError ? (
              <div className="flex flex-col items-center gap-3 px-6 py-10 text-center">
                <FileSearch className="size-5 text-muted-foreground" />
                <p className="max-w-sm text-sm text-muted-foreground">{loadError}</p>
                <Button
                  type="button"
                  size="sm"
                  variant="outline"
                  onClick={() => {
                    clearCachedHoldings(tenant);
                    setLoadError(null);
                    setReloadNonce((n) => n + 1);
                  }}
                >
                  Retry
                </Button>
              </div>
            ) : null}

            {canConsolidated && !loading && !loadError && !trimmed ? (
              <div className="flex flex-col items-center gap-3 px-6 py-8 text-center">
                <p className="text-sm font-medium">Find a holding</p>
                <p className="max-w-md text-xs leading-relaxed text-muted-foreground">
                  Type a name or ISIN, or narrow it down with filters — suggestions appear as you type and <kbd className="rounded border bg-muted px-1 font-mono text-[10px]">Tab</kbd> accepts the first. Filters use Overall Summary column names, written with <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">:</code> — country, Asset Class, AssetType, sector, industry, currency, ISIN, name. Combine them with <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">AND</code> / <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px]">OR</code> (AND binds first).
                </p>

                <div className="mt-1 flex flex-wrap justify-center gap-1.5 max-w-lg">
                  <span className="w-full text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider mb-0.5">Example searches</span>
                  {[
                    { label: "country:USA", q: "country:USA" },
                    { label: 'AssetType:"Digital assets"', q: 'AssetType:"Digital assets"' },
                    { label: "Asset Class:Stock", q: "Asset Class:Stock" },
                    { label: "country:USA AND Asset Class:Stock", q: "country:USA AND Asset Class:Stock" },
                    { label: "country:India OR country:China", q: "country:India OR country:China" },
                    { label: "sector:Technology AND currency:USD", q: "sector:Technology AND currency:USD" },
                  ].map((ex) => (
                    <button
                      key={ex.label}
                      type="button"
                      onClick={() => setQuery(ex.q)}
                      className="rounded-lg border border-border/80 bg-muted/40 px-2.5 py-1 text-xs font-mono text-muted-foreground transition-colors hover:border-primary/50 hover:bg-accent hover:text-foreground"
                    >
                      {ex.label}
                    </button>
                  ))}
                </div>
              </div>
            ) : null}

            {showResults && missingFilterFields.length > 0 ? (
              <div className="border-b px-3 py-2 text-xs text-muted-foreground">
                {missingFilterFields.join(" and ")} data is not available for these holdings, so that
                filter cannot match.
              </div>
            ) : null}

            {showResults ? (
              <>
                {visibleHits.length === 0 ? (
                  <CommandEmpty className="py-10">
                    <p>No matching holdings.</p>
                    {pillGroups.length > 0 ? (
                      <p className="mx-auto mt-1 max-w-xs text-xs text-muted-foreground">
                        {pillGroups.some((g) => g.some((p) => p.unknown))
                          ? "One of your filters isn’t recognised — fix or remove it above."
                          : pillGroups.flat().length > 1
                            ? "Nothing matches all of these filters. Remove one above to widen the search."
                            : "Try a different value, or press Tab after the colon to pick from what’s available."}
                      </p>
                    ) : null}
                  </CommandEmpty>
                ) : (
                  resultSections.map((section) => (
                    <CommandGroup
                      key={section.heading}
                      heading={classFilter ? undefined : section.heading}
                      className="px-1"
                    >
                      {section.hits.map((hit) => (
                        <CommandItem
                          key={hit.key}
                          value={`holding ${hit.ticker} ${hit.name} ${hit.assetClass} ${hit.assetTypes.join(" ")}`}
                          onSelect={() => openHolding(hit)}
                          className="items-start gap-3 rounded-lg px-3 py-2.5 aria-selected:bg-muted"
                        >
                          <div className="min-w-0 flex-1">
                            <div className="truncate text-sm font-medium leading-snug">
                              <Highlighted text={hit.name || "Untitled"} terms={terms} />
                            </div>
                            <div className="mt-0.5 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
                              <span className="font-mono tabular-nums">
                                {hit.ticker ? <Highlighted text={hit.ticker} terms={terms} /> : "—"}
                              </span>
                              <span className="text-border">·</span>
                              <span className="truncate">{collapsedSubtitle(hit)}</span>
                            </div>
                          </div>
                          {hit.marketValueRc !== 0 ? (
                            <span className="shrink-0 pt-0.5 text-xs font-medium tabular-nums">
                              {formatMoneyExact(hit.marketValueRc, hit.currency || "USD")}
                            </span>
                          ) : null}
                        </CommandItem>
                      ))}
                    </CommandGroup>
                  ))
                )}

                {collapsedHits.length > 0 ? (
                  <div className="px-3 pb-2 pt-1">
                    <button
                      type="button"
                      onClick={() => openReport(classFilter)}
                      className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed px-3 py-2 text-xs text-muted-foreground transition-colors hover:border-border hover:bg-muted/50 hover:text-foreground"
                    >
                      <ExternalLink className="size-3.5" />
                      {collapsedHits.length > HOLDINGS_SEARCH_MAX_RESULTS
                        ? `View all ${collapsedHits.length} in Consolidated Report`
                        : "Open in Consolidated Report"}
                    </button>
                  </div>
                ) : null}
              </>
            ) : null}
          </CommandList>

          <div className="flex items-center justify-between border-t px-3 py-2 text-[10px] text-muted-foreground">
            <div className="flex gap-3">
              <span>
                <kbd className="rounded border bg-muted px-1 font-mono">↵</kbd> detail
              </span>
              <span>
                <kbd className="rounded border bg-muted px-1 font-mono">esc</kbd> close
              </span>
            </div>
            <span>⌘⇧J</span>
          </div>
        </Command>
      </CommandDialog>

      <ConsolidatedIndividualDialog
        open={individualOpen}
        onOpenChange={setIndividualOpen}
        target={individualTarget}
      />
    </>
  );
}
