"use client";

import * as React from "react";
import { Search, X } from "lucide-react";

import { Button } from "@/components/ui/button";
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxTrigger,
} from "@/components/ui/combobox";
import { InputGroupAddon } from "@/components/ui/input-group";
import { cn } from "@/lib/utils";

import type { DeliveredHoldingsPosition } from "../_lib/delivered-holdings-history-types";

export type PortfolioIsinLeg = {
  name: string;
  isin: string;
};

export type PortfolioIsinOption = {
  value: string;
  isin: string;
  /** Structure / product name, or underlying display name. */
  name: string;
  kind: "product" | "underlying";
  /** Legs on a product ISIN. */
  underlyings: PortfolioIsinLeg[];
  /** Parent products that reference an underlying ISIN. */
  products: PortfolioIsinLeg[];
};

function normalizeNeedle(raw: string): string {
  return raw.replace(/\s+/g, "").trim().toUpperCase();
}

function shortStructureLabel(name: string): string {
  const trimmed = name.trim();
  if (!trimmed) return "";
  const linked = trimmed.match(/\bLinked to\s+(.+)$/i);
  if (linked?.[1]) return linked[1].trim();
  if (trimmed.length <= 42) return trimmed;
  return `${trimmed.slice(0, 39)}…`;
}

function mergeLegs(existing: PortfolioIsinLeg[], next: PortfolioIsinLeg[]): PortfolioIsinLeg[] {
  const byKey = new Map<string, PortfolioIsinLeg>();
  for (const leg of [...existing, ...next]) {
    const key = normalizeNeedle(leg.isin) || leg.name.toUpperCase();
    if (!key) continue;
    const prev = byKey.get(key);
    if (!prev || (leg.name && leg.name.length > prev.name.length)) {
      byKey.set(key, leg);
    }
  }
  return Array.from(byKey.values());
}

function matchesQuery(option: PortfolioIsinOption, q: string): boolean {
  if (!q) return true;
  const compact = normalizeNeedle(q);
  if (option.isin.toLowerCase().includes(q) || normalizeNeedle(option.isin).includes(compact)) {
    return true;
  }
  if (option.name.toLowerCase().includes(q)) return true;
  for (const leg of option.underlyings) {
    if (leg.name.toLowerCase().includes(q) || leg.isin.toLowerCase().includes(q)) return true;
    if (compact && normalizeNeedle(leg.isin).includes(compact)) return true;
  }
  for (const product of option.products) {
    if (product.name.toLowerCase().includes(q) || product.isin.toLowerCase().includes(q)) {
      return true;
    }
  }
  return false;
}

function rankedUnderlyings(option: PortfolioIsinOption, term: string): PortfolioIsinLeg[] {
  const q = term.trim().toLowerCase();
  const legs = [...option.underlyings];
  if (!q) return legs;
  return legs.sort((a, b) => {
    const aHit =
      a.name.toLowerCase().includes(q) ||
      a.isin.toLowerCase().includes(q) ||
      normalizeNeedle(a.isin).includes(normalizeNeedle(q));
    const bHit =
      b.name.toLowerCase().includes(q) ||
      b.isin.toLowerCase().includes(q) ||
      normalizeNeedle(b.isin).includes(normalizeNeedle(q));
    return Number(bHit) - Number(aHit);
  });
}

/** Parent / structure title — never replace with underlying names. */
function optionTitle(option: PortfolioIsinOption): string {
  if (option.kind === "underlying") {
    return option.name || option.isin;
  }
  const name = option.name.trim();
  if (name && name.toUpperCase() !== option.isin) return name;
  return option.isin;
}

function optionSubtitle(option: PortfolioIsinOption): string {
  if (option.kind === "underlying") {
    const parents = option.products
      .map((product) => shortStructureLabel(product.name) || product.isin)
      .filter(Boolean)
      .slice(0, 2);
    const parentBit = parents.length > 0 ? ` · in ${parents.join(", ")}` : "";
    return `${option.isin}${parentBit}`;
  }
  return option.isin;
}

