"use client";

import { useEffect, useMemo, useState } from "react";
import { useReducedMotion } from "framer-motion";
import { AlertTriangle, Loader2, RefreshCcw } from "lucide-react";

import { ConverterCardAmountInput } from "@/components/converter/converter-card-amount-input";
import { ConverterCardConvertedValue } from "@/components/converter/converter-card-converted-value";
import { ConverterCardHeader } from "@/components/converter/converter-card-header";
import {
  DEFAULT_FROM,
  DEFAULT_MULTI_CONVERSION_CODES,
  DEFAULT_TO,
  MARKET_RANGE_LABELS,
  MAX_MULTI_CONVERSIONS,
  MAX_PINNED_PAIRS,
  QUICK_AMOUNTS,
} from "@/components/converter/converter-card.constants";
import { ConverterCardMarketData } from "@/components/converter/converter-card-market-data";
import { ConverterCardMultiConversion } from "@/components/converter/converter-card-multi-conversion";
import { ConverterCardPairSelection } from "@/components/converter/converter-card-pair-selection";
import { ConverterCardPinnedPairs } from "@/components/converter/converter-card-pinned-pairs";
import { ConverterCardRatesSummary } from "@/components/converter/converter-card-rates-summary";
import {
  ConverterEmptyState,
  ConverterErrorState,
  ConverterSkeleton,
} from "@/components/converter/converter-card-states";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { useCryptoMarket } from "@/hooks/use-crypto-market";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { type PinnedPair, usePinnedPairs } from "@/hooks/use-pinned-pairs";
import { useMarketRates } from "@/hooks/use-market-rates";
import { getDisplaySymbol } from "@/lib/currency-display";
import { formatAmount, formatInverseRate, formatRate } from "@/lib/format";
import { type MarketChartRange } from "@/lib/market";
import { buildRateMap, convertAmount } from "@/lib/rates";
import { buildConversionShareUrl } from "@/lib/share-link";
import { validateAmount } from "@/lib/validation";

interface ConverterCardProps {
  forcedFromCode?: string;
  forcedToCode?: string;
  forcedAmount?: string;
  onPairChange?: (fromCode: string, toCode: string) => void;
  multiConversionCodes?: string[];
}

async function copyTextToClipboard(text: string): Promise<boolean> {
  if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch {
      // Fallback below.
    }
  }

  if (typeof document === "undefined") {
    return false;
  }

  const textArea = document.createElement("textarea");
  textArea.value = text;
  textArea.setAttribute("readonly", "");
  textArea.style.position = "fixed";
  textArea.style.opacity = "0";
  textArea.style.pointerEvents = "none";
  textArea.style.left = "-9999px";
  document.body.appendChild(textArea);
  textArea.select();
  textArea.setSelectionRange(0, textArea.value.length);

  try {
    return document.execCommand("copy");
  } finally {
    document.body.removeChild(textArea);
  }
}

