"use client"

import {
  Fragment,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react"
import { useMutation, useQuery } from "convex/react"
import { useRouter, useSearchParams } from "next/navigation"
import { format } from "date-fns"
import { ptBR } from "date-fns/locale"
import type { Id } from "@/convex/_generated/dataModel"
import { api } from "@/convex/_generated/api"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { useAuth } from "@/lib/auth-client"
import { cn } from "@/lib/utils"
import { ReportCanvas } from "@/components/dashboards/report-canvas"
import {
  DashboardFilters,
  DashboardWidgetRecord,
  WidgetConfig,
  WidgetRenderer,
  useMetricData,
} from "@/components/dashboards/widget-renderer"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Textarea } from "@/components/ui/textarea"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { Skeleton } from "@/components/ui/skeleton"
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet"
import { Checkbox } from "@/components/ui/checkbox"
import {
  SearchableCombobox,
  type SearchableComboboxOption,
} from "@/components/ui/searchable-combobox"
import { toast } from "sonner"
import { z } from "zod"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import {
  Check,
  Copy,
  Download,
  Edit3,
  LayoutTemplate,
  Maximize2,
  Minimize2,
  MonitorPlay,
  PauseCircle,
  PlayCircle,
  Plus,
  Sparkles,
  Table2,
  Trash2,
} from "lucide-react"
import { IconPencil } from "@tabler/icons-react"
import { getMetricDefinition, getMetricOptionsForRole } from "@/components/dashboards/metric-catalog"

const GRID_COLUMNS = 12
const DEFAULT_ROW_HEIGHT = 80
const MAX_ROWS = 32

type DashboardRecord = {
  id: Id<"dashboards">
  tenantId: string
  name: string
  description?: string | null
  aspectRatio?: string | null
  theme?: string | null
  filters?: Record<string, unknown> | null
  layout?: Array<LayoutItemFromServer> | null
  sections?: Array<DashboardSection> | null
  tvIntervalSeconds?: number | null
  readySelector?: string | null
  createdAt: number
  updatedAt: number
}

type DashboardSection = {
  id: string
  title?: string | null
  description?: string | null
  widgetKeys: string[]
  durationSeconds?: number | null
}

type DashboardShareRecord = {
  id: Id<"dashboardShares">
  audience: string
  token: string | null
  expiresAt: number | null
  canEdit: boolean
  createdBy: Id<"users">
  createdAt: number
  lastAccessAt: number | null
}

type LayoutItemFromServer = {
  i: string
  x: number
  y: number
  w: number
  h: number
  minW?: number
  minH?: number
  maxW?: number
  maxH?: number
  static?: boolean
}

type LayoutStateItem = {
  i: string
  w: number
  h: number
  minW?: number
  minH?: number
  maxW?: number
  maxH?: number
  static?: boolean
}

type PackedLayoutItem = LayoutStateItem & {
  x: number
  y: number
}

type CanvasRenderableItem = {
  key: string
  layout: PackedLayoutItem
  minW?: number
  minH?: number
  maxW?: number
  maxH?: number
  element: React.ReactNode
}

type DashboardDetailResult = {
  dashboard: DashboardRecord
  widgets: DashboardWidgetRecord[]
  shares: DashboardShareRecord[]
}

type DashboardBuilderProps = {
  dashboardId: string
  editable?: boolean
  mode?: "edit" | "view" | "tv" | "print"
}

const DEFAULT_FILTERS: DashboardFilters = {
  range: "30d",
  companyId: null,
  queueId: null,
}

const numberFormatter = new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 1 })

const WIDGET_LIBRARY: Array<{
  type: string
  title: string
  description: string
}> = [
  { type: "kpi", title: "Indicador KPI", description: "Valor agregado com destaque visual e variação opcional." },
  { type: "bar", title: "Gráfico de barras", description: "Comparação entre categorias ou períodos em colunas verticais." },
  { type: "line", title: "Gráfico de linhas", description: "Evolução temporal contínua para séries múltiplas." },
  { type: "area", title: "Gráfico de área", description: "Tendência temporal com preenchimento suave e suporte a empilhamento." },
  { type: "pie", title: "Gráfico de pizza/donut", description: "Participação percentual por categoria com rótulos opcionais." },
  { type: "radar", title: "Gráfico radar", description: "Comparação radial entre dimensões de performance." },
  { type: "gauge", title: "Indicador radial", description: "Mede um percentual (0-100%) em formato de gauge." },
  { type: "table", title: "Tabela dinâmica", description: "Lista tabular com cabeçalhos personalizáveis e ordenação." },
  { type: "text", title: "Bloco de texto", description: "Destaques, insights ou instruções em rich-text." },
]

const WIDGET_TYPE_LABELS = Object.fromEntries(WIDGET_LIBRARY.map((item) => [item.type, item.title])) as Record<
  string,
  string
>

const widgetSizePresets: Record<
  string,
  {
    default: { w: number; h: number }
    min: { w: number; h: number }
    max: { w: number; h: number }
  }
> = {
  kpi: { default: { w: 4, h: 4 }, min: { w: 3, h: 3 }, max: { w: 6, h: 6 } },
  bar: { default: { w: 6, h: 6 }, min: { w: 4, h: 4 }, max: { w: 10, h: 10 } },
  line: { default: { w: 6, h: 6 }, min: { w: 4, h: 4 }, max: { w: 10, h: 10 } },
  area: { default: { w: 6, h: 6 }, min: { w: 4, h: 4 }, max: { w: 10, h: 10 } },
  pie: { default: { w: 5, h: 6 }, min: { w: 4, h: 4 }, max: { w: 8, h: 9 } },
  radar: { default: { w: 5, h: 6 }, min: { w: 4, h: 4 }, max: { w: 8, h: 9 } },
  gauge: { default: { w: 4, h: 5 }, min: { w: 3, h: 4 }, max: { w: 6, h: 7 } },
  table: { default: { w: 8, h: 8 }, min: { w: 6, h: 5 }, max: { w: 12, h: 12 } },
  text: { default: { w: 6, h: 4 }, min: { w: 4, h: 3 }, max: { w: 10, h: 8 } },
}

function getWidgetSize(type: string) {
  const preset = widgetSizePresets[type] ?? widgetSizePresets.text
  return {
    ...preset.default,
    minW: preset.min.w,
    minH: preset.min.h,
    maxW: preset.max.w,
    maxH: preset.max.h,
  }
}

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const handle = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(handle)
  }, [value, delay])
  return debounced
}

function getWidgetConfigForWidget(widget: DashboardWidgetRecord | null): WidgetConfig | null {
  if (!widget) return null
  const raw = widget.config
  if (raw && typeof raw === "object") {
    return { type: widget.type, ...raw } as WidgetConfig
  }
  return { type: widget.type, title: widget.title ?? undefined }
}

function mergeFilterParams(
  base: Record<string, unknown> | undefined,
  filters: DashboardFilters,
) {
  const merged: Record<string, unknown> = { ...(base ?? {}) }
  if (filters.range) {
    if (filters.range === "custom") {
      if (filters.from) merged.from = filters.from
      if (filters.to) merged.to = filters.to
    } else {
      merged.range = filters.range
      delete merged.from
      delete merged.to
    }
  }
  if (filters.companyId) {
    merged.companyId = filters.companyId
  } else {
    delete merged.companyId
  }
  if (filters.queueId) {
    merged.queueId = filters.queueId
  } else {
    delete merged.queueId
  }
  return merged
}

function normalizeFilters(raw: unknown): DashboardFilters {
  if (!raw || typeof raw !== "object") {
    return { ...DEFAULT_FILTERS }
  }
  const record = raw as Record<string, unknown>
  const range = typeof record.range === "string" ? (record.range as DashboardFilters["range"]) : DEFAULT_FILTERS.range
  const from = typeof record.from === "string" ? record.from : null
  const to = typeof record.to === "string" ? record.to : null
  const companyId = typeof record.companyId === "string" ? record.companyId : null
  const queueId = typeof record.queueId === "string" ? record.queueId : null
  return {
    range,
    from,
    to,
    companyId,
    queueId,
  }
}

