"use client"

import { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react"
import { ArrowRightLeft, Search, X } from "lucide-react"

import { type BenchmarkEvaluationCardData } from "@/components/benchmark-evaluation-card"
import { DeveloperTable, type DeveloperTableSortCol } from "@/components/developer-table"
import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
import { ModelCompareDialog } from "@/components/model-compare-dialog"
import { ModelTable, type ModelTableSortCol } from "@/components/model-table"
import { Navigation } from "@/components/navigation"
import { PageLoadingState, type PageLoadingStage } from "@/components/page-loading-state"
import { ParamRangePicker } from "@/components/param-range-picker"
import { fetchCorpusAggregates, fetchDevelopers, fetchModelCards, fetchBenchmarkMetadata, type DeveloperListItem } from "@/lib/dashboard-data-client"
import type { BenchmarkCard } from "@/lib/benchmark-schema"
import { isOfficialDeveloper } from "@/lib/known-developers"
import { PARAM_RANGE_MAX_INDEX, paramStepToNumeric } from "@/lib/param-range"

const PAGE_SIZE = 40
const MAX_COMPARE_MODELS = 4

type ModelSort = ModelTableSortCol
type DevSort = DeveloperTableSortCol
type SortDir = "asc" | "desc"
type DevScope = "official" | "community" | "all"

// Default sort direction per column when the user first clicks it. Numeric /
// recency columns descend (newest, biggest first); name columns ascend.
const MODEL_DEFAULT_DIR: Record<ModelSort, SortDir> = {
  name: "asc",
  developer: "asc",
  released: "desc",
  params: "desc",
  results: "desc",
}
const DEV_DEFAULT_DIR: Record<DevSort, SortDir> = {
  name: "asc",
  models: "desc",
  benchmarks: "desc",
  results: "desc",
}

function safeTimestamp(value: string | null | undefined) {
  if (!value) return 0
  const numeric = Number(value)
  if (!Number.isNaN(numeric) && !value.includes("-")) {
    return numeric > 1_000_000_000_000 ? numeric : numeric * 1000
  }
  const parsed = new Date(value).getTime()
  return Number.isNaN(parsed) ? 0 : parsed
}

export default function ModelsPage() {
  const [evaluations, setEvaluations] = useState<BenchmarkEvaluationCardData[]>([])
  const [developers, setDevelopers] = useState<DeveloperListItem[]>([])
  const [benchmarkCards, setBenchmarkCards] = useState<Record<string, BenchmarkCard>>({})
  const [totalBenchmarksFromHeadline, setTotalBenchmarksFromHeadline] = useState<number | null>(null)
  const [modelsStageDone, setModelsStageDone] = useState(false)
  const [metadataStageDone, setMetadataStageDone] = useState(false)
  const [headlineStageDone, setHeadlineStageDone] = useState(false)
  const [loadingModels, setLoadingModels] = useState(true)
  const [loadingDevelopers, setLoadingDevelopers] = useState(false)
  const [developersReady, setDevelopersReady] = useState(false)
  const [groupByDeveloper, setGroupByDeveloper] = useState(false)
  const [modelSortBy, setModelSortBy] = useState<ModelSort>("released")
  const [modelSortDir, setModelSortDir] = useState<SortDir>("desc")
  const [developerSortBy, setDeveloperSortBy] = useState<DevSort>("models")
  const [developerSortDir, setDeveloperSortDir] = useState<SortDir>("desc")
  const [developerScope, setDeveloperScope] = useState<DevScope>("official")

  const handleModelSort = useCallback((col: ModelSort) => {
    setModelSortBy((current) => {
      if (current === col) {
        setModelSortDir((dir) => (dir === "asc" ? "desc" : "asc"))
        return current
      }
      setModelSortDir(MODEL_DEFAULT_DIR[col])
      return col
    })
  }, [])

  const handleDeveloperSort = useCallback((col: DevSort) => {
    setDeveloperSortBy((current) => {
      if (current === col) {
        setDeveloperSortDir((dir) => (dir === "asc" ? "desc" : "asc"))
        return current
      }
      setDeveloperSortDir(DEV_DEFAULT_DIR[col])
      return col
    })
  }, [])
  const [searchQuery, setSearchQuery] = useState("")
  const [selectedModelIds, setSelectedModelIds] = useState<string[]>([])
  const [compareOpen, setCompareOpen] = useState(false)
  const [visibleCount, setVisibleCount] = useState(PAGE_SIZE)
  const [minParamStep, setMinParamStep] = useState(0)
  const [maxParamStep, setMaxParamStep] = useState(PARAM_RANGE_MAX_INDEX)
  const [showUnknownSize, setShowUnknownSize] = useState(true)
  const deferredSearchQuery = useDeferredValue(searchQuery)
  const numericMinParams = useMemo(() => paramStepToNumeric(minParamStep, "min"), [minParamStep])
  const numericMaxParams = useMemo(() => paramStepToNumeric(maxParamStep, "max"), [maxParamStep])

  useEffect(() => {
    const modelCardsRequest = fetchModelCards()
      .then((cards) => {
        setEvaluations(cards)
        setModelsStageDone(true)
      })
      .catch((error) => {
        console.error("Failed to load evaluations:", error)
      })

    const benchmarkMetadataRequest = fetchBenchmarkMetadata()
      .then((metadata) => {
        setBenchmarkCards(metadata)
        setMetadataStageDone(true)
      })
      .catch((error) => {
        console.error("Failed to load benchmark metadata:", error)
      })

    const corpusAggregatesRequest = fetchCorpusAggregates()
      .then((aggregates) => {
        if (typeof aggregates?.total_benchmarks === "number") {
          setTotalBenchmarksFromHeadline(aggregates.total_benchmarks)
        }
        setHeadlineStageDone(true)
      })
      .catch((error) => {
        console.error("Failed to load corpus aggregates:", error)
        setHeadlineStageDone(true)
      })

    Promise.allSettled([modelCardsRequest, benchmarkMetadataRequest, corpusAggregatesRequest])
      .finally(() => setLoadingModels(false))
  }, [])

  const totalBenchmarks = useMemo(
    () => totalBenchmarksFromHeadline ?? Object.keys(benchmarkCards).length,
    [totalBenchmarksFromHeadline, benchmarkCards],
  )

  useEffect(() => {
    if (!groupByDeveloper || developersReady || loadingDevelopers) return
    setLoadingDevelopers(true)
    fetchDevelopers()
      .then(setDevelopers)
      .catch((error) => {
        console.error("Failed to load developers:", error)
      })
      .finally(() => {
        setLoadingDevelopers(false)
        setDevelopersReady(true)
      })
  }, [developersReady, groupByDeveloper, loadingDevelopers])

  const loadingStages = useMemo<PageLoadingStage[]>(() => {
    if (groupByDeveloper && !developersReady) {
      return [
        { label: "Model index", done: modelsStageDone },
        { label: "Benchmark metadata", done: metadataStageDone },
        { label: "Corpus totals", done: headlineStageDone },
        { label: "Developer rollups", done: developersReady },
      ]
    }

    return [
      { label: "Model index", done: modelsStageDone },
      { label: "Benchmark metadata", done: metadataStageDone },
      { label: "Corpus totals", done: headlineStageDone },
    ]
  }, [developersReady, groupByDeveloper, headlineStageDone, metadataStageDone, modelsStageDone])

  // Models — filter + sort
  const sortedEvaluations = useMemo(() => {
    const query = deferredSearchQuery.trim().toLowerCase()
    let filtered = evaluations

    filtered = filtered.filter((row) => {
      if (row.params_billions == null) return showUnknownSize
      if (numericMinParams != null && row.params_billions < numericMinParams) return false
      if (numericMaxParams != null && row.params_billions > numericMaxParams) return false
      return true
    })

    if (query) {
      filtered = filtered.filter((row) => {
        return (
          (row.model_name ?? "").toLowerCase().includes(query) ||
          (row.canonical_model_name ?? "").toLowerCase().includes(query) ||
          (row.developer ?? "").toLowerCase().includes(query) ||
          (row.benchmark_names ?? []).some((b) => (b ?? "").toLowerCase().includes(query))
        )
      })
    }

    const dirMul = modelSortDir === "asc" ? 1 : -1
    const nameOf = (row: BenchmarkEvaluationCardData) => row.model_name ?? ""
    const developerOf = (row: BenchmarkEvaluationCardData) => row.developer ?? ""
    return filtered.slice().sort((a, b) => {
      let cmp = 0
      switch (modelSortBy) {
        case "name":
          cmp = nameOf(a).localeCompare(nameOf(b))
          break
        case "developer":
          cmp = developerOf(a).localeCompare(developerOf(b))
          break
        case "released":
          cmp = safeTimestamp(a.release_date) - safeTimestamp(b.release_date)
          break
        case "params":
          cmp = (a.params_billions ?? -1) - (b.params_billions ?? -1)
          break
        case "results":
          cmp = a.evaluations_count - b.evaluations_count
          break
      }
      // Stable tie-break by model name so equal rows don't shuffle.
      if (cmp === 0) return nameOf(a).localeCompare(nameOf(b))
      return cmp * dirMul
    })
  }, [evaluations, deferredSearchQuery, modelSortBy, modelSortDir, numericMinParams, numericMaxParams, showUnknownSize])

  // Developers — filter + sort
  const sortedDevelopers = useMemo(() => {
    const query = deferredSearchQuery.trim().toLowerCase()
    let filtered = developers

    if (developerScope !== "all") {
      filtered = filtered.filter((dev) => {
        const official = isOfficialDeveloper(dev.developer)
        return developerScope === "official" ? official : !official
      })
    }

    if (query) {
      filtered = filtered.filter(
        (dev) =>
          (dev.developer ?? "").toLowerCase().includes(query) ||
          (dev.popular_evals ?? []).some((ev) => (ev?.benchmark ?? "").toLowerCase().includes(query)),
      )
    }

    const dirMul = developerSortDir === "asc" ? 1 : -1
    return filtered.slice().sort((a, b) => {
      const devA = a.developer ?? ""
      const devB = b.developer ?? ""
      let cmp = 0
      switch (developerSortBy) {
        case "name":
          cmp = devA.localeCompare(devB)
          break
        case "models":
          cmp = a.model_count - b.model_count
          break
        case "benchmarks":
          cmp = a.benchmark_count - b.benchmark_count
          break
        case "results":
          cmp = a.evaluation_count - b.evaluation_count
          break
      }
      if (cmp === 0) return devA.localeCompare(devB)
      return cmp * dirMul
    })
  }, [developers, deferredSearchQuery, developerSortBy, developerSortDir, developerScope])

  // Reset visible window when filter/sort changes
  useEffect(() => {
    setVisibleCount(PAGE_SIZE)
  }, [groupByDeveloper, modelSortBy, modelSortDir, developerSortBy, developerSortDir, developerScope, deferredSearchQuery, minParamStep, maxParamStep, showUnknownSize])

  const totalCount = groupByDeveloper ? sortedDevelopers.length : sortedEvaluations.length
  const visibleEvaluations = useMemo(
    () => sortedEvaluations.slice(0, visibleCount),
    [sortedEvaluations, visibleCount],
  )
  const visibleDevelopers = useMemo(
    () => sortedDevelopers.slice(0, visibleCount),
    [sortedDevelopers, visibleCount],
  )
  const hasMore = visibleCount < totalCount
  const handleLoadMore = useCallback(() => {
    setVisibleCount((current) => Math.min(current + PAGE_SIZE, totalCount))
  }, [totalCount])

  const toggleModelSelection = useCallback((id: string) => {
    setSelectedModelIds((current) => {
      if (current.includes(id)) return current.filter((existing) => existing !== id)
      if (current.length >= MAX_COMPARE_MODELS) return current
      return [...current, id]
    })
  }, [])

  const selectedModels = useMemo(
    () =>
      selectedModelIds
        .map((id) => evaluations.find((evaluation) => evaluation.id === id))
        .filter((evaluation): evaluation is BenchmarkEvaluationCardData => Boolean(evaluation)),
    [evaluations, selectedModelIds],
  )

  const loading = loadingModels || (groupByDeveloper && !developersReady)
  const totalResults = useMemo(
    () => sortedEvaluations.reduce((sum, row) => sum + row.evidence_count, 0),
    [sortedEvaluations],
  )

  return (
    <div className="min-h-screen bg-background">
      <Navigation />

      <main className="mx-auto w-full max-w-[96rem] px-4 pt-12 pb-24 sm:px-8">
        {/* HEADER --------------------------------------------------- */}
        <div className="kicker">Index</div>
        <h1 className="ec-page-h1">{groupByDeveloper ? "Model developers" : "Models"}</h1>
        <p className="ec-page-lede">
          {groupByDeveloper
            ? "Every reporting organisation in the corpus and the breadth of evaluation it ships."
            : (
              <>
                Every indexed model and the shape of its published evaluation record across{" "}
                <span className="font-mono text-[13px]">
                  {totalBenchmarks.toLocaleString()}
                </span>{" "}
                benchmarks reported on by the developer (or a third party).
              </>
            )}
        </p>

        {/* META + FILTER BAR — single row -------------------------- */}
        <div className="mb-6 flex flex-wrap items-center gap-x-8 gap-y-3 border-y border-[color:var(--border-soft)] py-4">
          {/* Inline meta — mono kicker stat strip */}
          <div className="flex shrink-0 flex-wrap items-baseline gap-x-4 gap-y-1 font-mono text-[11px] tracking-[0.1em] uppercase text-[color:var(--fg-subtle)]">
            <span>
              <span className="text-[color:var(--fg)] tabular-nums font-semibold mr-1">
                {totalCount.toLocaleString()}
              </span>
              {groupByDeveloper ? "developers" : "models"}
            </span>
            {!groupByDeveloper && (
              <span>
                · <span className="text-[color:var(--fg)] tabular-nums font-semibold mr-1">{totalResults.toLocaleString()}</span>
                results
              </span>
            )}
            {!groupByDeveloper && (
              <span>
                · <span className="text-[color:var(--fg)] tabular-nums font-semibold mr-1">{selectedModels.length}/{MAX_COMPARE_MODELS}</span>
                selected to compare
              </span>
            )}
          </div>

          <span className="hidden h-5 w-px bg-[color:var(--border-soft)] sm:block" />

          <div className="ec-mode-toggle">
            <button
              type="button"
              className={!groupByDeveloper ? "on" : ""}
              onClick={() => setGroupByDeveloper(false)}
            >
              Models
            </button>
            <button
              type="button"
              className={groupByDeveloper ? "on" : ""}
              onClick={() => setGroupByDeveloper(true)}
            >
              Developers
            </button>
          </div>

          {groupByDeveloper && (
            <div className="ec-mode-toggle" role="group" aria-label="Developer scope">
              <button
                type="button"
                className={developerScope === "official" ? "on" : ""}
                onClick={() => setDeveloperScope("official")}
              >
                Official
              </button>
              <button
                type="button"
                className={developerScope === "community" ? "on" : ""}
                onClick={() => setDeveloperScope("community")}
              >
                Community
              </button>
              <button
                type="button"
                className={developerScope === "all" ? "on" : ""}
                onClick={() => setDeveloperScope("all")}
              >
                All
              </button>
            </div>
          )}

          <div className="relative ml-auto min-w-[180px] flex-1 sm:max-w-[360px]">
            <Search className="pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[color:var(--fg-subtle)]" />
            <input
              className="ec-input pl-9"
              value={searchQuery}
              onChange={(event) => setSearchQuery(event.target.value)}
              placeholder={
                groupByDeveloper ? "Search developers…" : "Search model or developer…"
              }
            />
          </div>

        </div>

        {/* PARAM RANGE — its own row so the rail has room to breathe.
            Sharing the toolbar with stats / search / sort squished it. */}
        {!groupByDeveloper && (
          <div className="mb-6 -mt-2">
            <ParamRangePicker
              variant="inline"
              headline="Params"
              minStep={minParamStep}
              maxStep={maxParamStep}
              onMinChange={setMinParamStep}
              onMaxChange={setMaxParamStep}
              onReset={() => {
                setMinParamStep(0)
                setMaxParamStep(PARAM_RANGE_MAX_INDEX)
              }}
              showUnknownSize={showUnknownSize}
              onShowUnknownSizeChange={setShowUnknownSize}
            />
          </div>
        )}

        {/* TABLE ---------------------------------------------------- */}
        {loading ? (
          <PageLoadingState
            title={groupByDeveloper ? "Loading developers" : "Loading models"}
            description={
              groupByDeveloper
                ? "Refreshing the model index, benchmark metadata, and developer rollups."
                : "Refreshing the model index, benchmark metadata, and corpus totals."
            }
            stages={loadingStages}
            className="py-14"
          />
        ) : totalCount === 0 ? (
          <div className="py-16 text-center border border-dashed border-[color:var(--border-soft)] bg-[color:var(--bg-warm)]">
            <p className="mb-4 text-base text-[color:var(--fg-muted)]">
              {groupByDeveloper
                ? "No developers found matching your filters."
                : "No models found matching your filters."}
            </p>
            <button
              type="button"
              className="btn-ec outline"
              onClick={() => {
                setSearchQuery("")
                setModelSortBy("released")
                setModelSortDir("desc")
                setDeveloperSortBy("models")
                setDeveloperSortDir("desc")
                setDeveloperScope("all")
              }}
            >
              Reset filters
            </button>
          </div>
        ) : groupByDeveloper ? (
          <DeveloperTable
            rows={visibleDevelopers}
            sortCol={developerSortBy}
            sortDir={developerSortDir}
            onSort={handleDeveloperSort}
          />
        ) : (
          <ModelTable
            rows={visibleEvaluations}
            selectedIds={selectedModelIds}
            onToggleSelect={toggleModelSelection}
            maxCompare={MAX_COMPARE_MODELS}
            sortCol={modelSortBy}
            sortDir={modelSortDir}
            onSort={handleModelSort}
          />
        )}

        <InfiniteScrollSentinel
          hasMore={hasMore}
          onLoadMore={handleLoadMore}
          loadingLabel="Loading more…"
          endLabel={`Showing ${Math.min(visibleCount, totalCount).toLocaleString()} of ${totalCount.toLocaleString()} ${groupByDeveloper ? "developers" : "models"}`}
        />

        {/* COMPARE TRAY (sticky bottom) ---------------------------- */}
        {!groupByDeveloper && selectedModels.length > 0 && (
          <div className="pointer-events-none fixed inset-x-0 bottom-4 z-40 flex justify-center px-4">
            <div className="pointer-events-auto w-full max-w-5xl border border-[color:var(--fg)] bg-[color:var(--bg)] p-4 shadow-[var(--shadow-card)]">
              <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
                <div className="space-y-2">
                  <div className="kicker">Models selected to compare</div>
                  <div className="flex flex-wrap gap-2">
                    {selectedModels.map((model) => (
                      <span
                        key={model.id}
                        className="inline-flex items-center gap-2 border border-[color:var(--border-soft)] bg-[color:var(--bg-warm)] px-3 py-1.5 text-sm"
                      >
                        <span className="font-medium text-[color:var(--fg)]">
                          {model.model_name}
                        </span>
                        <button
                          type="button"
                          onClick={() => toggleModelSelection(model.id)}
                          className="text-[color:var(--fg-muted)] transition-colors hover:text-[color:var(--fg)]"
                          aria-label={`Remove ${model.model_name} from compare`}
                        >
                          <X className="h-3.5 w-3.5" />
                        </button>
                      </span>
                    ))}
                  </div>
                </div>

                <div className="flex flex-wrap items-center gap-2">
                  <button
                    type="button"
                    className="btn-ec ghost"
                    onClick={() => setSelectedModelIds([])}
                  >
                    Clear
                  </button>
                  <button
                    type="button"
                    onClick={() => setCompareOpen(true)}
                    disabled={selectedModels.length < 2}
                    className="btn-ec disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    <ArrowRightLeft className="h-3.5 w-3.5" />
                    Compare {selectedModels.length}
                  </button>
                </div>
              </div>
            </div>
          </div>
        )}
      </main>

      <ModelCompareDialog
        models={selectedModels}
        open={compareOpen}
        onOpenChange={setCompareOpen}
      />
    </div>
  )
}
