import { useEffect, useState } from 'react'
import {
  type ActionWidgetSnapshot,
  type DiskWidgetData,
  type MemoryWidgetData,
  type Node,
  type NodeKind,
  fetchAllActionWidgets,
  fetchNodes,
} from './api/client'
import { SECTION_TITLES } from './documentTitle'
import {
  navigateTo,
  pathFor,
  type ResourceSectionId,
} from './navigation'

const KIND_SECTIONS: ReadonlyArray<{
  kind: NodeKind
  section: ResourceSectionId
}> = [
  { kind: 'container', section: 'containers' },
  { kind: 'docker', section: 'docker' },
  { kind: 'vm', section: 'vms' },
]

/** Format a MiB quantity with auto MiB / GiB / TiB units. */
export function formatMemoryAmount(mib: number): string {
  const absolute = Math.abs(mib)
  if (absolute >= 1024 * 1024) {
    return `${(mib / (1024 * 1024)).toFixed(1)} TiB`
  }
  if (absolute >= 1024) {
    return `${(mib / 1024).toFixed(1)} GiB`
  }
  return `${Math.round(mib)} MiB`
}

/** Format free / total RAM from a memory widget snapshot. */
export function formatMemoryLine(memory: MemoryWidgetData): string {
  return `${formatMemoryAmount(memory.free)} / ${formatMemoryAmount(memory.total)}`
}

/** Parse df Use% into a free percent integer, or null if unparsable. */
export function freePercentFromUsePercent(usePercent: string): number | null {
  const match = usePercent.trim().match(/^(\d+(?:\.\d+)?)\s*%?$/)
  if (!match) {
    return null
  }
  const used = Number(match[1])
  if (!Number.isFinite(used)) {
    return null
  }
  return Math.max(0, Math.min(100, Math.round(100 - used)))
}

/** Format used / total (free%) from a disk widget snapshot. */
export function formatDiskLine(disk: DiskWidgetData): string {
  const freePercent = freePercentFromUsePercent(disk.use_percent)
  const base = `${disk.used} / ${disk.size}`
  if (freePercent === null) {
    return base
  }
  return `${base} (${freePercent}% free)`
}

function pickMemoryWidget(
  widgets: ReadonlyArray<ActionWidgetSnapshot>,
): MemoryWidgetData | null {
  for (const widget of widgets) {
    if (widget.widget_type === 'memory' && widget.memory) {
      return widget.memory
    }
  }
  return null
}

function pickDiskWidget(
  widgets: ReadonlyArray<ActionWidgetSnapshot>,
): DiskWidgetData | null {
  const diskWidgets = widgets.filter(
    (widget) => widget.widget_type === 'disk' && widget.disk,
  )
  if (diskWidgets.length === 0) {
    return null
  }
  const root = diskWidgets.find((widget) => widget.disk?.mounted_on === '/')
  return (root ?? diskWidgets[0]).disk ?? null
}

function healthDotClass(healthOk: boolean | null | undefined): string {
  if (healthOk === true) {
    return 'node-health-dot node-health-dot-ok'
  }
  if (healthOk === false) {
    return 'node-health-dot node-health-dot-error'
  }
  return 'node-health-dot node-health-dot-unknown'
}

function healthDotLabel(healthOk: boolean | null | undefined): string {
  if (healthOk === true) {
    return 'Healthy'
  }
  if (healthOk === false) {
    return 'Unhealthy'
  }
  return 'Health unknown'
}

function groupWidgetsByNode(
  widgets: ReadonlyArray<ActionWidgetSnapshot>,
): Map<string, ActionWidgetSnapshot[]> {
  const byNode = new Map<string, ActionWidgetSnapshot[]>()
  for (const widget of widgets) {
    const existing = byNode.get(widget.node_id)
    if (existing) {
      existing.push(widget)
    } else {
      byNode.set(widget.node_id, [widget])
    }
  }
  return byNode
}

function OverviewNodeCard({
  node,
  widgets,
  section,
}: {
  node: Node
  widgets: ReadonlyArray<ActionWidgetSnapshot>
  section: ResourceSectionId
}) {
  const memory = pickMemoryWidget(widgets)
  const disk = pickDiskWidget(widgets)

  return (
    <button
      type="button"
      className="overview-node-card"
      onClick={() => {
        navigateTo(pathFor(section, 'overview', 'overview', node.id, 'overview'))
      }}
    >
      <div className="overview-node-card-heading">
        <span
          className={healthDotClass(node.health_ok)}
          title={healthDotLabel(node.health_ok)}
          aria-label={healthDotLabel(node.health_ok)}
          role="img"
        />
        <strong className="overview-node-card-title">{node.name}</strong>
      </div>
      {memory ? (
        <p className="overview-node-metric">
          <span className="overview-node-metric-label">RAM</span>
          {formatMemoryLine(memory)}
        </p>
      ) : null}
      {disk ? (
        <p className="overview-node-metric">
          <span className="overview-node-metric-label">Disk</span>
          {formatDiskLine(disk)}
        </p>
      ) : null}
    </button>
  )
}

export function OverviewPanel() {
  const [nodes, setNodes] = useState<ReadonlyArray<Node> | null>(null)
  const [widgetsByNode, setWidgetsByNode] = useState<
    Map<string, ActionWidgetSnapshot[]>
  >(() => new Map())
  const [errorMessage, setErrorMessage] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    let isCancelled = false

    async function loadOverview() {
      setIsLoading(true)
      setErrorMessage(null)
      try {
        const [nodesResponse, widgetsResponse] = await Promise.all([
          fetchNodes(),
          fetchAllActionWidgets(),
        ])
        if (isCancelled) {
          return
        }
        setNodes(nodesResponse.nodes)
        setWidgetsByNode(groupWidgetsByNode(widgetsResponse.widgets ?? []))
      } catch (error) {
        if (!isCancelled) {
          setErrorMessage(
            error instanceof Error ? error.message : 'Failed to load overview',
          )
          setNodes([])
          setWidgetsByNode(new Map())
        }
      } finally {
        if (!isCancelled) {
          setIsLoading(false)
        }
      }
    }

    void loadOverview()

    return () => {
      isCancelled = true
    }
  }, [])

  if (isLoading) {
    return <p className="config-hint">Loading overview…</p>
  }

  if (errorMessage) {
    return <p className="form-error" role="alert">{errorMessage}</p>
  }

  const allNodes = nodes ?? []
  if (allNodes.length === 0) {
    return <p className="config-hint">No nodes yet.</p>
  }

  return (
    <div className="overview-panel">
      {KIND_SECTIONS.map(({ kind, section }) => {
        const sectionNodes = allNodes.filter((node) => node.kind === kind)
        if (sectionNodes.length === 0) {
          return null
        }
        return (
          <section
            key={kind}
            className="overview-kind-section"
            aria-label={SECTION_TITLES[section]}
          >
            <h3 className="overview-kind-heading">{SECTION_TITLES[section]}</h3>
            <div className="overview-node-grid">
              {sectionNodes.map((node) => (
                <OverviewNodeCard
                  key={node.id}
                  node={node}
                  widgets={widgetsByNode.get(node.id) ?? []}
                  section={section}
                />
              ))}
            </div>
          </section>
        )
      })}
    </div>
  )
}