function packLayout(items: LayoutStateItem[], columns: number): PackedLayoutItem[] {
  const occupied: boolean[][] = []
  const packed: PackedLayoutItem[] = []

  const ensureRows = (rows: number) => {
    while (occupied.length < rows) {
      occupied.push(Array.from({ length: columns }, () => false))
    }
  }

  const canPlace = (row: number, col: number, w: number, h: number) => {
    for (let y = row; y < row + h; y++) {
      for (let x = col; x < col + w; x++) {
        if (occupied[y]?.[x]) {
          return false
        }
      }
    }
    return true
  }

  const place = (row: number, col: number, w: number, h: number) => {
    ensureRows(row + h)
    for (let y = row; y < row + h; y++) {
      for (let x = col; x < col + w; x++) {
        occupied[y][x] = true
      }
    }
  }

  for (const item of items) {
    const maxAllowedWidth = Math.min(columns, item.maxW ?? columns)
    const maxAllowedHeight = Math.min(MAX_ROWS, item.maxH ?? MAX_ROWS)
    const width = Math.max(
      1,
      Math.min(maxAllowedWidth, Math.max(item.minW ?? 2, Math.round(item.w))),
    )
    const height = Math.max(
      1,
      Math.min(maxAllowedHeight, Math.max(item.minH ?? 2, Math.round(item.h))),
    )
    let placed = false
    let row = 0
    while (!placed && row < MAX_ROWS * 4) {
      ensureRows(row + height)
      for (let col = 0; col <= columns - width; col++) {
        if (canPlace(row, col, width, height)) {
          place(row, col, width, height)
          packed.push({
            i: item.i,
            w: width,
            h: height,
            x: col,
            y: row,
            minW: item.minW,
            minH: item.minH,
            maxW: item.maxW,
            maxH: item.maxH,
            static: item.static,
          })
          placed = true
          break
        }
      }
      if (!placed) {
        row += 1
      }
    }
    if (!placed) {
      ensureRows(occupied.length + height)
      place(occupied.length, 0, width, height)
      packed.push({
        i: item.i,
        w: width,
        h: height,
        x: 0,
        y: occupied.length - height,
        minW: item.minW,
        minH: item.minH,
        maxW: item.maxW,
        maxH: item.maxH,
        static: item.static,
      })
    }
  }

  return packed
}

function buildInitialLayout(
  widgets: DashboardWidgetRecord[],
  existingLayout?: Array<LayoutItemFromServer> | null,
): LayoutStateItem[] {
  const layoutMap = new Map<string, LayoutItemFromServer>()
  existingLayout?.forEach((item) => layoutMap.set(item.i, item))
  return widgets.map((widget) => {
    const current = layoutMap.get(widget.widgetKey)
    const defaults = getWidgetSize(widget.type)
    if (current) {
      return {
        i: widget.widgetKey,
        w: current.w,
        h: current.h,
        minW: current.minW ?? defaults.minW,
        minH: current.minH ?? defaults.minH,
        maxW: current.maxW ?? defaults.maxW,
        maxH: current.maxH ?? defaults.maxH,
        static: current.static ?? false,
      }
    }
    return {
      i: widget.widgetKey,
      w: defaults.w,
      h: defaults.h,
      minW: defaults.minW,
      minH: defaults.minH,
      maxW: defaults.maxW,
      maxH: defaults.maxH,
      static: false,
    }
  })
}

function syncLayoutWithWidgets(
  previous: LayoutStateItem[],
  widgets: DashboardWidgetRecord[],
  layoutFromServer?: Array<LayoutItemFromServer> | null,
): LayoutStateItem[] {
  const previousMap = new Map(previous.map((item) => [item.i, item]))
  const serverMap = new Map(layoutFromServer?.map((item) => [item.i, item]) ?? [])
  return widgets.map((widget) => {
    const existing = previousMap.get(widget.widgetKey)
    const defaults = getWidgetSize(widget.type)
    if (existing) {
      return {
        ...existing,
        minW: existing.minW ?? defaults.minW,
        minH: existing.minH ?? defaults.minH,
        maxW: existing.maxW ?? defaults.maxW,
        maxH: existing.maxH ?? defaults.maxH,
      }
    }
    const server = serverMap.get(widget.widgetKey)
    if (server) {
      return {
        i: widget.widgetKey,
        w: server.w,
        h: server.h,
        minW: server.minW ?? defaults.minW,
        minH: server.minH ?? defaults.minH,
        maxW: server.maxW ?? defaults.maxW,
        maxH: server.maxH ?? defaults.maxH,
        static: server.static ?? false,
      }
    }
    return {
      i: widget.widgetKey,
      w: defaults.w,
      h: defaults.h,
      minW: defaults.minW,
      minH: defaults.minH,
      maxW: defaults.maxW,
      maxH: defaults.maxH,
      static: false,
    }
  })
}

function layoutItemsEqual(a: LayoutStateItem[], b: LayoutStateItem[]) {
  if (a.length !== b.length) return false
  for (let idx = 0; idx < a.length; idx++) {
    const left = a[idx]
    const right = b[idx]
    if (
      left.i !== right.i ||
      left.w !== right.w ||
      left.h !== right.h ||
      left.minW !== right.minW ||
      left.minH !== right.minH
    ) {
      return false
    }
  }
  return true
}

function filtersEqual(a: DashboardFilters, b: DashboardFilters) {
  return (
    a.range === b.range &&
    a.from === b.from &&
    a.to === b.to &&
    a.companyId === b.companyId &&
    a.queueId === b.queueId
  )
}

function deepEqual<T>(a: T, b: T) {
  try {
    return JSON.stringify(a) === JSON.stringify(b)
  } catch {
    return false
  }
}

const widgetConfigSchema = z.object({
  title: z.string().min(1, "Informe um título"),
  type: z.string(),
  metricKey: z.string().min(1, "Informe a métrica"),
  stacked: z.boolean().optional(),
  legend: z.boolean().optional(),
  rangeOverride: z.string().optional(),
  showTooltip: z.boolean().optional(),
})

type WidgetConfigFormValues = z.infer<typeof widgetConfigSchema>

