"use client";

import { useState } from "react";
import Link from "next/link";
import dynamic from "next/dynamic";
import type { AllocationLike, AllocationReadModel, AllocationWithDetails, Resource, SkillEntry } from "@capakraken/shared";
import { trpc } from "~/lib/trpc/client.js";
import { formatDate, formatMoney } from "~/lib/format.js";
import { ResourceModal } from "./ResourceModal.js";
import { usePermissions } from "~/hooks/usePermissions.js";

const SkillRadarChart = dynamic(
  () => import("~/components/resources/SkillRadarChart.js").then((mod) => ({ default: mod.SkillRadarChart })),
  { ssr: false, loading: () => <div className="h-64 shimmer-skeleton rounded-xl" /> },
);

const AiSummaryCard = dynamic(
  () => import("~/components/resources/AiSummaryCard.js").then((mod) => ({ default: mod.AiSummaryCard })),
  { ssr: false },
);

const SkillMatrixUpload = dynamic(
  () => import("~/components/resources/SkillMatrixUpload.js").then((mod) => ({ default: mod.SkillMatrixUpload })),
  { ssr: false },
);
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { ProgressRing } from "~/components/ui/ProgressRing.js";
import { FadeIn } from "~/components/ui/FadeIn.js";
import { CommentThread } from "~/components/comments/CommentThread.js";

interface ResourceDetailProps {
  resourceId: string;
}

const proficiencyLabel: Record<number, string> = {
  1: "Beginner",
  2: "Elementary",
  3: "Intermediate",
  4: "Advanced",
  5: "Expert",
};

const proficiencyColor: Record<number, string> = {
  1: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300",
  2: "bg-blue-50 text-blue-600 dark:bg-blue-900/50 dark:text-blue-300",
  3: "bg-brand-50 text-brand-700 dark:bg-brand-900/50 dark:text-brand-200",
  4: "bg-amber-50 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
  5: "bg-green-50 text-green-700 dark:bg-green-900/50 dark:text-green-300",
};

const vacationStatusColor: Record<string, string> = {
  PENDING: "bg-yellow-100 text-yellow-700",
  APPROVED: "bg-green-100 text-green-700",
  REJECTED: "bg-red-100 text-red-700",
  CANCELLED: "bg-gray-100 text-gray-500",
};

const allocationStatusColor: Record<string, string> = {
  PROPOSED: "bg-gray-100 text-gray-600",
  CONFIRMED: "bg-blue-100 text-blue-700",
  ACTIVE: "bg-green-100 text-green-700",
  COMPLETED: "bg-purple-100 text-purple-700",
  CANCELLED: "bg-red-100 text-red-500",
};

function StatCard({ label, value, sub, tooltip, ring }: { label: string; value: string | number; sub?: string; tooltip?: string; ring?: { value: number; color: string } }) {
  return (
    <div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-4">
      <div className="text-xs text-gray-500 mb-1 flex items-center">{label}{tooltip && <InfoTooltip content={tooltip} />}</div>
      {ring ? (
        <div className="flex items-center gap-3">
          <ProgressRing value={ring.value} size={48} strokeWidth={3.5} color={ring.color}>
            <span className="text-sm font-bold text-gray-900 dark:text-gray-100">{value}</span>
          </ProgressRing>
        </div>
      ) : (
        <div className="text-xl font-bold text-gray-900 dark:text-gray-100">{value}</div>
      )}
      {sub && <div className="text-xs text-gray-400 mt-0.5">{sub}</div>}
    </div>
  );
}