export function ConverterCard({
  forcedFromCode,
  forcedToCode,
  forcedAmount,
  onPairChange,
  multiConversionCodes,
}: ConverterCardProps) {
  const shouldReduceMotion = useReducedMotion();
  const prefersReducedMotion = shouldReduceMotion ?? false;
  const { data, error, isLoading, refresh } = useMarketRates();

  const [amountInput, setAmountInput] = useState("1");
  const [fromCode, setFromCode] = useState(DEFAULT_FROM);
  const [toCode, setToCode] = useState(DEFAULT_TO);
  const [isCopied, setIsCopied] = useState(false);
  const [isShareLinkCopied, setIsShareLinkCopied] = useState(false);
  const [marketRange, setMarketRange] = useState<MarketChartRange>("24h");
  const [swapAnimationStep, setSwapAnimationStep] = useState(0);
  const [pinStarAnimationStep, setPinStarAnimationStep] = useState(0);

  const {
    pinnedPairs,
    isReady: isPinnedPairsReady,
    isLimitReached: isPinnedPairsLimitReached,
    isPinnedPair,
    removePinnedPair,
    togglePinnedPair,
  } = usePinnedPairs(MAX_PINNED_PAIRS);

  const debouncedAmount = useDebouncedValue(amountInput, 120);

  const assets = useMemo(() => data?.assets ?? [], [data]);
  const rateMap = useMemo(() => buildRateMap(assets), [assets]);

  useEffect(() => {
    if (!assets.length) {
      return;
    }

    if (!rateMap.has(fromCode)) {
      setFromCode(rateMap.has(DEFAULT_FROM) ? DEFAULT_FROM : assets[0].code);
    }

    if (!rateMap.has(toCode)) {
      const fallback = rateMap.has(DEFAULT_TO)
        ? DEFAULT_TO
        : (assets.find((asset) => asset.code !== fromCode)?.code ?? assets[0].code);

      setToCode(fallback);
    }
  }, [assets, fromCode, toCode, rateMap]);

  useEffect(() => {
    if (!assets.length) {
      return;
    }

    if (forcedFromCode && rateMap.has(forcedFromCode)) {
      setFromCode(forcedFromCode);
    }

    if (forcedToCode && rateMap.has(forcedToCode)) {
      setToCode(forcedToCode);
    }
  }, [assets, forcedFromCode, forcedToCode, rateMap]);

  useEffect(() => {
    onPairChange?.(fromCode, toCode);
  }, [fromCode, toCode, onPairChange]);

  useEffect(() => {
    if (!forcedAmount) {
      return;
    }

    const parsed = validateAmount(forcedAmount);

    if (parsed.ok) {
      setAmountInput(forcedAmount);
    }
  }, [forcedAmount]);

  const inputValidation = validateAmount(amountInput);
  const debouncedValidation = validateAmount(debouncedAmount);

  const fromAsset = rateMap.get(fromCode);
  const toAsset = rateMap.get(toCode);

  const convertedValue = useMemo(() => {
    if (!fromAsset || !toAsset || !debouncedValidation.ok) {
      return null;
    }

    return convertAmount(debouncedValidation.value, fromAsset, toAsset);
  }, [fromAsset, toAsset, debouncedValidation]);

  const resolvedMultiConversionCodes = useMemo(() => {
    const configuredCodes =
      multiConversionCodes && multiConversionCodes.length > 0
        ? multiConversionCodes
        : [...DEFAULT_MULTI_CONVERSION_CODES];

    return Array.from(
      new Set(
        [toCode, ...configuredCodes]
          .map((code) => code.trim().toUpperCase())
          .filter(Boolean),
      ),
    ).slice(0, MAX_MULTI_CONVERSIONS);
  }, [multiConversionCodes, toCode]);

  const multiConversions = useMemo(() => {
    if (!fromAsset || !debouncedValidation.ok) {
      return [];
    }

    return resolvedMultiConversionCodes
      .map((code) => rateMap.get(code))
      .filter((asset): asset is NonNullable<typeof asset> => Boolean(asset))
      .map((asset) => ({
        asset,
        value: convertAmount(debouncedValidation.value, fromAsset, asset),
      }));
  }, [fromAsset, debouncedValidation, resolvedMultiConversionCodes, rateMap]);

  const currentRate = useMemo(() => {
    if (!fromAsset || !toAsset) {
      return null;
    }

    return formatRate(fromAsset, toAsset);
  }, [fromAsset, toAsset]);

  const inverseRate = useMemo(() => {
    if (!fromAsset || !toAsset) {
      return null;
    }

    return formatInverseRate(fromAsset, toAsset);
  }, [fromAsset, toAsset]);

  const handleSwap = () => {
    setSwapAnimationStep((current) => current + 1);
    setFromCode(toCode);
    setToCode(fromCode);
  };

  const marketAsset = useMemo(() => {
    if (toAsset?.type === "crypto") {
      return toAsset;
    }

    if (fromAsset?.type === "crypto") {
      return fromAsset;
    }

    return null;
  }, [fromAsset, toAsset]);

  const {
    data: marketData,
    error: marketError,
    isLoading: isMarketLoading,
    refresh: refreshMarket,
  } = useCryptoMarket(marketAsset?.code ?? null, marketRange);

  const handleCopyConvertedValue = async () => {
    if (convertedValue === null || !toAsset) {
      return;
    }

    const text = `${formatAmount(convertedValue, toAsset)} ${toAsset.code}`;

    try {
      await navigator.clipboard.writeText(text);
      setIsCopied(true);
      window.setTimeout(() => setIsCopied(false), 1400);
    } catch {
      setIsCopied(false);
    }
  };

  const handleCopyShareLink = async () => {
    if (typeof window === "undefined") {
      return;
    }

    const link = buildConversionShareUrl({
      origin: window.location.origin,
      pathname: window.location.pathname,
      amount: amountInput,
      fromCode,
      toCode,
    });

    const copied = await copyTextToClipboard(link);

    if (!copied) {
      setIsShareLinkCopied(false);
      return;
    }

    setIsShareLinkCopied(true);
    window.setTimeout(() => setIsShareLinkCopied(false), 1400);
  };

  const displayUpdatedAt = useMemo(() => {
    if (!data) {
      return null;
    }

    const timestamps = [new Date(data.updatedAt).getTime()];

    if (marketData?.updatedAt) {
      timestamps.push(new Date(marketData.updatedAt).getTime());
    }

    const latest = Math.max(
      ...timestamps.filter((timestamp) => Number.isFinite(timestamp)),
    );

    return Number.isFinite(latest) ? new Date(latest).toISOString() : data.updatedAt;
  }, [data, marketData?.updatedAt]);

  const marketRangeChangePct = useMemo(() => {
    if (!marketData) {
      return null;
    }

    const points = marketData.priceHistory;

    if (points.length >= 2) {
      const first = points[0]?.priceUsd;
      const last = points[points.length - 1]?.priceUsd;

      if (
        typeof first === "number" &&
        typeof last === "number" &&
        Number.isFinite(first) &&
        Number.isFinite(last) &&
        first > 0
      ) {
        return ((last - first) / first) * 100;
      }
    }

    if (marketRange === "24h") {
      return marketData.change24hPct;
    }

    return null;
  }, [marketData, marketRange]);

  const amountPrefix = getDisplaySymbol(fromAsset);
  const amountInputPaddingLeft = useMemo(() => {
    if (!amountPrefix) {
      return undefined;
    }

    const prefixWidthCh = Math.max(amountPrefix.length, 1);
    return `calc(1rem + ${prefixWidthCh}ch + 0.85rem)`;
  }, [amountPrefix]);

  const currentPair = useMemo(() => ({ fromCode, toCode }), [fromCode, toCode]);
  const isCurrentPairPinned = isPinnedPair(currentPair);
  const hasReachedPinnedPairsLimit =
    isPinnedPairsLimitReached && !isCurrentPairPinned;

  const handleTogglePinnedCurrentPair = () => {
    setPinStarAnimationStep((current) => current + 1);
    togglePinnedPair(currentPair);
  };

  const pinnedPairItems = useMemo(
    () =>
      pinnedPairs
        .map((pair) => {
          const fromAssetEntry = rateMap.get(pair.fromCode);
          const toAssetEntry = rateMap.get(pair.toCode);

          if (!fromAssetEntry || !toAssetEntry || fromAssetEntry.code === toAssetEntry.code) {
            return null;
          }

          return {
            pair,
            fromAsset: fromAssetEntry,
            toAsset: toAssetEntry,
          };
        })
        .filter(
          (
            item,
          ): item is {
            pair: PinnedPair;
            fromAsset: (typeof assets)[number];
            toAsset: (typeof assets)[number];
          } => Boolean(item),
        ),
    [pinnedPairs, rateMap],
  );

  if (isLoading && !data) {
    return <ConverterSkeleton />;
  }

  if (!data && error) {
    return <ConverterErrorState message={error} onRetry={() => void refresh()} />;
  }

  if (!data || assets.length === 0) {
    return <ConverterEmptyState />;
  }

  const convertedDisplay =
    convertedValue !== null && toAsset
      ? `${formatAmount(convertedValue, toAsset)} ${toAsset.code}`
      : "--";

  const amountError = inputValidation.ok ? null : inputValidation.error;
  const activeQuickAmount = inputValidation.ok ? inputValidation.value : null;
  const forAmountDisplay =
    fromAsset && inputValidation.ok
      ? formatAmount(inputValidation.value, fromAsset)
      : "-";
  const pairTransitionKey =
    fromAsset && toAsset ? `${fromAsset.code}-${toAsset.code}` : "empty";

  return (
    <Card className="relative overflow-hidden border-border/70 bg-card/90">
      <ConverterCardHeader
        fiatSource={data.sources.fiat}
        cryptoSource={data.sources.crypto}
      />

      <CardContent className="relative z-10 space-y-5 pt-4 sm:pt-5">
        {error ? (
          <div className="flex items-center gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
            <AlertTriangle className="h-4 w-4" />
            <span>Using last successful data. Latest refresh failed: {error}</span>
          </div>
        ) : null}

        <ConverterCardAmountInput
          amountInput={amountInput}
          amountPrefix={amountPrefix}
          amountInputPaddingLeft={amountInputPaddingLeft}
          amountError={amountError}
          quickAmounts={QUICK_AMOUNTS}
          activeQuickAmount={activeQuickAmount}
          onAmountChange={setAmountInput}
          onQuickAmountSelect={(value) => setAmountInput(String(value))}
        />

        <ConverterCardPairSelection
          assets={assets}
          fromCode={fromCode}
          toCode={toCode}
          shouldReduceMotion={prefersReducedMotion}
          swapAnimationStep={swapAnimationStep}
          onFromChange={setFromCode}
          onToChange={setToCode}
          onSwap={handleSwap}
        />

        <ConverterCardPinnedPairs
          shouldReduceMotion={prefersReducedMotion}
          isCurrentPairPinned={isCurrentPairPinned}
          hasReachedPinnedPairsLimit={hasReachedPinnedPairsLimit}
          pinStarAnimationStep={pinStarAnimationStep}
          pinnedPairItems={pinnedPairItems}
          fromCode={fromCode}
          toCode={toCode}
          isPinnedPairsReady={isPinnedPairsReady}
          maxPinnedPairs={MAX_PINNED_PAIRS}
          onTogglePinnedCurrentPair={handleTogglePinnedCurrentPair}
          onSelectPinnedPair={(pair) => {
            setFromCode(pair.fromCode);
            setToCode(pair.toCode);
          }}
          onRemovePinnedPair={removePinnedPair}
        />

        <ConverterCardConvertedValue
          shouldReduceMotion={prefersReducedMotion}
          fromAsset={fromAsset}
          toAsset={toAsset}
          pairTransitionKey={pairTransitionKey}
          convertedDisplay={convertedDisplay}
          forAmountDisplay={forAmountDisplay}
          isShareLinkCopied={isShareLinkCopied}
          isCopied={isCopied}
          canCopyConvertedValue={convertedValue !== null && Boolean(toAsset)}
          onCopyShareLink={() => void handleCopyShareLink()}
          onCopyConvertedValue={() => void handleCopyConvertedValue()}
        />

        <ConverterCardMultiConversion
          shouldReduceMotion={prefersReducedMotion}
          debouncedValidation={debouncedValidation}
          fromAsset={fromAsset}
          multiConversions={multiConversions}
          isRatesLoading={isLoading}
        />

        <Separator className="bg-border/70" />

        <ConverterCardRatesSummary
          fromAsset={fromAsset}
          toAsset={toAsset}
          currentRate={currentRate}
          inverseRate={inverseRate}
          updatedAt={displayUpdatedAt ?? data.updatedAt}
        />

        <ConverterCardMarketData
          marketAsset={marketAsset}
          marketData={marketData}
          marketError={marketError}
          isMarketLoading={isMarketLoading}
          marketRange={marketRange}
          marketRangeLabels={MARKET_RANGE_LABELS}
          marketRangeChangePct={marketRangeChangePct}
          onMarketRangeChange={setMarketRange}
        />

        <div className="flex items-center justify-end">
          <Button
            type="button"
            variant="ghost"
            size="sm"
            onClick={() => {
              void refresh();
              void refreshMarket();
            }}
            className="text-muted-foreground"
          >
            <RefreshCcw className="mr-2 h-4 w-4" />
            Refresh rates
          </Button>
        </div>

        {isLoading ? (
          <div className="flex items-center justify-end gap-2 text-xs text-muted-foreground">
            <Loader2 className="h-3.5 w-3.5 animate-spin" />
            Updating market rates...
          </div>
        ) : null}
      </CardContent>
    </Card>
  );
}
