import { useCallback, useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api/client";
import { useToast } from "../contexts/ToastContext";

interface BlackoutEntry {
  userId: number;
  username: string;
  reason: string | null;
}

interface ScheduleEntry {
  date: string;
  gameNightId: number;
  campaignId: number | null;
  campaignName: string | null;
  blackouts: BlackoutEntry[];
  campaignDmUserIds: number[];
}

interface Campaign {
  id: number;
  name: string;
}

interface Member {
  userId: number;
  username: string;
  avatarUrl: string | null;
  role: string;
}

interface DashboardData {
  role: string;
  myUserId: number;
  defaultGameDay: number | null; // 0=Sun … 6=Sat
  members: Member[];
  entries: ScheduleEntry[];
  myCampaigns: Campaign[];
  allCampaigns: Campaign[];
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function parseLocalDate(dateStr: string): Date {
  const [y, m, d] = dateStr.split("-").map(Number);
  return new Date(y, m - 1, d);
}

function formatDate(dateStr: string) {
  return parseLocalDate(dateStr).toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
  });
}

function formatDateLong(dateStr: string) {
  return parseLocalDate(dateStr).toLocaleDateString("en-US", {
    weekday: "long",
    month: "long",
    day: "numeric",
  });
}

function getWeekday(dateStr: string) {
  return parseLocalDate(dateStr).toLocaleDateString("en-US", { weekday: "long" });
}

function todayStr() {
  return new Date().toISOString().slice(0, 10);
}

function toLocalDateStr(d: Date) {
  return [
    d.getFullYear(),
    String(d.getMonth() + 1).padStart(2, "0"),
    String(d.getDate()).padStart(2, "0"),
  ].join("-");
}

/** Returns the next unscheduled date that falls on defaultDay (0=Sun…6=Sat). */
function nextDefaultNight(defaultDay: number, scheduledDates: Set<string>): string {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  // Advance to the nearest upcoming occurrence of defaultDay (today counts)
  const daysUntil = (defaultDay - d.getDay() + 7) % 7;
  d.setDate(d.getDate() + daysUntil);
  // Skip weeks that are already scheduled
  while (scheduledDates.has(toLocalDateStr(d))) {
    d.setDate(d.getDate() + 7);
  }
  return toLocalDateStr(d);
}

// ─── Tonight hero card ────────────────────────────────────────────────────────

function TonightCard({ entry }: { entry: ScheduleEntry | undefined }) {
  if (!entry) {
    return (
      <div className="tonight-card" style={{ marginBottom: "24px" }}>
        <div className="tonight-eyebrow">Tonight</div>
        <div className="tonight-empty">No session scheduled for tonight.</div>
      </div>
    );
  }

  return (
    <div className="tonight-card" style={{ marginBottom: "24px" }}>
      <div className="tonight-eyebrow">Tonight · {formatDateLong(entry.date)}</div>
      {entry.campaignName ? (
        entry.campaignId ? (
          <Link to={`/campaigns/${entry.campaignId}`} className="tonight-campaign tonight-campaign-link">
            {entry.campaignName}
          </Link>
        ) : (
          <div className="tonight-campaign">{entry.campaignName}</div>
        )
      ) : (
        <div className="tonight-empty">No campaign selected yet.</div>
      )}
      {entry.blackouts.length > 0 && (
        <div style={{ marginTop: "14px", display: "flex", flexWrap: "wrap", gap: "6px" }}>
          {entry.blackouts.map((b) => (
            <span key={b.userId} className="avail-chip" title={b.reason ?? undefined}>
              {b.username} out
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Avatar stack ─────────────────────────────────────────────────────────────

const MAX_AVATARS = 8;

function AvatarStack({
  members,
  blackoutUserIds,
  campaignDmUserIds,
  allIn,
}: {
  members: Member[];
  blackoutUserIds: Set<number>;
  campaignDmUserIds: Set<number>;
  allIn: boolean;
}) {
  const visible = members.slice(0, MAX_AVATARS);
  const overflow = members.length - MAX_AVATARS;

  return (
    <div className="avatar-stack">
      <div
        className={`avatar-stack-group${allIn ? " all-in" : ""}`}
        title={allIn ? "Everyone's available" : undefined}
      >
        {visible.map((m) => {
          const isOut = blackoutUserIds.has(m.userId);
          const isDm = campaignDmUserIds.has(m.userId);
          return (
            <div
              key={m.userId}
              className={`avatar-wrap${isOut ? " out" : ""}`}
              title={`${m.username}${isDm ? " (DM)" : ""}${isOut ? " — out" : ""}`}
            >
              {isDm && <span className="avatar-crown">👑</span>}
              {m.avatarUrl ? (
                <img
                  src={m.avatarUrl}
                  className={`avatar-bubble${isOut ? " out" : " in"}`}
                  alt={m.username}
                />
              ) : (
                <div className={`avatar-bubble avatar-fallback${isOut ? " out" : " in"}`}>
                  {m.username[0].toUpperCase()}
                </div>
              )}
            </div>
          );
        })}
        {overflow > 0 && (
          <div className="avatar-overflow" title={`${overflow} more`}>
            +{overflow}
          </div>
        )}
        {allIn && <span className="avatar-all-in-check">✓</span>}
      </div>
    </div>
  );
}

// ─── Tab nav icons ────────────────────────────────────────────────────────────

function IconTabBoard() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" />
      <rect x="14" y="14" width="7" height="7" /><rect x="3" y="14" width="7" height="7" />
    </svg>
  );
}

function IconTabCharacters() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" />
      <circle cx="12" cy="7" r="4" />
    </svg>
  );
}

function IconTabRecaps() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <polygon points="23 7 16 12 23 17 23 7" />
      <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
    </svg>
  );
}

function IconTabWiki() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M4 19.5A2.5 2.5 0 016.5 17H20" />
      <path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z" />
    </svg>
  );
}

