<script lang="ts">
  import { onMount, tick } from 'svelte'
  import uPlot from 'uplot'
  import 'uplot/dist/uPlot.min.css'
  import { marked } from 'marked'
  import DOMPurify from 'dompurify'
  import {
    fetchEntity,
    fetchGraph,
    fetchMetrics,
    fetchEntityEvents,
    fetchEntitySignals,
    fetchEntityTasks,
    fetchEntityKnowledge,
    fetchKnowledgeContent,
    fetchChecksForTarget,
    fetchAgentActivity,
    fetchAudit,
    patchCheck,
    ackSignal,
    resolveSignal,
    muteSignal,
    type Entity,
    type Relationship,
    type MetricSeries,
    type Signal,
    type EntityTask,
    type KnowledgeHit,
    type KnowledgeContent,
    type Check,
    type AgentActivity,
    type AuditEntry
  } from '$lib/api'
  import { relativeTime, truncateMiddle } from '$lib/utils'
  import type { OikosEvent } from '$lib/stores/events'
  import DetailSection from '$lib/components/DetailSection.svelte'
  import { Badge } from '$lib/components/ui/badge'
  import { Button } from '$lib/components/ui/button'
  import { Skeleton } from '$lib/components/ui/skeleton'
  import { toast } from 'svelte-sonner'

  const KNOWLEDGE_TYPES = new Set(['document', 'investigation', 'runbook'])

  let { slug, onSelectEntity }: { slug: string; onSelectEntity?: (slug: string) => void } = $props()

  let entity = $state<Entity | null>(null)
  let relations = $state<Relationship[]>([])
  let metrics = $state<MetricSeries[]>([])
  let events = $state<OikosEvent[]>([])
  let signals = $state<Signal[]>([])
  let tasks = $state<EntityTask[]>([])
  let knowledge = $state<KnowledgeHit[]>([])
  let ownContent = $state<KnowledgeContent | null>(null)
  let checks = $state<Check[]>([])
  let agentActivity = $state<AgentActivity[]>([])
  let auditEntries = $state<AuditEntry[]>([])
  let loading = $state(true)
  let actingSignal = $state<string | null>(null)
  let chartContainers: Record<string, HTMLDivElement> = {}

  async function load(s: string) {
    loading = true
    entity = await fetchEntity(s)
    if (!entity) {
      loading = false
      return
    }
    const [graphView, m, ev, sig, tk, kh, oc, ch, aa, au] = await Promise.all([
      fetchGraph({ root: entity.id, depth: 1 }),
      fetchMetrics(entity.id),
      fetchEntityEvents(entity.id),
      fetchEntitySignals(entity.id),
      fetchEntityTasks(entity),
      fetchEntityKnowledge(entity.id),
      KNOWLEDGE_TYPES.has(entity.type) ? fetchKnowledgeContent(entity.id) : Promise.resolve(null),
      fetchChecksForTarget(entity.slug),
      fetchAgentActivity({ entity_id: entity.id, limit: 50 }),
      fetchAudit({ entity_id: entity.id, limit: 50 })
    ])
    relations = graphView?.edges ?? []
    metrics = m
    events = ev
    signals = sig
    tasks = tk
    knowledge = kh
    ownContent = oc
    checks = ch
    agentActivity = aa
    auditEntries = au
    loading = false

    await tick()
    renderCharts()
  }

  async function ackOpenSignal(id: string) {
    actingSignal = id
    const result = await ackSignal(id)
    actingSignal = null
    if (result) {
      toast.success('Signal acknowledged')
      signals = signals.map((s) => (s.id === id ? result : s))
    } else {
      toast.error('Acknowledge failed')
    }
  }

  async function resolveOpenSignal(id: string) {
    actingSignal = id
    const result = await resolveSignal(id)
    actingSignal = null
    if (result) {
      toast.success('Signal resolved')
      signals = signals.map((s) => (s.id === id ? result : s))
    } else {
      toast.error('Resolve failed')
    }
  }

  async function muteOpenSignal(id: string) {
    actingSignal = id
    const muteUntil = new Date(Date.now() + 60 * 60 * 1000).toISOString()
    const result = await muteSignal(id, muteUntil)
    actingSignal = null
    if (result) {
      toast.success('Signal muted for 1h')
      signals = signals.map((s) => (s.id === id ? result : s))
    } else {
      toast.error('Mute failed')
    }
  }

  onMount(() => {
    load(slug)
  })

  $effect(() => {
    if (slug) load(slug)
  })

  function renderCharts() {
    for (const series of metrics) {
      const el = chartContainers[series.metric]
      if (!el) continue
      el.innerHTML = ''
      const xs = series.samples.map((s) => new Date(s.ts).getTime() / 1000)
      const ys = series.samples.map((s) => s.value ?? s.avg ?? null)
      new uPlot(
        {
          width: el.clientWidth || 400,
          height: 160,
          series: [{}, { label: series.metric, stroke: '#58a6ff', width: 2 }],
          axes: [{ stroke: '#8b949e' }, { stroke: '#8b949e' }],
          scales: { x: { time: true } },
          legend: { show: false }
        },
        [xs, ys],
        el
      )
    }
  }

  function severityVariant(sev: string): 'default' | 'secondary' | 'destructive' {
    if (sev === 'critical') return 'destructive'
    if (sev === 'warning') return 'secondary'
    return 'default'
  }

  const healthDot: Record<string, string> = {
    healthy: 'bg-success',
    degraded: 'bg-warning',
    down: 'bg-destructive',
    stale: 'bg-warning/50',
    unknown: 'bg-muted-foreground/40'
  }

  async function toggleCheck(check: Check) {
    const updated = await patchCheck(check.id, check.version, { enabled: !check.enabled })
    if (updated) {
      checks = checks.map((c) => (c.id === check.id ? updated : c))
      toast.success(`${check.slug} ${updated.enabled ? 'enabled' : 'disabled'}`)
    } else {
      toast.error('Failed to update check')
    }
  }

  // Attributes are freeform (no attribute_schema on most entity types), so
  // the generic key/value list was truncating anything long — including a
  // document's whole changelog — to an unreadable single line. Recognize a
  // few common shapes and render them properly instead of hiding them.
  interface ChangelogEntry {
    date?: string
    title?: string
    body?: string
  }

  const LONG_TEXT_KEYS = new Set(['description', 'content', 'summary', 'notes', 'note', 'body', 'details'])

  function isChangelog(value: unknown): value is ChangelogEntry[] {
    return (
      Array.isArray(value) &&
      value.length > 0 &&
      value.every((v) => v && typeof v === 'object' && !Array.isArray(v) && ('title' in v || 'body' in v))
    )
  }

  function isFlatObject(value: unknown): value is Record<string, unknown> {
    return (
      !!value &&
      typeof value === 'object' &&
      !Array.isArray(value) &&
      Object.values(value as object).every((v) => v === null || typeof v !== 'object')
    )
  }

  type AttributeRow =
    | { key: string; kind: 'long-text'; value: string }
    | { key: string; kind: 'changelog'; value: ChangelogEntry[] }
    | { key: string; kind: 'flat-object'; value: Record<string, unknown> }
    | { key: string; kind: 'simple'; value: unknown }

  function renderMarkdown(text: string): string {
    return DOMPurify.sanitize(marked.parse(text, { async: false }) as string)
  }

  function classifyAttributes(attrs: Record<string, unknown>): AttributeRow[] {
    return Object.entries(attrs).map(([key, value]): AttributeRow => {
      if (typeof value === 'string' && (LONG_TEXT_KEYS.has(key) || value.length > 120)) {
        return { key, kind: 'long-text', value }
      }
      if (isChangelog(value)) return { key, kind: 'changelog', value }
      if (isFlatObject(value)) return { key, kind: 'flat-object', value }
      return { key, kind: 'simple', value }
    })
  }
