// /print — the newspaper edition. Server-rendered from current cached snapshots.
// Puppeteer loads this route to produce the PDF (see /api/edition).
//
// Layout: masthead → full-width weather hero (double-wide, with today's day-part
// strip) → personal sections, then the Screamer briefs, all in one classic
// two-column newspaper flow (briefs come last and simply spill down the columns
// and onto later pages). Each kind has a small paper renderer below; add a case
// to support a new kind.

import { gatherEdition, type EditionSection } from "@/lib/edition-data";
import type { SourceKind } from "@/lib/schemas/source";
import type { WeatherPayload } from "@/lib/schemas/sources/weather";
import type { NewsPayload } from "@/lib/schemas/sources/news";
import type { BriefsPayload } from "@/lib/schemas/sources/briefs";
import type { OnThisDayPayload } from "@/lib/schemas/sources/onthisday";
import type { ObsidianPayload } from "@/lib/schemas/sources/obsidian";
import type { CalendarPayload } from "@/lib/schemas/sources/calendar";
import type { SportsPayload } from "@/lib/schemas/sources/sports";
import type { GitHubPayload } from "@/lib/schemas/sources/github";
import type { VercelPayload } from "@/lib/schemas/sources/vercel";
import type { MastodonPayload } from "@/lib/schemas/sources/mastodon";
import type { BlueskyPayload } from "@/lib/schemas/sources/bluesky";
import type { AqiPayload } from "@/lib/schemas/sources/aqi";
import type { MarketsPayload } from "@/lib/schemas/sources/markets";
import type { HackerNewsPayload } from "@/lib/schemas/sources/hackernews";
import type { ThingsPayload } from "@/lib/schemas/sources/things";

export const dynamic = "force-dynamic";

// Classic newspaper heading per kind; falls back to the source's own label.
const SECTION_TITLE: Partial<Record<SourceKind, string>> = {
  news: "Headlines",
  briefs: "News Briefs",
  feeds: "My Feeds",
  aqi: "Air Quality",
  markets: "Markets",
  hackernews: "Hacker News",
  onthisday: "On This Day",
  obsidian: "From the Vault",
  calendar: "Calendar",
  sports: "Scores",
  github: "GitHub",
  vercel: "Deployments",
  links: "Links",
  mastodon: "Mastodon",
  bluesky: "Bluesky",
  todos: "Agenda",
};

// Small mono kicker shown at the right of each card header.
const SECTION_KICK: Partial<Record<SourceKind, string>> = {
  calendar: "Today",
  todos: "Things",
  sports: "Scores",
  feeds: "RSS",
  onthisday: "History",
  obsidian: "Obsidian",
};

function shortDate(iso: string) {
  return new Date(iso).toLocaleDateString(undefined, { month: "short", day: "numeric" });
}

function shortDateTime(iso: string) {
  return new Date(iso).toLocaleString(undefined, {
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });
}

function leagueName(path: string) {
  return (path.split("/").pop() ?? path).toUpperCase();
}

// WMO weather code → a grayscale-safe glyph for the weather strip.
function wmoGlyph(code: number): string {
  if (code === 0) return "☀";
  if (code <= 2) return "⛅";
  if (code === 3) return "☁";
  if (code <= 48) return "🌫";
  if (code <= 67) return "🌧";
  if (code <= 77) return "❄";
  if (code <= 82) return "🌦";
  if (code <= 86) return "🌨";
  return "⛈";
}

function fmtTime(iso: string) {
  return new Date(iso).toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
}

