"use client"

import Link from "next/link"
import * as React from "react"
import { Suspense } from "react"
import { ArrowLeft, FileX2 } from "lucide-react"
import { toast } from "sonner"

import { Button } from "@/components/ui/button"
import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"
import {
  UploadXmlImportFooter,
  type UploadXmlCustomerInsertTarget,
  type UploadXmlImportFooterHandle,
} from "@/components/xml-apis/upload-xml/components/upload-xml-import-footer"
import { UploadXmlImportHeader } from "@/components/xml-apis/upload-xml/components/upload-xml-import-header"
import { UploadXmlImportShell } from "@/components/xml-apis/upload-xml/components/upload-xml-import-shell"
import { UploadXmlImportSkeleton } from "@/components/xml-apis/upload-xml/components/upload-xml-import-skeleton"
import { UploadXmlImportWorkspace } from "@/components/xml-apis/upload-xml/components/upload-xml-import-workspace"
import { UploadXmlFetchPriceDirectDialog } from "@/components/xml-apis/upload-xml/components/upload-xml-fetch-price-direct-dialog"
import { UploadXmlEquityOptionPricesDialog } from "@/components/xml-apis/upload-xml/components/upload-xml-equity-option-prices-dialog"
import {
  buildEquityOptionPriceRows,
  collectOptionUnderlyingQuery,
} from "@/components/xml-apis/upload-xml/_lib/equity-option-prices"
import { useUploadXmlOptionUnderlyings } from "@/components/xml-apis/upload-xml/hooks/use-upload-xml-option-underlyings"
import { useUploadXmlImport } from "@/components/xml-apis/upload-xml/hooks/use-upload-xml-import"
import { useUploadXmlBondDetectMode } from "@/components/xml-apis/upload-xml/hooks/use-upload-xml-bond-detect-mode"
import type { UploadXmlBulkProcessSnapshot } from "@/components/xml-apis/upload-xml/_lib/import-grid-utils"
import type { UploadXmlImportView } from "@/components/xml-apis/upload-xml/types"
import { useSearchParams } from "next/navigation"

type UploadXmlImportPageProps = {
  uploadId: string
  /** Server-resolved `?detect=commonrules` (legacy `bonds` also accepted). */
  detectCommonRules?: boolean
  /** @deprecated use detectCommonRules */
  detectBonds?: boolean
  initialView?: UploadXmlImportView | null
  initialErrorMessage?: string | null
  serverResolved?: boolean
}