export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }: DashboardBuilderProps) {
  const router = useRouter()
  const searchParams = useSearchParams()
  const tvQuery = searchParams?.get("tv")
  const enforceTv = tvQuery === "1" || mode === "tv"
  const { session, convexUserId, isStaff } = useAuth()
  const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
  const viewerId = convexUserId as Id<"users"> | null
  const canEdit = editable && Boolean(viewerId) && isStaff
  const hasDashboardId = typeof dashboardId === "string" && dashboardId.length > 0
  const userRole = session?.user.role ?? null

  const detail = useQuery(
    api.dashboards.get,
    viewerId && hasDashboardId
      ? ({
          tenantId,
          viewerId: viewerId as Id<"users">,
          dashboardId: dashboardId as Id<"dashboards">,
        } as const)
      : "skip",
  ) as DashboardDetailResult | undefined

  const [dashboard, setDashboard] = useState<DashboardRecord | null>(null)
  const [widgets, setWidgets] = useState<DashboardWidgetRecord[]>([])
  const [shares, setShares] = useState<DashboardShareRecord[]>([])
  const [filters, setFilters] = useState<DashboardFilters>({ ...DEFAULT_FILTERS })
  const filtersHydratingRef = useRef(false)
  const [layoutState, setLayoutState] = useState<LayoutStateItem[]>([])
  const layoutRef = useRef<LayoutStateItem[]>([])
  const [readyWidgets, setReadyWidgets] = useState<Set<string>>(new Set())
  const [activeSectionIndex, setActiveSectionIndex] = useState(0)
  const [isConfigOpen, setIsConfigOpen] = useState(false)
  const [configTarget, setConfigTarget] = useState<DashboardWidgetRecord | null>(null)
  const [dataTarget, setDataTarget] = useState<DashboardWidgetRecord | null>(null)
  const [isAddingWidget, setIsAddingWidget] = useState(false)
  const [isExporting, setIsExporting] = useState(false)

  const updateLayoutMutation = useMutation(api.dashboards.updateLayout)
  const updateFiltersMutation = useMutation(api.dashboards.updateFilters)
  const addWidgetMutation = useMutation(api.dashboards.addWidget)
  const updateWidgetMutation = useMutation(api.dashboards.updateWidget)
  const duplicateWidgetMutation = useMutation(api.dashboards.duplicateWidget)
  const removeWidgetMutation = useMutation(api.dashboards.removeWidget)
  const updateMetadataMutation = useMutation(api.dashboards.updateMetadata)

  useEffect(() => {
    if (!detail) return
    setDashboard((prev) => (prev && deepEqual(prev, detail.dashboard) ? prev : detail.dashboard))
    setWidgets((prev) => (deepEqual(prev, detail.widgets) ? prev : detail.widgets))
    setShares((prev) => (deepEqual(prev, detail.shares) ? prev : detail.shares))
    const nextFilters = normalizeFilters(detail.dashboard.filters)
    if (!filtersEqual(filters, nextFilters)) {
      filtersHydratingRef.current = true
      setFilters(nextFilters)
    }
    const syncedLayout = syncLayoutWithWidgets(layoutRef.current, detail.widgets, detail.dashboard.layout)
    if (!layoutItemsEqual(layoutRef.current, syncedLayout)) {
      layoutRef.current = syncedLayout
      setLayoutState(syncedLayout)
    }
    filtersHydratingRef.current = false
  }, [detail, filters])

  useEffect(() => {
    layoutRef.current = layoutState
  }, [layoutState])

  const packedLayout = useMemo(() => packLayout(layoutState, GRID_COLUMNS), [layoutState])

  const metricOptions = useMemo(() => getMetricOptionsForRole(userRole), [userRole])

  const widgetMap = useMemo(() => {
    const map = new Map<string, DashboardWidgetRecord>()
    widgets.forEach((widget) => map.set(widget.widgetKey, widget))
    return map
  }, [widgets])

  const sections = useMemo(() => dashboard?.sections ?? [], [dashboard?.sections])

  useEffect(() => {
    if (!enforceTv || sections.length <= 1) return
    const intervalSeconds = dashboard?.tvIntervalSeconds && dashboard.tvIntervalSeconds > 0 ? dashboard.tvIntervalSeconds : 30
    const rotation = setInterval(() => {
      setActiveSectionIndex((prev) => (prev + 1) % sections.length)
    }, intervalSeconds * 1000)
    return () => clearInterval(rotation)
  }, [enforceTv, sections.length, dashboard?.tvIntervalSeconds])

  const visibleWidgetKeys = useMemo(() => {
    if (!enforceTv || sections.length === 0) return null
    const currentSection = sections[Math.min(activeSectionIndex, sections.length - 1)]
    return new Set(currentSection?.widgetKeys ?? [])
  }, [enforceTv, sections, activeSectionIndex])

  // Ready handlers (stable per widget) defined before usage in canvasItems
  const handleWidgetReady = useCallback((key: string, ready: boolean) => {
    setReadyWidgets((prev) => {
      const currentlyReady = prev.has(key)
      if (currentlyReady === ready) return prev
      const next = new Set(prev)
      if (ready) next.add(key)
      else next.delete(key)
      return next
    })
  }, [])

  const readyHandlersRef = useRef(new Map<string, (ready: boolean) => void>())
  const getReadyHandler = useCallback(
    (key: string) => {
      const cached = readyHandlersRef.current.get(key)
      if (cached) return cached
      const handler = (ready: boolean) => handleWidgetReady(key, ready)
      readyHandlersRef.current.set(key, handler)
      return handler
    },
    [handleWidgetReady],
  )

  const canvasItems = packedLayout
    .map((item) => {
      const widget = widgetMap.get(item.i)
      if (!widget) return null
      if (visibleWidgetKeys && !visibleWidgetKeys.has(item.i)) return null
      return {
        key: item.i,
        layout: item,
        element: (
          <BuilderWidgetCard
            key={widget.widgetKey}
            widget={widget}
            filters={filters}
            mode={enforceTv ? "tv" : mode}
            editable={canEdit}
            onEdit={() => {
              setConfigTarget(widget)
              setIsConfigOpen(true)
            }}
            onDuplicate={() => handleDuplicateWidget(widget)}
            onRemove={() => handleRemoveWidget(widget)}
            onViewData={() => setDataTarget(widget)}
            onReadyChange={getReadyHandler(widget.widgetKey)}
          />
        ),
        ...(item.minW !== undefined ? { minW: item.minW } : {}),
        ...(item.minH !== undefined ? { minH: item.minH } : {}),
        ...(item.maxW !== undefined ? { maxW: item.maxW } : {}),
        ...(item.maxH !== undefined ? { maxH: item.maxH } : {}),
      } satisfies CanvasRenderableItem
    })
    .filter(Boolean) as CanvasRenderableItem[]

  const allWidgetsReady = canvasItems.length > 0 && canvasItems.every((item) => readyWidgets.has(item.key))

  useEffect(() => {
    const keys = new Set(canvasItems.map((item) => item.key))
    setReadyWidgets((prev) => {
      const next = new Set<string>()
      keys.forEach((key) => {
        if (prev.has(key)) {
          next.add(key)
        }
      })
      if (next.size === prev.size) {
        let differs = false
        for (const key of next) {
          if (!prev.has(key)) {
            differs = true
            break
          }
        }
        if (!differs) {
          return prev
        }
      }
      return next
    })
  }, [canvasItems])

  const persistLayout = useCallback(
    async (nextState: LayoutStateItem[]) => {
      if (!canEdit || !viewerId || !dashboard) return
      const packed = packLayout(nextState, GRID_COLUMNS)
      const sanitized = packed.map(({ maxW, maxH, ...rest }) => rest)
      try {
        await updateLayoutMutation({
          tenantId,
          actorId: viewerId as Id<"users">,
          dashboardId: dashboard.id,
          layout: sanitized,
        })
      } catch (error) {
        console.error(error)
        toast.error("Não foi possível salvar o layout.")
      }
    },
    [canEdit, viewerId, dashboard, updateLayoutMutation, tenantId],
  )

  const handleLayoutResize = useCallback(
    (key: string, size: { w: number; h: number }, options?: { commit?: boolean }) => {
      setLayoutState((prev) => {
        const next = prev.map((item) => {
          if (item.i !== key) return item
          const minW = item.minW ?? 1
          const minH = item.minH ?? 1
          const maxW = item.maxW ?? GRID_COLUMNS
          const maxH = item.maxH ?? MAX_ROWS
          return {
            ...item,
            w: Math.min(maxW, Math.max(minW, size.w)),
            h: Math.min(maxH, Math.max(minH, size.h)),
          }
        })
        if (options?.commit) {
          persistLayout(next)
        }
        return next
      })
    },
    [persistLayout],
  )

  const handleLayoutReorder = useCallback(
    (order: string[]) => {
      setLayoutState((prev) => {
        const map = new Map(prev.map((item) => [item.i, item]))
        const next = order.map((key) => map.get(key)).filter(Boolean) as LayoutStateItem[]
        persistLayout(next)
        return next
      })
    },
    [persistLayout],
  )

  const debouncedFilters = useDebounce(filters, 400)

  useEffect(() => {
    if (!canEdit || !viewerId || !dashboard || filtersHydratingRef.current) return
    const serverFilters = normalizeFilters(dashboard.filters)
    if (filtersEqual(serverFilters, debouncedFilters)) return
    updateFiltersMutation({
      tenantId,
      actorId: viewerId as Id<"users">,
      dashboardId: dashboard.id,
      filters: debouncedFilters,
    }).catch((error) => {
      console.error(error)
      toast.error("Não foi possível salvar os filtros.")
    })
  }, [debouncedFilters, canEdit, viewerId, dashboard, updateFiltersMutation, tenantId])

  const handleAddWidget = async (type: string) => {
    if (!canEdit || !viewerId || !dashboard) return
    setIsAddingWidget(true)
    try {
      await addWidgetMutation({
        tenantId,
        actorId: viewerId as Id<"users">,
        dashboardId: dashboard.id,
        type,
      })
      toast.success("Widget adicionado ao painel!")
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível adicionar o widget.")
    } finally {
      setIsAddingWidget(false)
    }
  }

  const handleDuplicateWidget = async (widget: DashboardWidgetRecord) => {
    if (!canEdit || !viewerId || !dashboard) return
    try {
      await duplicateWidgetMutation({
        tenantId,
        actorId: viewerId as Id<"users">,
        widgetId: widget.id,
      })
      toast.success("Widget duplicado com sucesso!")
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível duplicar o widget.")
    }
  }

  const handleRemoveWidget = async (widget: DashboardWidgetRecord) => {
    if (!canEdit || !viewerId || !dashboard) return
    try {
      await removeWidgetMutation({
        tenantId,
        actorId: viewerId as Id<"users">,
        widgetId: widget.id,
      })
      setLayoutState((prev) => prev.filter((item) => item.i !== widget.widgetKey))
      toast.success("Widget removido do painel.")
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível remover o widget.")
    }
  }

  const handleUpdateWidgetConfig = async (values: WidgetConfigFormValues) => {
    if (!configTarget || !canEdit || !viewerId || !dashboard) return
    const currentConfig =
      configTarget.config && typeof configTarget.config === "object"
        ? (configTarget.config as Record<string, unknown>)
        : {}
    const preset = getMetricDefinition(values.metricKey)

    const baseDataSource = (currentConfig.dataSource as Record<string, unknown> | undefined) ?? {}
    const baseParams = (baseDataSource.params as Record<string, unknown> | undefined) ?? {}
    const params: Record<string, unknown> = { ...baseParams }
    if (values.rangeOverride && values.rangeOverride.trim().length > 0) {
      params.range = values.rangeOverride.trim()
    } else if ("range" in params) {
      delete params.range
    }

    const baseEncoding = (currentConfig.encoding as Record<string, unknown> | undefined) ?? {}
    const mergedEncoding: Record<string, unknown> = { ...baseEncoding }
    if (preset?.encoding) {
      Object.assign(mergedEncoding, preset.encoding)
    }
    mergedEncoding.stacked = values.stacked ?? preset?.stacked ?? false

    const baseOptions = (currentConfig.options as Record<string, unknown> | undefined) ?? {}
    const mergedOptions: Record<string, unknown> = {
      ...baseOptions,
      ...(preset?.options ?? {}),
      legend: values.legend ?? true,
      tooltip: values.showTooltip ?? true,
    }
    if (preset?.options?.indicator) {
      mergedOptions.indicator = preset.options.indicator
    }
    if (preset?.options?.valueFormatter) {
      mergedOptions.valueFormatter = preset.options.valueFormatter
    }

    const nextConfig: Record<string, unknown> = {
      ...currentConfig,
      type: values.type,
      title: values.title,
      dataSource: {
        ...baseDataSource,
        metricKey: values.metricKey,
        params,
      },
      encoding: mergedEncoding,
      options: mergedOptions,
    }

    if (preset?.columns) {
      nextConfig.columns = preset.columns
    }

    try {
      await updateWidgetMutation({
        tenantId,
        actorId: viewerId as Id<"users">,
        widgetId: configTarget.id,
        type: values.type,
        title: values.title,
        config: nextConfig,
      })
      toast.success("Widget atualizado.")
      setIsConfigOpen(false)
      setConfigTarget(null)
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível atualizar o widget.")
    }
  }

  const handleUpdateMetadata = async (payload: { name?: string; description?: string | null; tvIntervalSeconds?: number }) => {
    if (!dashboard || !viewerId || !canEdit) return
    try {
      await updateMetadataMutation({
        tenantId,
        actorId: viewerId as Id<"users">,
        dashboardId: dashboard.id,
        name: payload.name,
        description: payload.description ?? undefined,
        tvIntervalSeconds: payload.tvIntervalSeconds,
      })
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível atualizar os metadados.")
    }
  }

  const handleExport = async (format: "pdf" | "png") => {
    if (!dashboard) return
    setIsExporting(true)
    try {
      const response = await fetch("/api/export/pdf", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          url: `${window.location.origin}/dashboards/${dashboard.id}/print`,
          format,
          width: 1920,
          height: 1080,
          waitForSelector: dashboard.readySelector ?? "[data-dashboard-ready='true']",
        }),
      })
      if (!response.ok) {
        let msg = "Export request failed"
        try {
          const data = await response.json()
          if (data?.hint) msg = `${msg}: ${data.hint}`
          else if (data?.error) msg = `${msg}: ${data.error}`
        } catch {}
        throw new Error(msg)
      }
      const blob = await response.blob()
      const url = URL.createObjectURL(blob)
      const link = document.createElement("a")
      link.href = url
      link.download = `${dashboard.name ?? "dashboard"}.${format === "pdf" ? "pdf" : "png"}`
      link.click()
      URL.revokeObjectURL(url)
      toast.success("Exportação gerada com sucesso!")
    } catch (error) {
      console.error(error)
      const message = error instanceof Error ? error.message : String(error)
      toast.error(message.includes("Playwright browsers not installed") || message.includes("npx playwright install")
        ? "Exportação indisponível em DEV: rode `npx playwright install` para habilitar."
        : "Não foi possível exportar o dashboard.")
    } finally {
      setIsExporting(false)
    }
  }

  const handleToggleTvMode = useCallback(() => {
    if (!dashboardId) return
    const baseRoute = `/dashboards/${dashboardId}`
    const params = new URLSearchParams(searchParams ? searchParams.toString() : "")
    if (enforceTv) {
      params.delete("tv")
      const query = params.toString()
      router.push(query ? `${baseRoute}?${query}` : baseRoute)
    } else {
      params.set("tv", "1")
      router.push(`${baseRoute}?${params.toString()}`)
    }
  }, [dashboardId, enforceTv, router, searchParams])

  const handleFiltersChange = (next: DashboardFilters) => {
    setFilters(next)
  }

  if (!detail || !dashboard) {
    return (
      <div className="space-y-6">
        <Skeleton className="h-12 w-2/3 rounded-xl" />
        <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
          {Array.from({ length: 6 }).map((_, index) => (
            <Skeleton key={index} className="h-[280px] rounded-2xl" />
          ))}
        </div>
      </div>
    )
  }

  const visibleCount = canvasItems.length

  return (
    <div className="flex flex-col gap-6">
      <BuilderHeader
        dashboard={dashboard}
        canEdit={canEdit}
        onAddWidget={handleAddWidget}
        onExport={handleExport}
        isAddingWidget={isAddingWidget}
        isExporting={isExporting}
        onMetadataChange={handleUpdateMetadata}
        enforceTv={enforceTv}
        activeSectionIndex={activeSectionIndex}
        totalSections={sections.length}
        onToggleTvMode={handleToggleTvMode}
        totalWidgets={widgets.length}
      />

      <DashboardFilterBar filters={filters} onChange={handleFiltersChange} />

      {enforceTv && sections.length > 0 ? (
        <TvSectionIndicator
          sections={sections}
          activeIndex={activeSectionIndex}
          onChange={setActiveSectionIndex}
        />
      ) : null}

      {visibleCount === 0 ? (
        <Card className="border-dashed border-muted-foreground/40 bg-muted/10 py-12 text-center">
          <CardHeader>
            <CardTitle className="flex items-center justify-center gap-2 text-lg font-semibold">
              <Sparkles className="size-4 text-primary" />
              Comece adicionando widgets
            </CardTitle>
            <CardDescription>
              KPIs, gráficos ou tabelas podem ser combinados para contar histórias relevantes para a operação.
            </CardDescription>
          </CardHeader>
          <CardContent className="flex justify-center">
            {canEdit ? (
              <Button onClick={() => handleAddWidget("kpi")} disabled={isAddingWidget}>
                <Plus className="mr-2 size-4" />
                Adicionar primeiro widget
              </Button>
            ) : (
              <p className="text-sm text-muted-foreground">Nenhum widget visível para esta seção.</p>
            )}
          </CardContent>
        </Card>
      ) : null}

      <ReportCanvas
        items={canvasItems}
        editable={canEdit && !enforceTv && mode !== "print"}
        columns={GRID_COLUMNS}
        rowHeight={DEFAULT_ROW_HEIGHT}
        gap={20}
        ready={allWidgetsReady}
        onResize={handleLayoutResize}
        onReorder={handleLayoutReorder}
      />

      {canEdit && !enforceTv ? (
        <button
          type="button"
          onClick={() => handleAddWidget("kpi")}
          className="flex h-28 w-full items-center justify-center rounded-2xl border-2 border-dashed border-slate-300 bg-slate-100/40 text-sm font-medium text-slate-800 transition hover:border-slate-400 hover:bg-slate-200/60"
        >
          <Plus className="mr-2 size-4" />
          Adicionar novo espaço de widget
        </button>
      ) : null}

      <WidgetConfigDialog
        open={isConfigOpen}
        onOpenChange={(open) => {
          setIsConfigOpen(open)
          if (!open) setConfigTarget(null)
        }}
        widget={configTarget}
        metricOptions={metricOptions}
        onSubmit={handleUpdateWidgetConfig}
      />

      <WidgetDataSheet
        open={Boolean(dataTarget)}
        onOpenChange={(open) => {
          if (!open) setDataTarget(null)
        }}
        widget={dataTarget}
        filters={filters}
      />
    </div>
  )
}

