"use client";

import { useMemo, useState } from "react";

import {
  CheckCircle2,
  CircleDot,
  ExternalLink,
  Loader2,
  Milestone,
  XCircle,
} from "lucide-react";

import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import type { ForgejoIssue, ForgejoIssueLabel } from "@/lib/api-forgejo";
import type { ForgejoRepository } from "@/lib/api-forgejo";
import { CloseForgejoIssueDialog } from "@/components/git/CloseForgejoIssueDialog";
import { cn } from "@/lib/utils";

/** Normalize a Forgejo label color to a valid 6-char hex string or null. */
function normalizeLabelColor(raw: string | null | undefined): string | null {
  if (!raw) return null;
  const hex = raw.replace(/^#+/, "");
  return /^[0-9a-fA-F]{3,6}$/.test(hex) ? `#${hex.padEnd(6, "0")}` : null;
}

/** Return white or dark text based on WCAG relative luminance of a hex background. */
function labelTextColor(hex: string): string {
  const h = hex.replace("#", "");
  const r = parseInt(h.slice(0, 2), 16) / 255;
  const g = parseInt(h.slice(2, 4), 16) / 255;
  const b = parseInt(h.slice(4, 6), 16) / 255;
  const lin = (c: number) =>
    c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
  return L > 0.179 ? "#1a1a1a" : "#ffffff";
}

function LabelChip({ label }: { label: ForgejoIssueLabel }) {
  const bg = normalizeLabelColor(label.color);
  return (
    <Badge
      variant="outline"
      className="max-w-[14rem] shrink-0 truncate px-2 py-0.5 text-[11px] normal-case tracking-normal"
      style={
        bg
          ? { backgroundColor: bg, color: labelTextColor(bg), borderColor: bg }
          : undefined
      }
      title={label.description ?? label.name}
    >
      {label.name}
    </Badge>
  );
}

function formatDateTime(value: string | null | undefined): string {
  if (!value) return "Unknown date";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString(undefined, {
    dateStyle: "medium",
    timeStyle: "short",
  });
}

function formatRelativeTime(value: string | null | undefined): string {
  if (!value) return "unknown";
  const time = new Date(value).getTime();
  if (Number.isNaN(time)) return value;

  const diffSeconds = Math.round((Date.now() - time) / 1000);
  const absSeconds = Math.abs(diffSeconds);
  const direction = diffSeconds >= 0 ? "ago" : "from now";
  const units = [
    { max: 60, value: 1, name: "second" },
    { max: 60 * 60, value: 60, name: "minute" },
    { max: 60 * 60 * 24, value: 60 * 60, name: "hour" },
    { max: 60 * 60 * 24 * 30, value: 60 * 60 * 24, name: "day" },
    { max: 60 * 60 * 24 * 365, value: 60 * 60 * 24 * 30, name: "month" },
    { max: Number.POSITIVE_INFINITY, value: 60 * 60 * 24 * 365, name: "year" },
  ];
  const unit =
    units.find((candidate) => absSeconds < candidate.max) ?? units[0];
  const amount = Math.max(1, Math.round(absSeconds / unit.value));

  if (amount <= 5 && unit.name === "second") {
    return diffSeconds >= 0 ? "just now" : "in a few seconds";
  }

  return `${amount} ${unit.name}${amount === 1 ? "" : "s"} ${direction}`;
}

function getAssigneeLogin(assignee: Record<string, unknown>): string | null {
  const login = assignee.login ?? assignee.username ?? assignee.name;
  return typeof login === "string" && login.trim() ? login : null;
}

function getInitials(login: string): string {
  const normalized = login.trim();
  if (!normalized) return "?";
  const parts = normalized.split(/[\s._-]+/).filter(Boolean);
  if (parts.length >= 2) {
    return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
  }
  return normalized.slice(0, 2).toUpperCase();
}

function AssigneeStack({ issue }: { issue: ForgejoIssue }) {
  const assignees = issue.assignees
    .map(getAssigneeLogin)
    .filter((login): login is string => login !== null);

  if (assignees.length === 0) {
    return (
      <span className="whitespace-nowrap text-xs text-muted">Unassigned</span>
    );
  }

  const visible = assignees.slice(0, 3);
  const overflow = assignees.length - visible.length;

  return (
    <div
      className="flex items-center justify-end -space-x-2"
      title={assignees.join(", ")}
      aria-label={`Assigned to ${assignees.join(", ")}`}
    >
      {visible.map((login) => (
        <span
          key={login}
          className="flex h-7 w-7 items-center justify-center rounded-full border-2 border-[color:var(--surface)] bg-[color:var(--surface-muted)] text-[10px] font-semibold text-strong shadow-sm"
        >
          {getInitials(login)}
        </span>
      ))}
      {overflow > 0 ? (
        <span className="flex h-7 min-w-7 items-center justify-center rounded-full border-2 border-[color:var(--surface)] bg-[color:var(--accent-soft)] px-1.5 text-[10px] font-semibold text-[color:var(--accent)] shadow-sm">
          +{overflow}
        </span>
      ) : null}
    </div>
  );
}

function IssueStateIcon({ state }: { state: string }) {
  const isOpen = state === "open";
  const Icon = isOpen ? CircleDot : CheckCircle2;

  return (
    <span
      className={cn(
        "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center",
        isOpen ? "text-[color:var(--success)]" : "text-[color:var(--danger)]",
      )}
      title={isOpen ? "Open issue" : "Closed issue"}
    >
      <Icon className="h-4 w-4" />
    </span>
  );
}

function LoadingIssuesList() {
  return (
    <div className="divide-y divide-[color:var(--border)]">
      {Array.from({ length: 5 }).map((_, index) => (
        <div key={index} className="flex gap-3 px-4 py-4">
          <div className="mt-1 h-4 w-4 shrink-0 animate-pulse rounded-full bg-[color:var(--surface-muted)]" />
          <div className="min-w-0 flex-1 space-y-2">
            <div className="h-4 w-2/3 animate-pulse rounded bg-[color:var(--surface-muted)]" />
            <div className="h-3 w-1/2 animate-pulse rounded bg-[color:var(--surface-muted)]" />
          </div>
          <Loader2 className="h-4 w-4 animate-spin text-[color:var(--accent)]" />
        </div>
      ))}
    </div>
  );
}

export type ForgejoIssuesTableProps = {
  issues: ForgejoIssue[];
  repositories: ForgejoRepository[];
  isLoading?: boolean;
  canClose?: boolean;
  onRefresh: () => void;
};

export function ForgejoIssuesTable({
  issues,
  repositories,
  isLoading = false,
  canClose = false,
  onRefresh,
}: ForgejoIssuesTableProps) {
  const [issueToClose, setIssueToClose] = useState<ForgejoIssue | null>(null);
  const [isCloseDialogOpen, setIsCloseDialogOpen] = useState(false);

  const repositoryNameById = useMemo(() => {
    const map = new Map<string, string>();
    for (const repository of repositories) {
      map.set(
        repository.id,
        repository.display_name || `${repository.owner}/${repository.repo}`,
      );
    }
    return map;
  }, [repositories]);
  const issueCounts = useMemo(
    () =>
      issues.reduce(
        (counts, issue) => {
          if (issue.state === "closed") {
            counts.closed += 1;
          } else {
            counts.open += 1;
          }
          return counts;
        },
        { open: 0, closed: 0 },
      ),
    [issues],
  );

  return (
    <>
      <div className="overflow-hidden rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] shadow-lush">
        <div className="flex flex-col gap-3 border-b border-[color:var(--border)] bg-[color:var(--surface-muted)] px-4 py-3 text-sm sm:flex-row sm:items-center sm:justify-between">
          <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
            <span className="inline-flex items-center gap-1.5 font-medium text-strong">
              <CircleDot className="h-4 w-4 text-[color:var(--success)]" />
              {issueCounts.open} Open
            </span>
            <span className="inline-flex items-center gap-1.5 text-muted">
              <CheckCircle2 className="h-4 w-4" />
              {issueCounts.closed} Closed
            </span>
          </div>
          <span className="text-xs text-muted">
            {issues.length} visible issue{issues.length === 1 ? "" : "s"}
          </span>
        </div>

        {isLoading ? (
          <LoadingIssuesList />
        ) : issues.length === 0 ? (
          <div className="flex flex-col items-center justify-center px-6 py-16 text-center">
            <div className="mb-4 rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-4 text-[color:var(--text-muted)]">
              <CircleDot className="h-12 w-12" />
            </div>
            <h3 className="mb-2 text-lg font-semibold text-strong">
              No Git Project issues found
            </h3>
            <p className="max-w-md text-sm text-muted">
              Sync a repository to pull issues into Pipeline, or adjust your
              filters.
            </p>
          </div>
        ) : (
          <div className="divide-y divide-[color:var(--border)]">
            {issues.map((issue) => {
              const repositoryName =
                repositoryNameById.get(issue.repository_id) ??
                issue.repository_id;
              const visibleLabels = issue.labels.slice(0, 4);
              const hiddenLabelCount =
                issue.labels.length - visibleLabels.length;
              const updatedAt =
                issue.state === "closed"
                  ? issue.forgejo_closed_at || issue.forgejo_updated_at
                  : issue.forgejo_updated_at;
              const stateVerb = issue.state === "closed" ? "closed" : "updated";
              const canShowClose =
                canClose && issue.state === "open" && !issue.is_pull_request;

              return (
                <article
                  key={issue.id}
                  className="group grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-3 px-4 py-4 transition-colors hover:bg-[color:var(--surface-muted)] sm:grid-cols-[auto_minmax(0,1fr)_auto] sm:gap-x-4"
                >
                  <IssueStateIcon state={issue.state} />

                  <div className="min-w-0 flex-1">
                    <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                      <a
                        href={issue.html_url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="min-w-0 break-words text-sm font-semibold text-strong hover:text-[color:var(--accent)] hover:underline"
                        title={issue.title}
                      >
                        {issue.title}
                      </a>
                      {visibleLabels.map((label, i) => (
                        <LabelChip key={`${label.name}-${i}`} label={label} />
                      ))}
                      {hiddenLabelCount > 0 ? (
                        <span className="text-xs text-muted">
                          +{hiddenLabelCount}
                        </span>
                      ) : null}
                    </div>

                    <div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted">
                      <span className="font-medium text-strong">
                        {repositoryName}
                      </span>
                      <span className="font-mono">
                        #{issue.forgejo_issue_number}
                      </span>
                      <span title={formatDateTime(updatedAt)}>
                        {stateVerb} {formatRelativeTime(updatedAt)}
                      </span>
                      {issue.author ? <span>by {issue.author}</span> : null}
                      {issue.milestone?.title ? (
                        <span className="inline-flex min-w-0 items-center gap-1">
                          <Milestone className="h-3.5 w-3.5 shrink-0" />
                          <span className="truncate">
                            {issue.milestone.title}
                          </span>
                        </span>
                      ) : null}
                    </div>
                  </div>

                  <div className="col-span-2 flex shrink-0 items-center justify-between gap-2 pl-7 sm:col-span-1 sm:min-w-[9rem] sm:justify-end sm:pl-0">
                    <AssigneeStack issue={issue} />
                    <div className="flex items-center gap-1">
                      <a
                        href={issue.html_url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted transition-colors hover:bg-[color:var(--surface-strong)] hover:text-[color:var(--accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]"
                        title={`Open ${repositoryName}#${issue.forgejo_issue_number} in Forgejo`}
                        aria-label={`Open ${repositoryName}#${issue.forgejo_issue_number} in Forgejo`}
                      >
                        <ExternalLink className="h-4 w-4" />
                      </a>
                      {canShowClose ? (
                        <Button
                          type="button"
                          variant="ghost"
                          size="sm"
                          className="h-8 w-8 rounded-lg p-0 text-[color:var(--danger)] hover:bg-[color:rgba(248,113,113,0.08)]"
                          title={`Close ${repositoryName}#${issue.forgejo_issue_number}`}
                          aria-label={`Close ${repositoryName}#${issue.forgejo_issue_number}`}
                          onClick={() => {
                            setIssueToClose(issue);
                            setIsCloseDialogOpen(true);
                          }}
                        >
                          <XCircle className="h-4 w-4" />
                        </Button>
                      ) : null}
                    </div>
                  </div>
                </article>
              );
            })}
          </div>
        )}
      </div>
      <CloseForgejoIssueDialog
        issue={issueToClose}
        repositoryName={
          issueToClose
            ? (repositoryNameById.get(issueToClose.repository_id) ??
              issueToClose.repository_id)
            : "Repository"
        }
        open={isCloseDialogOpen}
        onOpenChange={(nextOpen) => {
          setIsCloseDialogOpen(nextOpen);
          if (!nextOpen) {
            setIssueToClose(null);
          }
        }}
        onCloseSuccess={onRefresh}
      />
    </>
  );
}
