import { themedHabitShade } from "./habit-palette";
import { themePalette, type Theme } from "./theme";
import type { ShareData } from "../sharing/contracts";
import type { PublicUser } from "../shared/user";
import { calendarTimeline } from "../components/design-system/calendar-model";
import { dayNumber } from "../habits/calendar";

export type CardPrivacy = { name: boolean; avatar: boolean; habitNames: boolean; timezone: boolean };
export const cardDate = (date: string) => new Date(`${date}T12:00:00Z`).toLocaleDateString("en", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
export const cardStats = (data: ShareData) => {
  const completed = data.habits.reduce((n, h) => n + h.completed, 0);
  const scheduled = data.habits.reduce((n, h) => n + h.scheduled, 0);
  return { completed, scheduled, percent: scheduled ? Math.round(completed / scheduled * 100) : null };
};

/** Reuse the dashboard's Sunday-first week geometry and preserve each server-supplied shade. */
export function cardCalendar(habit: ShareData["habits"][number]) {
  return calendarTimeline(habit.days.map(day => ({
    date: day.date, value: day.ratio ?? 0, target: 1,
    state: day.due ? "due" as const : "not-due" as const, color: day.color,
  })));
}

async function loadAvatar(url: string): Promise<HTMLImageElement | null> {
  return new Promise(resolve => {
    const img = new Image();
    const finish = (value: HTMLImageElement | null) => { clearTimeout(timer); img.onload = null; img.onerror = null; resolve(value); };
    const timer = setTimeout(() => finish(null), 5000);
    img.crossOrigin = "anonymous";
    img.onload = () => finish(img);
    img.onerror = () => finish(null);
    img.src = url;
  });
}

/** Compact summary: fixed typography stays readable when Discord scales the attachment. */
export async function renderProgressCard(data: ShareData, user: PublicUser, privacy: CardPrivacy, theme: Theme = "light"): Promise<{ blob: Blob; alt: string; avatarMissing: boolean }> {
  await document.fonts.load('64px "Instrument Serif"');
  const avatar = privacy.avatar && user.avatarUrl ? await loadAvatar(user.avatarUrl) : null;
  const canvas = document.createElement("canvas");
  const shortRange = dayNumber(data.to) - dayNumber(data.from) < 84;
  // Reserve the full seven-row grid plus a separate legend line.
  // Long ranges stay full-width so month labels and habit names never compete.
  const panelHeight = shortRange ? 444 : 344;
  const rows = data.habits.length;
  const height = 356 + rows * panelHeight + (privacy.timezone ? 44 : 0);
  // Keep the server’s 1200px PNG contract while drawing on a 960-unit grid.
  canvas.width = 1200;
  canvas.height = height * 1.25;
  const ctx = canvas.getContext("2d");
  if (!ctx) throw new Error("Your browser could not create the image. Try another browser.");
  ctx.scale(1.25, 1.25);
  const { ink, muted, paper, empty, rule, avatar: avatarBackground, dot } = themePalette[theme];
  const green = "#285d49";
  const sans = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
  const text = (value: string, x: number, y: number, size = 32, color: string = ink, weight = "400", maxWidth = 864, align: CanvasTextAlign = "left", serif = false) => {
    ctx.font = `${weight} ${size}px ${serif ? '"Instrument Serif", Georgia, serif' : sans}`;
    ctx.fillStyle = color; ctx.textAlign = align;
    const chars = Array.from(value);
    let fitted = value;
    while (chars.length && ctx.measureText(fitted).width > maxWidth) { chars.pop(); fitted = `${chars.join("")}…`; }
    ctx.fillText(fitted, x, y);
  };
  const rect = (x: number, y: number, w: number, h: number, color: string, radius = 0) => {
    if (w <= 0) return;
    ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(x, y, w, h, radius); ctx.fill();
  };
  rect(0, 0, 960, height, paper);
  rect(0, 0, 960, 8, data.habits[0]?.color ?? green);
  let profileX = 48;
  if (privacy.avatar) {
    ctx.save(); ctx.beginPath(); ctx.arc(80, 78, 32, 0, Math.PI * 2); ctx.clip();
    rect(48, 46, 64, 64, avatarBackground);
    if (avatar) ctx.drawImage(avatar, 48, 46, 64, 64);
    else { ctx.fillStyle = muted; ctx.beginPath(); ctx.arc(80, 71, 11, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(80, 105, 23, 0, Math.PI * 2); ctx.fill(); }
    ctx.restore(); profileX = 128;
  }
  text(privacy.name ? user.displayName || user.username : "My progress", profileX, 90, 34, ink, "600", 560 - profileX);
  text("Mina Habits.", 912, 92, 44, ink, "400", 254, "right", true);
  const daysInRange = dayNumber(data.to) - dayNumber(data.from) + 1;
  text("A little, every day.", 48, 193, 72, ink, "400", 864, "left", true);
  text(`${cardDate(data.from)} – ${cardDate(data.to)}`, 48, 244, 34, muted);
  const stats = cardStats(data);
  data.habits.forEach((habit, index) => {
    const top = 284 + index * panelHeight;
    const chart = cardCalendar(habit);
    rect(48, top, 864, 1, rule);
    // Short ranges get a spacious calendar beside their period total. Longer
    // ranges use the full card width, keeping a year as one unbroken timeline.
    const chartOrigin = shortRange ? 490 : 96;
    const chartY = top + (shortRange ? 79 : 100);
    const chartWidth = shortRange ? 422 : 816;
    const step = Math.min(shortRange ? 39 : 22, chartWidth / Math.max(1, chart.weeks));
    const cell = step - (shortRange ? 5 : 3);
    const chartX = chartOrigin + (shortRange ? (chartWidth - chart.weeks * step) / 2 : 0);
    if (shortRange) {
      text(privacy.habitNames ? habit.name : `Habit ${index + 1}`, 48, top + 48, 38, ink, "600", 864);
      text(`${habit.completed}`, 48, top + 179, 104, ink, "600", 320);
      text("check-ins complete", 48, top + 228, 34, ink, "500", 360);
      text(`of ${habit.scheduled} scheduled`, 48, top + 275, 32, muted, "400", 340);
      text(`over ${daysInRange} days`, 48, top + 318, 32, muted, "400", 340);
    } else {
      text(privacy.habitNames ? habit.name : `Habit ${index + 1}`, 48, top + 48, 36, ink, "600", 560);
      text(`${habit.completed} / ${habit.scheduled} complete`, 912, top + 48, 32, muted, "400", 290, "right");
    }
    for (const row of [1, 3, 5]) text(["S", "M", "T", "W", "T", "F", "S"][row]!, chartX - 18, chartY + row * step + cell * .8, shortRange ? 26 : 18, muted, "400", 30, "right");
    if (!shortRange) {
      let lastLabelX = -100;
      chart.months.forEach(month => {
        const x = chartX + (month.column - 1) * step;
        if (x - lastLabelX < 100 || x > 850) return;
        text(new Date(`${month.month}-01T12:00:00Z`).toLocaleDateString("en", { month: "short", timeZone: "UTC" }), x, chartY - 16, 26, muted, "400", 90);
        lastLabelX = x;
      });
    }
    chart.entries.forEach(({ day, column, row }) => {
      const x = chartX + (column - 1) * step, y = chartY + (row - 1) * step;
      if (day.state === "not-due") {
        ctx.fillStyle = dot; ctx.beginPath(); ctx.arc(x + cell / 2, y + cell / 2, Math.max(1.5, cell * .07), 0, Math.PI * 2); ctx.fill();
      } else rect(x, y, cell, cell, day.value === 0 ? empty : themedHabitShade(day.color!, habit.color, theme), Math.min(3, cell / 6));
    });
    const legendY = top + panelHeight - 38;
    text("0", 48, legendY + 8, 28, muted);
    const shades = [empty, ...[...habit.legend.partial, habit.legend.complete].map(shade => themedHabitShade(shade, habit.color, theme))];
    shades.forEach((color, i) => rect(76 + i * 25, legendY - 12, 20, 20, color, 2));
    text("Complete", 90 + shades.length * 25, legendY + 8, 28, muted);
    ctx.fillStyle = dot; ctx.beginPath(); ctx.arc(693, legendY - 2, 3, 0, Math.PI * 2); ctx.fill();
    text("Not scheduled", 912, legendY + 8, 28, muted, "400", 200, "right");
  });
  const footerY = 284 + rows * panelHeight + 36;
  text("Totals cover this entire period", 48, footerY, 30, muted);
  if (privacy.timezone) text(data.timezone, 48, footerY + 44, 30, muted);
  const blob = await new Promise<Blob>((resolve, reject) => canvas.toBlob(value => value ? resolve(value) : reject(new Error("Could not export the card.")), "image/png"));
  const names = data.habits.map((h, i) => `${privacy.habitNames ? h.name : `Habit ${i + 1}`}: ${h.completed} of ${h.scheduled} scheduled check-ins complete`).join(". ");
  return { blob, avatarMissing: privacy.avatar && !!user.avatarUrl && !avatar,
    alt: `${privacy.name ? `${user.displayName || user.username}: ` : ""}${cardDate(data.from)} to ${cardDate(data.to)}. ${stats.completed} of ${stats.scheduled} check-ins complete over ${daysInRange} days. ${names}. Calendar squares show daily progress; dots mean not scheduled. Totals cover the entire selected period; days off are excluded.${privacy.timezone ? ` ${data.timezone}.` : ""}` };
}
