import React, { useEffect, useMemo, useState } from "react";
import axios from "axios";
import { Card, CardBody, CardHeader, Divider, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow, Spinner, Tooltip } from "@heroui/react";
import { saveAs } from "file-saver";
import { useNavigate, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";


type LogResultItem = {
  id: number;
  log_id: number;
  band_id?: number | null;
  category_id?: number | null;
  power_category_id?: number | null;
  sixhr_category?: boolean | null;
  claimed_qso_count?: number | null;
  claimed_score?: number | null;
  total_qso_count?: number | null;
  discarded_qso_count?: number | null;
  discarded_qso_percent?: number | null;
  discarded_points?: number | null;
  unique_qso_count?: number | null;
  official_score?: number | null;
  valid_qso_count?: number | null;
  score_per_qso?: number | null;
  rank_overall?: number | null;
  rank_in_category?: number | null;
  rank_overall_ok?: number | null;
  rank_in_category_ok?: number | null;
  status?: string | null;
  status_reason?: string | null;
  log?: {
    id: number;
    pcall?: string | null;
    sixhr_category?: boolean | null;
    pwwlo?: string | null;
    power_watt?: number | null;
    codxc?: string | null;
    sante?: string | null;
    santh?: string | null;
  } | null;
  band?: { id: number; name?: string | null; order?: number | null } | null;
  category?: { id: number; name?: string | null; order?: number | null } | null;
  power_category?: { id: number; name?: string | null; order?: number | null } | null;
  evaluation_run?: { id: number; result_type?: string | null; rule_set?: { sixhr_ranking_mode?: string | null } | null } | null;
};

type ApiResponse<T> = {
  data: T[];
};

type ResultsTablesProps = {
  roundId: number | null;
  contestId?: number | null;
  filter?: "ALL" | "OK";
  mode?: "claimed" | "final";
  showResultTypeLabel?: boolean;
  onResultTypeChange?: (label: string | null, className: string, resultType: string | null) => void;
  refreshKey?: string | number | null;
  evaluationRunId?: number | null;
  isFinalPublished?: boolean;
  isAdmin?: boolean;
};


export default function ResultsTables({
  roundId,
  contestId = null,
  filter = "ALL",
  mode = "claimed",
  showResultTypeLabel = true,
  onResultTypeChange,
  refreshKey = null,
  evaluationRunId = null,
  isFinalPublished = false,
  isAdmin = false,
}: ResultsTablesProps) {
  const [items, setItems] = useState<LogResultItem[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [overrideReasons, setOverrideReasons] = useState<Record<number, string | null>>({});
  const [overrideFlags, setOverrideFlags] = useState<Record<number, {
    forced_log_status?: string | null;
    forced_band_id?: number | null;
    forced_category_id?: number | null;
    forced_power_category_id?: number | null;
    forced_power_w?: number | null;
    forced_sixhr_category?: boolean | null;
  }>>({});
  const { t } = useTranslation();
  const navigate = useNavigate();
  const location = useLocation();
  const showScoreTotal = mode === "final";
  const showLocatorOdx = mode === "final" || isAdmin || isFinalPublished;

  useEffect(() => {
    if (!roundId) return;
    let active = true;

    (async () => {
      try {
        setLoading(true);
        setError(null);
        const params: Record<string, unknown> = {
          per_page: 5000,
          only_ok: filter === "OK",
        };
        if (evaluationRunId) {
          params.evaluation_run_id = evaluationRunId;
        }
        if (mode === "claimed") {
          params.round_id = roundId;
          params.status = "CLAIMED";
        } else if (!evaluationRunId) {
          params.round_id = roundId;
          params.result_type = "AUTO";
        }
        const res = await axios.get<ApiResponse<LogResultItem>>("/api/log-results", {
          params,
          headers: { Accept: "application/json" },
        });
        if (!active) return;
        setItems(res.data.data ?? []);
      } catch (e: any) {
        if (!active) return;
        const msg =
          e?.response?.data?.message ||
          (mode === "final"
            ? "Nepodařilo se načíst finální výsledky."
            : "Nepodařilo se načíst deklarované výsledky.");
        setError(msg);
      } finally {
        if (active) setLoading(false);
      }
    })();

    return () => {
      active = false;
    };
  }, [roundId, filter, mode, refreshKey, evaluationRunId]);

  useEffect(() => {
    if (mode !== "final") {
      setOverrideReasons({});
      return;
    }
    const evaluationRunId = items[0]?.evaluation_run?.id ?? null;
    if (!evaluationRunId) {
      setOverrideReasons({});
      return;
    }
    let active = true;
    (async () => {
      try {
        const res = await axios.get<ApiResponse<{
          log_id: number;
          reason?: string | null;
          forced_log_status?: string | null;
          forced_band_id?: number | null;
          forced_category_id?: number | null;
          forced_power_category_id?: number | null;
          forced_power_w?: number | null;
          forced_sixhr_category?: boolean | null;
        }>>("/api/log-overrides", {
          params: {
            evaluation_run_id: evaluationRunId,
            per_page: 5000,
          },
          headers: { Accept: "application/json" },
          withCredentials: true,
        });
        if (!active) return;
        const map: Record<number, string | null> = {};
        const flagMap: Record<number, {
          forced_log_status?: string | null;
          forced_band_id?: number | null;
          forced_category_id?: number | null;
          forced_power_category_id?: number | null;
          forced_power_w?: number | null;
          forced_sixhr_category?: boolean | null;
        }> = {};
        (res.data.data ?? []).forEach((item) => {
          map[item.log_id] = item.reason ?? null;
          flagMap[item.log_id] = {
            forced_log_status: item.forced_log_status ?? null,
            forced_band_id: item.forced_band_id ?? null,
            forced_category_id: item.forced_category_id ?? null,
            forced_power_category_id: item.forced_power_category_id ?? null,
            forced_power_w: item.forced_power_w ?? null,
            forced_sixhr_category: item.forced_sixhr_category ?? null,
          };
        });
        setOverrideReasons(map);
        setOverrideFlags(flagMap);
      } catch {
        if (!active) return;
        setOverrideReasons({});
        setOverrideFlags({});
      }
    })();
    return () => {
      active = false;
    };
  }, [mode, items]);

  const grouped = useMemo(() => {
    const isSixhr = (r: LogResultItem) => (r.sixhr_category ?? r.log?.sixhr_category) === true;
    const sixhrRankingMode =
      mode === "final"
        ? (items[0]?.evaluation_run?.rule_set?.sixhr_ranking_mode ?? "IARU")
        : "CRK";
    const sixh = items.filter((r) => isSixhr(r));
    const standard = items.filter((r) => !isSixhr(r));
    const powerEligible = standard.filter(
      (r) => !isCheckCategory(r) && !isPowerA(r) && r.power_category_id && r.rank_in_category !== null
    );

    const groupOverall = (list: LogResultItem[]) =>
      groupBy(list, (r) => `${r.band_id ?? "null"}|${r.category_id ?? "null"}`);
    const groupSixhOverall = (list: LogResultItem[]) =>
      groupBy(list, (r) =>
        sixhrRankingMode === "IARU"
          ? `${r.band_id ?? "null"}|ALL`
          : `${r.band_id ?? "null"}|${r.category_id ?? "null"}`
      );
    const groupPower = (list: LogResultItem[]) =>
      groupBy(list, (r) => `${r.band_id ?? "null"}|${r.category_id ?? "null"}|${r.power_category_id ?? "null"}`);

    return {
      sixhOverall: groupSixhOverall(sixh),
      standardOverall: groupOverall(standard),
      standardPower: groupPower(powerEligible),
      sixhrRankingMode,
    };
  }, [items, mode]);

  const overrideVersion = useMemo(() => Object.keys(overrideReasons).sort().join(","), [overrideReasons]);
  const showCalculatedColumns = mode === "final";

  const renderGroup = (
    group: GroupedResults,
    title: string,
    rankField: RankField,
    includePowerInHeading = true,
    includeCategoryInHeading = true
  ) => {
    if (group.length === 0) return [];
    return group.map(({ key, items }) => {
      const sample = items[0];
      const [bandName, categoryName, powerName] = resolveNames(sample);
      const headingParts = [bandName, includeCategoryInHeading ? categoryName : null, title];
      if (includePowerInHeading && !isCheckCategory(sample)) {
        headingParts.push(powerName);
      }
      const heading = headingParts.filter(Boolean).join(" ");
      const sorted = sortResults(items, rankField, mode);
      const headerColumns = [
        <TableColumn key="rank" className="whitespace-nowrap">{t("results_table_rank") ?? "Pořadí"}</TableColumn>,
        <TableColumn key="callsign" className="whitespace-nowrap">{t("results_table_callsign") ?? "Značka v závodě"}</TableColumn>,
        showLocatorOdx
          ? <TableColumn key="locator" className="whitespace-nowrap">{t("results_table_locator") ?? "Lokátor"}</TableColumn>
          : null,
        <TableColumn key="category" className="whitespace-nowrap">{t("results_table_category") ?? "Kategorie"}</TableColumn>,
        <TableColumn key="band" className="whitespace-nowrap">{t("results_table_band") ?? "Pásmo"}</TableColumn>,
        <TableColumn key="power_watt" className="whitespace-nowrap">{t("results_table_power_watt") ?? "Výkon [W]"}</TableColumn>,
        <TableColumn key="power_category" className="whitespace-nowrap">{t("results_table_power_category") ?? "Výkonová kat."}</TableColumn>,
        showScoreTotal
          ? <TableColumn key="score_total" className="whitespace-nowrap">{t("results_table_score_total") ?? "Body celkem"}</TableColumn>
          : null,
        <TableColumn key="claimed_score" className="whitespace-nowrap">{t("results_table_claimed_score") ?? "Deklarované body"}</TableColumn>,
        <TableColumn key="qso_count" className="whitespace-nowrap">{t("results_table_qso_count") ?? "Počet QSO"}</TableColumn>,
        showCalculatedColumns
          ? (
            <TableColumn key="discarded_qso" className="whitespace-nowrap">
              <Tooltip content={t("results_table_discarded_qso_help") ?? "Počet QSO s is_valid=false."}>
                <span className="cursor-help">{t("results_table_discarded_qso") ?? "Vyřazeno QSO"}</span>
              </Tooltip>
            </TableColumn>
          )
          : null,
        showCalculatedColumns
          ? <TableColumn key="discarded_points" className="whitespace-nowrap">{t("results_table_discarded_points") ?? "Vyřazeno bodů"}</TableColumn>
          : null,
        showCalculatedColumns
          ? <TableColumn key="unique_qso" className="whitespace-nowrap">{t("results_table_unique_qso") ?? "Unique QSO"}</TableColumn>
          : null,
        <TableColumn key="score_per_qso" className="whitespace-nowrap">{t("results_table_score_per_qso") ?? "Body / QSO"}</TableColumn>,
        showLocatorOdx
          ? <TableColumn key="odx" className="whitespace-nowrap">{t("results_table_odx") ?? "ODX"}</TableColumn>
          : null,
        <TableColumn key="antenna" className="whitespace-nowrap">{t("results_table_antenna") ?? "Anténa"}</TableColumn>,
        <TableColumn key="antenna_height" className="whitespace-nowrap">{t("results_table_antenna_height") ?? "Ant. height"}</TableColumn>,
        showCalculatedColumns
          ? <TableColumn key="status" className="whitespace-nowrap">{t("results_table_status") ?? "Status"}</TableColumn>
          : null,
        showCalculatedColumns
          ? <TableColumn key="override_reason" className="whitespace-nowrap">{t("results_table_override_reason") ?? "Komentář rozhodčího"}</TableColumn>
          : null,
      ].filter(Boolean);

      return (
        <Card key={key} className="mb-4">
          <CardHeader>
            <div className="flex items-center justify-between w-full gap-2">
              <span className="text-md font-semibold">{heading}</span>
              <button
                type="button"
                onClick={() => exportCsv(heading, sorted, rankField, mode, showLocatorOdx, showScoreTotal)}
                className="text-xs text-primary-600 hover:text-primary-700 underline"
              >
                CSV
              </button>
            </div>
          </CardHeader>
          <Divider />
          <CardBody>
            <Table
              key={`${key}-${overrideVersion}`}
              radius="sm"
              isCompact
              aria-label={heading}
              removeWrapper
              fullWidth
              classNames={{
                th: "py-1 px-1 text-[11px] leading-tight",
                td: "py-0.5 px-1 text-[11px] leading-tight bg-transparent",
              }}
            >
              <TableHeader>{headerColumns}</TableHeader>
              <TableBody emptyContent="No rows to display" items={sorted} className="text-[11px] leading-tight">
                {(item) => {
                  const logId = item.log_id ?? item.log?.id;
                  const hasOverride =
                    logId != null && Object.prototype.hasOwnProperty.call(overrideReasons, logId);
                  const callsignClassName = "whitespace-nowrap";
                  const rowCells = [
                    <TableCell key="rank" className="whitespace-nowrap">{item[rankField] ?? "—"}</TableCell>,
                    <TableCell key="callsign" className={callsignClassName}>
                      {item.log?.pcall ?? "—"}
                    </TableCell>,
                    showLocatorOdx
                      ? <TableCell key="locator" className="whitespace-nowrap">{item.log?.pwwlo ?? "—"}</TableCell>
                      : null,
                    <TableCell key="category" className="whitespace-nowrap">
                      {item.category?.name ?? "—"}
                    </TableCell>,
                    <TableCell key="band" className="whitespace-nowrap">
                      {item.band?.name ?? "—"}
                    </TableCell>,
                    <TableCell key="power_watt" className="whitespace-nowrap">
                      {formatNumber(item.log?.power_watt)}
                    </TableCell>,
                    <TableCell key="power_category" className="whitespace-nowrap">
                      {item.power_category?.name ?? "—"}
                    </TableCell>,
                    showScoreTotal
                      ? <TableCell key="score_total" className="whitespace-nowrap">{formatScore(item, mode)}</TableCell>
                      : null,
                    <TableCell key="claimed_score" className="whitespace-nowrap">{formatNumber(item.claimed_score)}</TableCell>,
                    <TableCell key="qso_count" className="whitespace-nowrap">{formatQsoCount(item, mode)}</TableCell>,
                    showCalculatedColumns
                      ? <TableCell key="discarded_qso" className="whitespace-nowrap">{formatDiscardedQso(item)}</TableCell>
                      : null,
                    showCalculatedColumns
                      ? <TableCell key="discarded_points" className="whitespace-nowrap">{formatNumber(item.discarded_points)}</TableCell>
                      : null,
                    showCalculatedColumns
                      ? <TableCell key="unique_qso" className="whitespace-nowrap">{formatNumber(item.unique_qso_count)}</TableCell>
                      : null,
                    <TableCell key="score_per_qso" className="whitespace-nowrap">{formatScorePerQso(item.score_per_qso)}</TableCell>,
                    showLocatorOdx
                      ? <TableCell key="odx" className="whitespace-nowrap">{item.log?.codxc ?? "—"}</TableCell>
                      : null,
                    <TableCell key="antenna" className="whitespace-nowrap">{item.log?.sante ?? "—"}</TableCell>,
                    <TableCell key="antenna_height" className="whitespace-nowrap">{item.log?.santh ?? "—"}</TableCell>,
                    showCalculatedColumns
                      ? <TableCell key="status" className="whitespace-nowrap">{item.status ?? "—"}</TableCell>
                      : null,
                    showCalculatedColumns
                      ? (
                        <TableCell key="override_reason" className="whitespace-nowrap">
                          {(() => {
                            const overrideReason = hasOverride && logId != null ? overrideReasons[logId] : null;
                            const statusReason = item.status_reason ?? null;
                            if (overrideReason && statusReason) {
                              return `${overrideReason}; ${statusReason}`;
                            }
                            return overrideReason || statusReason || "—";
                          })()}
                        </TableCell>
                      )
                      : null,
                  ].filter(Boolean);
                  return (
                    <TableRow
                      key={item.id}
                      onClick={() => {
                        if (contestId && roundId) {
                          navigate(`/contests/${contestId}/rounds/${roundId}/logs/${item.log_id}`, {
                            state: { from: `${location.pathname}${location.search}` },
                          });
                        }
                      }}
                      className={contestId && roundId ? "cursor-pointer hover:bg-default-100" : undefined}
                    >
                      {rowCells}
                    </TableRow>
                  );
                }}
              </TableBody>
            </Table>
          </CardBody>
        </Card>
      );
    });
  };

  const rankOverallField: RankField = filter === "OK" ? "rank_overall_ok" : "rank_overall";
  const rankInCategoryField: RankField = filter === "OK" ? "rank_in_category_ok" : "rank_in_category";

  const renderOverallWithPowers = (
    overalls: GroupedResults,
    powers: GroupedResults,
    title: string,
    includeCategoryInHeading = true
  ) => {
    if (overalls.length === 0) return null;

    const powerIndex = powers.reduce<Record<string, GroupedResults>>((acc, group) => {
      const [bandId, categoryId] = group.key.split("|");
      const key = `${bandId}|${categoryId}`;
      acc[key] = acc[key] || [];
      acc[key].push(group);
      return acc;
    }, {});

    return sortGroups(overalls).flatMap(({ key, items }) => {
      const [bandId, categoryId] = key.split("|");
      const powerGroups = sortGroups(powerIndex[`${bandId}|${categoryId}`] ?? []);

      return [
        ...renderGroup([{ key, items }], title, rankOverallField, false, includeCategoryInHeading),
        ...renderGroup(powerGroups, "", rankInCategoryField, true),
      ];
    });
  };

  if (!roundId) return null;

  const resultType = items[0]?.evaluation_run?.result_type ?? null;
  const resultTypeLabel =
    resultType === "FINAL"
      ? (t("results_type_final") as string) || "Finální výsledky"
      : resultType === "PRELIMINARY"
        ? (t("results_type_preliminary") as string) || "Předběžné výsledky"
        : resultType === "TEST"
          ? (t("results_type_test") as string) || "Testovací výsledky"
          : null;
  const resultTypeClass =
    resultType === "FINAL"
      ? "bg-success-200 text-success-900"
      : resultType === "PRELIMINARY"
        ? "bg-success-100 text-success-900"
        : resultType === "TEST"
          ? "bg-warning-200 text-warning-900"
          : "";
  useEffect(() => {
    if (!onResultTypeChange) return;
    onResultTypeChange(resultTypeLabel, resultTypeClass, resultType);
  }, [onResultTypeChange, resultTypeLabel, resultTypeClass, resultType]);
  if (loading) {
    const label = mode === "final" ? "Načítám finální výsledky…" : "Načítám deklarované výsledky…";
    return <div className="flex items-center gap-2 text-sm text-foreground-500"><Spinner size="sm" /> {label}</div>;
  }
  if (error) return <div className="text-sm text-red-600">{error}</div>;

  return (
    <div className="space-y-4">
      {mode === "final" && showResultTypeLabel && resultTypeLabel && (
        <div
          className={[
            "inline-flex items-center rounded px-2 py-1 text-xs font-semibold",
            resultTypeClass,
          ]
            .filter(Boolean)
            .join(" ")}
        >
          {resultTypeLabel}
        </div>
      )}
      {renderOverallWithPowers(grouped.standardOverall, grouped.standardPower, "")}
      {renderOverallWithPowers(grouped.sixhOverall, [], "6H", grouped.sixhrRankingMode !== "IARU")}
    </div>
  );
}

type GroupedResults = Array<{ key: string; items: LogResultItem[] }>;

function groupBy(items: LogResultItem[], getKey: (r: LogResultItem) => string): GroupedResults {
  const map = new Map<string, LogResultItem[]>();
  items.forEach((item) => {
    const key = getKey(item);
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(item);
  });
  return Array.from(map.entries()).map(([key, list]) => ({ key, items: list }));
}

function sortGroups(groups: GroupedResults): GroupedResults {
  return [...groups].sort((a, b) => {
    const aItem = a.items[0];
    const bItem = b.items[0];
    const bandOrder = (aItem?.band?.order ?? Number.MAX_SAFE_INTEGER) - (bItem?.band?.order ?? Number.MAX_SAFE_INTEGER);
    if (bandOrder !== 0) return bandOrder;
    const categoryOrder = (aItem?.category?.order ?? Number.MAX_SAFE_INTEGER) - (bItem?.category?.order ?? Number.MAX_SAFE_INTEGER);
    if (categoryOrder !== 0) return categoryOrder;
    const powerOrder =
      (aItem?.power_category?.order ?? Number.MAX_SAFE_INTEGER) -
      (bItem?.power_category?.order ?? Number.MAX_SAFE_INTEGER);
    if (powerOrder !== 0) return powerOrder;
    const bandName = aItem?.band?.name ?? "";
    const bandNameB = bItem?.band?.name ?? "";
    if (bandName !== bandNameB) return bandName.localeCompare(bandNameB);
    const categoryName = aItem?.category?.name ?? "";
    const categoryNameB = bItem?.category?.name ?? "";
    if (categoryName !== categoryNameB) return categoryName.localeCompare(categoryNameB);
    const powerName = aItem?.power_category?.name ?? "";
    const powerNameB = bItem?.power_category?.name ?? "";
    return powerName.localeCompare(powerNameB);
  });
}

function resolveNames(item: LogResultItem): [string | null, string | null, string | null] {
  const band = item.band?.name ?? null;
  const category = item.category?.name ?? null;
  const power = item.power_category?.name ?? null;
  return [band, category, power];
}

type RankField = "rank_overall" | "rank_in_category" | "rank_overall_ok" | "rank_in_category_ok";

function sortResults(items: LogResultItem[], rankField: RankField, mode: "claimed" | "final") {
  return [...items].sort((a, b) => {
    const ra = a[rankField] ?? Number.MAX_SAFE_INTEGER;
    const rb = b[rankField] ?? Number.MAX_SAFE_INTEGER;
    if (ra !== rb) return ra - rb;
    const sa = getScore(a, mode) ?? 0;
    const sb = getScore(b, mode) ?? 0;
    if (sa !== sb) return sb - sa;
    const qa = getQsoCount(a, mode) ?? 0;
    const qb = getQsoCount(b, mode) ?? 0;
    return qb - qa;
  });
}

function formatNumber(value: number | null | undefined) {
  if (value === null || value === undefined) return "—";
  if (Number.isInteger(value)) return value.toString();
  return value.toFixed(1);
}

function formatDiscardedQso(item: LogResultItem) {
  const count = item.discarded_qso_count;
  if (count === null || count === undefined) return "—";
  const percent = item.discarded_qso_percent;
  if (percent === null || percent === undefined) return `${count}`;
  return `${count} (${percent.toFixed(2)}%)`;
}

function formatScorePerQso(value?: number | null) {
  if (value === null || value === undefined) return "—";
  return value.toFixed(2);
}

function getScore(item: LogResultItem, mode: "claimed" | "final") {
  return mode === "final" ? item.official_score ?? null : item.claimed_score ?? null;
}

function getQsoCount(item: LogResultItem, mode: "claimed" | "final") {
  return mode === "final" ? item.valid_qso_count ?? null : item.claimed_qso_count ?? null;
}

function formatScore(item: LogResultItem, mode: "claimed" | "final") {
  return getScore(item, mode) ?? "—";
}

function formatQsoCount(item: LogResultItem, mode: "claimed" | "final") {
  return getQsoCount(item, mode) ?? "—";
}

function isCheckCategory(item: LogResultItem) {
  const name = item.category?.name?.toLowerCase() ?? "";
  return name.includes("check");
}

function isPowerA(item: LogResultItem) {
  const name = item.power_category?.name?.toLowerCase() ?? "";
  return name === "a";
}

function exportCsv(
  title: string,
  rows: LogResultItem[],
  rankField: RankField,
  mode: "claimed" | "final",
  showLocatorOdx: boolean,
  showScoreTotal: boolean
) {
  const includeCalculated = mode === "final";
  const header = [
    "Poradi",
    "Značka v závodě",
    ...(showLocatorOdx ? ["Lokátor"] : []),
    "Kategorie",
    "Pásmo",
    "Výkon [W]",
    "Výkonová kat.",
    ...(showScoreTotal ? ["Body celkem"] : []),
    "Deklarované body",
    "Počet QSO",
    ...(includeCalculated ? ["Vyřazeno QSO", "Vyřazeno bodů", "Unique QSO"] : []),
    "Body / QSO",
    ...(showLocatorOdx ? ["ODX"] : []),
    "Anténa",
    "Ant. height",
  ];

  const lines = rows.map((r) => {
    const score = getScore(r, mode);
    const qsoCount = getQsoCount(r, mode);
    const ratio = formatScorePerQso(r.score_per_qso);
    const discardedQso = formatDiscardedQso(r);
    const base: Array<string | number> = [];
    base.push(r[rankField] ?? "");
    base.push(r.log?.pcall ?? "");
    if (showLocatorOdx) {
      base.push(r.log?.pwwlo ?? "");
    }
    base.push(r.category?.name ?? "");
    base.push(r.band?.name ?? "");
    base.push(r.log?.power_watt ?? "");
    base.push(r.power_category?.name ?? "");
    if (showScoreTotal) {
      base.push(score ?? "");
    }
    base.push(r.claimed_score ?? "");
    base.push(qsoCount ?? "");
    if (includeCalculated) {
      base.push(discardedQso === "—" ? "" : discardedQso);
      base.push(r.discarded_points ?? "");
      base.push(r.unique_qso_count ?? "");
    }
    base.push(ratio === "—" ? "" : ratio);
    if (showLocatorOdx) {
      base.push(r.log?.codxc ?? "");
    }
    base.push(r.log?.sante ?? "");
    base.push(r.log?.santh ?? "");
    return base.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(",");
  });

  const csv = [header.join(","), ...lines].join("\n");
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
  const safeTitle = title && title.trim() !== "" ? title : "results";
  saveAs(blob, `${safeTitle.replace(/\s+/g, "_")}.csv`);
}
