const { useEffect, useRef, useState } = React;

const STORAGE_KEY = "project-kitchen-board-state-v1";
const MS_PER_DAY = 24 * 60 * 60 * 1000;

function uid(prefix) {
  return `${prefix}-${Math.random().toString(36).slice(2, 8)}-${Date.now().toString(36)}`;
}

function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value));
}

function safeNumber(value, fallback) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
}

function nowIso() {
  return new Date().toISOString();
}

function daysAgo(days) {
  return new Date(Date.now() - days * MS_PER_DAY).toISOString();
}

function createDefaultColumns() {
  return [
    { id: "col-intake", label: "Sticky Pot", targetDays: 2 },
    { id: "col-scope", label: "Prep Counter", targetDays: 3 },
    { id: "col-build", label: "Main Burner", targetDays: 4 },
    { id: "col-review", label: "Taste Test", targetDays: 2 }
  ];
}

function createDefaultRows() {
  return [
    { id: "row-client", label: "Client Work" },
    { id: "row-internal", label: "Internal Ops" },
    { id: "row-field", label: "Field Projects" }
  ];
}

function createDefaultTickets() {
  return [
    {
      id: uid("ticket"),
      code: "PT-101",
      title: "Northside permit chase",
      owner: "Avery",
      rowId: "row-client",
      columnId: "col-scope",
      projectTargetDays: 12,
      details: "Waiting on permit comments and utility redlines before the install package can close.",
      createdAt: daysAgo(3.5),
      enteredColumnAt: daysAgo(2.2),
      updatedAt: daysAgo(0.6),
      moveHistory: [
        { columnId: "col-intake", enteredAt: daysAgo(3.5), leftAt: daysAgo(2.2) },
        { columnId: "col-scope", enteredAt: daysAgo(2.2), leftAt: null }
      ]
    },
    {
      id: uid("ticket"),
      code: "PT-102",
      title: "Warehouse lighting rollout",
      owner: "Jordan",
      rowId: "row-field",
      columnId: "col-build",
      projectTargetDays: 14,
      details: "Materials landed, but trench coordination is dragging and the schedule is heating up.",
      createdAt: daysAgo(8.5),
      enteredColumnAt: daysAgo(4.8),
      updatedAt: daysAgo(0.2),
      moveHistory: [
        { columnId: "col-intake", enteredAt: daysAgo(8.5), leftAt: daysAgo(7.9) },
        { columnId: "col-scope", enteredAt: daysAgo(7.9), leftAt: daysAgo(4.8) },
        { columnId: "col-build", enteredAt: daysAgo(4.8), leftAt: null }
      ]
    },
    {
      id: uid("ticket"),
      code: "PT-103",
      title: "Reporting dashboard refresh",
      owner: "Casey",
      rowId: "row-internal",
      columnId: "col-review",
      projectTargetDays: 9,
      details: "The analytics refresh is moving quickly and only needs a final sign-off pass.",
      createdAt: daysAgo(4.3),
      enteredColumnAt: daysAgo(0.8),
      updatedAt: daysAgo(0.1),
      moveHistory: [
        { columnId: "col-intake", enteredAt: daysAgo(4.3), leftAt: daysAgo(3.4) },
        { columnId: "col-scope", enteredAt: daysAgo(3.4), leftAt: daysAgo(1.4) },
        { columnId: "col-build", enteredAt: daysAgo(1.4), leftAt: daysAgo(0.8) },
        { columnId: "col-review", enteredAt: daysAgo(0.8), leftAt: null }
      ]
    }
  ];
}

function createDefaultState() {
  return {
    boardName: "Project Kitchen Board",
    kitchenTargetDays: 18,
    lastTicketNumber: 104,
    columns: createDefaultColumns(),
    rows: createDefaultRows(),
    tickets: createDefaultTickets()
  };
}

function inferNextTicketNumber(tickets, fallback) {
  const highest = tickets.reduce((maxValue, ticket) => {
    const match = String(ticket && ticket.code ? ticket.code : "").match(/(\d+)(?!.*\d)/);
    const numeric = match ? Number(match[1]) : NaN;
    return Number.isFinite(numeric) ? Math.max(maxValue, numeric) : maxValue;
  }, 0);

  return Math.max(safeNumber(fallback, 0), highest + 1, 1);
}

function normalizeColumns(rawColumns) {
  if (!Array.isArray(rawColumns) || !rawColumns.length) {
    return createDefaultColumns();
  }

  return rawColumns.map((column, index) => ({
    id: String(column && column.id ? column.id : uid(`column-${index + 1}`)),
    label: String(column && column.label ? column.label : `Stage ${index + 1}`),
    targetDays: clamp(safeNumber(column && column.targetDays, 3), 0.5, 90)
  }));
}

function normalizeRows(rawRows) {
  if (!Array.isArray(rawRows) || !rawRows.length) {
    return createDefaultRows();
  }

  return rawRows.map((row, index) => ({
    id: String(row && row.id ? row.id : uid(`row-${index + 1}`)),
    label: String(row && row.label ? row.label : `Project Type ${index + 1}`)
  }));
}

function normalizeMoveHistory(rawHistory, fallbackColumnId, fallbackCreatedAt, validColumnIds) {
  const history = Array.isArray(rawHistory) ? rawHistory : [];
  const safeHistory = history
    .map((entry) => {
      const columnId = validColumnIds.has(entry && entry.columnId) ? String(entry.columnId) : fallbackColumnId;
      const enteredAt = String((entry && entry.enteredAt) || fallbackCreatedAt || nowIso());
      const leftAt = entry && entry.leftAt ? String(entry.leftAt) : null;
      return { columnId, enteredAt, leftAt };
    })
    .filter((entry) => entry.columnId);

  if (!safeHistory.length) {
    return [{ columnId: fallbackColumnId, enteredAt: fallbackCreatedAt || nowIso(), leftAt: null }];
  }

  safeHistory[safeHistory.length - 1] = {
    ...safeHistory[safeHistory.length - 1],
    columnId: fallbackColumnId,
    leftAt: null
  };

  return safeHistory;
}

