import { useMemo } from "react";
import type { CSSProperties } from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import {
  GridData,
  ResolvedCell,
  planCategoryLabel,
  categoryMap,
  PlanCategory,
} from "../lib/queries/plan";
import type { Project } from "../lib/queries/projects";
import PlanRangeChips from "./PlanRangeChips";
import { LoadingState } from "../ui";
import { fonts } from "../theme";

/**
 * The planner surface, fully MUI/theme-driven (replaces the bespoke plan.css
 * grid styling). All colors come from the theme palette via `theme.vars`, so it
 * adapts to light/dark automatically; category colors come from the DB and are
 * injected per cell as the `--cat-color` CSS variable. Class names from the JSX
 * below are styled here as scoped nested selectors.
 */
const PlanGridRoot = styled(Box)(({ theme }) => ({
  position: "relative",
  overflow: "auto",
  WebkitOverflowScrolling: "touch",
  overscrollBehavior: "contain",
  background: theme.vars!.palette.background.default,
  border: `1px solid ${theme.vars!.palette.divider}`,
  borderRadius: 14,
  boxShadow: theme.shadows[2],
  maxHeight: "calc(100dvh - 240px)",
  isolation: "isolate",

  "& .plan-grid": {
    position: "relative",
    zIndex: 1,
    borderCollapse: "separate",
    borderSpacing: 0,
    width: "100%",
    fontSize: "12.5px",
    color: theme.vars!.palette.text.primary,
    fontFamily: fonts.body,
  },
  "& col.plan-grid-date-col": { width: 88 },
  "& tbody tr td + td": {
    borderLeft: `1px solid ${theme.vars!.palette.divider}`,
  },

  // Sticky header
  "& thead th": {
    position: "sticky",
    top: 0,
    zIndex: 3,
    padding: "14px 12px 12px",
    background: theme.vars!.palette.background.paper,
    color: theme.vars!.palette.text.primary,
    borderBottom: `1px solid ${theme.vars!.palette.divider}`,
    textAlign: "left",
    fontWeight: 600,
    whiteSpace: "nowrap",
  },
  "& thead th.plan-grid-date-col": {
    left: 0,
    zIndex: 4,
    minWidth: 88,
    padding: "12px 10px 10px 14px",
    fontSize: "10.5px",
    fontWeight: 700,
    letterSpacing: "0.18em",
    textTransform: "uppercase",
    color: theme.vars!.palette.text.secondary,
  },
  "& .plan-person-head": {
    display: "inline-flex",
    alignItems: "center",
    gap: 9,
    minWidth: 0,
  },
  "& .plan-person-dot": {
    flexShrink: 0,
    width: 9,
    height: 9,
    borderRadius: "50%",
    background: theme.vars!.palette.action.disabled,
  },
  "& .plan-person-name": {
    display: "inline-flex",
    flexDirection: "column",
    minWidth: 0,
    lineHeight: 1.1,
  },
  "& .plan-person-name strong": {
    fontWeight: 600,
    fontSize: 13,
    color: theme.vars!.palette.text.primary,
    whiteSpace: "nowrap",
    overflow: "hidden",
    textOverflow: "ellipsis",
    maxWidth: 180,
  },
  "& .plan-person-name small": {
    fontSize: 10,
    fontWeight: 500,
    color: theme.vars!.palette.text.disabled,
    letterSpacing: "0.04em",
    textTransform: "uppercase",
    fontFamily: fonts.mono,
    marginTop: 1,
  },

  // Sticky date column
  "& tbody td.plan-grid-date-col": {
    position: "sticky",
    left: 0,
    zIndex: 1,
    background: theme.vars!.palette.background.paper,
    fontWeight: 500,
    color: theme.vars!.palette.text.secondary,
    whiteSpace: "nowrap",
    minWidth: 88,
    padding: "9px 10px 9px 14px",
    borderRight: `1px solid ${theme.vars!.palette.divider}`,
    borderBottom: `1px solid ${theme.vars!.palette.divider}`,
  },
  "& .plan-date-stamp": {
    display: "flex",
    flexDirection: "column",
    alignItems: "flex-start",
    lineHeight: 1.1,
    gap: 1,
  },
  "& .plan-date-daynum": {
    fontFamily: fonts.mono,
    fontSize: 17,
    fontWeight: 600,
    color: theme.vars!.palette.text.primary,
    fontVariantNumeric: "tabular-nums",
    lineHeight: 1.05,
  },
  "& .plan-date-dow": {
    fontSize: 10,
    fontWeight: 600,
    letterSpacing: "0.14em",
    textTransform: "uppercase",
    color: theme.vars!.palette.text.disabled,
    lineHeight: 1.3,
  },

  // Today
  "& tbody tr.is-today td.plan-grid-date-col": {
    background: theme.vars!.palette.background.paper,
    "&::before": {
      content: '""',
      position: "absolute",
      left: 0,
      top: 6,
      bottom: 6,
      width: 3,
      borderRadius: "0 2px 2px 0",
      background: theme.vars!.palette.primary.main,
      boxShadow: `0 0 14px rgba(${theme.vars!.palette.primary.mainChannel} / 0.3)`,
    },
    "& .plan-date-daynum": { color: theme.vars!.palette.primary.main },
  },
  "& tbody tr.is-today td:not(.plan-grid-date-col)": {
    background: `rgba(${theme.vars!.palette.primary.mainChannel} / 0.06)`,
  },

  // Body cells
  "& tbody td": {
    padding: 0,
    borderBottom: `1px solid ${theme.vars!.palette.divider}`,
    verticalAlign: "stretch",
    minWidth: 168,
    background: theme.vars!.palette.background.paper,
  },

  // Weekend
  "& tr.plan-grid-weekend td": {
    background: `rgba(${theme.vars!.palette.text.primaryChannel} / 0.03)`,
  },
  "& tr.plan-grid-weekend td.plan-grid-date-col": {
    background: theme.vars!.palette.background.paper,
    color: theme.vars!.palette.text.disabled,
    "& .plan-date-dow": { color: theme.vars!.palette.warning.main },
    "& .plan-date-daynum": { color: theme.vars!.palette.text.secondary },
  },

  // The cell button + marker tape
  "& .plan-cell": {
    position: "relative",
    display: "block",
    width: "100%",
    height: "100%",
    minHeight: 60,
    textAlign: "left",
    background: "transparent",
    border: 0,
    borderRadius: 0,
    padding: "9px 11px 10px 16px",
    cursor: "pointer",
    font: "inherit",
    color: "inherit",
    transition: "background 150ms ease, transform 150ms ease",
    "&::before": {
      content: '""',
      position: "absolute",
      left: 4,
      top: 8,
      bottom: 8,
      width: 3,
      borderRadius: 2,
      background: `var(--cat-color, ${theme.vars!.palette.divider})`,
      transition:
        "background 150ms ease, box-shadow 150ms ease, transform 150ms ease",
    },
    "&:hover": { background: theme.vars!.palette.action.hover },
    "&:hover::before": {
      transform: "scaleX(1.4)",
      boxShadow: "0 0 12px currentColor",
    },
    "&:active": { transform: "translateY(0.5px)" },
    "&:focus-visible": {
      outline: "none",
      boxShadow: `inset 0 0 0 2px ${theme.vars!.palette.primary.main}, inset 0 0 0 4px ${theme.vars!.palette.background.paper}`,
    },
  },
  "& .plan-cell--readonly": {
    cursor: "default",
    "&:hover": { background: "transparent" },
    "&:hover::before": { transform: "none", boxShadow: "none" },
  },
  // A read-only cell that HAS data is still clickable (view mode).
  "& .plan-cell--readonly:not(.plan-cell--empty):not(.plan-cell--past)": {
    cursor: "pointer",
    "&:hover": { background: theme.vars!.palette.action.hover },
    "&:hover::before": {
      transform: "scaleX(1.4)",
      boxShadow: "0 0 12px currentColor",
    },
  },
  "& .plan-cell--past": {
    cursor: "default",
    opacity: 0.55,
    "&:hover": { background: "transparent" },
    "&:hover::before": { transform: "none", boxShadow: "none" },
  },
  // Empty cells: dashed tape + a "+" hover hint.
  "& .plan-cell--empty::before": {
    background: "transparent",
    border: `1px dashed ${theme.vars!.palette.divider}`,
    width: 2,
    left: 5,
  },
  "& .plan-cell--empty:hover::before": {
    background: theme.vars!.palette.primary.main,
    borderColor: theme.vars!.palette.primary.main,
    transform: "scaleX(1.6)",
  },
  "& .plan-cell--empty::after": {
    content: '"+"',
    position: "absolute",
    inset: 0,
    display: "grid",
    placeItems: "center",
    color: theme.vars!.palette.text.disabled,
    opacity: 0,
    fontSize: 18,
    fontWeight: 300,
    pointerEvents: "none",
    transition: "opacity 150ms ease",
  },
  "& .plan-cell--empty:hover::after": {
    opacity: 0.7,
    color: theme.vars!.palette.primary.main,
  },
  "& .plan-cell--empty.plan-cell--readonly::after": { content: "none" },

  // One-shot mutation pulse
  "@keyframes plan-cell-pulse": {
    "0%": {
      boxShadow: `inset 0 0 0 2px ${theme.vars!.palette.primary.main}, 0 0 0 0 rgba(${theme.vars!.palette.primary.mainChannel} / 0.4)`,
    },
    "60%": {
      boxShadow: `inset 0 0 0 2px ${theme.vars!.palette.primary.main}, 0 0 0 6px transparent`,
    },
    "100%": { boxShadow: "inset 0 0 0 2px transparent, 0 0 0 0 transparent" },
  },
  "& .plan-cell--pulse": { animation: "plan-cell-pulse 600ms ease-out" },

  "& .plan-cell-record": {
    display: "block",
    width: "100%",
    minWidth: 0,
  },
  "& .plan-cell-record + .plan-cell-record": {
    marginTop: 5,
    paddingTop: 5,
    borderTop: `1px dashed ${theme.vars!.palette.divider}`,
  },

  // Chips inside a cell
  "& .plan-chip-block": {
    display: "flex",
    flexDirection: "column",
    gap: 4,
    textAlign: "left",
    width: "100%",
    minWidth: 0,
  },
  "& .plan-chip-line": {
    display: "flex",
    alignItems: "center",
    gap: 7,
    flexWrap: "wrap",
    minWidth: 0,
  },
  "& .plan-chip": {
    display: "inline-flex",
    alignItems: "center",
    gap: 5,
    borderRadius: 4,
    padding: "2px 7px",
    fontSize: "10.5px",
    fontWeight: 600,
    letterSpacing: "0.04em",
    textTransform: "uppercase",
    color: `var(--cat-color, ${theme.vars!.palette.text.primary})`,
    background: `color-mix(in srgb, var(--cat-color, transparent) 12%, ${theme.vars!.palette.background.paper})`,
    border: `1px solid color-mix(in srgb, var(--cat-color, ${theme.vars!.palette.divider}) 35%, transparent)`,
    whiteSpace: "nowrap",
  },
  "& .plan-chip-project": {
    fontFamily: fonts.mono,
    fontSize: "12.5px",
    fontWeight: 600,
    color: theme.vars!.palette.text.primary,
    overflow: "hidden",
    textOverflow: "ellipsis",
    whiteSpace: "nowrap",
    maxWidth: "100%",
  },
  "& .plan-cell-note": {
    display: "-webkit-box",
    WebkitLineClamp: 2,
    WebkitBoxOrient: "vertical",
    overflow: "hidden",
    marginTop: 1,
    fontSize: "11.5px",
    color: theme.vars!.palette.text.secondary,
    lineHeight: 1.35,
    fontStyle: "italic",
  },

  "@media (max-width: 480px)": {
    "& col.plan-grid-date-col": { width: 72 },
    "& tbody td": { minWidth: 132 },
    "& .plan-cell": { minHeight: 56 },
  },
  // Touch: show a faint persistent "+" on empty editable cells.
  "@media (hover: none)": {
    "& .plan-cell--empty:not(.plan-cell--readonly)::after": {
      opacity: 0.26,
      color: theme.vars!.palette.text.disabled,
    },
  },
  "@media (prefers-reduced-motion: reduce)": {
    "& .plan-cell, & .plan-cell::before, & .plan-cell::after": {
      transition: "none",
    },
    "& .plan-cell--pulse": { animation: "none" },
  },
}));