function BuilderHeader({
  dashboard,
  canEdit,
  onAddWidget,
  onExport,
  isAddingWidget,
  isExporting,
  onMetadataChange,
  enforceTv,
  activeSectionIndex,
  totalSections,
  onToggleTvMode,
  totalWidgets,
}: {
  dashboard: DashboardRecord
  canEdit: boolean
  onAddWidget: (type: string) => void
  onExport: (format: "pdf" | "png") => Promise<void>
  isAddingWidget: boolean
  isExporting: boolean
  onMetadataChange: (payload: { name?: string; description?: string | null; tvIntervalSeconds?: number }) => void
  enforceTv: boolean
  activeSectionIndex: number
  totalSections: number
  onToggleTvMode: () => void
  totalWidgets: number
}) {
  const [name, setName] = useState(dashboard.name)
  const [description, setDescription] = useState(dashboard.description ?? "")
  const [isEditingHeader, setIsEditingHeader] = useState(false)
  const [draftName, setDraftName] = useState(dashboard.name)
  const [draftDescription, setDraftDescription] = useState(dashboard.description ?? "")
  const [isSavingHeader, setIsSavingHeader] = useState(false)
  const [isFullscreen, setIsFullscreen] = useState(false)

  useEffect(() => {
    setName(dashboard.name)
    setDescription(dashboard.description ?? "")
    if (!isEditingHeader) {
      setDraftName(dashboard.name)
      setDraftDescription(dashboard.description ?? "")
    }
  }, [dashboard.name, dashboard.description, isEditingHeader])

  useEffect(() => {
    if (typeof document === "undefined") return
    const handleFullscreenChange = () => {
      setIsFullscreen(Boolean(document.fullscreenElement))
    }
    document.addEventListener("fullscreenchange", handleFullscreenChange)
    handleFullscreenChange()
    return () => {
      document.removeEventListener("fullscreenchange", handleFullscreenChange)
    }
  }, [])

  const toggleFullscreen = useCallback(() => {
    if (typeof document === "undefined") return
    if (document.fullscreenElement) {
      const exit = document.exitFullscreen
      if (typeof exit === "function") {
        exit.call(document).catch(() => undefined)
      }
      return
    }
    const element = document.documentElement
    if (element && typeof element.requestFullscreen === "function") {
      element.requestFullscreen().catch(() => undefined)
    }
  }, [])

  const rotationInterval = Math.max(5, dashboard.tvIntervalSeconds ?? 30)

  const handleStartEditHeader = () => {
    setDraftName(name)
    setDraftDescription(description ?? "")
    setIsEditingHeader(true)
  }

  const handleCancelEditHeader = () => {
    setDraftName(name)
    setDraftDescription(description ?? "")
    setIsEditingHeader(false)
  }

  const handleSaveHeader = async () => {
    if (!canEdit) return
    const trimmedName = draftName.trim()
    if (!trimmedName) {
      toast.error("Informe um nome para o dashboard.")
      return
    }
    const trimmedDescription = draftDescription.trim()
    const nextDescription = trimmedDescription.length > 0 ? trimmedDescription : null
    const didChangeName = trimmedName !== name
    const didChangeDescription = nextDescription !== (description ?? null)
    if (!didChangeName && !didChangeDescription) {
      setIsEditingHeader(false)
      return
    }
    setIsSavingHeader(true)
    try {
      await onMetadataChange({
        name: trimmedName,
        description: nextDescription,
      })
      setName(trimmedName)
      setDescription(nextDescription ?? "")
      setIsEditingHeader(false)
      toast.success("Informações atualizadas.")
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível atualizar o dashboard.")
    } finally {
      setIsSavingHeader(false)
    }
  }

  const isTvMode = enforceTv
  const hasSections = totalSections > 0

  return (
    <Card className="rounded-2xl border border-border/60 bg-gradient-to-br from-white/90 via-white to-primary/5 shadow-sm">
      <CardContent className="flex flex-col gap-6 p-6 md:p-7">
        <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
          <div className="flex flex-col gap-3">
            {isEditingHeader ? (
              <div className="space-y-3">
                <Input
                  value={draftName}
                  onChange={(event) => setDraftName(event.target.value)}
                  placeholder="Nome do dashboard"
                  className="h-10 w-full max-w-xl rounded-lg border border-slate-300 px-3 text-lg font-semibold text-neutral-900"
                />
                <Textarea
                  value={draftDescription}
                  onChange={(event) => setDraftDescription(event.target.value)}
                  placeholder="Adicionar uma descrição opcional"
                  rows={3}
                  className="w-full max-w-xl resize-none overflow-hidden rounded-lg border border-slate-300 bg-white p-3 text-sm text-neutral-800 shadow-sm"
                />
              </div>
            ) : (
              <div className="space-y-2">
                <div className="flex flex-wrap items-center gap-3">
                  <h1 className="text-2xl font-semibold text-neutral-900">{name}</h1>
                  {canEdit ? (
                    <Button
                      size="icon"
                      variant="outline"
                      className="inline-flex items-center justify-center rounded-lg border border-slate-200 bg-white text-neutral-800 hover:bg-slate-50"
                      onClick={handleStartEditHeader}
                      aria-label="Editar título e descrição"
                    >
                      <IconPencil className="size-5" />
                    </Button>
                  ) : null}
                </div>
                {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
              </div>
            )}
            <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
              <Badge
                variant="outline"
                className="inline-flex items-center gap-2 rounded-full border-slate-200 bg-white px-4 py-1.5 text-xs font-semibold text-neutral-700 shadow-sm"
              >
                <span className="h-2 w-2 rounded-full bg-neutral-400" />
                Formato {dashboard.aspectRatio ?? "16:9"}
              </Badge>
              <Badge
                variant="outline"
                className="inline-flex items-center gap-2 rounded-full border-slate-200 bg-white px-4 py-1.5 text-xs font-semibold text-neutral-700 shadow-sm"
              >
                <span className="h-2 w-2 rounded-full bg-neutral-400" />
                Tema {dashboard.theme ?? "system"}
              </Badge>
              <Badge
                variant="outline"
                className="inline-flex items-center gap-2 rounded-full border-slate-200 bg-white px-4 py-1.5 text-xs font-semibold text-neutral-700 shadow-sm"
              >
                <span className="h-2 w-2 rounded-full bg-neutral-400" />
                {totalWidgets} bloco{totalWidgets === 1 ? "" : "s"}
              </Badge>
              {isTvMode && hasSections ? (
                <Badge className="inline-flex items-center gap-2 rounded-full border border-primary/40 bg-primary/10 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-primary">
                  <MonitorPlay className="size-3.5" />
                  Slide {activeSectionIndex + 1} de {totalSections}
                </Badge>
              ) : null}
            </div>
          </div>
          <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-end">
            {isEditingHeader ? (
              <div className="flex items-center justify-end gap-2">
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-sm font-semibold text-neutral-700"
                  onClick={handleCancelEditHeader}
                  disabled={isSavingHeader}
                >
                  Cancelar
                </Button>
                <Button
                  size="sm"
                  className="inline-flex items-center gap-2 rounded-lg border border-black bg-black px-3 py-1.5 text-sm font-semibold text-white transition hover:bg-black/85"
                  onClick={handleSaveHeader}
                  disabled={isSavingHeader}
                >
                  {isSavingHeader ? "Salvando..." : "Salvar"}
                </Button>
              </div>
            ) : null}
            <div className="flex flex-wrap items-center gap-2">
              {canEdit ? <WidgetPicker onSelect={onAddWidget} disabled={isAddingWidget} /> : null}
              <Button
                variant="outline"
                size="sm"
                className="gap-2"
                onClick={toggleFullscreen}
              >
                {isFullscreen ? <Minimize2 className="size-4" /> : <Maximize2 className="size-4" />}
                {isFullscreen ? "Sair da tela cheia" : "Tela cheia"}
              </Button>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button
                    variant={isTvMode ? "secondary" : "default"}
                    size="sm"
                    className="gap-2"
                  >
                    {isTvMode ? <PauseCircle className="size-4" /> : <PlayCircle className="size-4" />}
                    {isTvMode ? "Modo apresentação ativo" : "Modo apresentação"}
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end" className="w-60">
                  <DropdownMenuLabel>Modo apresentação</DropdownMenuLabel>
                  <DropdownMenuItem onSelect={onToggleTvMode}>
                    {isTvMode ? "Encerrar modo apresentação" : "Iniciar modo apresentação"}
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuLabel>Tempo por seção</DropdownMenuLabel>
                  {[10, 20, 30, 60, 90].map((seconds) => (
                    <DropdownMenuItem
                      key={seconds}
                      disabled={!canEdit}
                      className="flex items-center justify-between gap-2"
                      onSelect={() => {
                        if (!canEdit) return
                        onMetadataChange({ tvIntervalSeconds: seconds })
                      }}
                    >
                      <span>{seconds} segundos</span>
                      {rotationInterval === seconds ? <Check className="size-3.5" /> : null}
                    </DropdownMenuItem>
                  ))}
                  {canEdit ? null : (
                    <DropdownMenuItem disabled>
                      Apenas edição permite ajustar o tempo
                    </DropdownMenuItem>
                  )}
                </DropdownMenuContent>
              </DropdownMenu>
            </div>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button variant="outline" size="sm" className="gap-2">
                  <Download className="size-4" />
                  Exportar
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="end">
                <DropdownMenuLabel>Exportar dashboard</DropdownMenuLabel>
                <DropdownMenuSeparator />
                <DropdownMenuItem disabled={isExporting} onSelect={() => onExport("pdf")}>
                  Exportar como PDF
                </DropdownMenuItem>
                <DropdownMenuItem disabled={isExporting} onSelect={() => onExport("png")}>
                  Exportar como PNG
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        </div>
      </CardContent>
    </Card>
  )
}

function WidgetPicker({ onSelect, disabled }: { onSelect: (type: string) => void; disabled?: boolean }) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button size="sm" className="gap-2" disabled={disabled}>
          <Plus className="size-4" />
          Adicionar bloco
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="start" className="w-72">
        <DropdownMenuLabel>Blocos disponíveis</DropdownMenuLabel>
        <DropdownMenuSeparator />
        {WIDGET_LIBRARY.map((item) => (
          <DropdownMenuItem
            key={item.type}
            onSelect={() => onSelect(item.type)}
            className="flex items-start gap-3"
          >
            <LayoutTemplate className="mt-1 size-4 text-neutral-900" />
            <div className="flex flex-col">
              <span className="font-medium text-foreground">{item.title}</span>
              <span className="text-xs text-muted-foreground">{item.description}</span>
            </div>
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

function DashboardFilterBar({
  filters,
  onChange,
}: {
  filters: DashboardFilters
  onChange: (filters: DashboardFilters) => void
}) {
  const { session, convexUserId, isStaff } = useAuth()
  const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID
  const viewerId = convexUserId as Id<"users"> | null

  const companies = useQuery(
    api.companies.list,
    isStaff && viewerId
      ? ({
          tenantId,
          viewerId,
        } as const)
      : "skip",
  ) as Array<{ id: Id<"companies">; name: string }> | undefined

  const queues = useQuery(
    api.queues.list,
    isStaff && viewerId
      ? ({
          tenantId,
          viewerId,
        } as const)
      : "skip",
  ) as Array<{ id: Id<"queues">; name: string }> | undefined

  const companyOptions: SearchableComboboxOption[] = useMemo(() => {
    const base: SearchableComboboxOption[] = [{ value: "all", label: "Todas as empresas" }]
    if (!companies || companies.length === 0) return base
    const sorted = [...companies].sort((a, b) => a.name.localeCompare(b.name, "pt-BR"))
    return [
      base[0],
      ...sorted.map((company) => ({
        value: company.id,
        label: company.name,
      })),
    ]
  }, [companies])

  const queueOptions: SearchableComboboxOption[] = useMemo(() => {
    const base: SearchableComboboxOption[] = [{ value: "all", label: "Todas as filas" }]
    if (!queues || queues.length === 0) return base
    const sorted = [...queues].sort((a, b) => a.name.localeCompare(b.name, "pt-BR"))
    return [
      base[0],
      ...sorted.map((queue) => ({
        value: queue.id,
        label: queue.name,
      })),
    ]
  }, [queues])

  return (
    <Card className="rounded-2xl border border-border/60 bg-gradient-to-br from-white/90 via-white to-primary/5 shadow-sm">
      <CardContent className="flex flex-wrap items-end gap-4 px-4 py-5 sm:px-6">
        <div className="flex flex-col gap-2">
          <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
            Período
          </span>
          <Select
            value={filters.range ?? "30d"}
            onValueChange={(value) => onChange({ ...filters, range: value as DashboardFilters["range"] })}
          >
            <SelectTrigger className="h-[46px] w-44 rounded-full border border-slate-300 bg-white px-4 text-sm font-medium text-neutral-800 shadow-sm focus-visible:ring-0 data-[state=open]:border-neutral-600">
              <SelectValue placeholder="Selecione" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="7d">Últimos 7 dias</SelectItem>
              <SelectItem value="30d">Últimos 30 dias</SelectItem>
              <SelectItem value="90d">Últimos 90 dias</SelectItem>
            </SelectContent>
          </Select>
        </div>
        <div className="flex flex-col gap-2">
          <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
            Empresa
          </span>
          <SearchableCombobox
            value={filters.companyId ?? "all"}
            onValueChange={(value) => onChange({ ...filters, companyId: value === "all" ? null : value })}
            options={companyOptions}
            placeholder="Todas as empresas"
            className="min-w-[220px]"
          />
        </div>
        <div className="flex flex-col gap-2">
          <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
            Fila
          </span>
          <SearchableCombobox
            value={filters.queueId ?? "all"}
            onValueChange={(value) => onChange({ ...filters, queueId: value === "all" ? null : value })}
            options={queueOptions}
            placeholder="Todas as filas"
            className="min-w-[220px]"
          />
        </div>
        <Button
          variant="ghost"
          size="sm"
          className="ml-auto mt-2 text-muted-foreground hover:text-foreground"
          onClick={() => onChange({ ...DEFAULT_FILTERS })}
        >
          Limpar filtros
        </Button>
      </CardContent>
    </Card>
  )
}

function BuilderWidgetCard({
  widget,
  filters,
  mode,
  editable,
  onEdit,
  onDuplicate,
  onRemove,
  onViewData,
  onReadyChange,
}: {
  widget: DashboardWidgetRecord
  filters: DashboardFilters
  mode: "edit" | "view" | "tv" | "print"
  editable: boolean
  onEdit: () => void
  onDuplicate: () => void
  onRemove: () => void
  onViewData: () => void
  onReadyChange: (ready: boolean) => void
}) {
  return (
    <div className="group relative h-full">
      {editable && mode !== "tv" && mode !== "print" ? (
        <div className="absolute inset-x-3 top-3 z-10 flex items-center justify-end gap-1 opacity-0 transition group-hover:opacity-100">
          <Button
            size="icon"
            variant="outline"
            className="flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:border-slate-400 hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-slate-300"
            onClick={onEdit}
          >
            <IconPencil className="size-4" />
          </Button>
          <Button
            size="icon"
            variant="outline"
            className="flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:border-slate-400 hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-slate-300"
            onClick={onDuplicate}
          >
            <Copy className="size-3.5" />
          </Button>
          <Button
            size="icon"
            variant="outline"
            className="flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:border-slate-400 hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-slate-300"
            onClick={onViewData}
          >
            <Table2 className="size-3.5" />
          </Button>
          <Button
            size="icon"
            variant="outline"
            className="flex h-8 w-8 items-center justify-center rounded-lg border border-rose-200 bg-white text-rose-600 shadow-sm transition hover:border-rose-300 hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-2 focus-visible:ring-rose-200"
            onClick={onRemove}
          >
            <Trash2 className="size-3.5" />
          </Button>
        </div>
      ) : null}
      <WidgetRenderer widget={widget} filters={filters} mode={mode} onReadyChange={onReadyChange} />
    </div>
  )
}

function WidgetConfigDialog({
  open,
  onOpenChange,
  widget,
  metricOptions,
  onSubmit,
}: {
  open: boolean
  onOpenChange: (open: boolean) => void
  widget: DashboardWidgetRecord | null
  metricOptions: SearchableComboboxOption[]
  onSubmit: (values: WidgetConfigFormValues) => Promise<void>
}) {
  const normalizedConfig = getWidgetConfigForWidget(widget)
  const form = useForm<WidgetConfigFormValues>({
    resolver: zodResolver(widgetConfigSchema),
    defaultValues: {
      title: normalizedConfig?.title ?? widget?.title ?? "",
      type: normalizedConfig?.type ?? widget?.type ?? "kpi",
      metricKey: normalizedConfig?.dataSource?.metricKey ?? "",
      stacked: Boolean(normalizedConfig?.encoding && "stacked" in normalizedConfig.encoding ? normalizedConfig.encoding.stacked : false),
      legend: Boolean(normalizedConfig?.options && "legend" in normalizedConfig.options ? normalizedConfig.options.legend : true),
      rangeOverride:
        typeof normalizedConfig?.dataSource?.params?.range === "string"
          ? (normalizedConfig.dataSource?.params?.range as string)
          : "",
      showTooltip: Boolean(normalizedConfig?.options && "tooltip" in normalizedConfig.options ? normalizedConfig.options.tooltip : true),
    },
  })

  useEffect(() => {
    form.register("metricKey")
  }, [form])

  const metricKeyValue = form.watch("metricKey")
  const widgetTypeValue = form.watch("type")
  const legendValue = form.watch("legend")
  const stackedValue = form.watch("stacked")
  const showTooltipValue = form.watch("showTooltip")
  const selectedMetric = useMemo(() => getMetricDefinition(metricKeyValue), [metricKeyValue])
  const metricOptionValue = selectedMetric ? selectedMetric.key : null
  const recommendedWidgetTitle = selectedMetric ? WIDGET_TYPE_LABELS[selectedMetric.recommendedWidget] ?? selectedMetric.recommendedWidget : null
  const lastAppliedMetricRef = useRef<string | null>(null)
  const lastTitleFromPresetRef = useRef<string | null>(null)

  useEffect(() => {
    if (!widget) return
    const config = getWidgetConfigForWidget(widget)
    form.reset({
      title: config?.title ?? widget.title ?? "",
      type: config?.type ?? widget.type,
      metricKey: config?.dataSource?.metricKey ?? "",
      stacked: Boolean(config?.encoding && "stacked" in config.encoding ? config.encoding.stacked : false),
      legend: Boolean(config?.options && "legend" in config.options ? config.options.legend : true),
      rangeOverride:
        typeof config?.dataSource?.params?.range === "string"
          ? (config.dataSource?.params?.range as string)
          : "",
      showTooltip: Boolean(config?.options && "tooltip" in config.options ? config.options.tooltip : true),
    })
    lastAppliedMetricRef.current =
      typeof config?.dataSource?.metricKey === "string" ? (config.dataSource?.metricKey as string) : null
    lastTitleFromPresetRef.current = config?.title ?? widget.title ?? null
  }, [widget, form])

  useEffect(() => {
    if (!selectedMetric) {
      lastAppliedMetricRef.current = null
      return
    }
    const normalizedKey = selectedMetric.key
    if (lastAppliedMetricRef.current === normalizedKey) {
      return
    }
    form.setValue("type", selectedMetric.recommendedWidget, { shouldDirty: true })
    if (typeof selectedMetric.stacked === "boolean") {
      form.setValue("stacked", selectedMetric.stacked, { shouldDirty: true })
    }
    if (selectedMetric.options?.legend !== undefined) {
      form.setValue("legend", selectedMetric.options.legend, { shouldDirty: true })
    }
    if (selectedMetric.options?.tooltip !== undefined) {
      form.setValue("showTooltip", selectedMetric.options.tooltip, { shouldDirty: true })
    }
    const currentTitle = form.getValues("title")
    if (
      !currentTitle ||
      currentTitle.trim().length === 0 ||
      currentTitle === (lastTitleFromPresetRef.current ?? "")
    ) {
      form.setValue("title", selectedMetric.defaultTitle, { shouldDirty: true })
      lastTitleFromPresetRef.current = selectedMetric.defaultTitle
    }
    lastAppliedMetricRef.current = normalizedKey
  }, [form, selectedMetric])

  useEffect(() => {
    const supportsStacked = ["bar", "area"].includes(widgetTypeValue)
    if (!supportsStacked && stackedValue) {
      form.setValue("stacked", false, { shouldDirty: true })
    }
    const supportsLegendLocal = ["bar", "line", "area", "pie", "radar"].includes(widgetTypeValue)
    if (!supportsLegendLocal && legendValue) {
      form.setValue("legend", false, { shouldDirty: true })
    }
    const supportsTooltipLocal = ["bar", "line", "area", "pie", "radar"].includes(widgetTypeValue)
    if (!supportsTooltipLocal && showTooltipValue) {
      form.setValue("showTooltip", false, { shouldDirty: true })
    }
  }, [form, legendValue, showTooltipValue, stackedValue, widgetTypeValue])

  useEffect(() => {
    if (!selectedMetric) {
      lastAppliedMetricRef.current = null
    }
  }, [selectedMetric])

  const supportsLegend = ["bar", "line", "area", "pie", "radar"].includes(widgetTypeValue)
  const supportsStacked = ["bar", "area"].includes(widgetTypeValue)
  const supportsTooltip = ["bar", "line", "area", "pie", "radar"].includes(widgetTypeValue)

  const handleMetricOptionChange = (value: string | null) => {
    if (!value) {
      form.setValue("metricKey", "", { shouldDirty: true })
      lastAppliedMetricRef.current = null
      return
    }
    form.setValue("metricKey", value, { shouldDirty: true, shouldValidate: true })
  }

  const handleSubmit = form.handleSubmit(async (values) => {
    await onSubmit(values)
    form.reset()
  })

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-xl">
        <DialogHeader>
          <DialogTitle>Configurar widget</DialogTitle>
          <DialogDescription>Personalize título, métrica e comportamento visual do bloco.</DialogDescription>
        </DialogHeader>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div className="grid gap-4">
            <div className="flex flex-col gap-2">
              <label className="text-sm font-medium">Título</label>
              <Input {...form.register("title")} placeholder="Ex.: Tickets aguardando ação" />
            </div>
            <div className="flex flex-col gap-2">
              <label className="text-sm font-medium">Tipo</label>
              <Select value={form.watch("type")} onValueChange={(value) => form.setValue("type", value)}>
                <SelectTrigger className="h-10 rounded-xl">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {WIDGET_LIBRARY.map((item) => (
                    <SelectItem key={item.type} value={item.type}>
                      {item.title}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className="flex flex-col gap-2">
              <label className="text-sm font-medium">Métrica (fonte de dados)</label>
              <SearchableCombobox
                value={metricOptionValue}
                onValueChange={handleMetricOptionChange}
                options={metricOptions}
                placeholder="Selecione uma métrica"
                searchPlaceholder="Pesquisar por nome ou palavra-chave"
                emptyText="Nenhuma métrica encontrada."
                allowClear
                className="rounded-full"
                contentClassName="w-[420px]"
                scrollClassName="max-h-72"
                scrollProps={{
                  onWheel: (event) => {
                    event.currentTarget.scrollTop += event.deltaY
                    event.preventDefault()
                    event.stopPropagation()
                  },
                }}
                disabled={metricOptions.length === 0}
                renderValue={(option) =>
                  option?.label ?? (metricKeyValue ? `Chave: ${metricKeyValue}` : undefined)
                }
              />
              {selectedMetric ? (
                <div className="rounded-xl border border-border/60 bg-muted/20 p-3 text-xs leading-relaxed text-muted-foreground">
                  <div className="flex flex-wrap items-center justify-between gap-2 text-foreground">
                    <span className="font-semibold">{selectedMetric.name}</span>
                    {recommendedWidgetTitle ? (
                      <Badge variant="outline" className="rounded-full border-border/60 bg-white px-3 py-0.5 text-[11px] font-semibold">
                        {recommendedWidgetTitle}
                      </Badge>
                    ) : null}
                  </div>
                  <p className="mt-2 text-xs text-muted-foreground">{selectedMetric.description}</p>
                </div>
              ) : metricOptions.length === 0 ? (
                <p className="text-xs text-muted-foreground">
                  Não há métricas disponíveis para o seu perfil de acesso.
                </p>
              ) : (
                <p className="text-xs text-muted-foreground">
                  Selecione uma métrica para aplicar presets automáticos de layout e visualização.
                </p>
              )}
            </div>
            <div className="grid gap-4 md:grid-cols-2">
              {supportsLegend ? (
                <SwitchField
                  label="Exibir legenda"
                  description="Mostra os nomes das séries no gráfico."
                  checked={Boolean(legendValue)}
                  onCheckedChange={(checked) => form.setValue("legend", checked)}
                />
              ) : null}
              {supportsStacked ? (
                <SwitchField
                  label="Empilhar séries"
                  description="Acumula os valores das séries no eixo Y."
                  checked={Boolean(stackedValue)}
                  onCheckedChange={(checked) => form.setValue("stacked", checked)}
                />
              ) : null}
              {supportsTooltip ? (
                <SwitchField
                  label="Tooltip interativo"
                  description="Mantém os detalhes ao passar o cursor sobre os pontos."
                  checked={Boolean(showTooltipValue)}
                  onCheckedChange={(checked) => form.setValue("showTooltip", checked)}
                />
              ) : null}
            </div>
            <div className="flex flex-col gap-2">
              <label className="text-sm font-medium">Período específico (opcional)</label>
              <Input
                {...form.register("rangeOverride")}
                placeholder="Ex.: 7d, 30d, 90d"
              />
            </div>
          </div>
          <DialogFooter>
            <Button type="submit">
              Salvar widget
            </Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}

function SwitchField({
  label,
  description,
  checked,
  onCheckedChange,
  disabled = false,
}: {
  label: string
  description?: string
  checked: boolean
  onCheckedChange: (checked: boolean) => void
  disabled?: boolean
}) {
  return (
    <div
      className={cn(
        "flex items-center justify-between gap-3 rounded-xl border border-border/60 bg-gradient-to-r from-white via-white to-primary/5 px-3 py-3 transition",
        disabled ? "cursor-not-allowed opacity-50" : "hover:border-primary/40 hover:shadow-sm",
      )}
    >
      <div className="space-y-1">
        <p className="text-sm font-medium text-foreground">{label}</p>
        {description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
      </div>
      <Checkbox
        checked={checked}
        onCheckedChange={(value) => onCheckedChange(value === true)}
        disabled={disabled}
        className="size-5 rounded-md border-primary/50 data-[state=checked]:bg-primary"
      />
    </div>
  )
}

function WidgetDataSheet({
  open,
  onOpenChange,
  widget,
  filters,
}: {
  open: boolean
  onOpenChange: (open: boolean) => void
  widget: DashboardWidgetRecord | null
  filters: DashboardFilters
}) {
  const config = getWidgetConfigForWidget(widget)
  const metric = useMetricData({
    metricKey: config?.dataSource?.metricKey,
    params: mergeFilterParams(config?.dataSource?.params, filters),
    enabled: open && Boolean(config?.dataSource?.metricKey),
  })

  const rows = Array.isArray(metric.data) ? (metric.data as Array<Record<string, unknown>>) : []
  const columns = rows.length > 0 ? Object.keys(rows[0]!) : []

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent side="bottom" className="max-h-[80vh] overflow-y-auto bg-gradient-to-br from-background via-background to-primary/10 px-6 pb-10 pt-12 sm:px-10">
        <SheetHeader>
          <SheetTitle>Dados do widget {widget?.title ?? widget?.widgetKey}</SheetTitle>
        </SheetHeader>
        {metric.isLoading ? (
          <div className="grid gap-2 pt-6">
            {Array.from({ length: 10 }).map((_, index) => (
              <Skeleton key={index} className="h-8 w-full rounded-sm" />
            ))}
          </div>
        ) : rows.length === 0 ? (
          <div className="mt-6 rounded-2xl border border-dashed border-primary/40 bg-primary/5 p-8 text-center text-sm font-medium text-primary/80">
            Sem dados disponíveis para os filtros atuais.
          </div>
        ) : (
          <div className="mt-6 overflow-hidden rounded-2xl border border-border/60 bg-white/95 shadow-sm">
            <div className="max-h-[360px] overflow-auto">
              <table className="w-full text-sm">
                <thead className="bg-muted/60">
                  <tr>
                    {columns.map((column) => (
                      <th key={column} className="px-4 py-2 text-left font-medium text-foreground">
                        {column}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {rows.slice(0, 400).map((row, index) => (
                    <tr key={index} className="border-b border-border/40 transition hover:bg-muted/40">
                      {columns.map((column) => (
                        <td key={column} className="px-4 py-2 text-muted-foreground">
                          {renderTableCellValue(row[column])}
                        </td>
                      ))}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        )}
      </SheetContent>
    </Sheet>
  )
}

function renderTableCellValue(value: unknown) {
  if (typeof value === "number") {
    return numberFormatter.format(value)
  }
  if (typeof value === "string") {
    if (/^\d{4}-\d{2}-\d{2}T/.test(value) || /^\d+$/.test(value)) {
      const date = new Date(value)
      if (!Number.isNaN(date.getTime())) {
        return format(date, "dd/MM/yyyy HH:mm", { locale: ptBR })
      }
    }
    return value
  }
  if (value === null || value === undefined) {
    return "—"
  }
  if (typeof value === "boolean") {
    return value ? "Sim" : "Não"
  }
  if (Array.isArray(value)) {
    return value.join(", ")
  }
  if (typeof value === "object") {
    try {
      return JSON.stringify(value)
    } catch {
      return "—"
    }
  }
  return String(value)
}

function TvSectionIndicator({
  sections,
  activeIndex,
  onChange,
}: {
  sections: DashboardSection[]
  activeIndex: number
  onChange: (index: number) => void
}) {
  if (sections.length === 0) return null
  return (
    <div className="flex items-center justify-between rounded-xl border border-primary/30 bg-primary/5 px-4 py-2 text-sm text-primary">
      <div className="flex items-center gap-2">
        <MonitorPlay className="size-4" />
        <span>
          Seção {activeIndex + 1} de {sections.length}: {sections[activeIndex]?.title ?? "Sem título"}
        </span>
      </div>
      <div className="flex items-center gap-1">
        {sections.map((section, index) => (
          <button
            key={section.id}
            onClick={() => onChange(index)}
            className={cn(
              "h-2.5 w-2.5 rounded-full transition",
              index === activeIndex ? "bg-primary" : "bg-primary/30 hover:bg-primary/60",
            )}
          />
        ))}
      </div>
    </div>
  )
}