function normalizeTickets(rawTickets, rows, columns) {
  const fallbackTickets = createDefaultTickets();
  const sourceTickets = Array.isArray(rawTickets) && rawTickets.length ? rawTickets : fallbackTickets;
  const rowIds = new Set(rows.map((row) => row.id));
  const columnIds = new Set(columns.map((column) => column.id));
  const firstRowId = rows[0].id;
  const firstColumnId = columns[0].id;

  return sourceTickets.map((ticket, index) => {
    const rowId = rowIds.has(ticket && ticket.rowId) ? String(ticket.rowId) : firstRowId;
    const columnId = columnIds.has(ticket && ticket.columnId) ? String(ticket.columnId) : firstColumnId;
    const createdAt = String((ticket && ticket.createdAt) || nowIso());
    const history = normalizeMoveHistory(ticket && ticket.moveHistory, columnId, createdAt, columnIds);

    return {
      id: String(ticket && ticket.id ? ticket.id : uid(`ticket-${index + 1}`)),
      code: String(ticket && ticket.code ? ticket.code : `PT-${index + 1}`),
      title: String(ticket && ticket.title ? ticket.title : `Untitled Ticket ${index + 1}`),
      owner: String(ticket && ticket.owner ? ticket.owner : "Unassigned"),
      rowId,
      columnId,
      projectTargetDays: clamp(safeNumber(ticket && ticket.projectTargetDays, 12), 1, 365),
      details: String(ticket && ticket.details ? ticket.details : ""),
      createdAt,
      enteredColumnAt: String((ticket && ticket.enteredColumnAt) || history[history.length - 1].enteredAt),
      updatedAt: String((ticket && ticket.updatedAt) || createdAt),
      moveHistory: history
    };
  });
}

function normalizeState(rawState) {
  const fallback = createDefaultState();
  const source = rawState && typeof rawState === "object" ? rawState : fallback;
  const columns = normalizeColumns(source.columns);
  const rows = normalizeRows(source.rows);
  const tickets = normalizeTickets(source.tickets, rows, columns);

  return {
    boardName: String(source.boardName || fallback.boardName),
    kitchenTargetDays: clamp(safeNumber(source.kitchenTargetDays, fallback.kitchenTargetDays), 1, 365),
    lastTicketNumber: inferNextTicketNumber(tickets, source.lastTicketNumber || fallback.lastTicketNumber),
    columns,
    rows,
    tickets
  };
}

function loadInitialState() {
  try {
    const stored = window.localStorage.getItem(STORAGE_KEY);
    if (!stored) {
      return createDefaultState();
    }
    return normalizeState(JSON.parse(stored));
  } catch (error) {
    return createDefaultState();
  }
}

function createTicketDraft(rowId, projectTargetDays) {
  return {
    title: "",
    owner: "",
    rowId,
    projectTargetDays: clamp(safeNumber(projectTargetDays, 12), 1, 365),
    details: ""
  };
}

function downloadJson(filename, payload) {
  const blob = new Blob([JSON.stringify(payload, null, 2)], {
    type: "application/json;charset=utf-8"
  });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = filename;
  link.click();
  URL.revokeObjectURL(url);
}

function readJsonFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      try {
        resolve(JSON.parse(String(reader.result || "")));
      } catch (error) {
        reject(error);
      }
    };
    reader.onerror = () => reject(reader.error || new Error("Could not read the selected file."));
    reader.readAsText(file);
  });
}

function formatDateTime(value) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return "Unknown";
  }
  return date.toLocaleString([], {
    year: "numeric",
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit"
  });
}

function formatDuration(ms) {
  if (!Number.isFinite(ms) || ms <= 0) {
    return "0h";
  }

  const days = ms / MS_PER_DAY;
  if (days >= 1) {
    return days >= 10 ? `${Math.round(days)}d` : `${days.toFixed(1)}d`;
  }

  const hours = ms / (60 * 60 * 1000);
  return `${hours.toFixed(1)}h`;
}

function getHistoryDurationMs(entry, nowMs) {
  const start = Date.parse(entry && entry.enteredAt ? entry.enteredAt : "");
  const finish = entry && entry.leftAt ? Date.parse(entry.leftAt) : nowMs;

  if (!Number.isFinite(start) || !Number.isFinite(finish)) {
    return 0;
  }

  return Math.max(0, finish - start);
}

function getColumnActualMs(ticket, columnId, nowMs) {
  return ticket.moveHistory.reduce((total, entry) => {
    if (entry.columnId !== columnId) {
      return total;
    }
    return total + getHistoryDurationMs(entry, nowMs);
  }, 0);
}

function getTargetMs(days) {
  return clamp(safeNumber(days, 1), 0.5, 365) * MS_PER_DAY;
}

function getHeatRatio(actualMs, targetDays) {
  const targetMs = getTargetMs(targetDays);
  if (!targetMs) {
    return 0;
  }
  return actualMs / targetMs;
}

function getHeatLabel(ratio) {
  if (ratio < 0.55) {
    return "Cool";
  }
  if (ratio < 0.95) {
    return "Steady";
  }
  if (ratio < 1.2) {
    return "Warming";
  }
  if (ratio < 1.5) {
    return "Hot";
  }
  return "Burnt";
}

function getHeatColor(ratio) {
  const normalized = clamp(ratio / 1.6, 0, 1);
  const hue = 208 - normalized * 200;
  const saturation = 85;
  const lightness = 84 - normalized * 24;
  return `hsl(${hue}deg ${saturation}% ${lightness}%)`;
}

function getHeatGlow(ratio) {
  const normalized = clamp(ratio / 1.7, 0, 1);
  const hue = 210 - normalized * 205;
  return `hsla(${hue}deg 95% 62% / 0.34)`;
}

function getGradeFromRatio(ratio, hasHistory) {
  if (!hasHistory) {
    return "--";
  }
  if (ratio <= 0.85) {
    return "A";
  }
  if (ratio <= 1.05) {
    return "B";
  }
  if (ratio <= 1.25) {
    return "C";
  }
  if (ratio <= 1.5) {
    return "D";
  }
  return "F";
}

