package ontology

// Monitoring resolution: which check kinds an entity type warrants.
//
// Coverage is not uniform. A `service` warrants an HTTP probe; a `site` is a
// physical location with nothing to probe; a `dns-zone` warrants a check whose
// checker does not exist yet. Collapsing those three into "has no check_def"
// is what made the fleet's monitoring gap invisible — 86 of 89 active entities
// had no check, and staleSweep's INNER JOIN against check_defs meant none of
// them could ever be marked stale.
//
// So the declaration lives on the entity TYPE, in seeds/ontology.yaml, and
// resolves through the same is-a hierarchy the validator already walks:
// declaring `monitoring: [ping, resource]` on abstract `machine` covers
// proxmox-host, standalone-server, workstation and appliance.

// MonitoringResolution is the outcome of resolving a type's monitoring
// declaration. The three states are deliberately distinguishable:
//
//	Declared=false            — nobody in the chain said anything. An ontology
//	                            gap: report it, but as a modelling problem
//	                            rather than as a fleet monitoring problem.
//	Declared=true, len(0)     — explicitly unmonitorable. Working as intended;
//	                            never raise an `unmonitored` signal for it.
//	Declared=true, len(n)     — these kinds are expected to exist.
type MonitoringResolution struct {
	Kinds []string

	// Declared reports whether anything in the chain (or the layer default)
	// settled the question.
	Declared bool

	// Source names the type that supplied the answer — the type itself, an
	// ancestor, or "" when the layer default applied. Useful in log lines
	// that explain why an entity has the checks it has.
	Source string
}

// None reports an explicit "this type is not monitored".
func (m MonitoringResolution) None() bool {
	return m.Declared && len(m.Kinds) == 0
}

// Wants reports whether the type expects a check of this kind.
func (m MonitoringResolution) Wants(kind string) bool {
	for _, k := range m.Kinds {
		if k == kind {
			return true
		}
	}
	return false
}

// Monitoring resolves the check kinds a type warrants, walking parent types
// until one carries a declaration.
//
// Types outside the infrastructure layer (governance, cognition, meta) fall
// back to an implicit "none": a signal, an approval, a document and a person
// are records, not running things. That default keeps ~20 record types out of
// the ontology without needing an explicit `monitoring: none` on each, while
// still treating an undeclared *infrastructure* type as a genuine gap — those
// are the ones somebody should have made a decision about. A non-infrastructure
// type that really is probeable (agent, which serves a gateway on :8092) just
// declares its kinds explicitly and wins on the first rule.
func (t *TypeTree) Monitoring(typ string) MonitoringResolution {
	seen := map[string]bool{}
	for cur := typ; cur != ""; cur = t.Types[cur].Parent {
		info, ok := t.Types[cur]
		if !ok {
			break
		}
		if seen[cur] {
			break // cycle guard — ingest rejects cycles, belt and braces
		}
		seen[cur] = true

		if info.Monitoring != nil {
			return MonitoringResolution{
				Kinds:    *info.Monitoring,
				Declared: true,
				Source:   cur,
			}
		}
	}

	if info, ok := t.Types[typ]; ok && info.Layer != "infrastructure" {
		return MonitoringResolution{Declared: true}
	}
	return MonitoringResolution{}
}