function WeatherHero({ w }: { w: WeatherPayload }) {
  const deg = w.units === "metric" ? "°C" : "°F";
  const wind = w.units === "metric" ? "km/h" : "mph";
  const today = w.daily[0];
  const showFeels =
    w.current.feelsLike != null && Math.abs(w.current.feelsLike - w.current.temp) >= 2;
  const highPollen = w.pollen.filter((p) => p.level !== "Low");

  return (
    <>
      {w.alerts.map((a, i) => (
        <div className="paper-wx-alert" key={i}>
          ⚠ {a.event}{a.headline ? ` — ${a.headline}` : ""}
        </div>
      ))}
      <div className="paper-wx">
        <div className="paper-wx-now">
          <div className="paper-wx-temp">
            {w.current.temp}
            {deg}
          </div>
          <div>
            <div className="paper-wx-loc">{w.location}</div>
            <div className="paper-wx-cond">{w.current.text}</div>
            {today && (
              <div className="paper-wx-hl">
                H {today.max}{deg} · L {today.min}{deg} · Wind {w.current.wind} {wind}
                {showFeels ? ` · Feels ${w.current.feelsLike}${deg}` : ""}
                {w.current.humidity != null ? ` · ${w.current.humidity}% RH` : ""}
                {w.current.uvIndex != null ? ` · UV ${w.current.uvIndex}` : ""}
                {w.aqi != null ? ` · AQI ${w.aqi} ${w.aqiCategory}` : ""}
              </div>
            )}
            {(w.sunrise || w.sunset || w.moonPhase) && (
              <div className="paper-wx-astro">
                {w.sunrise ? `↑ ${fmtTime(w.sunrise)}` : ""}
                {w.sunset ? ` · ↓ ${fmtTime(w.sunset)}` : ""}
                {w.moonPhase ? ` · ${w.moonEmoji} ${w.moonPhase}` : ""}
              </div>
            )}
            {highPollen.length > 0 && (
              <div className="paper-wx-astro">
                Pollen: {highPollen.map((p) => `${p.label} ${p.level}`).join(" · ")}
              </div>
            )}
          </div>
        </div>
        {w.parts.length > 0 && (
          <div className="paper-wx-strip">
            {w.parts.map((p) => (
              <div className="paper-wx-seg" key={p.label}>
                <div className="paper-wx-seg-t">{p.label}</div>
                <div className="paper-wx-seg-g">{wmoGlyph(p.code)}</div>
                <div className="paper-wx-seg-d">
                  {p.temp}
                  {deg}
                </div>
                <div className="paper-wx-seg-p">{p.precipProb != null ? `${p.precipProb}%` : "—"}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </>
  );
}

// Render a section's body for its kind. Returns null when there's nothing to show.
function SectionBody({ kind, payload }: { kind: SourceKind; payload: unknown }) {
  switch (kind) {
    case "news":
    case "feeds": {
      const p = payload as NewsPayload;
      if (p.items.length === 0) return null;
      const cap = kind === "news" ? 9 : 12;
      return (
        <>
          {p.items.slice(0, cap).map((it, i) => (
            <p className="paper-item" key={i}>
              {it.title} <span className="src">— {it.source}</span>
            </p>
          ))}
        </>
      );
    }
    case "aqi": {
      const p = payload as AqiPayload;
      if (p.aqi == null && p.pollen.length === 0) return null;
      return (
        <>
          <p className="paper-item">
            <span className="yr">AQI {p.aqi ?? "—"}</span> {p.category}
            {p.pm25 != null && <span className="src"> — PM2.5 {p.pm25}</span>}
          </p>
          {p.pollen.slice(0, 3).map((pl) => (
            <p className="paper-item" key={pl.label}>
              {pl.label} <span className="src">— {pl.level}</span>
            </p>
          ))}
        </>
      );
    }
    case "markets": {
      const p = payload as MarketsPayload;
      if (p.quotes.length === 0) return null;
      return (
        <>
          {p.quotes.slice(0, 6).map((q) => (
            <p className="paper-item" key={q.symbol}>
              <span className="yr">{q.symbol}</span>{" "}
              {q.price.toLocaleString(undefined, { maximumFractionDigits: 2 })}
              {q.changePct != null && (
                <span className="src">
                  {" "}
                  — {q.changePct >= 0 ? "+" : ""}
                  {q.changePct}%
                </span>
              )}
            </p>
          ))}
        </>
      );
    }
    case "hackernews": {
      const p = payload as HackerNewsPayload;
      if (p.stories.length === 0) return null;
      return (
        <>
          {p.stories.slice(0, 5).map((s, i) => (
            <p className="paper-item" key={i}>
              {s.title} <span className="src">— {s.source}</span>
            </p>
          ))}
        </>
      );
    }
    case "onthisday": {
      const p = payload as OnThisDayPayload;
      if (p.events.length === 0 && p.births.length === 0) return null;
      return (
        <>
          {p.events.slice(0, 8).map((e, i) => (
            <p className="paper-item" key={i}>
              {e.year != null && <span className="yr">{e.year} </span>}
              {e.text}
            </p>
          ))}
          {p.births.length > 0 && (
            <>
              <h3>Born</h3>
              {p.births.slice(0, 5).map((e, i) => (
                <p className="paper-item" key={i}>
                  {e.year != null && <span className="yr">{e.year} </span>}
                  {e.text}
                </p>
              ))}
            </>
          )}
        </>
      );
    }
    case "obsidian": {
      const p = payload as ObsidianPayload;
      if (p.notes.length === 0) return null;
      return (
        <>
          {p.notes.map((n) => (
            <p className="paper-item" key={n.path}>
              {n.title} <span className="src">— {shortDate(n.modified)}</span>
            </p>
          ))}
        </>
      );
    }
    case "calendar": {
      const p = payload as CalendarPayload;
      // Calendar always prints — an empty day is itself worth stating.
      if (p.events.length === 0)
        return <p className="paper-item paper-empty">No events scheduled today.</p>;
      return (
        <>
          {p.events.map((e, i) => (
            <p className="paper-item" key={i}>
              {e.summary}{" "}
              <span className="src">
                — {e.allDay ? shortDate(e.start) : shortDateTime(e.start)}
              </span>
            </p>
          ))}
        </>
      );
    }
    case "sports": {
      const p = payload as SportsPayload;
      if (p.games.length === 0) return null;
      type G = SportsPayload["games"][number];
      // Order within a tier: Active (live), then Finished, then Upcoming.
      const rank = (g: G): number => {
        if (g.state === "in") return 0;
        if (g.state === "post") return 1;
        return 2;
      };
      const line = (g: G) =>
        g.state === "pre"
          ? `${g.away} @ ${g.home}`
          : `${g.away} ${g.awayScore ?? ""} @ ${g.home} ${g.homeScore ?? ""}`.replace(/\s+/g, " ").trim();
      // The module already scopes to yesterday + today (+ each favorite's next
      // game); split into Favorites vs Leagues, matching the TUI.
      const tiers = ([
        { key: "fav", label: "Favorites", games: p.games.filter((g) => g.favorite) },
        { key: "lg", label: "Leagues", games: p.games.filter((g) => !g.favorite) },
      ] as const).filter((s) => s.games.length > 0);
      if (tiers.length === 0) return null;
      return (
        <>
          {tiers.map((s) => {
            const groups: Record<string, G[]> = {};
            const ordered = [...s.games].sort(
              (a, b) => rank(a) - rank(b) || (a.startTime ?? "").localeCompare(b.startTime ?? ""),
            );
            for (const g of ordered) (groups[g.league] ||= []).push(g);
            return (
              <div key={s.key}>
                <h3>{s.label}</h3>
                {Object.entries(groups).map(([league, games]) => (
                  <div key={league}>
                    <p className="paper-item"><strong>{leagueName(league)}</strong></p>
                    {games.map((g, i) => (
                      <p className="paper-item" key={i}>
                        {line(g)} <span className="src">— {g.status}</span>
                      </p>
                    ))}
                  </div>
                ))}
              </div>
            );
          })}
        </>
      );
    }
    case "github": {
      const p = payload as GitHubPayload;
      if (p.events.length === 0) return null;
      return (
        <>
          {p.events.slice(0, 10).map((e, i) => (
            <p className="paper-item" key={i}>
              <span className="src">{e.type}</span> {e.repo}
            </p>
          ))}
        </>
      );
    }
    case "vercel": {
      const p = payload as VercelPayload;
      if (p.deployments.length === 0) return null;
      return (
        <>
          {p.deployments.map((d, i) => (
            <p className="paper-item" key={i}>
              {d.name}
              {d.target ? ` · ${d.target}` : ""} <span className="src">— {d.state}</span>
            </p>
          ))}
        </>
      );
    }
    case "mastodon": {
      const p = payload as MastodonPayload;
      if (p.notifications.length === 0) return null;
      return (
        <>
          {p.notifications.map((n, i) => (
            <p className="paper-item" key={i}>
              <span className="yr">{n.account} </span>
              <span className="src">{n.type}</span>
              {n.text ? ` — ${n.text}` : ""}
            </p>
          ))}
        </>
      );
    }
    case "bluesky": {
      const p = payload as BlueskyPayload;
      if (p.notifications.length === 0) return null;
      return (
        <>
          {p.notifications.map((n, i) => (
            <p className="paper-item" key={i}>
              <span className="yr">{n.author} </span>
              <span className="src">{n.reason}</span>
              {n.text ? ` — ${n.text}` : ""}
            </p>
          ))}
        </>
      );
    }
    case "todos": {
      const p = payload as ThingsPayload;
      if (p.tasks.length === 0)
        return <p className="paper-item paper-empty">No tasks today.</p>;
      return (
        <>
          {p.tasks.map((t) => (
            <p className="paper-todo paper-item" key={t.id}>
              ☐ {t.title}
              {t.project ? <span className="src"> — {t.project}</span> : null}
            </p>
          ))}
        </>
      );
    }
    default:
      return null;
  }
}

// A personal-section bento card on page one.
function PaperCard({ section }: { section: EditionSection }) {
  const body = SectionBody({ kind: section.kind, payload: section.payload });
  if (!body) return null;
  const kick = SECTION_KICK[section.kind];
  return (
    <section className="paper-card">
      <h2>
        {SECTION_TITLE[section.kind] ?? section.label}
        {kick && <span className="kick">{kick}</span>}
      </h2>
      {body}
    </section>
  );
}

// One Screamer brief category as a flowing column card (title → summary →
// source publications). These come last, after every personal section, and just
// flow down the two columns and onto following pages — no balancing.
function BriefCard({ category }: { category: BriefsPayload["categories"][number] }) {
  if (category.stories.length === 0) return null;
  return (
    <section className="paper-card paper-flow">
      <h2>
        {category.name}
        <span className="kick">Screamer</span>
      </h2>
      {category.stories.map((s, i) => (
        <div className="paper-story" key={i}>
          <div className="t">{s.title}</div>
          {s.summary && <div className="s">{s.summary}</div>}
          {s.sources.length > 0 && <div className="src">{s.sources.join(", ")}</div>}
        </div>
      ))}
    </section>
  );
}

export default function PrintPage() {
  const d = gatherEdition();
  const briefs = d.sections.find((s) => s.kind === "briefs");
  const grid = d.sections.filter((s) => s.kind !== "briefs");
  const briefCats = briefs
    ? (briefs.payload as BriefsPayload).categories.filter((c) => c.stories.length > 0)
    : [];

  return (
    <div className="paper">
      <div className="paper-masthead">
        <h1>The Daily Dashboard</h1>
        <div className="paper-dateline">
          <span>{d.date}</span>
          <span>Personal Edition</span>
        </div>
      </div>

      {d.weather && <WeatherHero w={d.weather} />}

      <div className="paper-grid">
        {grid.map((s) => (
          <PaperCard key={s.id} section={s} />
        ))}
        {briefCats.map((c) => (
          <BriefCard key={c.key} category={c} />
        ))}
      </div>
    </div>
  );
}