function getOverallVerdict(ratio) {
  if (ratio <= 0.85) {
    return "Cooling ahead of plan";
  }
  if (ratio <= 1.05) {
    return "Properly cooked";
  }
  if (ratio <= 1.25) {
    return "Needs a quick stir";
  }
  if (ratio <= 1.5) {
    return "Overcooking";
  }
  return "Burnt and blocking";
}

function getTiltFromId(id) {
  return ((String(id || "")
    .split("")
    .reduce((total, char) => total + char.charCodeAt(0), 0) %
    7) -
    3) *
    0.5;
}

function buildTicketReport(ticket, columns, nowMs) {
  const stageReports = columns.map((column) => {
    const actualMs = getColumnActualMs(ticket, column.id, nowMs);
    const ratio = getHeatRatio(actualMs, column.targetDays);
    const hasHistory = ticket.moveHistory.some((entry) => entry.columnId === column.id);

    return {
      columnId: column.id,
      label: column.label,
      targetDays: column.targetDays,
      actualMs,
      actualText: formatDuration(actualMs),
      ratio,
      heatLabel: getHeatLabel(ratio),
      heatColor: getHeatColor(ratio),
      heatGlow: getHeatGlow(ratio),
      grade: getGradeFromRatio(ratio, hasHistory)
    };
  });

  const totalActualMs = stageReports.reduce((total, stage) => total + stage.actualMs, 0);
  const totalTargetMs = getTargetMs(ticket.projectTargetDays);
  const overallRatio = totalTargetMs ? totalActualMs / totalTargetMs : 0;
  const currentStage = stageReports.find((stage) => stage.columnId === ticket.columnId) || stageReports[0];

  return {
    currentStage,
    stageReports,
    totalActualMs,
    totalActualText: formatDuration(totalActualMs),
    totalTargetMs,
    totalTargetText: formatDuration(totalTargetMs),
    overallRatio,
    overallGrade: getGradeFromRatio(overallRatio, true),
    verdict: getOverallVerdict(overallRatio)
  };
}

function getColumnSnapshot(tickets, column, nowMs) {
  const liveTickets = tickets.filter((ticket) => ticket.columnId === column.id);
  const hottestRatio = liveTickets.reduce((highest, ticket) => {
    const actualMs = getColumnActualMs(ticket, column.id, nowMs);
    return Math.max(highest, getHeatRatio(actualMs, column.targetDays));
  }, 0);
  const burntCount = liveTickets.filter((ticket) => {
    const actualMs = getColumnActualMs(ticket, column.id, nowMs);
    return getHeatRatio(actualMs, column.targetDays) >= 1.5;
  }).length;

  return {
    liveTickets,
    hottestRatio,
    burntCount,
    heatLabel: liveTickets.length ? getHeatLabel(hottestRatio) : "Idle",
    heatColor: liveTickets.length ? getHeatColor(hottestRatio) : "hsl(200deg 22% 88%)"
  };
}

function TicketCard({
  ticket,
  column,
  columnIndex,
  columns,
  nowMs,
  onMoveByOffset,
  onOpen,
  onDragStart,
  onDragEnd
}) {
  const report = buildTicketReport(ticket, columns, nowMs);
  const leftDisabled = columnIndex === 0;
  const rightDisabled = columnIndex === columns.length - 1;

  return (
    <article
      className="ticket-note"
      draggable
      onDragStart={(event) => onDragStart(event, ticket.id)}
      onDragEnd={onDragEnd}
      onDoubleClick={() => onOpen(ticket.id)}
      title="Double-click to open ticket details and history."
      style={{
        "--note-glow": report.currentStage.heatGlow,
        "--note-paper": report.currentStage.heatColor,
        "--note-tilt": `${getTiltFromId(ticket.id)}deg`
      }}
    >
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="mono-ui text-[11px] uppercase tracking-[0.18em] text-slate-700">
            {ticket.code}
          </div>
          <h3 className="mt-2 text-base font-semibold leading-5 text-slate-950">{ticket.title}</h3>
        </div>
        <span className="heat-pill" style={{ backgroundColor: report.currentStage.heatColor }}>
          {report.currentStage.heatLabel}
        </span>
      </div>

      <div className="mt-3 flex flex-wrap gap-2 text-[11px] uppercase tracking-[0.14em] text-slate-700">
        <span>{ticket.owner || "Unassigned"}</span>
        <span>Target {ticket.projectTargetDays}d</span>
      </div>

      <p className="mt-3 line-clamp-3 text-sm leading-6 text-slate-800">
        {ticket.details || "Double-click this sticky to add more project detail."}
      </p>

      <div className="mt-4 rounded-2xl bg-white/70 px-3 py-3">
        <div className="flex items-center justify-between gap-3 text-xs text-slate-700">
          <span>{report.currentStage.actualText} in {column.label}</span>
          <span>{Math.round(report.currentStage.ratio * 100)}%</span>
        </div>
        <div className="heat-track mt-2">
          <div
            className="heat-fill"
            style={{
              width: `${Math.min(report.currentStage.ratio * 100, 100)}%`,
              background: `linear-gradient(90deg, ${getHeatColor(0.15)}, ${report.currentStage.heatColor})`
            }}
          />
        </div>
      </div>

      <div className="mt-4 flex items-center justify-between gap-2">
        <div className="flex gap-2">
          <button
            type="button"
            className="ticket-button"
            onClick={(event) => {
              event.stopPropagation();
              onMoveByOffset(ticket.id, -1);
            }}
            disabled={leftDisabled}
          >
            Left
          </button>
          <button
            type="button"
            className="ticket-button"
            onClick={(event) => {
              event.stopPropagation();
              onMoveByOffset(ticket.id, 1);
            }}
            disabled={rightDisabled}
          >
            Right
          </button>
        </div>

        <button
          type="button"
          className="ticket-button ticket-button-strong"
          onClick={(event) => {
            event.stopPropagation();
            onOpen(ticket.id);
          }}
        >
          Open
        </button>
      </div>
    </article>
  );
}