</script>

<div class="flex h-full flex-col gap-2 overflow-y-auto p-3 md:p-4">
  {#if loading}
    <Skeleton class="h-6 w-48" />
    <Skeleton class="h-8 w-full" />
    <Skeleton class="h-8 w-full" />
    <Skeleton class="h-8 w-full" />
  {:else if !entity}
    <p class="text-sm text-muted-foreground">Entity "{slug}" not found.</p>
  {:else}
    <h1 class="font-mono text-sm font-semibold">{entity.slug}</h1>

    {#snippet detailsContent()}
      <dl class="flex flex-col gap-1 text-xs">
        <div class="flex items-center justify-between gap-3 border-b pb-1">
          <dt class="shrink-0 text-muted-foreground">Type</dt>
          <dd><Badge variant="outline">{entity.type}</Badge></dd>
        </div>
        <div class="flex items-center justify-between gap-3 border-b pb-1">
          <dt class="shrink-0 text-muted-foreground">State</dt>
          <dd>{#if entity.state}<Badge>{entity.state}</Badge>{:else}<span class="text-muted-foreground">—</span>{/if}</dd>
        </div>
        <div class="flex items-center justify-between gap-3 border-b pb-1">
          <dt class="shrink-0 text-muted-foreground">Health</dt>
          <dd>
            {#if entity.health}
              <span class="flex items-center gap-1.5" title="checked {relativeTime(entity.last_check_at)}">
                <span class="size-2 rounded-full {healthDot[entity.health] ?? healthDot.unknown}"></span>
                {entity.health} · checked {relativeTime(entity.last_check_at)}
              </span>
            {:else}
              <span class="text-muted-foreground">not monitored</span>
            {/if}
          </dd>
        </div>
        <div class="flex items-center justify-between gap-3 border-b pb-1">
          <dt class="shrink-0 text-muted-foreground">Version</dt>
          <dd>{entity.version}</dd>
        </div>
        <div class="flex items-center justify-between gap-3 border-b pb-1">
          <dt class="shrink-0 text-muted-foreground">Created</dt>
          <dd title={entity.created_at}>{relativeTime(entity.created_at)}</dd>
        </div>
        <div class="flex items-center justify-between gap-3 {entity.maintenance_until ? 'border-b pb-1' : ''}">
          <dt class="shrink-0 text-muted-foreground">Updated</dt>
          <dd title={entity.updated_at}>{relativeTime(entity.updated_at)}</dd>
        </div>
        {#if entity.maintenance_until}
          <div class="flex items-center justify-between gap-3">
            <dt class="shrink-0 text-muted-foreground">Maintenance until</dt>
            <dd>{new Date(entity.maintenance_until).toLocaleString()}</dd>
          </div>
        {/if}
      </dl>
    {/snippet}

    {#snippet monitoringContent()}
      <div class="flex flex-col gap-1">
        {#each checks as check (check.id)}
          <div class="flex items-center justify-between gap-2 rounded-md border px-2 py-1 text-xs">
            <div class="flex items-center gap-2">
              <Badge variant="outline" class="font-mono">{check.kind}</Badge>
              <span class="text-muted-foreground">every {check.interval_s}s</span>
            </div>
            <button
              type="button"
              class="cursor-pointer"
              onclick={() => toggleCheck(check)}
              title={check.enabled ? 'Click to disable' : 'Click to enable'}
            >
              <Badge variant={check.enabled ? 'default' : 'secondary'}>{check.enabled ? 'enabled' : 'disabled'}</Badge>
            </button>
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No checks configured for this entity.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet contentContent()}
      {#if ownContent}
        <!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized via DOMPurify -->
        <div class="prose-chat max-w-none text-xs">{@html renderMarkdown(ownContent.content)}</div>
      {:else}
        <p class="text-xs text-muted-foreground">No content.</p>
      {/if}
    {/snippet}

    {#snippet attributesContent()}
      {#if entity.attributes && Object.keys(entity.attributes).length}
        {@const rows = classifyAttributes(entity.attributes)}
        <div class="flex flex-col gap-2 text-xs">
          {#each rows as row (row.key)}
            {#if row.kind === 'long-text'}
              <div class="flex flex-col gap-0.5">
                <p class="font-mono text-muted-foreground">{row.key}</p>
                <p class="whitespace-pre-wrap break-words rounded-md bg-muted/40 p-1.5">{row.value}</p>
              </div>
            {:else if row.kind === 'changelog'}
              <div class="flex flex-col gap-0.5">
                <p class="font-mono text-muted-foreground">{row.key} ({row.value.length})</p>
                <div class="flex flex-col gap-1">
                  {#each row.value as entry}
                    <div class="rounded-sm border-l-2 border-muted-foreground/30 pl-1.5">
                      <div class="flex items-baseline gap-1.5">
                        {#if entry.date}<span class="shrink-0 font-mono text-muted-foreground">{entry.date}</span>{/if}
                        {#if entry.title}<span class="font-medium">{entry.title}</span>{/if}
                      </div>
                      {#if entry.body}<p class="whitespace-pre-wrap break-words text-muted-foreground">{entry.body}</p>{/if}
                    </div>
                  {/each}
                </div>
              </div>
            {:else if row.kind === 'flat-object'}
              <div class="flex flex-col gap-0.5">
                <p class="font-mono text-muted-foreground">{row.key}</p>
                <dl class="flex flex-col gap-0.5 rounded-md bg-muted/40 p-1.5">
                  {#each Object.entries(row.value) as [subKey, subValue]}
                    <div class="flex items-start justify-between gap-3">
                      <dt class="shrink-0 font-mono text-muted-foreground">{subKey}</dt>
                      <dd class="min-w-0 flex-1 break-words text-right">{String(subValue)}</dd>
                    </div>
                  {/each}
                </dl>
              </div>
            {:else}
              <div class="flex items-start justify-between gap-3 border-b pb-1 last:border-0">
                <dt class="shrink-0 font-mono text-muted-foreground">{row.key}</dt>
                <dd class="min-w-0 flex-1 break-words text-right">
                  {#if row.value !== null && typeof row.value === 'object'}
                    <pre class="overflow-x-auto whitespace-pre-wrap break-words text-left">{JSON.stringify(row.value, null, 2)}</pre>
                  {:else}
                    {String(row.value)}
                  {/if}
                </dd>
              </div>
            {/if}
          {/each}
        </div>
      {:else}
        <p class="text-xs text-muted-foreground">No attributes.</p>
      {/if}
    {/snippet}

    {#snippet relationsContent()}
      <div class="flex flex-col gap-1">
        {#each relations as rel}
          <div class="flex min-w-0 items-center gap-1 font-mono text-xs">
            {#if onSelectEntity}
              <button type="button" class="min-w-0 shrink hover:underline hover:text-foreground" title={rel.source} onclick={() => onSelectEntity(rel.source)}>{truncateMiddle(rel.source)}</button>
              <span class="shrink-0 text-muted-foreground">—{rel.type}→</span>
              <button type="button" class="min-w-0 shrink hover:underline hover:text-foreground" title={rel.target} onclick={() => onSelectEntity(rel.target)}>{truncateMiddle(rel.target)}</button>
            {:else}
              <span class="min-w-0 shrink truncate" title={rel.source}>{truncateMiddle(rel.source)}</span>
              <span class="shrink-0 text-muted-foreground">—{rel.type}→</span>
              <span class="min-w-0 shrink truncate" title={rel.target}>{truncateMiddle(rel.target)}</span>
            {/if}
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No direct relations.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet metricsContent()}
      {#if metrics.length}
        <div class="grid grid-cols-1 gap-4 @2xl:grid-cols-2">
          {#each metrics as series (series.metric)}
            <div>
              <p class="mb-1 text-xs text-muted-foreground">{series.metric} ({series.rollup})</p>
              <div bind:this={chartContainers[series.metric]}></div>
            </div>
          {/each}
        </div>
      {:else}
        <p class="text-xs text-muted-foreground">No metrics tracked.</p>
      {/if}
    {/snippet}

    {#snippet signalsContent()}
      <div class="flex flex-col gap-1.5">
        {#each signals as signal (signal.id)}
          <div class="flex flex-col gap-1 border-b pb-1.5 text-xs last:border-0 last:pb-0">
            <div class="flex items-center justify-between gap-2">
              <span>{signal.kind}</span>
              <div class="flex items-center gap-1">
                <Badge variant={severityVariant(signal.severity)}>{signal.severity}</Badge>
                <Badge variant="outline">{signal.state}</Badge>
              </div>
            </div>
            {#if ['raised', 'acknowledged', 'acting'].includes(signal.state)}
              <div class="flex justify-end gap-1.5">
                {#if signal.state === 'raised'}
                  <Button
                    size="sm"
                    variant="outline"
                    class="h-6 px-2 text-xs"
                    disabled={actingSignal === signal.id}
                    onclick={() => ackOpenSignal(signal.id)}>Ack</Button
                  >
                {/if}
                <Button
                  size="sm"
                  variant="outline"
                  class="h-6 px-2 text-xs"
                  disabled={actingSignal === signal.id}
                  onclick={() => muteOpenSignal(signal.id)}>Mute 1h</Button
                >
                <Button
                  size="sm"
                  class="h-6 px-2 text-xs"
                  disabled={actingSignal === signal.id}
                  onclick={() => resolveOpenSignal(signal.id)}>Resolve</Button
                >
              </div>
            {/if}
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">None.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet tasksContent()}
      <div class="flex flex-col gap-1">
        {#each tasks as { task, executionCount } (task.id)}
          {@const title = typeof task.attributes?.title === 'string' ? task.attributes.title : task.name}
          {@const outcome = typeof task.attributes?.outcome === 'string' ? task.attributes.outcome : undefined}
          <div class="flex items-center justify-between gap-2 border-b pb-1 text-xs last:border-0 last:pb-0">
            {#if onSelectEntity}
              <button type="button" class="min-w-0 flex-1 truncate text-left hover:underline hover:text-foreground" title={title} onclick={() => onSelectEntity(task.slug)}>
                {title}
              </button>
            {:else}
              <span class="min-w-0 flex-1 truncate" title={title}>{title}</span>
            {/if}
            <div class="flex shrink-0 items-center gap-1">
              {#if outcome}
                <Badge variant={outcome === 'success' ? 'default' : 'destructive'}>{outcome}</Badge>
              {/if}
              <Badge variant="outline">{executionCount} action{executionCount === 1 ? '' : 's'}</Badge>
            </div>
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No tasks have acted on this entity.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet knowledgeContent()}
      <div class="flex flex-col gap-1">
        {#each knowledge as hit (hit.id)}
          <div class="text-xs">
            <Badge variant="outline" class="mr-1">{hit.type}</Badge>{hit.title}
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">None linked.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet eventsContent()}
      <div class="flex max-h-72 flex-col gap-1 overflow-y-auto">
        {#each events as ev (ev.id)}
          <div class="flex items-center justify-between gap-2 text-xs">
            <span class="font-mono text-muted-foreground">{new Date(ev.ts).toLocaleString()}</span>
            <span class="truncate">{ev.type}</span>
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No events yet.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet agentActivityContent()}
      <div class="flex max-h-72 flex-col gap-1 overflow-y-auto">
        {#each agentActivity as activity (activity.id)}
          <div class="flex flex-col gap-0.5 border-b pb-1 text-xs last:border-0 last:pb-0">
            <div class="flex items-center justify-between gap-2">
              <span class="font-mono text-muted-foreground">{new Date(activity.ts).toLocaleString()}</span>
              <Badge variant={activity.success === false ? 'destructive' : 'outline'}>{activity.activity_type}</Badge>
            </div>
            <span class="truncate text-muted-foreground"
              >{activity.agent_id}{activity.tool_name ? ` · ${activity.tool_name}` : ''}</span
            >
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No agent activity.</p>
        {/each}
      </div>
    {/snippet}

    {#snippet auditContent()}
      <div class="flex max-h-72 flex-col gap-1 overflow-y-auto">
        {#each auditEntries as entry (entry.id)}
          <div class="flex flex-col gap-0.5 border-b pb-1 text-xs last:border-0 last:pb-0">
            <div class="flex items-center justify-between gap-2">
              <span class="font-mono text-muted-foreground">{new Date(entry.ts).toLocaleString()}</span>
              <Badge variant="outline">{entry.actor_type}</Badge>
            </div>
            <span class="truncate text-muted-foreground">{entry.actor_id ?? '—'} · {entry.action}</span>
          </div>
        {:else}
          <p class="text-xs text-muted-foreground">No audit entries.</p>
        {/each}
      </div>
    {/snippet}

    {@const sections = [
      ...(ownContent ? [{ key: 'content', title: 'Content', count: 1, content: contentContent }] : []),
      { key: 'details', title: 'Details', count: 1, content: detailsContent },
      { key: 'monitoring', title: 'Monitoring', count: checks.length, content: monitoringContent },
      { key: 'attributes', title: 'Attributes', count: Object.keys(entity.attributes ?? {}).length, content: attributesContent },
      { key: 'relations', title: 'Relations', count: relations.length, content: relationsContent },
      { key: 'metrics', title: 'Metrics', count: metrics.length, content: metricsContent },
      { key: 'signals', title: 'Signals', count: signals.length, content: signalsContent },
      { key: 'tasks', title: 'Tasks', count: tasks.length, content: tasksContent },
      { key: 'knowledge', title: 'Knowledge', count: knowledge.length, content: knowledgeContent },
      { key: 'events', title: 'Recent events', count: events.length, content: eventsContent },
      { key: 'agentActivity', title: 'Agent activity', count: agentActivity.length, content: agentActivityContent },
      { key: 'audit', title: 'Audit trail', count: auditEntries.length, content: auditContent }
    ].sort((a, b) => (b.count > 0 ? 1 : 0) - (a.count > 0 ? 1 : 0))}

    {#each sections as section (section.key)}
      <DetailSection title={section.title} count={section.count} defaultOpen={section.count > 0}>
        {@render section.content()}
      </DetailSection>
    {/each}
  {/if}
</div>

<style>
  /* Minimal markdown styling for document/investigation/runbook content —
     mirrors Chat.svelte's .prose-chat (Svelte scopes styles per-component,
     so it can't be shared directly). */
  .prose-chat :global(p) {
    margin: 0 0 0.5rem;
  }
  .prose-chat :global(p:last-child) {
    margin-bottom: 0;
  }
  .prose-chat :global(ul),
  .prose-chat :global(ol) {
    margin: 0 0 0.5rem;
    padding-left: 1.25rem;
  }
  .prose-chat :global(li) {
    margin-bottom: 0.125rem;
  }
  .prose-chat :global(code) {
    background: var(--muted);
    border-radius: 4px;
    padding: 0.1em 0.35em;
    font-family: var(--font-mono);
    font-size: 0.85em;
  }
  .prose-chat :global(pre) {
    background: var(--muted);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.625rem 0.75rem;
    overflow-x: auto;
    margin: 0 0 0.5rem;
  }
  .prose-chat :global(pre code) {
    background: none;
    padding: 0;
    font-size: 0.8125rem;
  }
  .prose-chat :global(h1),
  .prose-chat :global(h2),
  .prose-chat :global(h3) {
    font-weight: 600;
    margin: 0.75rem 0 0.375rem;
    font-size: 1em;
  }
  .prose-chat :global(table) {
    border-collapse: collapse;
    margin: 0 0 0.5rem;
    font-size: 0.8125rem;
  }
  .prose-chat :global(th),
  .prose-chat :global(td) {
    border: 1px solid var(--border);
    padding: 0.25rem 0.5rem;
    text-align: left;
  }
  .prose-chat :global(blockquote) {
    border-left: 3px solid var(--border);
    padding-left: 0.75rem;
    color: var(--muted-foreground);
    margin: 0 0 0.5rem;
  }
</style>
