"use client"

import { Plus } from "lucide-react"
import { useRouter } from "next/navigation"
import { useMemo, useState } from "react"

import { useChanged } from "@/hooks/use-changed"
import { dashboardCsrfHeader } from "@/lib/csrf.client"
import { cn } from "@/lib/utils"
import { errorMessageOr } from "@/lib/toast-api-error"

import { DetectWhyAcPopover, DetectWhyAtPopover } from "./isin-asset-master-detect-why"
import {
  LIST_HREF,
  type DetectAcProvenance,
  type DetectAtProvenance,
} from "./schema"

export type AcModuleOption = { code: string; label: string }

export type ATypeOptions = {
  byAc: Record<string, string[]>
  all: string[]
}

export type MacroAcOption = {
  name: string
  modules: string[]
  defaultModule: string
}

type Props = {
  id: string
  aClass: string
  aType: string
  aClassLabel: string
  /** Resolved Macro AC for the saved Base AC / AT. */
  macroAssetClass?: string
  /** Macro → Base AC module map for editing. */
  macroOptions?: MacroAcOption[]
  /** Default Asset Type per Base AC (from AT Detect / seed). */
  defaultAtByAc?: Record<string, string>
  moduleOptions: AcModuleOption[]
  aTypeOptions: ATypeOptions
  acAtEditAllowed: boolean
  acAtEditExcludedCodes: string[]
  detectAc?: DetectAcProvenance | null
  detectAt?: DetectAtProvenance | null
  detectFlagged?: boolean
  className?: string
}