/** Full searchable text so Combobox / local filter match ISIN, name, and underlyings. */
function optionSearchText(option: PortfolioIsinOption): string {
  const parts = [
    option.isin,
    option.name,
    ...option.underlyings.flatMap((leg) => [leg.name, leg.isin]),
    ...option.products.flatMap((product) => [product.name, product.isin]),
  ];
  return parts.filter(Boolean).join(" ");
}

function optionSearchRank(option: PortfolioIsinOption, term: string): number {
  const q = term.trim().toLowerCase();
  if (!q) return 0;
  if (option.kind === "underlying") {
    if (option.name.toLowerCase().includes(q)) return 0;
    if (option.isin.toLowerCase().includes(q)) return 1;
    return 2;
  }
  const legHit = option.underlyings.some(
    (leg) => leg.name.toLowerCase().includes(q) || leg.isin.toLowerCase().includes(q),
  );
  if (legHit) return 0;
  if (option.name.toLowerCase().includes(q)) return 1;
  return 2;
}

/** Distinct product + underlying ISINs from loaded scorecard rows (portfolio only). */
export function buildPortfolioIsinOptions(
  positions: DeliveredHoldingsPosition[],
): PortfolioIsinOption[] {
  const byIsin = new Map<string, PortfolioIsinOption>();

  const ensure = (
    isinRaw: string,
    nameRaw: string,
    kind: PortfolioIsinOption["kind"],
  ): PortfolioIsinOption | null => {
    const isin = normalizeNeedle(isinRaw);
    if (!isin || isin === "—") return null;
    const name = nameRaw.trim();
    const display = name && name.toUpperCase() !== isin ? name : isin;
    const existing = byIsin.get(isin);
    if (!existing) {
      const created: PortfolioIsinOption = {
        value: `${kind}:${isin}`,
        isin,
        name: display,
        kind,
        underlyings: [],
        products: [],
      };
      byIsin.set(isin, created);
      return created;
    }
    if (existing.kind !== "product" && kind === "product") {
      existing.kind = "product";
      existing.value = `product:${isin}`;
    }
    if (
      display !== isin &&
      (existing.name === existing.isin || display.length > existing.name.length)
    ) {
      existing.name = display;
    }
    return existing;
  };

  for (const pos of positions) {
    const productIsin = String(pos.isin || pos.underlying_isin || "").trim();
    const productName = String(
      pos.structure_name || pos.name || pos.underlying_name || pos.ticker || "",
    ).trim();
    const product = ensure(productIsin, productName, "product");
    const legs = Array.isArray(pos.underlyings) ? pos.underlyings : [];
    const productLegs: PortfolioIsinLeg[] = [];

    for (const leg of legs) {
      const legIsin = String(leg.isin || leg.ticker || "").trim();
      const legName = String(leg.name || leg.isin || leg.ticker || "").trim();
      if (!legIsin && !legName) continue;
      const legRef: PortfolioIsinLeg = {
        isin: normalizeNeedle(legIsin) || legName,
        name: legName || legIsin,
      };
      productLegs.push(legRef);

      const underlying = ensure(legIsin || legName, legName || legIsin, "underlying");
      if (underlying && product) {
        underlying.products = mergeLegs(underlying.products, [
          { isin: product.isin, name: product.name },
        ]);
      }
    }

    if (product && productLegs.length > 0) {
      product.underlyings = mergeLegs(product.underlyings, productLegs);
    }
  }

  return Array.from(byIsin.values()).sort((a, b) =>
    (a.name || a.isin).localeCompare(b.name || b.isin),
  );
}

