import type { LiveQueryEvent, PolicyReason, QueryRow, RouteKind } from "@/lib/types";

/**
 * What the query-log table renders for one row, whichever surface it came from.
 *
 * The stored list row and the live stream's provenance event describe the same
 * query in two different shapes — flat summary against nested full detail — and
 * both pages share one set of cells, so both project into this.
 *
 * `id` is null for a streamed event: the frame precedes its own insert, so no
 * row exists to link to yet.
 */
export interface QuerySummary {
	id: number | null;
	ts: number;
	domain: string;
	client_ip: string;
	qtype: number | null;
	blocked: boolean;
	policy_reason: PolicyReason;
	/** The twelve-bit extended code the client saw, including a synthesized SERVFAIL. */
	rcode: number;
	route_kind: RouteKind;
	response_time_us: number | null;
	cache_hit: boolean | null;
	upstream: string;
}

/**
 * Whether the cache answered, or null where it never applied. Mirrors
 * `Context.cacheHit` in src/server/handler.zig, which derives the stored
 * `cache_hit` column from the same route: a local record, a blocked answer and
 * a protocol refusal all bypass the cache, and "miss" would claim a lookup that
 * never happened.
 */
export function cacheHitFor(kind: RouteKind): boolean | null {
	switch (kind) {
		case "cache":
			return true;
		case "upstream":
		case "forward_zone":
			return false;
		case "local":
		case "blocked":
		case "rejected":
			return null;
	}
}

export function summarizeRow(row: QueryRow): QuerySummary {
	return {
		id: row.id,
		ts: row.ts,
		domain: row.domain,
		client_ip: row.client_ip,
		qtype: row.qtype,
		blocked: row.blocked,
		policy_reason: row.policy_reason,
		rcode: row.rcode,
		route_kind: row.route_kind,
		response_time_us: row.response_time_us,
		cache_hit: row.cache_hit,
		upstream: row.upstream,
	};
}

/**
 * The same summary out of a live frame. `blocked` and `cache_hit` are derived
 * rather than sent: the server derives the stored columns from exactly these
 * two fields (handler.zig's `Entry.init` call), so the projection reproduces
 * them instead of the DTO carrying the same fact twice.
 */
export function summarizeEvent(event: LiveQueryEvent): QuerySummary {
	return {
		id: null,
		ts: event.request.time,
		domain: event.request.domain,
		client_ip: event.request.client,
		qtype: event.request.qtype,
		blocked: event.policy.action === "block",
		policy_reason: event.policy.reason,
		rcode: event.response.rcode,
		route_kind: event.route.kind,
		response_time_us: event.response.duration_us,
		cache_hit: cacheHitFor(event.route.kind),
		upstream: event.route.upstream,
	};
}