function ReportCard({ ticket, columns, nowMs, onOpen }) {
  const report = buildTicketReport(ticket, columns, nowMs);

  return (
    <button type="button" className="report-card" onClick={() => onOpen(ticket.id)}>
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="mono-ui text-[11px] uppercase tracking-[0.18em] text-slate-600">
            {ticket.code}
          </div>
          <h4 className="mt-1 text-sm font-semibold text-slate-950">{ticket.title}</h4>
        </div>
        <span className="grade-pill">{report.overallGrade}</span>
      </div>

      <p className="mt-2 text-xs uppercase tracking-[0.14em] text-slate-600">
        {report.verdict}
      </p>

      <div className="mt-3 flex flex-wrap gap-2">
        {report.stageReports.map((stage) => (
          <span
            key={`${ticket.id}-${stage.columnId}`}
            className="stage-grade"
            style={{
              borderColor: stage.heatColor,
              backgroundColor: stage.heatGlow
            }}
          >
            {stage.label}: {stage.grade}
          </span>
        ))}
      </div>

      <div className="mt-3 flex items-center justify-between gap-3 text-xs text-slate-700">
        <span>{report.currentStage.label}</span>
        <span>{report.totalActualText} / {report.totalTargetText}</span>
      </div>
    </button>
  );
}