export function mergePortfolioIsinOptions(
  previous: PortfolioIsinOption[],
  next: PortfolioIsinOption[],
): PortfolioIsinOption[] {
  const byIsin = new Map(previous.map((option) => [option.isin, option]));
  for (const option of next) {
    const existing = byIsin.get(option.isin);
    if (!existing) {
      byIsin.set(option.isin, option);
      continue;
    }
    byIsin.set(option.isin, {
      ...existing,
      kind: existing.kind === "product" || option.kind === "product" ? "product" : option.kind,
      value:
        existing.kind === "product" || option.kind === "product"
          ? `product:${option.isin}`
          : `underlying:${option.isin}`,
      name:
        option.name !== option.isin &&
        (existing.name === existing.isin || option.name.length > existing.name.length)
          ? option.name
          : existing.name,
      underlyings: mergeLegs(existing.underlyings, option.underlyings),
      products: mergeLegs(existing.products, option.products),
    });
  }
  return Array.from(byIsin.values()).sort((a, b) =>
    (a.name || a.isin).localeCompare(b.name || b.isin),
  );
}

type PortfolioIsinComboboxProps = {
  options: PortfolioIsinOption[];
  value: string;
  disabled?: boolean;
  onSelect: (isin: string) => void;
  onClear: () => void;
};

