// What an entity window should say, and which sections it should show.
//
// The window used to render the same 13 collapsible sections for every entity,
// sorted only by "does it have content" — so a host with 223 relations and
// 2.7M metric samples looked exactly like an ingress route with three facts,
// and Audit trail carried the same visual weight as Health. Worse, it could
// never say *why* something was unhealthy: it rendered checks as configuration
// ("ssh-script, every 60s, enabled") rather than as results.
//
// Both problems are decided here, as pure functions, so they can be tested
// without a browser and without a database.

import type { Check, Entity, EntityHealth } from '$lib/api'

// ─── Verdict ──────────────────────────────────────────────────────────────

export interface Verdict {
  health: EntityHealth | 'unmonitored'
  /** One line explaining the health, or '' when there is nothing to explain. */
  reason: string
  /** Checks whose own verdict is worse than healthy, worst first. */
  failing: Check[]
  passing: number
  total: number
}

const SEVERITY: Record<string, number> = {
  down: 0,
  degraded: 1,
  stale: 2,
  unknown: 3,
  healthy: 4
}

function severity(h: string | null | undefined): number {
  return SEVERITY[h ?? 'unknown'] ?? 3
}

/** What a check is actually probing, for use in a human-readable reason. */
export function checkLabel(check: Check): string {
  const script = (check.config as Record<string, unknown> | undefined)?.script
  if (typeof script === 'string' && script) return script.replace(/\.sh$/, '')
  return check.kind
}

/**
 * Derives the entity's health and a one-line reason from its own checks.
 *
 * Mirrors the backend's aggregation (WorstHealthForTarget): an entity is as
 * healthy as its unhealthiest check. Deriving it here as well means the header
 * can name the responsible probe, which the entity's stored health alone can
 * never do.
 */
export function deriveVerdict(entity: Entity | null, checks: Check[]): Verdict {
  const enabled = checks.filter((c) => c.enabled)
  const withVerdict = enabled.filter((c) => c.last_health)

  if (!entity) {
    return { health: 'unknown', reason: '', failing: [], passing: 0, total: 0 }
  }

  // No checks at all is a distinct state from "checks that all pass" — it is
  // the coverage gap the unmonitored signal reports, and saying "healthy"
  // here would be a lie by omission.
  if (enabled.length === 0) {
    return {
      health: 'unmonitored',
      reason: 'no checks configured for this entity',
      failing: [],
      passing: 0,
      total: 0
    }
  }

  const failing = withVerdict
    .filter((c) => c.last_health && c.last_health !== 'healthy')
    .sort((a, b) => severity(a.last_health) - severity(b.last_health))
  const passing = withVerdict.length - failing.length

  // Prefer the entity's stored health (the backend is authoritative and
  // accounts for staleness), falling back to the derived worst.
  const health: EntityHealth =
    entity.health ?? (failing[0]?.last_health as EntityHealth) ?? 'unknown'

  if (failing.length === 0) {
    const pending = enabled.length - withVerdict.length
    return {
      health,
      reason:
        pending > 0 ? `${passing} of ${enabled.length} checks passing, ${pending} not yet run` : '',
      failing,
      passing,
      total: enabled.length
    }
  }

  // Name the probes, not the count: "ping failing" is actionable in a way that
  // "1 check failing" is not.
  const names = failing.slice(0, 2).map(checkLabel)
  const more = failing.length - names.length
  const who = names.join(', ') + (more > 0 ? ` +${more} more` : '')
  const verb = failing[0].last_health === 'down' ? 'failing' : failing[0].last_health

  return {
    health,
    reason: `${who} ${verb} · ${passing} of ${enabled.length} checks passing`,
    failing,
    passing,
    total: enabled.length
  }
}

// ─── Section composition ──────────────────────────────────────────────────

export type SectionKey = 'content' | 'status' | 'impact' | 'activity' | 'metrics' | 'reference'

// Knowledge entities are documents: their content is the point, and they have
// no checks, metrics or signals to show.
const KNOWLEDGE_TYPES = new Set(['document', 'investigation', 'runbook'])

// Records of something that happened, not things that can be observed.
// Offering them monitoring sections is meaningless.
const RECORD_TYPES = new Set([
  'execution',
  'signal',
  'check',
  'approval',
  'classification',
  'feedback',
  'pattern',
  'skill'
])

const INFRASTRUCTURE: SectionKey[] = ['status', 'impact', 'activity', 'metrics', 'reference']
const KNOWLEDGE: SectionKey[] = ['content', 'impact', 'reference']
const RECORD: SectionKey[] = ['activity', 'reference']

/**
 * The sections this entity type should show, in order.
 *
 * Unknown types fall back to the infrastructure list rather than rendering
 * nothing, so a newly added entity type is never a blank window.
 */
export function sectionsForType(type: string): SectionKey[] {
  if (KNOWLEDGE_TYPES.has(type)) return KNOWLEDGE
  if (RECORD_TYPES.has(type)) return RECORD
  return INFRASTRUCTURE
}

/** Whether this type is worth showing monitoring affordances for at all. */
export function isObservable(type: string): boolean {
  return !KNOWLEDGE_TYPES.has(type) && !RECORD_TYPES.has(type)
}

// ─── Ask Nomos ────────────────────────────────────────────────────────────

/**
 * A task prompt scoped to what the operator is currently looking at, so
 * investigating is one click from seeing rather than a retyped question.
 */
export function nomosPrompt(entity: Entity, verdict: Verdict): string {
  if (verdict.health === 'unmonitored') {
    return `${entity.slug} has no checks configured. Work out what monitoring it should have and set it up.`
  }
  if (verdict.failing.length > 0) {
    return `Investigate ${entity.slug} — it reads ${verdict.health}: ${verdict.reason}. Find the cause and report what you find.`
  }
  return `Give me a status summary of ${entity.slug}: what it is, what depends on it, and anything that looks off.`
}
