import { gitBranchCurrent, gitCommitLog, gitBranchList, gitCommitReflog, gitCommitGraphReact } from "@/client";
import { RepoHeader } from "@/components/repository/header";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useQuery } from "@tanstack/react-query";
import { formatWithGlobalTimezone } from "@/lib/timezone";
import { toast } from "sonner";
import { Calendar, Check, Copy, GitBranch, GitCommit, GitGraph, Loader2, Search } from "lucide-react";
import { useMemo, useState, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import type { BranchInfoResponse, CommitMetaResponse, CommitGraphReactCommit, LaneInfo } from "@/client";


const getRelativeTime = (dateString: string | number | null | undefined) => {
  if (!dateString) return "";
  const date = new Date(dateString);
  const now = new Date();
  const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
  if (diffInSeconds < 60) return "just now";
  if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
  if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
  if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
  if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)} months ago`;
  return `${Math.floor(diffInSeconds / 31536000)} years ago`;
};

const getCommitMessageLines = (message: string | null | undefined) => {
  if (!message) return { title: "No message", description: "" };
  const lines = message.split("\n");
  return {
    title: lines[0] || "No message",
    description: lines.slice(1).join("\n").trim(),
  };
};

const copyToClipboard = async (text: string, sha: string) => {
  try {
    await navigator.clipboard.writeText(text);
    toast.success("SHA copied to clipboard");
    return sha;
  } catch {
    toast.error("Failed to copy");
    return null;
  }
};

export const RepoCommits = () => {
  const { namespace, repoName } = useParams<{ namespace: string; repoName: string }>();
  const navigate = useNavigate();
  const [searchQuery, setSearchQuery] = useState("");
  const [selectedBranch, setSelectedBranch] = useState("");
  const [copiedSha, setCopiedSha] = useState<string | null>(null);
  const [showBranchDropdown, setShowBranchDropdown] = useState(false);
  const [activeTab, setActiveTab] = useState<"list" | "graph" | "reflog">("list");

  const { data: branches = [] } = useQuery({
    queryKey: ["repo-branches", namespace, repoName],
    queryFn: async () => {
      if (!namespace || !repoName) return [];
      const resp = await gitBranchList({ path: { namespace, repo: repoName } });
      return (resp.data?.data as Array<BranchInfoResponse>) ?? [];
    },
    enabled: !!namespace && !!repoName,
    staleTime: 60 * 1000,
  });

  const branchOptions = branches as BranchInfoResponse[];

  const { data: commitsPageData, isLoading, isFetching, error } = useQuery({
    queryKey: ["repo-commits", namespace, repoName],
    queryFn: async () => {
      if (!namespace || !repoName) return null;
      const resp = await gitCommitLog({
        path: { namespace, repo: repoName },
      });
      return resp.data?.data ?? null;
    },
    enabled: !!namespace && !!repoName,
    staleTime: 30 * 1000,
  });

  const commits = (() => {
    const seen = new Set<string>();
    // commitsPageData is CommitLogResponse { data: CommitMetaResponse[], total, page, per_page }
    const data = commitsPageData?.data;
    return Array.isArray(data) ? data.filter((c) => {
      if (seen.has(c.oid)) return false;
      seen.add(c.oid);
      return true;
    }) : [];
  })();
  const totalCount = commitsPageData?.total ?? 0;

  const { data: currentBranchData } = useQuery({
    queryKey: ["repo-current-branch", namespace, repoName],
    queryFn: async () => {
      if (!namespace || !repoName) return null;
      const resp = await gitBranchCurrent({ path: { namespace, repo: repoName } });
      return (resp.data as BranchInfoResponse | undefined) ?? null;
    },
    enabled: !!namespace && !!repoName,
    staleTime: 30 * 1000,
  });

  // Fetch reflog for commit history
  const { data: reflogData } = useQuery({
    queryKey: ["repo-reflog", namespace, repoName],
    queryFn: async () => {
      if (!namespace || !repoName) return null;
      const resp = await gitCommitReflog({ path: { namespace, repo: repoName } });
      return (resp.data as unknown as Array<any>) ?? null;
    },
    enabled: !!namespace && !!repoName,
    staleTime: 60 * 1000,
  });

  // Fetch enriched graph data for @gitgraph/react
  const { data: graphReactData } = useQuery({
    queryKey: ["repo-commit-graph-react", namespace, repoName],
    queryFn: async () => {
      if (!namespace || !repoName) return null;
      const resp = await gitCommitGraphReact({ path: { namespace, repo: repoName } });
      const d = resp.data?.data;
      if (!d) return null;
      return {
        commits: d.commits as CommitGraphReactCommit[],
        lanes: d.lanes as LaneInfo[],
        max_parents: d.max_parents,
      };
    },
    enabled: !!namespace && !!repoName && activeTab === "graph",
    staleTime: 60 * 1000,
  });

  const filteredCommits = useMemo(() => {
    let result = commits as CommitMetaResponse[];
    // Deduplicate by oid to prevent React key warnings
    const seen = new Set<string>();
    result = result.filter((c) => {
      if (seen.has(c.oid)) return false;
      seen.add(c.oid);
      return true;
    });
    if (searchQuery.trim()) {
      const q = searchQuery.toLowerCase();
      result = result.filter(
        (c) =>
          c.message?.toLowerCase().includes(q) ||
          c.author?.name?.toLowerCase().includes(q) ||
          c.oid?.toLowerCase().includes(q),
      );
    }
    return result;
  }, [commits, searchQuery]);

  // Reset to page 1 when search changes
  const handleSearchChange = (value: string) => {
    setSearchQuery(value);
  };

  const handleCopySha = async (sha: string) => {
    const result = await copyToClipboard(sha, sha);
    if (result) {
      setCopiedSha(result);
      setTimeout(() => setCopiedSha(null), 2000);
    }
  };

  const defaultBranch = useMemo(() => {
    const headB = branchOptions.find((b: BranchInfoResponse) => b.is_head);
    return headB?.name?.replace("refs/heads/", "") || "main";
  }, [branchOptions]);

  const currentBranch = selectedBranch || defaultBranch;

  if (isLoading) {
    return (
      <div className="flex items-center justify-center h-64">
        <Loader2 className="h-6 w-6 animate-spin" />
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-6">
        <div className="rounded-md border border-red-300 bg-red-50 dark:bg-red-950/30 p-4">
          <h2 className="text-lg font-semibold text-red-600 dark:text-red-400 mb-2">Error loading commits</h2>
          <p className="text-sm text-red-500">{(error as Error).message}</p>
        </div>
      </div>
    );
  }

  return (
    <>
      <div className="p-6 pb-0 max-w-5xl mx-auto space-y-6">
        <RepoHeader />
      </div>
      <div className="p-6 pt-0 max-w-5xl mx-auto space-y-4">
        <div className="flex items-center justify-between py-4">
          <div className="flex items-center gap-2">
            <GitCommit className="h-5 w-5 text-muted-foreground" />
            <span className="text-lg font-semibold">Commits</span>
            <span className="text-sm text-muted-foreground">
              ({totalCount > 0 ? totalCount.toLocaleString() : filteredCommits.length})
            </span>
            {isFetching && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
          </div>
          {currentBranchData && (
            <div className="text-sm text-muted-foreground">
              on <span className="font-medium">{currentBranchData.name?.replace("refs/heads/", "")}</span>
            </div>
          )}
        </div>

        <div className="flex flex-wrap items-center gap-3 pb-3">
          <div className="inline-flex rounded-lg bg-muted p-[3px]">
            {(["list", "graph", "reflog"] as const).map((t) => (
              <button
                key={t}
                onClick={() => setActiveTab(t)}
                className={`px-3 py-1 text-sm rounded-md transition-colors ${
                  activeTab === t
                    ? "bg-background text-foreground shadow-sm"
                    : "text-muted-foreground hover:text-foreground"
                }`}
              >
                {t.charAt(0).toUpperCase() + t.slice(1)}
              </button>
            ))}
          </div>
          <div className="relative">
            <Button
              variant="outline"
              size="sm"
              onClick={() => setShowBranchDropdown(!showBranchDropdown)}
              className="w-full sm:w-48 justify-start"
            >
              <GitBranch className="h-4 w-4 mr-2" />
              <span className="truncate">{currentBranch}</span>
            </Button>

            {showBranchDropdown && (
              <div className="absolute left-0 top-full mt-1 w-48 bg-background border rounded-md shadow-lg z-10 max-h-60 overflow-y-auto">
                <button
                  onClick={() => {
                    setSelectedBranch("");
                    setShowBranchDropdown(false);
                  }}
                  className={`w-full px-3 py-2 text-left text-sm hover:bg-muted ${
                    selectedBranch === "" ? "bg-muted" : ""
                  }`}
                >
                  All branches
                </button>
                {branchOptions.map((b: BranchInfoResponse) => (
                  <button
                    key={b.name}
                    onClick={() => {
                      setSelectedBranch(b.name.replace("refs/heads/", ""));
                      setShowBranchDropdown(false);
                    }}
                    className={`w-full px-3 py-2 text-left text-sm hover:bg-muted flex items-center gap-2 ${
                      currentBranch === b.name.replace("refs/heads/", "") ? "bg-muted" : ""
                    }`}
                  >
                    <GitBranch className="h-4 w-4" />
                    <span className="truncate">{b.name.replace("refs/heads/", "")}</span>
                    {b.is_head && (
                      <span className="ml-auto text-xs text-muted-foreground">HEAD</span>
                    )}
                  </button>
                ))}
              </div>
            )}
          </div>

          {/* Search input */}
          <div className="relative flex-1 max-w-md">
            <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
            <Input
              placeholder="Search commits..."
              value={searchQuery}
              onChange={(e) => handleSearchChange(e.target.value)}
              className="pl-9"
            />
          </div>
        </div>

        {activeTab === "list" && (
          <div className="border rounded-lg bg-card">
            {filteredCommits.length === 0 ? (
              <div className="p-12 text-center">
                <GitCommit className="h-12 w-12 mx-auto text-muted-foreground/50 mb-4" />
                <h3 className="text-lg font-medium mb-2">
                  {searchQuery ? "No commits match your search" : "No commits found"}
                </h3>
                {searchQuery && (
                  <Button variant="outline" size="sm" onClick={() => setSearchQuery("")}>
                    Clear search
                  </Button>
                )}
              </div>
            ) : (
              <div className="divide-y">
                {filteredCommits.slice(0, 100).map((commit, index) => {
                  const { title, description } = getCommitMessageLines(commit.message);
                  const isCopied = copiedSha === commit.oid;
                  const isMergeCommit = commit.parent_ids?.length > 1;
                  const isLast = index === Math.min(filteredCommits.length, 100) - 1;

                  return (
                    <div
                      key={commit.oid}
                      className="group flex gap-3 p-4 hover:bg-muted/50 cursor-pointer transition-colors"
                      onClick={() => navigate(`/repository/${namespace}/${repoName}/commit/${commit.oid}`)}
                    >
                      <div className="flex flex-col items-center relative">
                        <div
                          className={`relative z-10 w-3 h-3 rounded-full border-2 border-background ${
                            isMergeCommit ? "bg-purple-500" : "bg-green-500"
                          }`}
                        />
                        {!isLast && (
                          <div className="w-0.5 bg-border absolute top-3 bottom-0" />
                        )}
                      </div>

                      {/* Avatar */}
                      <div className="h-8 w-8 flex-shrink-0 rounded-full bg-muted flex items-center justify-center">
                        <span className="text-xs font-semibold">
                          {(commit.author?.name || "U").charAt(0).toUpperCase()}
                        </span>
                      </div>

                      {/* Content */}
                      <div className="flex-1 min-w-0">
                        <div className="flex flex-wrap items-start gap-x-3 gap-y-1">
                          <span className="font-medium truncate">{title}</span>
                        </div>

                        {description && (
                          <p className="text-sm text-muted-foreground mt-1 line-clamp-2">
                            {description}
                          </p>
                        )}

                        <div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-2 text-sm text-muted-foreground">
                          <span className="font-medium">
                            {commit.author?.name || "Unknown"}
                          </span>
                          <span className="flex items-center gap-1" title={formatWithGlobalTimezone(commit.committer?.time_secs)}>
                            <Calendar className="h-3.5 w-3.5" />
                            {getRelativeTime(commit.committer?.time_secs)}
                          </span>
                        </div>
                      </div>

                      {/* SHA */}
                      <div className="flex items-start gap-1 flex-shrink-0">
                        <code
                          className={`px-2 py-1 rounded-md bg-muted font-mono text-xs border transition-colors cursor-pointer ${
                            isCopied
                              ? "border-green-500 bg-green-500/10 text-green-600"
                              : "hover:border-muted-foreground/50"
                          }`}
                          onClick={(e) => {
                            e.stopPropagation();
                            handleCopySha(commit.oid);
                          }}
                        >
                          {commit.oid.substring(0, 7)}
                        </code>
                        <button
                          className={`p-1 rounded hover:bg-muted transition-opacity ${
                            isCopied ? "opacity-100" : "opacity-0 group-hover:opacity-100"
                          }`}
                          onClick={(e) => {
                            e.stopPropagation();
                            handleCopySha(commit.oid);
                          }}
                        >
                          {isCopied ? (
                            <Check className="h-3.5 w-3.5 text-green-600" />
                          ) : (
                            <Copy className="h-3.5 w-3.5" />
                          )}
                        </button>
                      </div>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        )}

        {activeTab === "graph" && (
          <div className="border rounded-lg bg-card overflow-hidden">
            {!graphReactData?.commits || graphReactData.commits.length === 0 ? (
              <div className="text-center py-12 text-muted-foreground">
                <GitGraph className="h-12 w-12 mx-auto mb-4 opacity-50" />
                <p>No graph data available</p>
              </div>
            ) : (
              <GitGraphView commits={graphReactData.commits} lanes={graphReactData.lanes} namespace={namespace} repoName={repoName} />
            )}
          </div>
        )}

        {activeTab === "reflog" && (
          <div className="border rounded-lg bg-card">
            {reflogData && Array.isArray(reflogData) && reflogData.length > 0 ? (
              <div className="divide-y">
                {reflogData.slice(0, 50).map((entry: any, index: number) => (
                  <div key={index} className="px-4 py-3">
                    <div className="flex items-center gap-3">
                      <code className="text-xs font-mono text-muted-foreground">
                        {entry.old_sha?.substring(0, 7) || "0000000"}
                      </code>
                      <span className="text-muted-foreground">→</span>
                      <code className="text-xs font-mono">
                        {entry.new_sha?.substring(0, 7) || "0000000"}
                      </code>
                      <span className="text-xs text-muted-foreground ml-auto">
                        {entry.ref_name || "HEAD"}
                      </span>
                    </div>
                    <div className="text-sm text-muted-foreground mt-1">
                      {entry.message || entry.action || "Ref update"}
                    </div>
                  </div>
                ))}
              </div>
            ) : (
              <div className="p-12 text-center text-muted-foreground">
                <GitCommit className="h-12 w-12 mx-auto mb-4 opacity-50" />
                <p>No reflog entries found</p>
              </div>
            )}
          </div>
        )}

        {filteredCommits.length > 100 && activeTab === "list" && (
          <p className="text-sm text-muted-foreground text-center py-2">
            Showing 100 of {filteredCommits.length} commits
          </p>
        )}
      </div>
    </>
  );
};

// Gitlen-style commit graph: compact per-row layout.
// Each row's graph column width = just enough for its own lane + dot.
// No wasted space from historical lanes.
const LANE_COLORS = [
  '#1a7f37', // green
  '#0969da', // blue
  '#6e40c9', // purple
  '#bf3989', // pink
  '#d29922', // yellow
  '#e6544a', // red
  '#1b7c83', // teal
  '#5a45a0', // indigo
  '#c68642', // orange
  '#2d6a4f', // forest
  '#7c4dff', // violet
  '#0f766e', // cyan
];

const ROW_HEIGHT = 36;
const DOT_SIZE = 10;
const LANE_WIDTH = 30; // pixels per lane in graph column
const GRAPH_MARGIN = 20; // left padding of graph column

type GraphCommit = CommitGraphReactCommit;

function GitGraphView({ commits: rawCommits, lanes, namespace, repoName }: {
  commits: GraphCommit[];
  lanes: LaneInfo[];
  namespace?: string;
  repoName?: string;
}) {
  const navigate = useNavigate();
  const containerRef = useRef<HTMLDivElement>(null);
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: 40 });

  const { commits, oidToIdx } = useMemo(() => {
    const seen = new Set<string>();
    const unique: GraphCommit[] = [];
    for (const c of rawCommits) {
      if (seen.has(c.oid)) continue;
      seen.add(c.oid);
      unique.push(c);
    }
    const oidToIdx = new Map<string, number>();
    unique.forEach((c, i) => oidToIdx.set(c.oid, i));
    return { commits: unique, oidToIdx };
  }, [rawCommits]);

  const totalHeight = commits.length * ROW_HEIGHT;

  // Lane center X within a row's graph column
  const laneX = (lane: number) => GRAPH_MARGIN + lane * LANE_WIDTH + LANE_WIDTH / 2;

  // Parse the rightmost active lane index from ASCII graph_chars.
  // The '*' marks current commit's lane; '/' marks a parent on a different lane.
  // Example: "* | | /" -> max lane = 3
  const maxLaneFromGraphChars = (graphChars: string): number => {
    // Count column groups: each group is "XY " where X in {*,|,/, \} and Y is space or /
    // The rightmost non-space character determines the max lane.
    let maxLane = 0;
    const cols = graphChars.split('');
    let colIdx = 0;
    for (const ch of cols) {
      if (ch === ' ' || ch === '\n') {
        // skip
      } else {
        // Each non-space char represents a column position
        maxLane = colIdx;
        colIdx++;
      }
    }
    // Subtract 1 because the last non-space is the '*' which occupies its own column
    // Actually, let's just return maxLane as the 0-based index of the last non-space char.
    // For "  * | /" the last non-space is at index 5 (the /), representing lane 3.
    return maxLane;
  };

  // Graph column width for a specific row: based on graph_chars parsing.
  // This captures all lanes active on this row, not just the commit's own lane.
  const graphWidthForGraphChars = (graphChars: string) => {
    const maxLane = maxLaneFromGraphChars(graphChars);
    return laneX(maxLane) + DOT_SIZE / 2 + 12;
  };

  // Merge arcs within visible range
  const mergeArcs = useMemo(() => {
    const arcs: Array<{
      fromIdx: number;
      toIdx: number;
      fromX: number;
      toX: number;
      fromY: number;
      toY: number;
      color: string;
    }> = [];
    for (let i = 0; i < commits.length; i++) {
      const c = commits[i];
      if (c.parent_hashes.length > 1) {
        for (let p = 1; p < c.parent_hashes.length; p++) {
          const parentOid = c.parent_hashes[p];
          const toIdx = oidToIdx.get(parentOid);
          if (toIdx === undefined) continue;
          const toCommit = commits[toIdx];
          const color = LANE_COLORS[c.lane_index % LANE_COLORS.length];
          arcs.push({
            fromIdx: i,
            toIdx,
            fromX: laneX(c.lane_index),
            toX: laneX(toCommit.lane_index),
            fromY: i * ROW_HEIGHT + ROW_HEIGHT / 2,
            toY: toIdx * ROW_HEIGHT + ROW_HEIGHT / 2,
            color,
          });
        }
      }
    }
    return arcs;
  }, [commits, oidToIdx]);

  // Lane bounds for drawing vertical lane lines
  const laneBounds = useMemo(() => {
    const map = new Map<number, { startIdx: number; endIdx: number | null }>();
    for (const lane of lanes) {
      const startIdx = oidToIdx.get(lane.start_oid);
      if (startIdx === undefined) continue;
      const endIdx = lane.end_oid ? oidToIdx.get(lane.end_oid) ?? null : null;
      map.set(lane.lane_index, { startIdx, endIdx });
    }
    return map;
  }, [lanes, oidToIdx]);

  // Max lane across all commits — determines the graph column width for every row.
  // All active lanes at any point in history need their space in every row.
  const maxLane = useMemo(() => {
    let max = 0;
    for (const c of commits) {
      if (c.lane_index > max) max = c.lane_index;
    }
    return max;
  }, [commits]);

  // Each row's graph column width = enough for all lanes that have ever existed.
  // The rightmost lane's dot + a small gap = message starts here.
  const graphWidth = useMemo(() => {
    return laneX(maxLane) + DOT_SIZE / 2 + 12;
  }, [maxLane]);

  const visibleCommits = useMemo(() => {
    return commits.slice(visibleRange.start, visibleRange.end);
  }, [commits, visibleRange]);

  // Total SVG width = graph column
  const svgWidth = graphWidth;

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    const handleScroll = () => {
      const scrollTop = container.scrollTop;
      const viewportHeight = container.clientHeight;
      const start = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - 5);
      const end = Math.min(commits.length, Math.ceil((scrollTop + viewportHeight) / ROW_HEIGHT) + 5);
      setVisibleRange({ start, end });
    };
    container.addEventListener('scroll', handleScroll, { passive: true });
    handleScroll();
    return () => container.removeEventListener('scroll', handleScroll);
  }, [commits.length]);

  return (
    <div ref={containerRef} className="overflow-auto" style={{ maxHeight: 'calc(100vh - 300px)' }}>
      <div
        className="relative"
        style={{ minWidth: svgWidth + 480, minHeight: totalHeight }}
      >
        {/* Full-height SVG for lane lines + merge arcs */}
        <svg
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: svgWidth,
            height: totalHeight,
            pointerEvents: 'none',
            zIndex: 1,
            backgroundColor: 'hsl(var(--card))',
          }}
        >
          {/* Lane vertical lines — only within active range */}
          {Array.from({ length: maxLane + 1 }, (_, lane) => {
            const x = laneX(lane);
            const bounds = laneBounds.get(lane);
            const color = LANE_COLORS[lane % LANE_COLORS.length];
            if (!bounds) return null;
            const y1 = bounds.startIdx * ROW_HEIGHT + ROW_HEIGHT / 2;
            const y2 = bounds.endIdx !== null
              ? bounds.endIdx * ROW_HEIGHT + ROW_HEIGHT / 2
              : totalHeight;
            return (
              <line
                key={`lane-${lane}`}
                x1={x}
                y1={y1}
                x2={x}
                y2={y2}
                stroke={color}
                strokeWidth={2}
                opacity={0.65}
              />
            );
          })}

          {/* Merge arcs */}
          {mergeArcs.map((arc, i) => {
            const midY = (arc.fromY + arc.toY) / 2;
            const d = `M ${arc.fromX} ${arc.fromY} C ${arc.fromX} ${midY}, ${arc.toX} ${midY}, ${arc.toX} ${arc.toY}`;
            return (
              <path
                key={`arc-${i}`}
                d={d}
                fill="none"
                stroke={arc.color}
                strokeWidth={2}
                opacity={0.7}
              />
            );
          })}
        </svg>

        {/* Per-row content: each row is independently sized */}
        {visibleCommits.map((commit, visIdx) => {
          const actualIdx = visibleRange.start + visIdx;
          const y = actualIdx * ROW_HEIGHT;
          const lane = commit.lane_index;
          const color = LANE_COLORS[lane % LANE_COLORS.length];
          const isMerge = commit.parent_hashes.length > 1;
          const dotX = laneX(lane);
          const thisRowGraphWidth = graphWidthForGraphChars(commit.graph_chars);

          return (
            <div
              key={commit.oid}
              className="absolute left-0 flex items-center hover:bg-muted/50 cursor-pointer transition-colors border-b border-border/30"
              style={{
                top: y,
                height: ROW_HEIGHT,
                width: thisRowGraphWidth + 600,
                backgroundColor: 'hsl(var(--card))',
              }}
              onClick={() => navigate(`/repository/${namespace}/${repoName}/commit/${commit.oid}`)}
            >
              {/* Graph area: dot positioned at its lane, zIndex above SVG line */}
              <div
                className="flex-shrink-0 relative"
                style={{ width: thisRowGraphWidth, zIndex: 2 }}
              >
                {/* Commit dot — centered on lane, overlaid on SVG line */}
                <div
                  className="absolute rounded-full border-2"
                  style={{
                    left: dotX - DOT_SIZE / 2,
                    top: ROW_HEIGHT / 2 - DOT_SIZE / 2,
                    width: DOT_SIZE,
                    height: DOT_SIZE,
                    backgroundColor: color,
                    borderColor: 'var(--background)',
                    boxShadow: `0 0 0 1.5px ${color}`,
                  }}
                />
              </div>

              {/* Message column: inline right after dot */}
              <div className="flex-1 min-w-0 pl-3 pr-4 flex items-center gap-2 overflow-hidden">
                {/* Merge icon */}
                {isMerge && (
                  <svg className="h-3.5 w-3.5 flex-shrink-0 text-purple-500" viewBox="0 0 16 16" fill="currentColor">
                    <path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0M9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1M6.854 7.146 8 8.293l1.146-1.147a.5.5 0 1 1 .708.708L8.707 9l1.147 1.146a.5.5 0 0 1-.708.708L8 9.707l-1.146 1.147a.5.5 0 0 1-.708-.708L7.293 9 6.146 7.854a.5.5 0 1 1 .708-.708" />
                  </svg>
                )}

                {/* Branch labels */}
                {commit.branches.map((branch, bi) => (
                  <span
                    key={`br-${bi}`}
                    className="flex-shrink-0 text-xs font-semibold px-1.5 py-0.5 rounded text-white"
                    style={{ backgroundColor: color }}
                  >
                    {branch}
                  </span>
                ))}

                {/* Tag labels */}
                {commit.tags.map((tag, ti) => (
                  <span
                    key={`tag-${ti}`}
                    className="flex-shrink-0 text-xs font-semibold px-1.5 py-0.5 rounded border"
                    style={{
                      backgroundColor: `color-mix(in srgb, ${color} 12%, transparent)`,
                      borderColor: `${color}66`,
                      color: color,
                    }}
                  >
                    {tag}
                  </span>
                ))}

                {/* Subject */}
                <span className="text-sm font-medium truncate">{commit.subject}</span>

                <span className="text-muted-foreground/30 flex-shrink-0">·</span>
                <span className="text-xs text-muted-foreground flex-shrink-0">{commit.author_name}</span>
                <span className="text-muted-foreground/30 flex-shrink-0">·</span>
                <code className="text-xs text-muted-foreground font-mono flex-shrink-0">{commit.oid.substring(0, 7)}</code>
                <span className="text-muted-foreground/30 flex-shrink-0">·</span>
                <span className="text-xs text-muted-foreground flex-shrink-0">
                  {getRelativeTime(commit.committer_timestamp ? new Date(commit.committer_timestamp * 1000).toISOString() : null)}
                </span>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}