function TicketModal({
  ticket,
  columns,
  rows,
  nowMs,
  onClose,
  onUpdateTicketField,
  onMoveTicketToColumn
}) {
  if (!ticket) {
    return null;
  }

  const report = buildTicketReport(ticket, columns, nowMs);

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div
        className="modal-panel"
        onClick={(event) => event.stopPropagation()}
        role="dialog"
        aria-modal="true"
      >
        <div className="flex items-start justify-between gap-4">
          <div>
            <div className="mono-ui text-[11px] uppercase tracking-[0.18em] text-slate-500">
              {ticket.code}
            </div>
            <h2 className="mt-2 text-3xl font-semibold tracking-tight text-slate-950">{ticket.title}</h2>
            <p className="mt-2 text-sm text-slate-600">{report.verdict}</p>
          </div>

          <button type="button" className="close-button" onClick={onClose}>
            Close
          </button>
        </div>

        <div className="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1fr)_320px]">
          <section className="space-y-5">
            <div className="panel-card">
              <div className="grid gap-4 md:grid-cols-2">
                <label className="field">
                  <span className="field-label">Ticket title</span>
                  <input
                    value={ticket.title}
                    onChange={(event) => onUpdateTicketField(ticket.id, "title", event.target.value)}
                  />
                </label>

                <label className="field">
                  <span className="field-label">Owner</span>
                  <input
                    value={ticket.owner}
                    onChange={(event) => onUpdateTicketField(ticket.id, "owner", event.target.value)}
                  />
                </label>
              </div>

              <div className="mt-4 grid gap-4 md:grid-cols-3">
                <label className="field">
                  <span className="field-label">Project type</span>
                  <select
                    value={ticket.rowId}
                    onChange={(event) => onUpdateTicketField(ticket.id, "rowId", event.target.value)}
                  >
                    {rows.map((row) => (
                      <option key={row.id} value={row.id}>
                        {row.label}
                      </option>
                    ))}
                  </select>
                </label>

                <label className="field">
                  <span className="field-label">Current stage</span>
                  <select
                    value={ticket.columnId}
                    onChange={(event) => onMoveTicketToColumn(ticket.id, event.target.value)}
                  >
                    {columns.map((column) => (
                      <option key={column.id} value={column.id}>
                        {column.label}
                      </option>
                    ))}
                  </select>
                </label>

                <label className="field">
                  <span className="field-label">Timeline target (days)</span>
                  <input
                    type="number"
                    min="1"
                    step="1"
                    value={ticket.projectTargetDays}
                    onChange={(event) =>
                      onUpdateTicketField(
                        ticket.id,
                        "projectTargetDays",
                        clamp(safeNumber(event.target.value, ticket.projectTargetDays), 1, 365)
                      )
                    }
                  />
                </label>
              </div>

              <label className="field mt-4">
                <span className="field-label">Project details</span>
                <textarea
                  rows={6}
                  value={ticket.details}
                  onChange={(event) => onUpdateTicketField(ticket.id, "details", event.target.value)}
                />
              </label>
            </div>

            <div className="panel-card">
              <div className="flex items-center justify-between gap-3">
                <div>
                  <h3 className="text-lg font-semibold text-slate-950">Move history</h3>
                  <p className="text-sm text-slate-600">
                    Every stage change is stamped automatically when the ticket moves.
                  </p>
                </div>
                <div className="mono-ui text-xs text-slate-500">
                  Created {formatDateTime(ticket.createdAt)}
                </div>
              </div>

              <div className="mt-4 space-y-3">
                {ticket.moveHistory.map((entry, index) => {
                  const column = columns.find((candidate) => candidate.id === entry.columnId);
                  const actualMs = getHistoryDurationMs(entry, nowMs);
                  const ratio = getHeatRatio(actualMs, column ? column.targetDays : 1);
                  const hasLeft = !!entry.leftAt;

                  return (
                    <div key={`${ticket.id}-history-${index}`} className="timeline-entry">
                      <div className="flex items-center justify-between gap-3">
                        <div>
                          <div className="text-sm font-semibold text-slate-900">
                            {column ? column.label : "Unknown stage"}
                          </div>
                          <div className="mt-1 text-xs text-slate-600">
                            Entered {formatDateTime(entry.enteredAt)}
                            {hasLeft ? ` and left ${formatDateTime(entry.leftAt)}` : " and is still active"}
                          </div>
                        </div>
                        <span className="heat-pill" style={{ backgroundColor: getHeatColor(ratio) }}>
                          {getHeatLabel(ratio)}
                        </span>
                      </div>
                      <div className="mt-3 flex items-center justify-between gap-3 text-xs text-slate-700">
                        <span>{formatDuration(actualMs)} spent here</span>
                        <span>Stage grade {getGradeFromRatio(ratio, true)}</span>
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          </section>

          <aside className="space-y-5">
            <div className="panel-card">
              <h3 className="text-lg font-semibold text-slate-950">Timeline scorecard</h3>
              <div className="mt-4 space-y-3">
                {report.stageReports.map((stage) => (
                  <div key={`${ticket.id}-stage-${stage.columnId}`} className="score-row">
                    <div className="flex items-center justify-between gap-3">
                      <div className="text-sm font-medium text-slate-900">{stage.label}</div>
                      <div className="text-xs uppercase tracking-[0.14em] text-slate-600">
                        Grade {stage.grade}
                      </div>
                    </div>
                    <div className="mt-2 flex items-center justify-between gap-3 text-xs text-slate-700">
                      <span>{stage.actualText}</span>
                      <span>Target {stage.targetDays}d</span>
                    </div>
                    <div className="heat-track mt-2">
                      <div
                        className="heat-fill"
                        style={{
                          width: `${Math.min(stage.ratio * 100, 100)}%`,
                          background: `linear-gradient(90deg, ${getHeatColor(0.15)}, ${stage.heatColor})`
                        }}
                      />
                    </div>
                  </div>
                ))}
              </div>
            </div>

            <div className="panel-card">
              <div className="text-[11px] uppercase tracking-[0.18em] text-slate-500">Overall cook</div>
              <div className="mt-2 text-4xl font-semibold text-slate-950">{report.overallGrade}</div>
              <div className="mt-2 text-sm text-slate-600">{report.verdict}</div>

              <div className="mt-4 rounded-3xl bg-slate-950 px-4 py-4 text-slate-100">
                <div className="text-xs uppercase tracking-[0.16em] text-slate-400">Project timeline</div>
                <div className="mt-3 flex items-center justify-between gap-3 text-sm">
                  <span>Actual</span>
                  <span>{report.totalActualText}</span>
                </div>
                <div className="mt-2 flex items-center justify-between gap-3 text-sm">
                  <span>Target</span>
                  <span>{report.totalTargetText}</span>
                </div>
                <div className="mt-2 flex items-center justify-between gap-3 text-sm">
                  <span>Updated</span>
                  <span>{formatDateTime(ticket.updatedAt)}</span>
                </div>
              </div>
            </div>
          </aside>
        </div>
      </div>
    </div>
  );
}

function App() {
  const fileInputRef = useRef(null);
  const [state, setState] = useState(loadInitialState);
  const [status, setStatus] = useState("Kitchen board ready. Add a ticket and move it through the stages.");
  const [selectedTicketId, setSelectedTicketId] = useState(null);
  const [dragTicketId, setDragTicketId] = useState(null);
  const [clock, setClock] = useState(Date.now());
  const [draftTicket, setDraftTicket] = useState(() => {
    const initialState = loadInitialState();
    return createTicketDraft(initialState.rows[0].id, initialState.kitchenTargetDays);
  });

  useEffect(() => {
    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  }, [state]);

  useEffect(() => {
    document.title = `${state.boardName} | Project Kitchen Board`;
  }, [state.boardName]);

  useEffect(() => {
    const timer = window.setInterval(() => setClock(Date.now()), 30 * 1000);
    return () => window.clearInterval(timer);
  }, []);

  useEffect(() => {
    if (!state.rows.some((row) => row.id === draftTicket.rowId)) {
      setDraftTicket((current) => ({
        ...current,
        rowId: state.rows[0].id
      }));
    }
  }, [draftTicket.rowId, state.rows]);

  useEffect(() => {
    if (selectedTicketId && !state.tickets.some((ticket) => ticket.id === selectedTicketId)) {
      setSelectedTicketId(null);
    }
  }, [selectedTicketId, state.tickets]);

  const nowMs = clock;
  const selectedTicket = state.tickets.find((ticket) => ticket.id === selectedTicketId) || null;
  const gridTemplateColumns = `240px repeat(${state.columns.length}, minmax(280px, 1fr)) 340px`;
  const totalTickets = state.tickets.length;
  const activeHotTickets = state.tickets.filter((ticket) => {
    const column = state.columns.find((candidate) => candidate.id === ticket.columnId);
    if (!column) {
      return false;
    }
    const actualMs = getColumnActualMs(ticket, column.id, nowMs);
    return getHeatRatio(actualMs, column.targetDays) >= 1.2;
  }).length;
  const burntTickets = state.tickets.filter((ticket) => {
    const column = state.columns.find((candidate) => candidate.id === ticket.columnId);
    if (!column) {
      return false;
    }
    const actualMs = getColumnActualMs(ticket, column.id, nowMs);
    return getHeatRatio(actualMs, column.targetDays) >= 1.5;
  }).length;

  function updateBoardField(field, value) {
    setState((current) => ({
      ...current,
      [field]: value
    }));
  }

  function handleDraftChange(field, value) {
    setDraftTicket((current) => ({
      ...current,
      [field]: value
    }));
  }

  function handleKitchenTargetChange(value) {
    const nextValue = clamp(safeNumber(value, state.kitchenTargetDays), 1, 365);
    updateBoardField("kitchenTargetDays", nextValue);
    setDraftTicket((current) => ({
      ...current,
      projectTargetDays: nextValue
    }));
  }

  function addTicket() {
    if (!draftTicket.title.trim()) {
      setStatus("Add a ticket name before dropping a new sticky onto the board.");
      return;
    }

    setState((current) => {
      const createdAt = nowIso();
      const code = `PT-${current.lastTicketNumber}`;
      const firstColumnId = current.columns[0].id;
      const nextTicket = {
        id: uid("ticket"),
        code,
        title: draftTicket.title.trim(),
        owner: draftTicket.owner.trim() || "Unassigned",
        rowId: draftTicket.rowId,
        columnId: firstColumnId,
        projectTargetDays: clamp(safeNumber(draftTicket.projectTargetDays, 12), 1, 365),
        details: draftTicket.details.trim(),
        createdAt,
        enteredColumnAt: createdAt,
        updatedAt: createdAt,
        moveHistory: [{ columnId: firstColumnId, enteredAt: createdAt, leftAt: null }]
      };

      return {
        ...current,
        lastTicketNumber: current.lastTicketNumber + 1,
        tickets: [...current.tickets, nextTicket]
      };
    });

    setDraftTicket(createTicketDraft(state.rows[0].id, state.kitchenTargetDays));
    setStatus("New project ticket added to the left-most sticky stage.");
  }

  function addColumn() {
    setState((current) => ({
      ...current,
      columns: [
        ...current.columns,
        {
          id: uid("column"),
          label: `New Stage ${current.columns.length + 1}`,
          targetDays: 3
        }
      ]
    }));
    setStatus("Added a new timeline stage.");
  }

  function addRow() {
    const nextRowId = uid("row");
    setState((current) => ({
      ...current,
      rows: [
        ...current.rows,
        {
          id: nextRowId,
          label: `Project Type ${current.rows.length + 1}`
        }
      ]
    }));
    setDraftTicket((current) => ({
      ...current,
      rowId: nextRowId
    }));
    setStatus("Added a new project type row.");
  }

  function updateColumnField(columnId, field, value) {
    setState((current) => ({
      ...current,
      columns: current.columns.map((column) =>
        column.id === columnId
          ? {
              ...column,
              [field]: field === "targetDays" ? clamp(safeNumber(value, column.targetDays), 0.5, 365) : value
            }
          : column
      )
    }));
  }

  function updateRowField(rowId, value) {
    setState((current) => ({
      ...current,
      rows: current.rows.map((row) => (row.id === rowId ? { ...row, label: value } : row))
    }));
  }

  function relocateTicket(ticket, nextRowId, nextColumnId) {
    const movedAt = nowIso();
    const sameColumn = ticket.columnId === nextColumnId;
    const nextHistory = sameColumn
      ? ticket.moveHistory
      : [
          ...ticket.moveHistory.map((entry, index, entries) =>
            index === entries.length - 1 && !entry.leftAt ? { ...entry, leftAt: movedAt } : entry
          ),
          { columnId: nextColumnId, enteredAt: movedAt, leftAt: null }
        ];

    return {
      ...ticket,
      rowId: nextRowId,
      columnId: nextColumnId,
      enteredColumnAt: sameColumn ? ticket.enteredColumnAt : movedAt,
      updatedAt: movedAt,
      moveHistory: nextHistory
    };
  }

  function moveTicket(ticketId, nextRowId, nextColumnId) {
    const currentTicket = state.tickets.find((ticket) => ticket.id === ticketId);
    if (!currentTicket) {
      return;
    }

    if (currentTicket.rowId === nextRowId && currentTicket.columnId === nextColumnId) {
      return;
    }

    setState((current) => ({
      ...current,
      tickets: current.tickets.map((ticket) => {
        if (ticket.id !== ticketId) {
          return ticket;
        }
        return relocateTicket(ticket, nextRowId, nextColumnId);
      })
    }));

    setStatus("Ticket moved and the heat timer was restamped.");
  }

  function moveTicketToColumn(ticketId, nextColumnId) {
    const ticket = state.tickets.find((candidate) => candidate.id === ticketId);
    if (!ticket) {
      return;
    }
    moveTicket(ticketId, ticket.rowId, nextColumnId);
  }

  function moveTicketByOffset(ticketId, offset) {
    const ticket = state.tickets.find((candidate) => candidate.id === ticketId);
    if (!ticket) {
      return;
    }

    const columnIndex = state.columns.findIndex((column) => column.id === ticket.columnId);
    const nextColumn = state.columns[columnIndex + offset];
    if (!nextColumn) {
      return;
    }

    moveTicket(ticketId, ticket.rowId, nextColumn.id);
  }

  function updateTicketField(ticketId, field, value) {
    setState((current) => ({
      ...current,
      tickets: current.tickets.map((ticket) =>
        ticket.id === ticketId
          ? {
              ...ticket,
              [field]: value,
              updatedAt: nowIso()
            }
          : ticket
      )
    }));
  }

  function getTicketsForCell(rowId, columnId) {
    return state.tickets
      .filter((ticket) => ticket.rowId === rowId && ticket.columnId === columnId)
      .sort((left, right) => {
        const leftColumn = state.columns.find((column) => column.id === left.columnId);
        const rightColumn = state.columns.find((column) => column.id === right.columnId);
        const leftRatio = leftColumn
          ? getHeatRatio(getColumnActualMs(left, leftColumn.id, nowMs), leftColumn.targetDays)
          : 0;
        const rightRatio = rightColumn
          ? getHeatRatio(getColumnActualMs(right, rightColumn.id, nowMs), rightColumn.targetDays)
          : 0;
        return rightRatio - leftRatio;
      });
  }

  function getTicketsForRow(rowId) {
    return state.tickets
      .filter((ticket) => ticket.rowId === rowId)
      .sort((left, right) => {
        const leftReport = buildTicketReport(left, state.columns, nowMs);
        const rightReport = buildTicketReport(right, state.columns, nowMs);
        return rightReport.overallRatio - leftReport.overallRatio;
      });
  }

  async function handleLoadClick(event) {
    const file = event.target.files && event.target.files[0];
    if (!file) {
      return;
    }

    try {
      const parsed = await readJsonFile(file);
      const nextState = normalizeState(parsed);
      setState(nextState);
      setDraftTicket(createTicketDraft(nextState.rows[0].id, nextState.kitchenTargetDays));
      setStatus(`Loaded a board snapshot from ${file.name}.`);
    } catch (error) {
      setStatus(`Could not load ${file.name}: ${error.message}`);
    } finally {
      event.target.value = "";
    }
  }

  function handleSave() {
    downloadJson("project-kitchen-board.json", state);
    setStatus("Saved the board as a JSON snapshot.");
  }

  function handleReset() {
    if (!window.confirm("Reset the board back to the sample kitchen layout?")) {
      return;
    }

    const nextState = createDefaultState();
    setState(nextState);
    setDraftTicket(createTicketDraft(nextState.rows[0].id, nextState.kitchenTargetDays));
    window.localStorage.removeItem(STORAGE_KEY);
    setStatus("Reset the board to the sample project kitchen.");
  }

  function handleDragStart(event, ticketId) {
    event.dataTransfer.setData("text/plain", ticketId);
    event.dataTransfer.effectAllowed = "move";
    setDragTicketId(ticketId);
  }

  function handleDragEnd() {
    setDragTicketId(null);
  }

  function handleCellDrop(event, rowId, columnId) {
    event.preventDefault();
    const ticketId = event.dataTransfer.getData("text/plain") || dragTicketId;
    setDragTicketId(null);
    if (!ticketId) {
      return;
    }
    moveTicket(ticketId, rowId, columnId);
  }

  return (
    <div className="min-h-screen px-4 py-5 md:px-6 lg:px-8">
      <div className="mx-auto max-w-[1800px]">
        <section className="hero-panel overflow-hidden rounded-[34px] p-5 md:p-8">
          <div className="grid gap-6 xl:grid-cols-[minmax(0,1.25fr)_420px] xl:items-start">
            <div>
              <div className="eyebrow">Heat-mapped project tracker</div>
              <h1 className="mt-4 max-w-4xl text-4xl font-semibold tracking-tight text-slate-950 md:text-6xl">
                {state.boardName}
              </h1>
              <p className="mt-4 max-w-3xl text-base leading-7 text-slate-700 md:text-lg">
                Create project tickets as sticky notes, move them across timeline stages, and let the
                board show which work is cooling smoothly or burning out in place.
              </p>

              <div className="mt-6 grid gap-3 sm:grid-cols-3">
                <div className="hero-stat">
                  <div className="hero-stat-label">Open tickets</div>
                  <div className="hero-stat-value">{totalTickets}</div>
                </div>
                <div className="hero-stat">
                  <div className="hero-stat-label">Heating up</div>
                  <div className="hero-stat-value">{activeHotTickets}</div>
                </div>
                <div className="hero-stat">
                  <div className="hero-stat-label">Burnt blockers</div>
                  <div className="hero-stat-value">{burntTickets}</div>
                </div>
              </div>

              <div className="mt-6 flex flex-wrap gap-3">
                <button className="action-button action-button-strong" onClick={handleSave}>
                  Save JSON
                </button>
                <button
                  className="action-button"
                  onClick={() => fileInputRef.current && fileInputRef.current.click()}
                >
                  Load JSON
                </button>
                <button className="action-button" onClick={addColumn}>
                  Add stage
                </button>
                <button className="action-button" onClick={addRow}>
                  Add project type
                </button>
                <button className="action-button action-button-danger" onClick={handleReset}>
                  Reset board
                </button>
                <input
                  ref={fileInputRef}
                  type="file"
                  accept=".json,application/json"
                  className="hidden"
                  onChange={handleLoadClick}
                />
              </div>
            </div>

            <div className="panel-card">
              <h2 className="text-2xl font-semibold tracking-tight text-slate-950">Drop a new sticky</h2>
              <p className="mt-2 text-sm leading-6 text-slate-600">
                New tickets always land in the left-most column, then the heat timer starts ticking.
              </p>

              <div className="mt-5 space-y-4">
                <label className="field">
                  <span className="field-label">Board title</span>
                  <input
                    value={state.boardName}
                    onChange={(event) => updateBoardField("boardName", event.target.value)}
                  />
                </label>

                <label className="field">
                  <span className="field-label">Ticket title</span>
                  <input
                    value={draftTicket.title}
                    placeholder="Example: Branch permit close-out"
                    onChange={(event) => handleDraftChange("title", event.target.value)}
                  />
                </label>

                <div className="grid gap-4 md:grid-cols-2">
                  <label className="field">
                    <span className="field-label">Owner</span>
                    <input
                      value={draftTicket.owner}
                      placeholder="Who is cooking this?"
                      onChange={(event) => handleDraftChange("owner", event.target.value)}
                    />
                  </label>

                  <label className="field">
                    <span className="field-label">Project type row</span>
                    <select
                      value={draftTicket.rowId}
                      onChange={(event) => handleDraftChange("rowId", event.target.value)}
                    >
                      {state.rows.map((row) => (
                        <option key={row.id} value={row.id}>
                          {row.label}
                        </option>
                      ))}
                    </select>
                  </label>
                </div>

                <div className="grid gap-4 md:grid-cols-2">
                  <label className="field">
                    <span className="field-label">Project target (days)</span>
                    <input
                      type="number"
                      min="1"
                      step="1"
                      value={draftTicket.projectTargetDays}
                      onChange={(event) =>
                        handleDraftChange(
                          "projectTargetDays",
                          clamp(safeNumber(event.target.value, draftTicket.projectTargetDays), 1, 365)
                        )
                      }
                    />
                  </label>

                  <label className="field">
                    <span className="field-label">Kitchen target (days)</span>
                    <input
                      type="number"
                      min="1"
                      step="1"
                      value={state.kitchenTargetDays}
                      onChange={(event) => handleKitchenTargetChange(event.target.value)}
                    />
                  </label>
                </div>

                <label className="field">
                  <span className="field-label">Project notes</span>
                  <textarea
                    rows={4}
                    value={draftTicket.details}
                    placeholder="What makes this ticket risky, blocked, or time-sensitive?"
                    onChange={(event) => handleDraftChange("details", event.target.value)}
                  />
                </label>

                <button className="action-button action-button-strong w-full" onClick={addTicket}>
                  Create sticky ticket
                </button>
              </div>
            </div>
          </div>
        </section>

        <section className="mt-5 rounded-[30px] bg-white/72 p-4 shadow-[0_25px_80px_rgba(15,23,42,0.12)] ring-1 ring-slate-950/6 md:p-5">
          <div className="flex flex-wrap items-center gap-3 text-sm text-slate-700">
            <span className="rounded-full bg-slate-950 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.16em] text-white">
              Double-click a sticky for details
            </span>
            <span>Drag tickets between cells or use the Left and Right buttons.</span>
            <span>The fixed report column grades every project against the stage targets.</span>
          </div>
        </section>

        <section className="mt-5">
          <div className="overflow-x-auto pb-3">
            <div className="board-shell">
              <div className="board-grid" style={{ gridTemplateColumns }}>
                <div className="header-card header-card-corner">
                  <div className="text-xs uppercase tracking-[0.18em] text-slate-500">Project types</div>
                  <div className="mt-2 text-xl font-semibold text-slate-950">Rows</div>
                  <div className="mt-2 text-sm text-slate-600">Add more lanes as your portfolio expands.</div>
                </div>

                {state.columns.map((column) => {
                  const snapshot = getColumnSnapshot(state.tickets, column, nowMs);
                  return (
                    <div key={column.id} className="header-card">
                      <label className="field">
                        <span className="field-label">Column label</span>
                        <input
                          value={column.label}
                          onChange={(event) => updateColumnField(column.id, "label", event.target.value)}
                        />
                      </label>

                      <div className="mt-4 flex items-end justify-between gap-3">
                        <label className="field field-compact">
                          <span className="field-label">Target days</span>
                          <input
                            type="number"
                            min="0.5"
                            step="0.5"
                            value={column.targetDays}
                            onChange={(event) =>
                              updateColumnField(column.id, "targetDays", event.target.value)
                            }
                          />
                        </label>

                        <div className="text-right">
                          <div className="text-[11px] uppercase tracking-[0.14em] text-slate-500">
                            Heat timer
                          </div>
                          <div className="mt-1 text-lg font-semibold text-slate-950">
                            {snapshot.heatLabel}
                          </div>
                        </div>
                      </div>

                      <div className="heat-track mt-4">
                        <div
                          className="heat-fill"
                          style={{
                            width: `${Math.min(snapshot.hottestRatio * 100, 100)}%`,
                            background: `linear-gradient(90deg, ${getHeatColor(0.15)}, ${snapshot.heatColor})`
                          }}
                        />
                      </div>

                      <div className="mt-3 flex items-center justify-between gap-3 text-xs text-slate-700">
                        <span>{snapshot.liveTickets.length} live</span>
                        <span>{snapshot.burntCount} burnt</span>
                      </div>
                    </div>
                  );
                })}

                <div className="header-card report-header">
                  <div className="text-xs uppercase tracking-[0.18em] text-slate-500">Final column</div>
                  <div className="mt-2 text-xl font-semibold text-slate-950">Report Card</div>
                  <div className="mt-2 text-sm text-slate-600">
                    Each card grades how every stage handled the ticket timeline.
                  </div>
                </div>
              </div>

              {state.rows.map((row) => {
                const rowTickets = getTicketsForRow(row.id);

                return (
                  <div key={row.id} className="board-grid mt-4" style={{ gridTemplateColumns }}>
                    <div className="row-card">
                      <label className="field">
                        <span className="field-label">Project type label</span>
                        <input
                          value={row.label}
                          onChange={(event) => updateRowField(row.id, event.target.value)}
                        />
                      </label>

                      <div className="mt-4 text-sm text-slate-700">
                        {rowTickets.length} ticket{rowTickets.length === 1 ? "" : "s"} in this lane
                      </div>
                    </div>

                    {state.columns.map((column) => {
                      const cellTickets = getTicketsForCell(row.id, column.id);
                      const isDropTarget = dragTicketId && cellTickets.every((ticket) => ticket.id !== dragTicketId);

                      return (
                        <div
                          key={`${row.id}-${column.id}`}
                          className={isDropTarget ? "drop-cell drop-cell-active" : "drop-cell"}
                          onDragOver={(event) => event.preventDefault()}
                          onDrop={(event) => handleCellDrop(event, row.id, column.id)}
                        >
                          {cellTickets.length ? (
                            <div className="space-y-4">
                              {cellTickets.map((ticket) => (
                                <TicketCard
                                  key={ticket.id}
                                  ticket={ticket}
                                  column={column}
                                  columnIndex={state.columns.findIndex((candidate) => candidate.id === column.id)}
                                  columns={state.columns}
                                  nowMs={nowMs}
                                  onMoveByOffset={moveTicketByOffset}
                                  onOpen={setSelectedTicketId}
                                  onDragStart={handleDragStart}
                                  onDragEnd={handleDragEnd}
                                />
                              ))}
                            </div>
                          ) : (
                            <div className="empty-cell">
                              Drop a ticket here
                            </div>
                          )}
                        </div>
                      );
                    })}

                    <div className="report-lane">
                      {rowTickets.length ? (
                        <div className="space-y-3">
                          {rowTickets.map((ticket) => (
                            <ReportCard
                              key={`${ticket.id}-report`}
                              ticket={ticket}
                              columns={state.columns}
                              nowMs={nowMs}
                              onOpen={setSelectedTicketId}
                            />
                          ))}
                        </div>
                      ) : (
                        <div className="empty-cell">No projects in this row yet.</div>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </section>

        <section className="mt-5 rounded-[28px] bg-slate-950 px-5 py-4 text-slate-100 shadow-[0_25px_80px_rgba(15,23,42,0.2)]">
          <div className="text-[11px] uppercase tracking-[0.18em] text-slate-400">Board status</div>
          <div className="mt-2 text-sm leading-7">{status}</div>
          <div className="mono-ui mt-3 text-xs leading-6 text-slate-400">
            Storage key: {STORAGE_KEY}
          </div>
        </section>
      </div>

      <TicketModal
        ticket={selectedTicket}
        columns={state.columns}
        rows={state.rows}
        nowMs={nowMs}
        onClose={() => setSelectedTicketId(null)}
        onUpdateTicketField={updateTicketField}
        onMoveTicketToColumn={moveTicketToColumn}
      />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