interface Props {
  data: GridData | undefined;
  canEdit: boolean;
  projects: Project[];
  categories: PlanCategory[];
  // The (userId, date) of the most recent successful mutation. The
  // matching cell gets a one-shot highlight pulse. `nonce` is part of
  // the value so back-to-back mutations on the same cell re-trigger
  // the animation (React needs a new key to re-mount the class).
  pulseKey?: { userId: number; date: string; nonce: number } | null;
  onCellClick: (userId: number, date: string, cells: ResolvedCell[]) => void;
}

// Full Czech weekday names, indexed by Date.getUTCDay() (0 = Sunday).
// Used in the date column "stamp" so the day name reads in full rather
// than as a 2-letter abbreviation — easier to scan when the planner
// spans several weeks.
const CZECH_WEEKDAYS = [
  "Neděle",
  "Pondělí",
  "Úterý",
  "Středa",
  "Čtvrtek",
  "Pátek",
  "Sobota",
];

function eachDay(from: string, to: string): string[] {
  const out: string[] = [];
  const a = new Date(from + "T00:00:00.000Z");
  const b = new Date(to + "T00:00:00.000Z");
  for (let d = new Date(a); d <= b; d.setUTCDate(d.getUTCDate() + 1)) {
    out.push(d.toISOString().slice(0, 10));
  }
  return out;
}