export function ResourceDetail({ resourceId }: ResourceDetailProps) {
  const [editOpen, setEditOpen] = useState(false);
  const [includeProposedChargeability, setIncludeProposedChargeability] = useState(false);
  const [uploadOpen, setUploadOpen] = useState(false);
  const utils = trpc.useUtils();
  const { canViewCosts, canEdit, canViewScores } = usePermissions();

  const _resourceQuery = trpc.resource.getById.useQuery({ id: resourceId });
  const resource = _resourceQuery.data as unknown as Resource | undefined;
  const loadingResource = _resourceQuery.isLoading;
  const error = _resourceQuery.error;
  const errorCode = (error as any)?.data?.code as string | undefined;

  // Fetch allocations for this resource (all non-cancelled)
  const now = new Date();
  const windowEnd = new Date(now);
  windowEnd.setDate(windowEnd.getDate() + 90);

  const _allocQuery = trpc.allocation.listView.useQuery(
    { resourceId },
    { enabled: !!resourceId },
  ) as { data: AllocationReadModel<AllocationLike> | undefined; isLoading: boolean };
  const allocations = (_allocQuery.data?.assignments ?? []) as unknown as Array<Pick<
    AllocationWithDetails,
    "id" | "startDate" | "endDate" | "hoursPerDay" | "dailyCostCents" | "status" | "role" | "roleEntity" | "project"
  >>;
  const loadingAllocations = _allocQuery.isLoading;

  // Fetch upcoming/recent vacations
  const vacationStart = new Date(now);
  vacationStart.setMonth(vacationStart.getMonth() - 1);

  const { data: vacations, isLoading: loadingVacations } = trpc.vacation.list.useQuery(
    {
      resourceId,
      startDate: vacationStart,
      limit: 20,
    },
    { enabled: !!resourceId },
  );
  const vacationList = (vacations ?? []) as Array<{
    endDate: Date | string;
    id: string;
    note?: string | null;
    startDate: Date | string;
    status: string;
    type: string;
  }>;

  const chargeabilityStatsResult = trpc.resource.getChargeabilityStats.useQuery(
    { includeProposed: includeProposedChargeability, resourceId },
    { enabled: canViewCosts, staleTime: 60_000 },
  );
  const chargeStats = (chargeabilityStatsResult.data as unknown as Array<{
    actualChargeability: number;
    expectedChargeability: number;
  }> | undefined)?.[0];

  if (loadingResource) {
    return (
      <div className="p-6">
        <div className="space-y-4">
          <div className="h-8 shimmer-skeleton rounded w-64" />
          <div className="h-4 shimmer-skeleton rounded w-48" />
          <div className="grid grid-cols-4 gap-4">
            {[0, 1, 2, 3].map((i) => <div key={i} className="h-20 shimmer-skeleton rounded-xl" style={{ animationDelay: `${i * 50}ms` }} />)}
          </div>
        </div>
      </div>
    );
  }

  if (errorCode === "NOT_FOUND") {
    return (
      <div className="p-6">
        <div className="bg-red-50 border border-red-200 rounded-xl p-6 text-red-700 text-sm">
          Resource not found.{" "}
          <Link href="/resources" className="underline">Back to resources</Link>
        </div>
      </div>
    );
  }

  if (error || !resource) {
    return (
      <div className="p-6">
        <div className="bg-amber-50 border border-amber-200 rounded-xl p-6 text-amber-800 text-sm">
          This resource could not be loaded right now.{" "}
          <Link href="/resources" className="underline">Back to resources</Link>
        </div>
      </div>
    );
  }

  const skills = resource.skills as unknown as SkillEntry[];
  const resourceRoles = (resource as unknown as {
    resourceRoles?: { isPrimary: boolean; role: { id: string; name: string; color: string | null } }[];
  }).resourceRoles ?? [];
  const mainSkills = skills.filter((s) => s.isMainSkill);

  // Determine if current user owns this resource (self-service)
  const resourceWithMeta = resource as unknown as {
    userId?: string | null;
    portfolioUrl?: string | null;
    roleId?: string | null;
    aiSummary?: string | null;
    aiSummaryUpdatedAt?: Date | null;
    skillMatrixUpdatedAt?: Date | null;
    areaRole?: { name: string } | null;
    valueScore?: number | null;
    valueScoreBreakdown?: {
      skillDepth: number;
      skillBreadth: number;
      costEfficiency: number;
      chargeability: number;
      experience: number;
      total: number;
    } | null;
    valueScoreUpdatedAt?: Date | null;
    isOwnedByCurrentUser?: boolean;
  };
  const isOwner = resourceWithMeta.isOwnedByCurrentUser === true;
  const canUpload = isOwner || canEdit;

  // Compute stats
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);

  const thisMonthAllocs = (allocations ?? []).filter((a) => {
    const start = new Date(a.startDate);
    const end = new Date(a.endDate);
    return start <= monthEnd && end >= monthStart;
  });

  let totalHoursThisMonth = 0;
  let totalCostCentsThisMonth = 0;
  const activeProjectIds = new Set<string>();

  for (const a of thisMonthAllocs) {
    const start = new Date(Math.max(new Date(a.startDate).getTime(), monthStart.getTime()));
    const end = new Date(Math.min(new Date(a.endDate).getTime(), monthEnd.getTime()));
    const days = Math.max(0, (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24) + 1);
    totalHoursThisMonth += a.hoursPerDay * days;
    totalCostCentsThisMonth += a.dailyCostCents * days;
    if (a.project?.id) activeProjectIds.add(a.project.id);
  }

  const avgDailyCost =
    thisMonthAllocs.length > 0
      ? Math.round(totalCostCentsThisMonth / 100 / (thisMonthAllocs.length || 1))
      : 0;

  // Filter upcoming/active allocations (not cancelled, ending >= today)
  const upcomingAllocations = (allocations ?? []).filter(
    (a) => a.status !== "CANCELLED" && new Date(a.endDate) >= now,
  );

  return (
    <div className="p-6 space-y-6">
      {/* Back navigation */}
      <Link
        href="/resources"
        className="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 transition-colors"
      >
        <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
        </svg>
        Back to Resources
      </Link>

      {/* Header */}
      <div className="flex items-start justify-between gap-4">
        <div className="min-w-0">
          <div className="flex items-center gap-3 flex-wrap">
            <h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 truncate">{resource.displayName}</h1>
            <span
              className={`flex-shrink-0 px-2.5 py-0.5 text-xs font-medium rounded-full ${
                resource.isActive ? "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300" : "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"
              }`}
            >
              {resource.isActive ? "Active" : "Inactive"}
            </span>
          </div>
          <p className="text-sm text-gray-500 mt-1">
            <span className="font-mono">{resource.eid}</span>
            {" · "}
            <a href={`mailto:${resource.email}`} className="hover:underline">{resource.email}</a>
            {resource.chapter && (
              <>
                {" · "}
                <span>{resource.chapter}</span>
              </>
            )}
          </p>
        </div>
        <div className="flex items-center gap-2">
          {canUpload && (
            <button
              type="button"
              onClick={() => setUploadOpen(true)}
              className="flex-shrink-0 flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
            >
              <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                <path strokeLinecap="round" strokeLinejoin="round" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
              </svg>
              Update Skill Matrix
            </button>
          )}
          <button
            type="button"
            onClick={() => setEditOpen(true)}
            className="flex-shrink-0 flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
          >
            <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
              <path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
            </svg>
            Edit
          </button>
        </div>
      </div>

      {/* Stats cards */}
      {canViewCosts && (
        <div className="flex justify-end">
          <label className="flex items-center gap-2 text-sm text-gray-600">
            <input
              type="checkbox"
              checked={includeProposedChargeability}
              onChange={(event) => setIncludeProposedChargeability(event.target.checked)}
              className="rounded border-gray-300"
            />
            Include proposed in chargeability
          </label>
        </div>
      )}
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        {canViewCosts && (
          <StatCard
            label="LCR"
            value={`${formatMoney(resource.lcrCents, resource.currency)}/h`}
            tooltip="Loaded Cost Rate: fully-loaded hourly cost including salary, benefits, and overhead. Used in budget calculations."
          />
        )}
        {canViewCosts && (
          <StatCard
            label="UCR"
            value={`${formatMoney(resource.ucrCents, resource.currency)}/h`}
            tooltip="Unit Cost Rate: the rate charged to the client or project for this resource's time."
          />
        )}
        <StatCard
          label="Chargeability Target"
          value={`${resource.chargeabilityTarget}%`}
          tooltip="The percentage of working time this resource is expected to spend on chargeable/billable work."
          ring={{
            value: resource.chargeabilityTarget,
            color: "var(--color-blue-500, #3b82f6)",
          }}
        />
        {canViewCosts && (
        <StatCard
          label="Actual (this month)"
          value={chargeStats != null ? `${chargeStats.actualChargeability}%` : "—"}
          tooltip="Actual chargeability = chargeable hours / total available hours x 100 for the current month."
          sub={
            includeProposedChargeability
              ? "Incl. proposed + imported TBD planning"
              : "Confirmed + active only"
          }
          {...(chargeStats != null ? {
            ring: {
              value: chargeStats.actualChargeability,
              color: chargeStats.actualChargeability >= resource.chargeabilityTarget
                ? "var(--color-green-500, #22c55e)"
                : chargeStats.actualChargeability >= resource.chargeabilityTarget - 10
                  ? "var(--color-amber-500, #f59e0b)"
                  : "var(--color-red-500, #ef4444)",
            },
          } : {})}
        />
        )}
        {canViewCosts && (
          <StatCard
            label="Expected (this month)"
            value={chargeStats != null ? `${chargeStats.expectedChargeability}%` : "—"}
            tooltip="Expected chargeability based on all non-cancelled bookings for the current month."
            sub="All non-cancelled bookings"
          />
        )}
        <StatCard
          label="Hours This Month"
          value={`${Math.round(totalHoursThisMonth)}h`}
          tooltip="Sum of allocated hours for all active projects in the current calendar month."
          sub={`${activeProjectIds.size} active project${activeProjectIds.size !== 1 ? "s" : ""}`}
        />
        {canViewScores && resourceWithMeta.valueScore != null && (
          <div className="relative group">
            <StatCard
              label="Value Score"
              value={resourceWithMeta.valueScore}
              tooltip="Composite score (0-100) combining skill depth, breadth, cost efficiency, chargeability, and experience. Hover for breakdown."
              sub="Price/Quality"
            />
            {resourceWithMeta.valueScoreBreakdown && (
              <div className="absolute left-0 top-full mt-1 z-10 hidden group-hover:block w-56 bg-white rounded-xl border border-gray-200 shadow-lg p-3 text-xs space-y-1.5">
                <p className="font-semibold text-gray-700 mb-2">Score Breakdown</p>
                {(
                  [
                    ["Skill Depth", resourceWithMeta.valueScoreBreakdown.skillDepth],
                    ["Skill Breadth", resourceWithMeta.valueScoreBreakdown.skillBreadth],
                    ["Cost Efficiency", resourceWithMeta.valueScoreBreakdown.costEfficiency],
                    ["Chargeability", resourceWithMeta.valueScoreBreakdown.chargeability],
                    ["Experience", resourceWithMeta.valueScoreBreakdown.experience],
                  ] as [string, number][]
                ).map(([label, val]) => (
                  <div key={label}>
                    <div className="flex justify-between text-gray-600 mb-0.5">
                      <span>{label}</span>
                      <span className="font-mono">{val}</span>
                    </div>
                    <div className="h-1.5 bg-gray-100 rounded-full overflow-hidden">
                      <div
                        className={`h-full rounded-full ${val >= 70 ? "bg-green-500" : val >= 40 ? "bg-amber-400" : "bg-red-400"}`}
                        style={{ width: `${val}%` }}
                      />
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}
      </div>

      {/* Profile meta (area role, portfolio, last import) */}
      {(resourceWithMeta.areaRole || resourceWithMeta.portfolioUrl || resourceWithMeta.skillMatrixUpdatedAt) && (
        <div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-4 flex flex-wrap gap-4 text-sm">
          {resourceWithMeta.areaRole && (
            <div className="flex items-center gap-2">
              <span className="text-gray-500 dark:text-gray-400 text-xs">Area:</span>
              <span className="font-medium text-gray-800 dark:text-gray-200">{resourceWithMeta.areaRole.name}</span>
            </div>
          )}
          {resourceWithMeta.portfolioUrl && (
            <div className="flex items-center gap-2">
              <span className="text-gray-500 text-xs">Portfolio:</span>
              <a
                href={resourceWithMeta.portfolioUrl}
                target="_blank"
                rel="noopener noreferrer"
                className="text-brand-600 hover:underline truncate max-w-xs"
              >
                {resourceWithMeta.portfolioUrl}
              </a>
            </div>
          )}
          {resourceWithMeta.skillMatrixUpdatedAt && (
            <div className="flex items-center gap-2">
              <span className="text-gray-500 text-xs">Skill matrix updated:</span>
              <span className="text-gray-600">{formatDate(resourceWithMeta.skillMatrixUpdatedAt)}</span>
            </div>
          )}
        </div>
      )}

      {/* AI Summary */}
      <AiSummaryCard
        resourceId={resourceId}
        aiSummary={resourceWithMeta.aiSummary ?? null}
        aiSummaryUpdatedAt={resourceWithMeta.aiSummaryUpdatedAt ?? null}
        onGenerated={async () => { await utils.resource.getById.invalidate({ id: resourceId }); }}
      />

      <section
        id="comments"
        className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-5 scroll-mt-24"
      >
        <div className="mb-4">
          <h2 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Comments</h2>
          <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
            Discussion for this resource follows the same visibility as the resource detail itself.
          </p>
        </div>
        <CommentThread
          commentTarget={{
            entityType: "resource",
            entityId: resourceId,
          }}
        />
      </section>

      {/* Main Skills Badges */}
      {mainSkills.length > 0 && (
        <div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
          <h2 className="text-sm font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center">Main Skills<InfoTooltip content="Up to 2 skills flagged as primary strengths. Used for staffing matching priority." /></h2>
          <div className="flex flex-wrap gap-2">
            {mainSkills.map((s) => (
              <span
                key={s.skill}
                className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full bg-amber-50 text-amber-800 border border-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:border-amber-700"
              >
                <span className="text-amber-500">★</span>
                {s.skill}
                <span className={`text-[10px] font-medium px-1.5 py-0.5 rounded ${proficiencyColor[s.proficiency] ?? "bg-gray-100 text-gray-500"}`}>
                  {proficiencyLabel[s.proficiency] ?? `L${s.proficiency}`}
                </span>
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Skill Radar Chart */}
      <FadeIn delay={0.1} direction="up">
        <SkillRadarChart skills={skills} />
      </FadeIn>

      {/* Roles */}
      {resourceRoles.length > 0 && (
        <div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
          <h2 className="text-sm font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center">Roles<InfoTooltip content="Job functions assigned to this resource. The primary role is used in staffing and timeline displays." /></h2>
          <div className="flex flex-wrap gap-2">
            {resourceRoles.map((rr) => (
              <span
                key={rr.role.id}
                className="inline-flex items-center gap-1.5 px-3 py-1 text-sm font-medium rounded-full"
                style={{
                  backgroundColor: `${rr.role.color ?? "#6366f1"}22`,
                  color: rr.role.color ?? "#6366f1",
                }}
              >
                {rr.isPrimary && <span className="text-[11px]">★</span>}
                {rr.role.name}
                {rr.isPrimary && <span className="text-[10px] opacity-70">Primary</span>}
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Skills */}
      {skills.length > 0 && (
        <div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
          <h2 className="text-sm font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center">Skills<InfoTooltip content="Full skill inventory with proficiency level (1-5) and years of experience. Imported via skill matrix XLSX." /></h2>
          <div className="flex flex-wrap gap-2">
            {skills.map((s) => (
              <span
                key={s.skill}
                className="inline-flex items-center gap-1.5 px-3 py-1 text-sm rounded-full bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-200"
              >
                {s.skill}
                {s.proficiency != null && (
                  <span
                    className={`text-[10px] font-medium px-1.5 py-0.5 rounded ${
                      proficiencyColor[s.proficiency] ?? "bg-gray-100 text-gray-500"
                    }`}
                  >
                    {proficiencyLabel[s.proficiency] ?? `L${s.proficiency}`}
                  </span>
                )}
                {s.yearsExperience != null && (
                  <span className="text-xs text-gray-400 dark:text-gray-500">{s.yearsExperience}y</span>
                )}
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Active / Upcoming Allocations */}
      <div className="bg-white rounded-xl border border-gray-200">
        <div className="px-5 py-4 border-b border-gray-100 flex items-center justify-between">
          <h2 className="text-sm font-semibold text-gray-800">Active &amp; Upcoming Allocations</h2>
          <span className="text-xs text-gray-400">Next 90 days</span>
        </div>
        {loadingAllocations ? (
          <div className="p-6 text-center text-gray-400 text-sm animate-pulse">Loading…</div>
        ) : (
          <div className="overflow-x-auto">
            <table className="w-full text-sm">
              <thead className="bg-gray-50">
                <tr>
                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-500">Project</th>
                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-500">Role</th>
                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-500">Period</th>
                  <th className="px-4 py-3 text-right text-xs font-medium text-gray-500">h/Day</th>
                  {canViewCosts && <th className="px-4 py-3 text-right text-xs font-medium text-gray-500">Daily Cost</th>}
                  <th className="px-4 py-3 text-right text-xs font-medium text-gray-500">Status</th>
                </tr>
              </thead>
              <tbody className="divide-y divide-gray-100">
                {upcomingAllocations.map((a) => {
                  const isOver = a.hoursPerDay > 8;
                  return (
                    <tr key={a.id} className={`hover:bg-gray-50 ${isOver ? "bg-amber-50" : ""}`}>
                      <td className="px-4 py-3">
                        {a.project ? (
                          <>
                            <span className="font-mono text-xs text-gray-500 mr-1">{a.project.shortCode}</span>
                            {a.project.name}
                          </>
                        ) : (
                          <span className="text-gray-400">—</span>
                        )}
                      </td>
                      <td className="px-4 py-3 text-gray-600">{a.role ?? (a.roleEntity?.name ?? "—")}</td>
                      <td className="px-4 py-3 text-xs text-gray-500">
                        {formatDate(a.startDate)} → {formatDate(a.endDate)}
                      </td>
                      <td className={`px-4 py-3 text-right font-medium ${isOver ? "text-amber-600" : "text-gray-900"}`}>
                        {a.hoursPerDay}h
                      </td>
                      {canViewCosts && (
                        <td className="px-4 py-3 text-right text-gray-700">
                          {a.dailyCostCents > 0
                            ? `${formatMoney(a.dailyCostCents)}/d`
                            : "—"}
                        </td>
                      )}
                      <td className="px-4 py-3 text-right">
                        <span
                          className={`inline-block px-2 py-0.5 text-xs rounded-full font-medium ${
                            allocationStatusColor[a.status] ?? "bg-gray-100 text-gray-600"
                          }`}
                        >
                          {a.status}
                        </span>
                      </td>
                    </tr>
                  );
                })}
                {upcomingAllocations.length === 0 && (
                  <tr>
                    <td colSpan={6} className="text-center py-8 text-gray-400 text-sm">
                      No active or upcoming allocations.
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>
        )}
      </div>

      {/* Vacations */}
      <div className="bg-white rounded-xl border border-gray-200">
        <div className="px-5 py-4 border-b border-gray-100 flex items-center justify-between">
          <h2 className="text-sm font-semibold text-gray-800">Vacations</h2>
          <span className="text-xs text-gray-400">Last month + upcoming</span>
        </div>
        {loadingVacations ? (
          <div className="p-6 text-center text-gray-400 text-sm animate-pulse">Loading…</div>
        ) : vacationList.length === 0 ? (
          <div className="text-center py-8 text-gray-400 text-sm">No vacations recorded.</div>
        ) : (
          <div className="divide-y divide-gray-100">
            {vacationList.map((v) => {
              const days =
                Math.round(
                  (new Date(v.endDate).getTime() - new Date(v.startDate).getTime()) / (1000 * 60 * 60 * 24),
                ) + 1;
              return (
                <div key={v.id} className="px-5 py-3 flex items-center justify-between gap-4">
                  <div className="min-w-0">
                    <div className="text-sm font-medium text-gray-800">
                      {v.type.replace(/_/g, " ")}
                    </div>
                    <div className="text-xs text-gray-500 mt-0.5">
                      {formatDate(v.startDate)} → {formatDate(v.endDate)}
                      <span className="ml-1 text-gray-400">({days} day{days !== 1 ? "s" : ""})</span>
                    </div>
                    {v.note && (
                      <div className="text-xs text-gray-400 mt-0.5 italic truncate max-w-sm">{v.note}</div>
                    )}
                  </div>
                  <span
                    className={`flex-shrink-0 px-2.5 py-0.5 text-xs font-medium rounded-full ${
                      vacationStatusColor[v.status] ?? "bg-gray-100 text-gray-600"
                    }`}
                  >
                    {v.status}
                  </span>
                </div>
              );
            })}
          </div>
        )}
      </div>

      {/* Edit modal */}
      {editOpen && (
        <ResourceModal
          mode="edit"
          resource={resource as unknown as Resource}
          onClose={async () => {
            setEditOpen(false);
            await utils.resource.getById.invalidate({ id: resourceId });
          }}
        />
      )}

      {/* Skill Matrix Upload modal */}
      {uploadOpen && (
        <SkillMatrixUpload
          resourceId={resourceId}
          isOwner={isOwner}
          onClose={() => setUploadOpen(false)}
          onSuccess={async () => {
            setUploadOpen(false);
            await utils.resource.getById.invalidate({ id: resourceId });
          }}
        />
      )}
    </div>
  );
}