function UploadXmlImportPageContent({
  uploadId,
  detectCommonRules = false,
  detectBonds = false,
  initialView = null,
  initialErrorMessage = null,
  serverResolved = false,
}: UploadXmlImportPageProps) {
  const searchParams = useSearchParams()
  const detectRaw = (searchParams.get("detect") ?? "").trim().toLowerCase()
  const detectFromUrl =
    detectRaw === "commonrules" ||
    detectRaw === "common-rules" ||
    detectRaw === "bonds"
  const detectMode = detectCommonRules || detectBonds || detectFromUrl

  const {
    view,
    isLoading,
    isLoadingRemaining,
    errorMessage,
    page,
    reload,
    wholeFile,
    recordsComplete,
  } = useUploadXmlImport(uploadId, {
    initialView,
    initialErrorMessage,
    serverResolved,
    // Common-rules needs every row. Normal import stays on the first page
    // (or upgrades to whole-file only when customer tags require it).
    loadAllRecords: detectMode,
  })

  const recordsLoading = Boolean(view) && isLoadingRemaining

  // Wait for the full file before running file-wide rules (first page may already be painted).
  const bondDetect = useUploadXmlBondDetectMode(
    uploadId,
    detectMode && recordsComplete,
    view,
  )

  // The server re-reads Extraction Studio when an upload opens. Say so when it changed the records, or when it
  // could not because rows were already staged (the file is then left as it was, so the view may be out of date).
  const studioRefresh = view?.studioRefresh ?? null
  React.useEffect(() => {
    if (!studioRefresh) return
    if (studioRefresh.status === "refreshed") {
      toast.success("Updated from Extraction Studio", {
        id: `studio-refresh-${uploadId}`,
        description:
          studioRefresh.recordsBefore !== undefined && studioRefresh.recordsAfter !== undefined
            ? `${studioRefresh.recordsBefore} → ${studioRefresh.recordsAfter} records`
            : undefined,
      })
    } else if (studioRefresh.status === "skipped_temp_data") {
      toast.warning("Extraction Studio has newer data", {
        id: `studio-refresh-${uploadId}`,
        description:
          "This upload already has temp data, so its records were not changed. Delete the temp data and reopen to pick up the Studio changes.",
      })
    }
  }, [studioRefresh, uploadId])

  const resolveInsertedColumnIds = React.useCallback((importView: UploadXmlImportView) => {
    // Portal-inserted only — a pending temp_id means the row already exists in
    // mw_temp_transactions but has not been added to the customer portal yet.
    // Keep sending temp_id on save so ajax_save can upsert; do not treat it as done.
    return new Set(
      importView.recordColumns
        .filter((column) => column.isInserted)
        .map((column) => column.id)
    )
  }, [])

  const [savedColumnIds, setSavedColumnIds] = React.useState<Set<string>>(() =>
    initialView ? resolveInsertedColumnIds(initialView) : new Set()
  )

  // Track the upload/page we last synced against. Navigating to a different
  // upload or page is an authoritative reset; a plain view refresh (e.g. after
  // an insert) must merge — never replace — so optimistic inserts aren't lost
  // when the backend hasn't yet reported them as inserted.
  const syncedKeyRef = React.useRef<string | null>(null)

  React.useEffect(() => {
    if (!view) {
      syncedKeyRef.current = null
      // Merges optimistic inserts with the server view (ref-tracked sync key); not derivable.
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setSavedColumnIds(new Set())
      return
    }

    const key = `${uploadId}::${page}`
    const serverInserted = resolveInsertedColumnIds(view)

    setSavedColumnIds((current) => {
      if (syncedKeyRef.current !== key) {
        syncedKeyRef.current = key
        return serverInserted
      }
      if (serverInserted.size === 0) return current
      const next = new Set(current)
      for (const columnId of serverInserted) next.add(columnId)
      return next
    })
  }, [uploadId, page, resolveInsertedColumnIds, view])

  const handleRecordSaved = React.useCallback((columnId: string) => {
    setSavedColumnIds((current) => {
      if (current.has(columnId)) return current
      const next = new Set(current)
      next.add(columnId)
      return next
    })
  }, [])

  const handleRecordsSaved = React.useCallback((columnIds: string[]) => {
    setSavedColumnIds((current) => {
      const next = new Set(current)
      for (const columnId of columnIds) {
        next.add(columnId)
      }
      return next
    })
  }, [])

  const bulkProcessStateRef = React.useRef<UploadXmlBulkProcessSnapshot | null>(null)
  const handleBulkProcessStateChange = React.useCallback(
    (snapshot: UploadXmlBulkProcessSnapshot) => {
      bulkProcessStateRef.current = snapshot
    },
    []
  )
  const getBulkProcessState = React.useCallback(
    () => bulkProcessStateRef.current,
    []
  )

  const footerRef = React.useRef<UploadXmlImportFooterHandle>(null)
  const [bulkBusy, setBulkBusy] = React.useState(false)
  const handleInsertCustomer = React.useCallback(
    (target: UploadXmlCustomerInsertTarget) => {
      void footerRef.current?.processCustomer(target)
    },
    [],
  )

  const [priceDirectOpen, setPriceDirectOpen] = React.useState(false)
  const [priceDirectBusy, setPriceDirectBusy] = React.useState(false)

  const handlePriceDirectOpenChange = React.useCallback(
    (open: boolean) => {
      setPriceDirectOpen(open)
      if (!open) {
        setPriceDirectBusy(false)
        reload()
      }
    },
    [reload],
  )

  const [equityOptionPricesOpen, setEquityOptionPricesOpen] = React.useState(false)
  const optionUnderlyingQuery = React.useMemo(
    () =>
      view && detectMode && equityOptionPricesOpen
        ? collectOptionUnderlyingQuery(view, bondDetect.matches)
        : { phrases: [], currency: "USD", assetCodes: [] },
    [view, detectMode, equityOptionPricesOpen, bondDetect.matches],
  )
  const optionUnderlyings = useUploadXmlOptionUnderlyings(
    uploadId,
    detectMode && equityOptionPricesOpen,
    optionUnderlyingQuery,
  )
  const resetOptionUnderlyings = optionUnderlyings.reset
  const handleEquityOptionPricesOpenChange = React.useCallback(
    (open: boolean) => {
      setEquityOptionPricesOpen(open)
      if (!open) resetOptionUnderlyings()
    },
    [resetOptionUnderlyings],
  )
  const equityOptionPrices = React.useMemo(
    () =>
      view && detectMode
        ? buildEquityOptionPriceRows(
            view,
            bondDetect.matches,
            optionUnderlyings.underlyings,
            optionUnderlyings.byAssetCode,
          )
        : null,
    [
      view,
      detectMode,
      bondDetect.matches,
      optionUnderlyings.underlyings,
      optionUnderlyings.byAssetCode,
    ],
  )

  // Keep a partial grid on screen while the rest of a large file loads (badge in the header).
  const pageBody = isLoading && !view ? (
    <UploadXmlImportSkeleton />
  ) : !view ? (
    (() => {
      const detailMessage = errorMessage ?? initialErrorMessage
      const emptyXml =
        typeof detailMessage === "string" &&
        /contains no records|empty <RECORDS\/>|no import records were returned/i.test(
          detailMessage
        )
      const backendFault =
        typeof detailMessage === "string" &&
        /ErrorException|Deprecated|HTTP 5\d\d|unavailable|timed out|invalid response/i.test(
          detailMessage
        )

      return (
        <UploadXmlImportShell>
          <div className="flex min-h-0 flex-1 items-center justify-center p-6">
            <Empty className="max-w-md border border-border/60 bg-muted/10 py-12">
              <EmptyHeader>
                <EmptyMedia variant="icon" className="size-12 rounded-xl bg-destructive/10 text-destructive [&_svg:not([class*='size-'])]:size-6">
                  <FileX2 />
                </EmptyMedia>
                <EmptyTitle className="text-base">
                  {emptyXml
                    ? "XML file has no records"
                    : backendFault
                      ? "Could not load import view"
                      : "Upload XML record not found"}
                </EmptyTitle>
                <EmptyDescription>
                  {detailMessage
                    ? detailMessage
                    : "This upload may have been deleted, or the link is no longer valid."}
                </EmptyDescription>
              </EmptyHeader>
              <EmptyContent>
                <p className="rounded-md bg-muted px-2.5 py-1 font-mono text-muted-foreground text-xs">
                  ID {uploadId}
                </p>
                <Button variant="outline" size="sm" className="mt-1 gap-1.5" asChild>
                  <Link href="/dashboard/xml-apis/upload-xml">
                    <ArrowLeft className="size-3.5" />
                    Back to upload XML list
                  </Link>
                </Button>
              </EmptyContent>
            </Empty>
          </div>
        </UploadXmlImportShell>
      )
    })()
  ) : (
    <UploadXmlImportShell>
      <div className="flex min-h-0 min-w-0 flex-1 flex-col">
        <UploadXmlImportHeader
          view={view}
          recordsLoading={recordsLoading}
          bondDetect={
            detectMode
              ? {
                  isLoading: bondDetect.isLoading,
                  isAssetTypeLoading:
                    recordsComplete && bondDetect.isAssetTypeLoading,
                  classProgress: bondDetect.classProgress,
                  assetTypeProgress: bondDetect.assetTypeProgress,
                  errorMessage: bondDetect.errorMessage,
                  bondCount: bondDetect.bondCount,
                  structureCount: bondDetect.structureCount,
                  equityAccumulatorCount: bondDetect.equityAccumulatorCount,
                  equityOptionCount: bondDetect.equityOptionCount,
                  bondFundCount: bondDetect.bondFundCount,
                  equityFundCount: bondDetect.equityFundCount,
                  mixedFundCount: bondDetect.mixedFundCount,
                  alternativeInvestmentCount: bondDetect.alternativeInvestmentCount,
                  commodityCount: bondDetect.commodityCount,
                  cryptoCount: bondDetect.cryptoCount,
                  interestCount: bondDetect.interestCount,
                  cashCount: bondDetect.cashCount,
                  callCount: bondDetect.callCount,
                  fdCount: bondDetect.fdCount,
                  odCount: bondDetect.odCount,
                  loanCount: bondDetect.loanCount,
                  equityCount: bondDetect.equityCount,
                  totalRows: bondDetect.totalRows,
                }
              : null
          }
        />
        <UploadXmlImportWorkspace
          uploadId={uploadId}
          view={view}
          savedColumnIds={savedColumnIds}
          onRecordSaved={handleRecordSaved}
          onBulkProcessStateChange={handleBulkProcessStateChange}
          detectMode={detectMode}
          clientPaged={wholeFile}
          detectLoading={
            detectMode ? bondDetect.isLoading || !recordsComplete : false
          }
          bondDetectMatches={detectMode ? bondDetect.matches : undefined}
          masterByIsin={detectMode ? bondDetect.masterByIsin : undefined}
          onInsertCustomer={handleInsertCustomer}
          bulkBusy={bulkBusy}
          onCustomerAssigned={reload}
        />
        
        <UploadXmlImportFooter
          ref={footerRef}
          onBusyChange={setBulkBusy}
          uploadId={uploadId}
          view={view}
          savedColumnIds={savedColumnIds}
          getBulkProcessState={getBulkProcessState}
          onRefresh={reload}
          onRecordsSaved={handleRecordsSaved}
          priceDirectBusy={priceDirectBusy}
          onFetchPriceDirect={() => {
            setPriceDirectBusy(true)
            setPriceDirectOpen(true)
          }}
          onOpenEquityOptionPrices={
            detectMode ? () => setEquityOptionPricesOpen(true) : undefined
          }
          equityOptionPricesLoading={detectMode && bondDetect.isLoading}
          equityOptionCount={bondDetect.equityOptionCount}
        />
      </div>
    </UploadXmlImportShell>
  )

  return (
    <>
      {pageBody}
      <UploadXmlFetchPriceDirectDialog
        open={priceDirectOpen}
        onOpenChange={handlePriceDirectOpenChange}
        uploadId={uploadId}
        onBusyChange={setPriceDirectBusy}
      />
      {equityOptionPrices ? (
        <UploadXmlEquityOptionPricesDialog
          open={equityOptionPricesOpen}
          onOpenChange={handleEquityOptionPricesOpenChange}
          result={equityOptionPrices}
          underlyings={{
            loading: optionUnderlyings.isLoading,
            errorMessage: optionUnderlyings.errorMessage,
            assetUploadId: optionUnderlyings.assetUploadId,
            assetDate: optionUnderlyings.assetDate,
          }}
        />
      ) : null}
    </>
  )
}

export function UploadXmlImportPage(props: UploadXmlImportPageProps) {
  return (
    <Suspense fallback={<UploadXmlImportSkeleton />}>
      <UploadXmlImportPageContent {...props} />
    </Suspense>
  )
}