function IconTabNotes() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
      <polyline points="14 2 14 8 20 8" />
      <line x1="16" y1="13" x2="8" y2="13" />
      <line x1="16" y1="17" x2="8" y2="17" />
      <line x1="10" y1="9" x2="8" y2="9" />
    </svg>
  );
}

// ─── Schedule row ─────────────────────────────────────────────────────────────

interface ScheduleRowProps {
  entry: ScheduleEntry;
  role: string;
  myUserId: number;
  members: Member[];
  availableCampaigns: Campaign[];
  expanded: boolean;
  saving: boolean;
  onToggle: () => void;
  onAssign: (date: string, campaignId: number) => void;
  onUnassign: (date: string) => void;
  onBlackout: (date: string) => void;
  onRemoveBlackout: (date: string) => void;
}

function ScheduleRow({
  entry,
  role,
  myUserId,
  members,
  availableCampaigns,
  expanded,
  saving,
  onToggle,
  onAssign,
  onUnassign,
  onBlackout,
  onRemoveBlackout,
}: ScheduleRowProps) {
  const canManage = role === "dm" || role === "admin";
  const myBlackout = entry.blackouts.find((b) => b.userId === myUserId);
  const blackoutUserIds = new Set(entry.blackouts.map((b) => b.userId));
  const campaignDmUserIds = new Set(entry.campaignDmUserIds);

  return (
    <div className={`schedule-row${expanded ? " expanded" : ""}`}>
      <div className="schedule-row-header" onClick={onToggle}>
        <div style={{ flexShrink: 0 }}>
          <div className="schedule-date">{formatDate(entry.date)}</div>
          <div className="schedule-weekday">{getWeekday(entry.date)}</div>
        </div>

        {/* Avatar stack fills the middle */}
        <AvatarStack
          members={members}
          blackoutUserIds={blackoutUserIds}
          campaignDmUserIds={campaignDmUserIds}
          allIn={entry.blackouts.length === 0}
        />

        <div className="flex items-center gap-3" style={{ flexShrink: 0 }}>
          {entry.campaignId ? (
            <Link
              to={`/campaigns/${entry.campaignId}`}
              className="schedule-campaign selected schedule-campaign-link"
              onClick={e => e.stopPropagation()}
              title={`Open ${entry.campaignName}`}
            >
              {entry.campaignName}
            </Link>
          ) : (
            <span className="schedule-campaign">No campaign</span>
          )}
          <span className="schedule-chevron">{expanded ? "▲" : "▼"}</span>
        </div>
      </div>

      {expanded && (
        <div className="schedule-expand">
          {/* Campaign + Availability — combined row */}
          <div className="expand-row">
            <div className="expand-section" style={{ flex: 1 }}>
              <div className="expand-label">Campaign</div>
              {canManage ? (
                availableCampaigns.length > 0 ? (
                  <div className="flex items-center gap-2" style={{ flexWrap: "wrap" }}>
                    <select
                      className="form-input form-input-sm"
                      value={entry.campaignId ?? ""}
                      disabled={saving}
                      onChange={(e) => {
                        if (e.target.value) onAssign(entry.date, Number(e.target.value));
                      }}
                    >
                      <option value="">— select campaign —</option>
                      {availableCampaigns.map((c) => (
                        <option key={c.id} value={c.id}>
                          {c.name}
                        </option>
                      ))}
                    </select>
                    {entry.campaignId && (
                      <button
                        className="btn btn-danger btn-sm"
                        onClick={() => onUnassign(entry.date)}
                        disabled={saving}
                      >
                        Remove
                      </button>
                    )}
                  </div>
                ) : (
                  <span className="text-muted" style={{ fontSize: "13px" }}>
                    No campaigns yet —{" "}
                    <a href="/campaigns" style={{ color: "var(--teal)" }}>
                      create one first
                    </a>
                  </span>
                )
              ) : entry.campaignName ? (
                <span style={{ fontSize: "14px", fontWeight: 600, color: "var(--teal)" }}>
                  {entry.campaignName}
                </span>
              ) : (
                <span className="text-muted" style={{ fontSize: "13px" }}>No campaign assigned</span>
              )}
            </div>

            <div className="expand-section">
              <div className="expand-label">My Availability</div>
              <label className={`avail-toggle${saving ? " disabled" : ""}`}>
                <input
                  type="checkbox"
                  checked={!myBlackout}
                  disabled={saving}
                  onChange={() =>
                    myBlackout ? onRemoveBlackout(entry.date) : onBlackout(entry.date)
                  }
                />
                <span className="avail-toggle-track" />
                <span className="avail-toggle-label">
                  {myBlackout ? "I'm Out" : "I'm In"}
                </span>
              </label>
            </div>
          </div>

          {/* Others out */}
          {entry.blackouts.filter((b) => b.userId !== myUserId).length > 0 && (
            <div className="avail-row">
              {entry.blackouts
                .filter((b) => b.userId !== myUserId)
                .map((b) => (
                  <span key={b.userId} className="avail-chip" title={b.reason ?? undefined}>
                    {b.username} out
                  </span>
                ))}
            </div>
          )}

          {/* Quick-nav tab buttons — only when a campaign is assigned */}
          {entry.campaignId && (
            <div className="expand-section">
              <div className="expand-label" style={{ textAlign: "center" }}>Quick Nav</div>
              <div className="schedule-tab-links">
                <Link to={`/campaigns/${entry.campaignId}?tab=board`} className="schedule-tab-btn" title="Board">
                  <IconTabBoard /><span>Board</span>
                </Link>
                <Link to={`/campaigns/${entry.campaignId}?tab=characters`} className="schedule-tab-btn" title="Characters">
                  <IconTabCharacters /><span>Characters</span>
                </Link>
                <Link to={`/campaigns/${entry.campaignId}?tab=recaps`} className="schedule-tab-btn" title="Recaps">
                  <IconTabRecaps /><span>Recaps</span>
                </Link>
                <Link to={`/campaigns/${entry.campaignId}?tab=wiki`} className="schedule-tab-btn" title="Wiki">
                  <IconTabWiki /><span>Wiki</span>
                </Link>
                <Link to={`/campaigns/${entry.campaignId}?tab=notes`} className="schedule-tab-btn" title="My Notes">
                  <IconTabNotes /><span>My Notes</span>
                </Link>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Right panel ──────────────────────────────────────────────────────────────

const ROLE_ORDER = ["admin", "dm", "pending_dm", "player"];

function roleBadgeClass(role: string) {
  if (role === "admin") return "badge badge-admin";
  if (role === "dm") return "badge badge-dm";
  if (role === "pending_dm") return "badge badge-pending";
  return "badge badge-archived";
}

function RightPanel({
  data,
  saving,
  onRemoveBlackout,
}: {
  data: DashboardData;
  saving: string | null;
  onRemoveBlackout: (date: string) => void;
}) {
  const myBlackouts = data.entries.filter((e) =>
    e.blackouts.some((b) => b.userId === data.myUserId)
  );

  const sortedMembers = [...data.members].sort(
    (a, b) =>
      ROLE_ORDER.indexOf(a.role) - ROLE_ORDER.indexOf(b.role) ||
      a.username.localeCompare(b.username)
  );

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
      {/* Dates I'm Out */}
      <div className="card">
        <div className="card-header">
          <span className="card-title">Dates I'm Out</span>
          {myBlackouts.length > 0 && (
            <span className="badge badge-pending">{myBlackouts.length}</span>
          )}
        </div>
        <div className="card-body">
          {myBlackouts.length === 0 ? (
            <p style={{ fontSize: "13px", color: "var(--text-muted)", lineHeight: 1.6 }}>
              All clear — you're in for every session.
            </p>
          ) : (
            <div style={{ display: "flex", flexDirection: "column" }}>
              {myBlackouts.map((e, i) => (
                <div
                  key={e.date}
                  className="flex items-center justify-between"
                  style={{
                    padding: "6px 0",
                    borderBottom:
                      i < myBlackouts.length - 1 ? "1px solid var(--border)" : "none",
                  }}
                >
                  <div>
                    <div style={{ fontSize: "13px", fontWeight: 600 }}>
                      {formatDate(e.date)}
                    </div>
                    <div style={{ fontSize: "11px", color: "var(--text-dim)" }}>
                      {getWeekday(e.date)}
                    </div>
                  </div>
                  <button
                    className="btn btn-secondary btn-sm"
                    onClick={() => onRemoveBlackout(e.date)}
                    disabled={saving !== null}
                  >
                    I'm back in
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Guild roster */}
      <div className="card">
        <div className="card-header">
          <span className="card-title">Players</span>
          <span className="badge badge-archived">{sortedMembers.length}</span>
        </div>
        <div className="card-body" style={{ padding: "4px 20px 8px" }}>
          {sortedMembers.map((m) => (
            <div key={m.userId} className="roster-row">
              {m.avatarUrl ? (
                <img src={m.avatarUrl} className="roster-avatar" alt={m.username} />
              ) : (
                <div className="roster-avatar-fallback">{m.username[0]}</div>
              )}
              <span className="roster-name">{m.username}</span>
              <span className={roleBadgeClass(m.role)}>
                {m.role === "pending_dm" ? "pending" : m.role}
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─── Dashboard page ───────────────────────────────────────────────────────────

export function DashboardPage() {
  const toast = useToast();
  const [data, setData] = useState<DashboardData | null>(null);
  const [expandedDate, setExpandedDate] = useState<string | null>(null);
  const [saving, setSaving] = useState<string | null>(null);
  const [error, setError] = useState("");
  const [addDate, setAddDate] = useState("");
  const [addCampaignId, setAddCampaignId] = useState("");
  const [creating, setCreating] = useState(false);

  const load = useCallback(() => {
    api<DashboardData>("/dashboard")
      .then(setData)
      .catch(() => {
        toast.addToast("Failed to load dashboard. Please try again.", "error");
      });
  }, [toast]);

  useEffect(() => {
    load();
  }, [load]);

  async function run(key: string, fn: () => Promise<unknown>) {
    setSaving(key);
    setError("");
    try {
      await fn();
      load();
    } catch (e) {
      toast.addToast("Operation failed. Please try again.", "error");
    } finally {
      setSaving(null);
    }
  }

  function assignCampaign(date: string, campaignId: number) {
    run(`assign:${date}`, () =>
      api(`/game-nights/${date}/select-campaign`, {
        method: "POST",
        body: JSON.stringify({ campaignId }),
      })
    ).then(() => {
      toast.addToast(`Campaign assigned to ${formatDate(date)}`, "success");
    });
  }

  function unassignCampaign(date: string) {
    run(`unassign:${date}`, () =>
      api(`/game-nights/${date}/campaign`, { method: "DELETE" })
    ).then(() => {
      toast.addToast("Campaign removed", "success");
    });
  }

  function addBlackout(date: string) {
    run(`blackout:${date}`, () =>
      api(`/game-nights/${date}/blackout`, { method: "POST", body: JSON.stringify({}) })
    ).then(() => {
      toast.addToast(`You're marked as out on ${formatDate(date)}`, "info");
    });
  }

  function removeBlackout(date: string) {
    run(`rmblackout:${date}`, () =>
      api(`/game-nights/${date}/blackout`, { method: "DELETE" })
    ).then(() => {
      toast.addToast(`You're back in for ${formatDate(date)}`, "success");
    });
  }

  async function createNight() {
    const dateToUse = addDate || suggestedDate;
    if (!dateToUse) return;
    setCreating(true);
    setError("");
    try {
      const body: Record<string, unknown> = { date: dateToUse };
      if (addCampaignId) body.campaignId = Number(addCampaignId);
      await api("/game-nights", { method: "POST", body: JSON.stringify(body) });
      setAddDate(""); // clears any manual override; suggestedDate advances automatically on reload
      setAddCampaignId("");
      load();
      toast.addToast(`Game night scheduled for ${formatDate(dateToUse)}`, "success");
    } catch (e) {
      toast.addToast("Failed to schedule game night. Please try again.", "error");
    } finally {
      setCreating(false);
    }
  }

  // Auto-suggest the next unscheduled occurrence of the default game day
  // Must be before any early return to satisfy Rules of Hooks
  const suggestedDate = useMemo(() => {
    if (!data || data.defaultGameDay == null) return "";
    const scheduled = new Set(data.entries.map((e) => e.date));
    return nextDefaultNight(data.defaultGameDay, scheduled);
  }, [data]);

  if (!data) {
    return (
      <div className="page">
        <div style={{ display: "flex", justifyContent: "center", padding: "60px" }}>
          <span className="spinner" style={{ width: "24px", height: "24px" }} />
        </div>
      </div>
    );
  }

  const today = todayStr();
  const todayEntry = data.entries.find((e) => e.date === today);
  const upcomingEntries = data.entries.filter((e) => e.date >= today);
  const role = data.role;
  const canManage = role === "dm" || role === "admin";
  const availableCampaigns = role === "admin" ? data.allCampaigns : data.myCampaigns;

  // The displayed value: user override takes precedence, else the suggestion
  const displayDate = addDate || suggestedDate;

  return (
    <div className="page">
      <div className="page-header">
        <h1 className="page-title">Dashboard</h1>
        <p className="page-subtitle">Upcoming sessions and shared availability</p>
      </div>

      <div className="dashboard-layout">
        {/* ── Left column: tonight + schedule ── */}
        <div>
          <TonightCard entry={todayEntry} />

          <div className="card">
            <div className="card-header">
              <span className="card-title">Upcoming Schedule</span>
              {upcomingEntries.length > 0 && (
                <span className="badge badge-pending">{upcomingEntries.length}</span>
              )}
            </div>

            <div className="schedule-list">
              {upcomingEntries.length === 0 ? (
                <div className="empty-state">
                  <div className="empty-state-icon">📅</div>
                  <div className="empty-state-title">Nothing scheduled yet</div>
                  {canManage && (
                    <div className="empty-state-text">
                      Add a date below to schedule your next session.
                    </div>
                  )}
                </div>
              ) : (
                upcomingEntries.map((entry) => (
                  <ScheduleRow
                    key={entry.date}
                    entry={entry}
                    role={role}
                    myUserId={data.myUserId}
                    members={data.members}
                    availableCampaigns={availableCampaigns}
                    expanded={expandedDate === entry.date}
                    saving={saving !== null && saving.endsWith(`:${entry.date}`)}
                    onToggle={() =>
                      setExpandedDate(expandedDate === entry.date ? null : entry.date)
                    }
                    onAssign={assignCampaign}
                    onUnassign={unassignCampaign}
                    onBlackout={addBlackout}
                    onRemoveBlackout={removeBlackout}
                  />
                ))
              )}
            </div>

            {/* Add game night — DM and admin only */}
            {canManage && (
              <div className="add-night-form">
                <div className="add-night-label">Add a game night</div>
                <div className="flex items-center gap-2" style={{ flexWrap: "wrap" }}>
                  <input
                    type="date"
                    className="form-input"
                    style={{ maxWidth: "180px" }}
                    value={displayDate}
                    onChange={(e) => setAddDate(e.target.value)}
                  />
                  {availableCampaigns.length > 0 && (
                    <select
                      className="form-input"
                      style={{ maxWidth: "240px" }}
                      value={addCampaignId}
                      onChange={(e) => setAddCampaignId(e.target.value)}
                    >
                      <option value="">campaign (optional)</option>
                      {availableCampaigns.map((c) => (
                        <option key={c.id} value={c.id}>
                          {c.name}
                        </option>
                      ))}
                    </select>
                  )}
                  <button
                    className="btn btn-primary"
                    onClick={createNight}
                    disabled={!displayDate || creating}
                  >
                    {creating ? <span className="spinner" /> : "Schedule"}
                  </button>
                </div>
              </div>
            )}
          </div>
        </div>

        {/* ── Right column: availability + roster ── */}
        <RightPanel data={data} saving={saving} onRemoveBlackout={removeBlackout} />
      </div>
    </div>
  );
}
