"use client";

import { Monitor, MonitorStatus, Rollup } from "@/lib/api";

/*
 * Shared vocabulary for the monitors screens.
 *
 * State is never carried by colour alone: every chip pairs a distinct shape
 * with a text label, and every row carries a stripe on its left edge. Colour
 * is the third signal, not the only one.
 */

export type DisplayStatus = MonitorStatus | "paused";

export function displayStatus(m: Monitor): DisplayStatus {
    return m.enabled ? m.state.status : "paused";
}

export const statusLabel: Record<DisplayStatus, string> = {
    up: "Up",
    down: "Down",
    pending: "Pending",
    paused: "Paused",
};

/** The stripe colour for a row or panel edge. */
export const statusStripe: Record<DisplayStatus, string> = {
    up: "bg-success",
    down: "bg-danger",
    pending: "bg-warning",
    paused: "bg-text-tertiary",
};

const chipTone: Record<DisplayStatus, string> = {
    up: "text-success border-success/35 bg-success/10",
    down: "text-danger border-danger/40 bg-danger/10",
    pending: "text-warning border-warning/35 bg-warning/10",
    paused: "text-text-tertiary border-border",
};

/** Shape is the primary signal — round for up, triangle for down, and so on. */
function StatusMark({ status }: { status: DisplayStatus }) {
    if (status === "up") return <span className="block h-[7px] w-[7px] rounded-full bg-success" />;
    if (status === "down") return <span className="block h-0 w-0 border-x-[4px] border-b-[7px] border-x-transparent border-b-danger" />;
    if (status === "pending") return <span className="block h-[6px] w-[6px] rotate-45 bg-warning" />;
    return <span className="block h-[2px] w-[7px] bg-text-tertiary" />;
}

export function StatusChip({ status, className = "" }: { status: DisplayStatus; className?: string }) {
    return (
        <span
            className={`inline-flex items-center gap-1.5 rounded-sm border px-2 py-0.5 font-mono text-[11px] uppercase tracking-[0.08em] ${chipTone[status]} ${className}`}
        >
            <StatusMark status={status} />
            {statusLabel[status]}
        </span>
    );
}

/* ---------------------------------------------------------------- numbers */

export function uptimePct(rollups: Rollup[]): number | null {
    const checks = rollups.reduce((a, r) => a + r.checks, 0);
    if (checks === 0) return null;
    return (rollups.reduce((a, r) => a + r.up_count, 0) / checks) * 100;
}

export function avgLatency(rollups: Rollup[]): number | null {
    const checks = rollups.reduce((a, r) => a + r.checks, 0);
    if (checks === 0) return null;
    return rollups.reduce((a, r) => a + r.sum_latency, 0) / checks;
}

export function formatPct(pct: number | null): string {
    if (pct === null) return "—";
    return pct >= 99.995 ? "100" : pct.toFixed(2);
}

export function formatMs(ms: number | null): string {
    if (ms === null) return "—";
    if (ms >= 1000) return `${(ms / 1000).toFixed(2)} s`;
    return `${Math.round(ms)} ms`;
}

export function targetSummary(m: Monitor): string {
    if (m.type === "http") return m.target.url ?? "";
    if (m.type === "tls") return `${m.target.host ?? ""}:${m.target.port || 443}`;
    if (m.type === "icmp") return m.target.host ?? "";
    return `${m.target.host ?? ""}:${m.target.port ?? ""}`;
}

export function relativeTime(iso?: string): string {
    if (!iso) return "never";
    const secs = Math.round((Date.now() - new Date(iso).getTime()) / 1000);
    if (secs < 60) return `${Math.max(secs, 0)}s ago`;
    if (secs < 3600) return `${Math.round(secs / 60)}m ago`;
    if (secs < 86400) return `${Math.round(secs / 3600)}h ago`;
    return `${Math.round(secs / 86400)}d ago`;
}

export function formatDuration(fromIso: string, toIso?: string): string {
    const mins = Math.max(Math.round((new Date(toIso ?? Date.now()).getTime() - new Date(fromIso).getTime()) / 60000), 0);
    if (mins < 60) return `${mins} min`;
    const hours = Math.floor(mins / 60);
    if (hours < 24) return `${hours}h ${mins % 60}m`;
    return `${Math.floor(hours / 24)}d ${hours % 24}h`;
}

/* ------------------------------------------------------------------- tape */

export interface Slot {
    /** Start of the hour this slot covers. */
    at: Date;
    /** Percentage of checks that passed, or null when no check ran. */
    pct: number | null;
    checks: number;
    latency: number | null;
}

/**
 * Bucket rollups into the last `hours` whole hours, ending with the hour in
 * progress. Rollups are sparse — an hour the scheduler never reached has no
 * document at all — so the slots are built from the clock and filled in, not
 * read off the array. A missing hour has to read as a gap rather than
 * silently shifting the rest of the tape sideways.
 */
export function buildSlots(rollups: Rollup[], hours = 48): Slot[] {
    const byHour = new Map<number, Rollup>();
    for (const r of rollups) {
        const t = new Date(r.period_start);
        t.setMinutes(0, 0, 0);
        byHour.set(t.getTime(), r);
    }

    const cursor = new Date();
    cursor.setMinutes(0, 0, 0);

    const slots: Slot[] = [];
    for (let i = hours - 1; i >= 0; i--) {
        const at = new Date(cursor.getTime() - i * 3600_000);
        const r = byHour.get(at.getTime());
        slots.push({
            at,
            checks: r?.checks ?? 0,
            pct: r && r.checks > 0 ? (r.up_count / r.checks) * 100 : null,
            latency: r && r.checks > 0 ? r.sum_latency / r.checks : null,
        });
    }
    return slots;
}

function slotColor(s: Slot): string {
    if (s.pct === null) return "bg-border-soft";
    if (s.pct >= 99.5) return "bg-success";
    if (s.pct >= 80) return "bg-warning";
    return "bg-danger";
}

function slotHeight(s: Slot): number {
    if (s.pct === null) return 26;
    if (s.pct < 80) return 100;
    return 55 + (s.pct - 80) * 2.2;
}

function slotTitle(s: Slot): string {
    const when = s.at.toLocaleString(undefined, { weekday: "short", hour: "2-digit", minute: "2-digit" });
    if (s.pct === null) return `${when} · no checks ran`;
    return `${when} · ${s.pct.toFixed(1)}% up · ${s.checks} checks`;
}

/**
 * The tape: 48 hours of history as one strip. This is the spine of both
 * screens — a row shows what has been happening, not only what is true this
 * second.
 */
export function Tape({ slots, height = "h-9", live = true }: { slots: Slot[]; height?: string; live?: boolean }) {
    const covered = slots.filter((s) => s.checks > 0).length;

    return (
        <div className={`relative flex ${height} items-end gap-px rounded-sm bg-well p-[3px]`}>
            {slots.map((s) => (
                <div key={s.at.getTime()} className="flex h-full flex-1 items-end" title={slotTitle(s)}>
                    <div className={`w-full rounded-[1px] ${slotColor(s)}`} style={{ height: `${slotHeight(s)}%` }} />
                </div>
            ))}
            {live && <span className="absolute inset-y-0 right-0 w-px bg-accent/60" />}
            {covered === 0 && (
                <span className="absolute inset-0 flex items-center justify-center font-mono text-[10px] uppercase tracking-[0.14em] text-text-tertiary">
                    Awaiting first checks
                </span>
            )}
        </div>
    );
}