function czechWeekday(dateStr: string): string {
  return CZECH_WEEKDAYS[new Date(dateStr + "T00:00:00.000Z").getUTCDay()];
}

function isWeekend(dateStr: string): boolean {
  const day = new Date(dateStr + "T00:00:00.000Z").getUTCDay();
  return day === 0 || day === 6;
}

// Today's local date as YYYY-MM-DD. Used to mark the current row and to
// gate past-day editing. Uses local time (matches the project's date
// conventions in CLAUDE.md and the server's `assertNotPastDate` guard in
// src/services/plan.service.ts).
function todayIso(): string {
  const d = new Date();
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

// True when the given YYYY-MM-DD cell date is strictly before today (local).
// Used to gate the create / edit UI on past days — the server still enforces
// the rule with a 403, but the client should never put the user in the
// "I clicked, modal opened, error toast" state in the first place.
function isPastDate(dateStr: string, today: string): boolean {
  return dateStr < today;
}

// Split a full name into first / last for the column header.
function splitName(full: string): { first: string; last: string } {
  const parts = full.trim().split(/\s+/);
  if (parts.length === 1) return { first: parts[0], last: "" };
  return { first: parts[0], last: parts.slice(1).join(" ") };
}

// Short role label for the column header sub-line.
function shortRole(role: string | null): string {
  if (!role) return "";
  // Keep it brief — column header is tight
  if (/^admin$/i.test(role)) return "Admin";
  if (/^viewer$/i.test(role)) return "Viewer";
  return role.length > 10 ? role.slice(0, 9) + "…" : role;
}

export default function PlanGrid({
  data,
  canEdit,
  projects,
  categories,
  pulseKey,
  onCellClick,
}: Props) {
  const today = useMemo(() => todayIso(), []);
  const catMap = useMemo(() => categoryMap(categories), [categories]);
  // Index projects by id once per projects change instead of a linear
  // projects.find() inside the per-cell render loop (which was
  // O(days * users * cells * projects)). Hooks must run before the early
  // return below, so this is computed unconditionally.
  const projectMap = useMemo(() => {
    const m = new Map<number, Project>();
    for (const p of projects) m.set(p.id, p);
    return m;
  }, [projects]);
  // Memoize the day list so it isn't rebuilt on every render (only when the
  // visible range changes). Stable identity also keeps the row map cheap.
  const days = useMemo(
    () => (data ? eachDay(data.date_from, data.date_to) : []),
    [data?.date_from, data?.date_to],
  );

  if (!data) return <LoadingState />;
  const users = data.users;
  // pulseKey?.nonce is included in the data-pulse attribute so a second
  // mutation on the same cell re-triggers the animation (CSS animations
  // don't restart unless the keyframe applies to a fresh element/class
  // binding).
  const pulseAttr = pulseKey
    ? `${pulseKey.userId}|${pulseKey.date}|${pulseKey.nonce}`
    : undefined;

  return (
    <PlanGridRoot data-pulse={pulseAttr}>
      <table className="plan-grid">
        {/* The colgroup is what actually controls column width in a
            table — `min-width` on `<th>`/`<td>` is just a floor that
            `table-layout: auto` happily blows past. The first column
            gets a fixed width via the col element so the date stamp
            doesn't get stretched to share space with person columns. */}
        <colgroup>
          <col className="plan-grid-date-col" />
          {users.map((u) => (
            <col key={u.id} />
          ))}
        </colgroup>
        <thead>
          <tr>
            <th className="plan-grid-date-col">Datum</th>
            {users.map((u) => {
              const { first, last } = splitName(u.full_name);
              return (
                <th key={u.id}>
                  <span className="plan-person-head">
                    <span className="plan-person-dot" aria-hidden />
                    <span className="plan-person-name">
                      <strong title={u.full_name}>
                        {first}
                        {last ? ` ${last}` : ""}
                      </strong>
                      {shortRole(u.role_name) && (
                        <small>{shortRole(u.role_name)}</small>
                      )}
                    </span>
                  </span>
                </th>
              );
            })}
          </tr>
        </thead>
        <tbody>
          {days.map((date) => {
            const dow = czechWeekday(date);
            const dayNum = date.slice(8, 10);
            const isToday = date === today;
            const trCls = [
              isWeekend(date) ? "plan-grid-weekend" : "",
              isToday ? "is-today" : "",
            ]
              .filter(Boolean)
              .join(" ");
            return (
              <tr key={date} className={trCls}>
                <td className="plan-grid-date-col">
                  <span className="plan-date-stamp">
                    <span className="plan-date-daynum">{dayNum}</span>
                    <span className="plan-date-dow">{dow}</span>
                  </span>
                </td>
                {users.map((u) => {
                  const cellArr = data.cells[u.id]?.[date] ?? [];
                  const cell = cellArr[0] ?? null;
                  const past = isPastDate(date, today);
                  // Past-day cells are read-only in the UI: an empty past
                  // cell is non-interactive (no create modal, no "+" hint),
                  // a past cell with data opens in view mode only. The
                  // server still enforces the past-date rule with a 403
                  // (defense in depth), but the click path here never
                  // reaches a create/edit submission for a past date.
                  const isLocked = !canEdit || past;
                  // `isPulsing` is true for the single (user, date) cell
                  // that the most recent successful mutation touched.
                  // CSS restarts the keyframe animation whenever the
                  // `nonce` changes (we embed it in data-pulse on the
                  // wrapper, see above), so back-to-back mutations on the
                  // same cell re-trigger the pulse.
                  const isPulsing =
                    !!pulseKey &&
                    pulseKey.userId === u.id &&
                    pulseKey.date === date;
                  let cls: string;
                  if (cell) {
                    cls = isLocked
                      ? `plan-cell plan-cell--readonly${past ? " plan-cell--past" : ""}`
                      : "plan-cell";
                  } else {
                    cls = isLocked
                      ? `plan-cell plan-cell--empty plan-cell--readonly${past ? " plan-cell--past" : ""}`
                      : "plan-cell plan-cell--empty";
                  }
                  if (isPulsing) cls += " plan-cell--pulse";
                  return (
                    <td key={u.id}>
                      <button
                        type="button"
                        className={cls}
                        style={
                          cell
                            ? ({
                                "--cat-color": catMap[cell.category]?.color,
                              } as CSSProperties)
                            : undefined
                        }
                        onClick={() => onCellClick(u.id, date, cellArr)}
                        aria-label={
                          cell
                            ? cellArr.length === 1
                              ? `${u.full_name}, ${date}, ${planCategoryLabel(cell.category, catMap)}`
                              : `${u.full_name}, ${date}, ${cellArr.length} záznamy`
                            : isLocked
                              ? `${u.full_name}, ${date}, ${past ? "uplynulý den" : "prázdné"} — bez záznamu`
                              : `${u.full_name}, ${date}, prázdné — přidat záznam`
                        }
                      >
                        {cellArr.map((c, i) => (
                          <Box
                            key={
                              c.entryId != null
                                ? c.entryId
                                : c.overrideId != null
                                  ? `o${c.overrideId}`
                                  : i
                            }
                            className="plan-cell-record"
                            style={
                              {
                                "--cat-color": catMap[c.category]?.color,
                              } as CSSProperties
                            }
                          >
                            <PlanRangeChips
                              cell={c}
                              project={
                                c.project_id
                                  ? (projectMap.get(c.project_id) ?? null)
                                  : null
                              }
                              categoryLabel={planCategoryLabel(
                                c.category,
                                catMap,
                              )}
                            />
                          </Box>
                        ))}
                      </button>
                    </td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </PlanGridRoot>
  );
}