export function PortfolioIsinCombobox({
  options,
  value,
  disabled = false,
  onSelect,
  onClear,
}: PortfolioIsinComboboxProps) {
  const [term, setTerm] = React.useState("");
  const selectedIsin = normalizeNeedle(value);

  const selected = React.useMemo(() => {
    if (!selectedIsin) return null;
    return (
      options.find((option) => option.isin === selectedIsin) ?? {
        value: `custom:${selectedIsin}`,
        isin: selectedIsin,
        name: selectedIsin,
        kind: "product" as const,
        underlyings: [],
        products: [],
      }
    );
  }, [options, selectedIsin]);

  const filtered = React.useMemo(() => {
    const q = term.trim().toLowerCase();
    const list = q ? options.filter((option) => matchesQuery(option, q)) : options;
    return [...list].sort((a, b) => {
      const rank = optionSearchRank(a, term) - optionSearchRank(b, term);
      if (rank !== 0) return rank;
      return (a.name || a.isin).localeCompare(b.name || b.isin);
    });
  }, [options, term]);

  const items = React.useMemo(() => {
    if (selected && !filtered.some((option) => option.isin === selected.isin)) {
      return [selected, ...filtered];
    }
    return filtered;
  }, [filtered, selected]);

  const triggerLabel = selected ? optionTitle(selected) : "Search ISIN or name";

  const commitTypedIsin = () => {
    const typed = normalizeNeedle(term);
    if (!typed) return;
    const hit = options.find(
      (option) =>
        option.isin === typed ||
        option.underlyings.some((leg) => normalizeNeedle(leg.isin) === typed),
    );
    onSelect(hit?.isin ?? typed);
    setTerm("");
  };

  return (
    <div className="flex min-w-[220px] items-center gap-1">
      <Combobox
        items={options}
        filteredItems={items}
        filter={null}
        value={selected}
        disabled={disabled}
        onValueChange={(next) => {
          if (!next) {
            onClear();
            setTerm("");
            return;
          }
          onSelect(next.isin);
          setTerm("");
        }}
        onInputValueChange={(next) => setTerm(String(next ?? ""))}
        itemToStringLabel={(item) => optionSearchText(item)}
        isItemEqualToValue={(item, current) => item.isin === current.isin}
      >
        <ComboboxTrigger
          className={cn(
            "flex h-9 min-w-[220px] flex-1 items-center justify-between gap-1.5 rounded-md border border-input bg-transparent px-2 text-left text-sm outline-none",
            "hover:bg-accent/40 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40",
            !selected && "text-muted-foreground",
            disabled && "pointer-events-none opacity-50",
          )}
          aria-label="Search portfolio ISIN"
        >
          <span className="inline-flex min-w-0 flex-1 items-center gap-1.5">
            <Search className="size-3.5 shrink-0 text-muted-foreground" aria-hidden />
            <span className="min-w-0 truncate">{triggerLabel}</span>
          </span>
        </ComboboxTrigger>
        <ComboboxContent
          side="bottom"
          sideOffset={4}
          align="end"
          className={cn(
            "w-(--anchor-width) min-w-[360px] overflow-hidden p-0",
            "*:data-[slot=input-group]:m-0 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-9",
            "*:data-[slot=input-group]:rounded-md *:data-[slot=input-group]:border-input",
            "*:data-[slot=input-group]:bg-background *:data-[slot=input-group]:shadow-none",
          )}
        >
          <div className="sticky top-0 z-10 border-b border-border/70 bg-muted/40 p-2">
            <ComboboxInput
              placeholder="Type ISIN, structure, or underlying…"
              showTrigger={false}
              className="w-full bg-background"
              onKeyDown={(event) => {
                if (event.key === "Enter" && term.trim()) {
                  event.preventDefault();
                  commitTypedIsin();
                }
              }}
            >
              <InputGroupAddon align="inline-start">
                <Search className="size-3.5 text-muted-foreground" aria-hidden />
              </InputGroupAddon>
            </ComboboxInput>
          </div>
          <ComboboxEmpty>
            {term.trim()
              ? options.length === 0
                ? "No portfolio ISINs loaded yet — press Enter to search this ISIN"
                : "No portfolio ISINs match — press Enter to search anyway"
              : options.length === 0
                ? "Load the report to search portfolio ISINs"
                : "Type ISIN, structure name, or underlying (e.g. Netflix)"}
          </ComboboxEmpty>
          <ComboboxList className="max-h-64 p-1">
            {(item) => {
              const legs =
                item.kind === "product" ? rankedUnderlyings(item, term).slice(0, 4) : [];
              const extraLegs =
                item.kind === "product"
                  ? Math.max(0, item.underlyings.length - legs.length)
                  : 0;
              return (
                <ComboboxItem key={item.value} value={item}>
                  <span className="flex min-w-0 flex-1 items-start justify-between gap-2">
                    <span className="min-w-0">
                      <span className="block truncate font-medium">{optionTitle(item)}</span>
                      <span className="mt-0.5 block truncate font-mono text-muted-foreground text-xs tabular-nums">
                        {optionSubtitle(item)}
                      </span>
                      {legs.length > 0 ? (
                        <span className="mt-1 flex min-w-0 flex-wrap gap-1">
                          {legs.map((leg) => {
                            const q = term.trim().toLowerCase();
                            const hit =
                              q !== "" &&
                              (leg.name.toLowerCase().includes(q) ||
                                leg.isin.toLowerCase().includes(q));
                            const label =
                              leg.name &&
                              leg.isin &&
                              leg.name.toUpperCase() !== leg.isin.toUpperCase()
                                ? `${leg.name} · ${leg.isin}`
                                : leg.name || leg.isin;
                            return (
                              <span
                                key={`${leg.isin}-${leg.name}`}
                                className={cn(
                                  "max-w-[200px] truncate rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground",
                                  hit &&
                                    "bg-emerald-600/15 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300",
                                )}
                                title={label}
                              >
                                {label}
                              </span>
                            );
                          })}
                          {extraLegs > 0 ? (
                            <span className="rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
                              +{extraLegs}
                            </span>
                          ) : null}
                        </span>
                      ) : null}
                    </span>
                    <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
                      {item.kind === "underlying" ? "Underlying" : "Portfolio"}
                    </span>
                  </span>
                </ComboboxItem>
              );
            }}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
      {selectedIsin ? (
        <Button
          type="button"
          size="icon"
          variant="ghost"
          className="size-9 shrink-0 text-muted-foreground hover:text-foreground"
          aria-label="Clear ISIN search"
          title="Clear"
          disabled={disabled}
          onClick={() => {
            onClear();
            setTerm("");
          }}
        >
          <X className="size-3.5" />
        </Button>
      ) : null}
    </div>
  );
}