export function IsinAssetMasterAcAtEditors({
  id,
  aClass,
  aType,
  aClassLabel,
  macroAssetClass = "",
  macroOptions = [],
  defaultAtByAc = {},
  moduleOptions,
  aTypeOptions,
  acAtEditAllowed,
  acAtEditExcludedCodes,
  detectAc = null,
  detectAt = null,
  detectFlagged = false,
  className,
}: Props) {
  const router = useRouter()
  const [ac, setAc] = useState(aClass)
  const [at, setAt] = useState(aType)
  const [macro, setMacro] = useState(macroAssetClass)
  // Base AC picker stays hidden until the user changes the Macro Asset Class.
  const [macroTouched, setMacroTouched] = useState(false)
  const [busy, setBusy] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [message, setMessage] = useState<string | null>(null)
  const [extraTypes, setExtraTypes] = useState<string[]>([])
  const [isAddOpen, setIsAddOpen] = useState(false)
  const [newAssetType, setNewAssetType] = useState("")
  const [isCreating, setIsCreating] = useState(false)

  const excluded = useMemo(
    () => new Set(acAtEditExcludedCodes),
    [acAtEditExcludedCodes]
  )

  if (useChanged(aClass)) {
    setAc(aClass)
    setMacroTouched(false)
  }
  if (useChanged(aType)) {
    setAt(aType)
  }
  if (useChanged(macroAssetClass)) {
    setMacro(macroAssetClass)
    setMacroTouched(false)
  }

  const optionByCode = useMemo(() => {
    const map = new Map(moduleOptions.map((opt) => [opt.code, opt]))
    if (ac && !map.has(ac)) {
      map.set(ac, { code: ac, label: aClassLabel || ac })
    }
    return map
  }, [moduleOptions, ac, aClassLabel])
  const allOptions = useMemo(
    () =>
      Array.from(optionByCode.values())
        .filter((opt) => !excluded.has(opt.code))
        .sort((a, b) => a.code.localeCompare(b.code)),
    [optionByCode, excluded]
  )

  const labelForCode = (code: string) =>
    optionByCode.get(code)?.label || code

  const macroByName = useMemo(() => {
    const map = new Map<string, MacroAcOption>()
    for (const opt of macroOptions) {
      map.set(opt.name, opt)
    }
    return map
  }, [macroOptions])

  const activeMacro = macroByName.get(macro) ?? null
  const macroSelectOptions = useMemo(() => {
    const names = macroOptions.map((opt) => opt.name)
    if (macro && !names.includes(macro)) {
      return [macro, ...names]
    }
    return names
  }, [macroOptions, macro])
  const macroModuleCodes = useMemo(() => {
    if (!activeMacro) return [] as string[]
    return activeMacro.modules.filter((code) => !excluded.has(code))
  }, [activeMacro, excluded])

  /** Base AC choices limited to the selected Macro (not the full catalog). */
  const baseAcOptionsForMacro = useMemo(() => {
    const byCode = new Map(allOptions.map((opt) => [opt.code, opt]))
    const codes =
      macroModuleCodes.length > 0
        ? macroModuleCodes
        : allOptions.map((opt) => opt.code)
    const list = codes.map((code) => {
      const known = byCode.get(code)
      return { code, label: known?.label || code }
    })
    if (ac && !list.some((opt) => opt.code === ac)) {
      list.unshift({ code: ac, label: aClassLabel || byCode.get(ac)?.label || ac })
    }
    return list
  }, [macroModuleCodes, allOptions, ac, aClassLabel])

  /** ATs for every Base AC under the selected Macro (consolidated). */
  const atSelectOptions = useMemo(() => {
    const set = new Set<string>()
    if (macroModuleCodes.length > 0) {
      for (const code of macroModuleCodes) {
        for (const type of aTypeOptions.byAc[code] ?? []) {
          set.add(type)
        }
        const defaultAt = (defaultAtByAc[code] || "").trim()
        if (defaultAt) set.add(defaultAt)
      }
    } else if (ac && aTypeOptions.byAc[ac]?.length) {
      for (const type of aTypeOptions.byAc[ac]) set.add(type)
    } else {
      for (const type of aTypeOptions.all) set.add(type)
    }
    for (const type of extraTypes) set.add(type)
    if (at) set.add(at)
    return Array.from(set).sort((a, b) =>
      a.localeCompare(b, undefined, { sensitivity: "base" })
    )
  }, [macroModuleCodes, ac, at, aTypeOptions, extraTypes, defaultAtByAc])

  const dirty = ac !== aClass || at !== aType

  function defaultAtForAc(nextAc: string): string {
    const mapped = (defaultAtByAc[nextAc] || "").trim()
    if (mapped) return mapped
    const scoped = nextAc && aTypeOptions.byAc[nextAc]?.length
      ? aTypeOptions.byAc[nextAc]
      : []
    return scoped[0] || ""
  }

  /** Base ACs under the active macro that list this AT (or claim it as default). */
  function baseAcOwnersForAt(assetType: string): string[] {
    const type = assetType.trim()
    if (!type || macroModuleCodes.length === 0) return []
    return macroModuleCodes.filter((code) => {
      if ((aTypeOptions.byAc[code] ?? []).includes(type)) return true
      return (defaultAtByAc[code] || "").trim() === type
    })
  }

  function syncAtForAc(nextAc: string, currentAt: string, forceDefault = false) {
    const owners = currentAt ? baseAcOwnersForAt(currentAt) : []
    const stillValidOnMacro =
      Boolean(currentAt) &&
      (owners.includes(nextAc) ||
        extraTypes.includes(currentAt) ||
        ((aTypeOptions.byAc[nextAc] ?? []).includes(currentAt)))
    if (forceDefault || !stillValidOnMacro || !currentAt) {
      return defaultAtForAc(nextAc)
    }
    return currentAt
  }

  function applyBaseAc(nextAc: string, forceDefaultAt = false) {
    setAc(nextAc)
    setAt((prev) => syncAtForAc(nextAc, prev, forceDefaultAt))
    setMessage(null)
  }

  function onMacroChange(nextMacro: string) {
    setMacro(nextMacro)
    setMacroTouched(true)
    setMessage(null)
    const opt = macroByName.get(nextMacro)
    if (!opt) return
    const modules = opt.modules.filter((code) => !excluded.has(code))
    if (modules.length === 0) return
    // Keep current Base AC when it still belongs to the new macro.
    if (ac && modules.includes(ac)) return
    // Auto-tag preferred Base AC + Default AT for this macro.
    applyBaseAc(opt.defaultModule || modules[0], true)
  }

  function onAcChange(next: string) {
    applyBaseAc(next, true)
    // Keep Macro label in sync with the chosen Base AC (AT refine ignored until save).
    const match = macroOptions.find((opt) => opt.modules.includes(next))
    if (match) {
      setMacro(match.name)
    }
  }

  function onAtChange(nextAt: string) {
    setAt(nextAt)
    setMessage(null)
    const owners = baseAcOwnersForAt(nextAt)
    if (owners.length === 0) return
    // Keep Base AC when it already owns this AT; otherwise tag the first owner.
    if (ac && owners.includes(ac)) return
    setAc(owners[0])
  }

  async function onSave() {
    if (!acAtEditAllowed || !dirty || busy || ac.trim() === "") return
    setBusy(true)
    setError(null)
    setMessage(null)
    try {
      const res = await fetch(`${LIST_HREF}/${id}/update-ac-at`, {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          ...dashboardCsrfHeader(),
        },
        body: JSON.stringify({ id, a_class: ac.trim(), a_type: at.trim() }),
      })
      const json = await res.json().catch(() => ({}))
      if (!res.ok || json?.status === "error") {
        throw new Error(json?.message || "Could not update asset class / type.")
      }
      setMessage("Saved (locked). PIB refreshed from map.")
      router.refresh()
    } catch (e) {
      setError(errorMessageOr(e, "Could not update asset class / type."))
    } finally {
      setBusy(false)
    }
  }

  async function onCreateAssetType() {
    const title = newAssetType.trim()
    if (!title) {
      setError("Enter an asset type name.")
      return
    }

    setIsCreating(true)
    setError(null)
    setMessage(null)
    try {
      const res = await fetch(
        "/dashboard/master-table/normalize-asset-types/insert-definition",
        {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
            ...dashboardCsrfHeader(),
          },
          body: JSON.stringify({ name: title, a_class: ac.trim() }),
        }
      )
      const json = await res.json().catch(() => ({}))
      if (!res.ok || json?.status === "error") {
        throw new Error(json?.message || "Could not add asset type.")
      }

      const label =
        typeof json?.data?.label === "string" && json.data.label.trim() !== ""
          ? json.data.label.trim()
          : title

      setExtraTypes((prev) => (prev.includes(label) ? prev : [...prev, label]))
      setAt(label)
      setNewAssetType("")
      setIsAddOpen(false)
      setMessage(`Added "${label}" to catalog — save AC / AT to apply.`)
      router.refresh()
    } catch (e) {
      setError(e instanceof Error ? e.message : "Could not add asset type.")
    } finally {
      setIsCreating(false)
    }
  }

  function onReset() {
    setAc(aClass)
    setAt(aType)
    setMacro(macroAssetClass)
    setMacroTouched(false)
    setError(null)
    setMessage(null)
    setIsAddOpen(false)
    setNewAssetType("")
  }

  const atLabel = (
    <span className="mb-1 flex items-center gap-1.5 text-xs text-muted-foreground">
      {acAtEditAllowed ? (
        <label htmlFor={`at-${id}`}>Asset Type</label>
      ) : (
        <span>Asset Type</span>
      )}
      <DetectWhyAtPopover detectAt={detectAt} detectFlagged={detectFlagged} />
    </span>
  )

  if (!acAtEditAllowed) {
    return (
      <div className={cn("flex flex-col gap-2", className)}>
        <div className="grid gap-3 sm:grid-cols-2">
          <div>
            <dt className="text-xs text-muted-foreground">Macro Asset Class</dt>
            <dd className="text-sm font-medium">
              {macroAssetClass.trim() !== "" ? macroAssetClass : "—"}
            </dd>
          </div>
          <div>
            {atLabel}
            <dd className="text-sm font-medium">{aType || "—"}</dd>
          </div>
        </div>
        <p className="text-[10px] text-muted-foreground">
          Base AC: {aClassLabel || aClass} ({aClass || "—"})
          <span className="ml-1 inline-flex align-middle">
            <DetectWhyAcPopover detectAc={detectAc} />
          </span>{" "}
          — editing disabled for structure, accumulator, decumulator, stock option, and FX / cash
          deposit classes.
        </p>
      </div>
    )
  }

  return (
    <div className={cn("flex flex-col gap-3", className)}>
      <div className="grid gap-3 sm:grid-cols-2">
        <div>
          <label
            className="mb-1 block text-xs text-muted-foreground"
            htmlFor={`macro-${id}`}
          >
            Macro Asset Class
          </label>
          <select
            id={`macro-${id}`}
            className={cn(
              "h-8 w-full max-w-[18rem] rounded border border-input bg-background px-2 text-xs",
              "disabled:cursor-not-allowed disabled:opacity-50"
            )}
            value={macro}
            disabled={busy || isCreating || macroSelectOptions.length === 0}
            onChange={(e) => onMacroChange(e.target.value)}
          >
            {macro === "" ? <option value="">—</option> : null}
            {macroSelectOptions.map((name) => (
              <option key={name} value={name}>
                {name}
              </option>
            ))}
          </select>
          <p className="mt-0.5 text-[10px] text-muted-foreground">
            Changing Macro auto-tags Base AC
            {activeMacro?.defaultModule
              ? ` (${labelForCode(activeMacro.defaultModule)} · ${activeMacro.defaultModule})`
              : ""}
            {activeMacro?.defaultModule && defaultAtByAc[activeMacro.defaultModule]
              ? ` and Default AT (${defaultAtByAc[activeMacro.defaultModule]})`
              : " and its Default AT"}
            .
          </p>
        </div>
        <div>
          {atLabel}
          <div className="flex max-w-[18rem] items-center gap-1.5">
            <select
              id={`at-${id}`}
              className={cn(
                "h-8 min-w-0 flex-1 rounded border border-input bg-background px-2 text-xs",
                "disabled:cursor-not-allowed disabled:opacity-50"
              )}
              value={at}
              disabled={busy || isCreating}
              onChange={(e) => onAtChange(e.target.value)}
            >
              <option value="">—</option>
              {atSelectOptions.map((type) => (
                <option key={type} value={type}>
                  {type}
                </option>
              ))}
            </select>
            <button
              type="button"
              title="Create asset type"
              aria-label="Create asset type"
              className={cn(
                "inline-flex size-8 shrink-0 items-center justify-center rounded border border-border",
                "bg-background text-foreground hover:bg-muted",
                "disabled:cursor-not-allowed disabled:opacity-50"
              )}
              disabled={busy || isCreating || ac.trim() === ""}
              onClick={() => setIsAddOpen((prev) => !prev)}
            >
              <Plus className="size-3.5" />
            </button>
          </div>
          {isAddOpen ? (
            <div className="mt-2 flex max-w-[18rem] items-center gap-1.5">
              <input
                type="text"
                value={newAssetType}
                placeholder="Enter new asset type"
                disabled={busy || isCreating}
                className={cn(
                  "h-8 min-w-0 flex-1 rounded border border-input bg-background px-2 text-xs",
                  "disabled:cursor-not-allowed disabled:opacity-50"
                )}
                onChange={(e) => setNewAssetType(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter") {
                    e.preventDefault()
                    void onCreateAssetType()
                  }
                }}
              />
              <button
                type="button"
                className={cn(
                  "rounded border px-2 py-1 text-xs font-medium",
                  "border-sky-700/40 bg-sky-50 text-sky-950 hover:bg-sky-100",
                  "disabled:cursor-not-allowed disabled:opacity-50"
                )}
                disabled={busy || isCreating || newAssetType.trim() === ""}
                onClick={() => void onCreateAssetType()}
              >
                {isCreating ? "Adding…" : "Create"}
              </button>
            </div>
          ) : null}
          <p className="mt-1 text-[10px] text-muted-foreground">
            Consolidated ATs for {macro || "this macro"}
            {macroModuleCodes.length > 0
              ? ` (${macroModuleCodes.map((code) => labelForCode(code)).join(" · ")})`
              : ac
                ? ` (${ac})`
                : ""}
            {" "}
            — picking an AT tags the matching Base AC.
          </p>
        </div>
      </div>

      {macroTouched && baseAcOptionsForMacro.length > 1 ? (
        <div>
          <span className="mb-1 flex items-center gap-1.5 text-xs text-muted-foreground">
            <label htmlFor={`ac-${id}`}>Base Asset Class</label>
            <DetectWhyAcPopover detectAc={detectAc} />
          </span>
          <select
            id={`ac-${id}`}
            className={cn(
              "h-8 w-full max-w-[18rem] rounded border border-input bg-background px-2 text-xs",
              "disabled:cursor-not-allowed disabled:opacity-50"
            )}
            value={ac}
            disabled={busy || isCreating}
            onChange={(e) => onAcChange(e.target.value)}
          >
            {ac === "" ? <option value="">—</option> : null}
            {baseAcOptionsForMacro.map((opt) => (
              <option key={opt.code} value={opt.code}>
                {opt.label} ({opt.code})
              </option>
            ))}
          </select>
          <p className="mt-0.5 text-[10px] text-muted-foreground">
            Modules under {macro || "this macro"} — this is what gets saved.
          </p>
        </div>
      ) : ac ? (
        <p className="text-[10px] text-muted-foreground">
          Base AC: {labelForCode(ac)} ({ac})
          <span className="ml-1 inline-flex align-middle">
            <DetectWhyAcPopover detectAc={detectAc} />
          </span>{" "}
          — saved value.
        </p>
      ) : null}

      <div className="flex flex-wrap items-center gap-2">
        <button
          type="button"
          className={cn(
            "rounded border px-2.5 py-1 text-xs font-medium",
            "border-sky-700/40 bg-sky-50 text-sky-950 hover:bg-sky-100",
            "disabled:cursor-not-allowed disabled:opacity-50"
          )}
          disabled={!dirty || busy || isCreating || ac.trim() === ""}
          onClick={() => void onSave()}
        >
          {busy ? "Saving…" : "Save AC / AT"}
        </button>
        <button
          type="button"
          className={cn(
            "rounded border border-border bg-background px-2.5 py-1 text-xs font-medium hover:bg-muted",
            "disabled:cursor-not-allowed disabled:opacity-50"
          )}
          disabled={!dirty || busy || isCreating}
          onClick={onReset}
        >
          Reset
        </button>
        <span className="text-[10px] text-muted-foreground">
          Saving locks the row. PIB is edited separately and is not changed here.
        </span>
      </div>
      {error ? <p className="text-xs text-destructive">{error}</p> : null}
      {message ? <p className="text-xs text-muted-foreground">{message}</p> : null}
    </div>
  )
}
