import { memo, useCallback, useEffect, useMemo, useState } from "react";
import axios from "axios";
import {
  Button,
  Checkbox,
  Input,
  Modal,
  ModalBody,
  ModalContent,
  ModalHeader,
  Select,
  SelectItem,
  Table,
  TableBody,
  TableCell,
  TableColumn,
  TableHeader,
  TableRow,
  Textarea,
  Tooltip,
  type Selection,
  useDisclosure,
} from "@heroui/react";
import { useTranslation } from "react-i18next";

type LogQso = {
  id: number;
  log_id: number;
  time_on?: string | null;
  band?: string | null;
  mode?: string | null;
  my_call?: string | null;
  dx_call?: string | null;
  my_locator?: string | null;
  my_rst?: string | null;
  dx_rst?: string | null;
  my_serial?: string | null;
  dx_serial?: string | null;
  rx_wwl?: string | null;
  log?: {
    id: number;
    round_id?: number | null;
  } | null;
};

type WorkingQso = {
  loc_norm?: string | null;
  rloc_norm?: string | null;
  errors?: string[] | null;
};

type QsoResult = {
  id: number;
  log_qso_id: number;
  matched_qso_id?: number | null;
  error_code?: string | null;
  error_side?: string | null;
  match_confidence?: string | null;
  error_detail?: string | null;
  is_nil?: boolean;
  is_duplicate?: boolean;
  is_busted_call?: boolean;
  is_busted_exchange?: boolean;
  is_time_out_of_window?: boolean;
  log_qso?: LogQso | null;
  matched_qso?: LogQso | null;
  working_qso?: WorkingQso | null;
};

type QsoOverride = {
  id: number;
  evaluation_run_id: number;
  log_qso_id: number;
  forced_matched_log_qso_id?: number | null;
  forced_status?: string | null;
  reason?: string | null;
};

type LogEntry = {
  id: number;
  pcall?: string | null;
  psect?: string | null;
  pband?: string | null;
  power_category?: string | null;
  sixhr_category?: boolean | null;
  pwwlo?: string | null;
  locator?: string | null;
};

type PaginatedResponse<T> = {
  data: T[];
  current_page: number;
  last_page: number;
  total: number;
};

type Props = {
  roundId: number | null;
  evaluationRunId: number;
};

type OverrideForm = {
  status: string;
  matchedId: string;
  reason: string;
  saving: boolean;
  error?: string | null;
  success?: string | null;
};

const statusOptions = [
  { value: "AUTO", label: "AUTO" },
  { value: "VALID", label: "VALID" },
  { value: "INVALID", label: "INVALID" },
  { value: "NIL", label: "NIL" },
  { value: "DUPLICATE", label: "DUPLICATE" },
  { value: "BUSTED_CALL", label: "BUSTED_CALL" },
  { value: "BUSTED_EXCHANGE", label: "BUSTED_EXCHANGE" },
  { value: "OUT_OF_WINDOW", label: "OUT_OF_WINDOW" },
];

const issueOptions = [
  { value: "PROBLEMS", label: "Všechny problémy" },
  { value: "OK_ONLY", label: "Pouze v pořádku" },
  { value: "ALL", label: "Všechny QSO" },
  { value: "NOT_IN_COUNTERPART_LOG", label: "NOT_IN_COUNTERPART_LOG" },
  { value: "NO_COUNTERPART_LOG", label: "NO_COUNTERPART_LOG" },
  { value: "UNIQUE", label: "UNIQUE" },
  { value: "DUP", label: "DUP" },
  { value: "BUSTED_CALL", label: "BUSTED_CALL" },
  { value: "BUSTED_RST", label: "BUSTED_RST" },
  { value: "BUSTED_SERIAL", label: "BUSTED_SERIAL" },
  { value: "BUSTED_LOCATOR", label: "BUSTED_LOCATOR" },
  { value: "TIME_MISMATCH", label: "TIME_MISMATCH" },
  { value: "OUT_OF_WINDOW", label: "OUT_OF_WINDOW" },
  { value: "MISSING_LOCATOR", label: "Chybí lokátor" },
];

const getFirstSelection = (keys: Selection) => {
  if (keys === "all") return "";
  const [first] = Array.from(keys);
  return typeof first === "string" || typeof first === "number" ? String(first) : "";
};

type OverridePayload = {
  status: string;
  matchedId: string;
  reason: string;
};

type QsoOverrideRowProps = {
  item: QsoResult;
  form?: OverrideForm;
  override?: QsoOverride;
  selected: boolean;
  onToggleSelected: (logQsoId: number, checked: boolean) => void;
  onOpenMatcher: (item: QsoResult) => void;
  onFieldChange: (logQsoId: number, field: keyof OverrideForm, value: string) => void;
  onReasonCommit: (logQsoId: number, reason: string) => void;
  onSave: (logQsoId: number, payload: OverridePayload) => void;
  problemLabel: (item: QsoResult) => string;
  errorDetailLabel: (item: QsoResult) => string | null;
  problemTooltip: (item: QsoResult) => string[] | null;
  t: (key: string) => string;
};

const QsoOverrideRow = memo(function QsoOverrideRow({
  item,
  form,
  override,
  selected,
  onToggleSelected,
  onOpenMatcher,
  onFieldChange,
  onReasonCommit,
  onSave,
  problemLabel,
  errorDetailLabel,
  problemTooltip,
  t,
}: QsoOverrideRowProps) {
  const logQso = item.log_qso;
  const matched = item.matched_qso;
  const issue = problemLabel(item);
  const errorDetail = errorDetailLabel(item);
  const tooltipContent = problemTooltip(item);
  const highlightStatus = !!override?.forced_status && override.forced_status !== "AUTO";
  const highlightMatched =
    override?.forced_matched_log_qso_id !== null && override?.forced_matched_log_qso_id !== undefined;

  const statusValue = form?.status ?? "AUTO";
  const matchedId = form?.matchedId ?? "";
  const [localReason, setLocalReason] = useState(form?.reason ?? override?.reason ?? "");

  useEffect(() => {
    setLocalReason(form?.reason ?? override?.reason ?? "");
  }, [form?.reason, override?.reason]);

  return (
    <div className="rounded border border-divider px-2 py-2 text-xs">
      <div className="flex flex-col gap-2 md:flex-row md:items-start md:gap-3">
        <div className="flex-1 min-w-0">
          <div className="flex flex-wrap items-center gap-2 mb-1">
            <Checkbox
              size="sm"
              isSelected={selected}
              onValueChange={(checked) => onToggleSelected(item.log_qso_id, checked)}
            >
              Vybrat
            </Checkbox>
            <span className="font-semibold">{logQso?.my_call ?? "—"}</span>
            <span>→ {logQso?.dx_call ?? "—"}</span>
            <span>{logQso?.band ?? "—"}</span>
            <span>{logQso?.time_on ?? "—"}</span>
            {tooltipContent ? (
              <Tooltip
                content={
                  <div className="text-xs leading-snug">
                    {tooltipContent.map((line, index) => (
                      <div key={index}>{line}</div>
                    ))}
                  </div>
                }
              >
                <span className="text-foreground-500 cursor-help">
                  {t("qso_problem_label") ?? "Problém"}: {issue}
                </span>
              </Tooltip>
            ) : (
              <span className="text-foreground-500">
                {t("qso_problem_label") ?? "Problém"}: {issue}
              </span>
            )}
            <span className="text-foreground-500">
              {t("qso_problem_side") ?? "Strana"}: {item.error_side ?? "—"}
            </span>
            <span className="text-foreground-500">
              {t("qso_problem_confidence") ?? "Match"}: {item.match_confidence ?? "—"}
            </span>
          </div>
          {errorDetail && (
            <div className="text-foreground-500 mb-2">
              <span className="font-semibold">{t("qso_problem_reason") ?? "Důvod"}:</span>{" "}
              {errorDetail}
            </div>
          )}
          <div className="text-foreground-500 mb-1">
            Matched: {item.matched_qso_id ?? "—"}{" "}
            {matched ? `(${matched.my_call} → ${matched.dx_call})` : ""}
          </div>
          <div className="mb-1">
            <div className="grid grid-cols-8 gap-2 text-[11px] text-foreground-500">
              <span>Stanice</span>
              <span>Odesl.</span>
              <span>Přijat.</span>
              <span>RST odesl.</span>
              <span>RST přijat.</span>
              <span>Loc DE</span>
              <span>Loc DX</span>
              <span>ID</span>
            </div>
            <div className="grid grid-cols-8 gap-2 text-[11px] text-foreground-600">
              <span>{logQso?.my_call ?? "—"}</span>
              <span>{logQso?.my_serial ?? "—"}</span>
              <span>{logQso?.dx_serial ?? "—"}</span>
              <span>{logQso?.my_rst ?? "—"}</span>
              <span>{logQso?.dx_rst ?? "—"}</span>
              <span>{item.working_qso?.loc_norm ?? "—"}</span>
              <span>{item.working_qso?.rloc_norm ?? "—"}</span>
              <span>{logQso?.id ?? "—"}</span>
            </div>
            <div className="grid grid-cols-8 gap-2 text-[11px] text-foreground-600">
              <span>{matched?.my_call ?? "—"}</span>
              <span>{matched?.my_serial ?? "—"}</span>
              <span>{matched?.dx_serial ?? "—"}</span>
              <span>{matched?.my_rst ?? "—"}</span>
              <span>{matched?.dx_rst ?? "—"}</span>
              <span>{matched?.my_locator ?? "—"}</span>
              <span>{matched?.rx_wwl ?? "—"}</span>
              <span>{matched?.id ?? "—"}</span>
            </div>
            {item.working_qso?.errors && item.working_qso.errors.length > 0 && (
              <div className="mt-1 text-[11px] text-foreground-500">
                Errors: {item.working_qso.errors.join(", ")}
              </div>
            )}
          </div>
          {override?.reason && (
            <div className="mb-1 text-foreground-500">Důvod: {override.reason}</div>
          )}
        </div>
        <div className="w-full md:w-auto">
          <div className="flex flex-col gap-3 md:flex-row">
            <div className="w-full md:w-56 lg:w-60">
              <div className="grid gap-1">
                <Select
                  aria-label="Forced status"
                  size="sm"
                  variant="bordered"
                  label="Forced status"
                  classNames={
                    highlightStatus ? { trigger: "border-warning-400 bg-warning-50" } : undefined
                  }
                  selectedKeys={new Set([statusValue])}
                  onSelectionChange={(keys) =>
                    onFieldChange(item.log_qso_id, "status", getFirstSelection(keys) || "AUTO")
                  }
                  selectionMode="single"
                  disallowEmptySelection={true}
                >
                  {statusOptions.map((opt) => (
                    <SelectItem key={opt.value} textValue={opt.label}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </Select>
                <div className="flex flex-col gap-1">
                  <div className="flex items-center gap-2">
                    <Button
                      type="button"
                      size="sm"
                      variant="bordered"
                      className={highlightMatched ? "border-warning-400 bg-warning-50" : undefined}
                      onPress={() => onOpenMatcher(item)}
                    >
                      Ručně vybrat
                    </Button>
                    <span className="text-foreground-500 text-xs">
                      {matchedId ? `Vybráno: ${matchedId}` : "AUTO"}
                    </span>
                    {matchedId && (
                      <Button
                        type="button"
                        size="sm"
                        variant="light"
                        onPress={() => onFieldChange(item.log_qso_id, "matchedId", "")}
                      >
                        Zrušit
                      </Button>
                    )}
                  </div>
                </div>
              </div>
            </div>
            <div className="w-full md:w-64 lg:w-72">
              <Textarea
                label="Důvod změny"
                size="sm"
                variant="bordered"
                minRows={2}
                value={localReason}
                onChange={(e) => setLocalReason(e.target.value)}
                onBlur={() => onReasonCommit(item.log_qso_id, localReason)}
                placeholder="Krátce popiš, proč zasahuješ."
              />
              <div className="mt-1 flex items-center gap-2">
                <Button
                  type="button"
                  size="sm"
                  color="primary"
                  onPress={() =>
                    onSave(item.log_qso_id, {
                      status: statusValue,
                      matchedId,
                      reason: localReason,
                    })
                  }
                  isDisabled={form?.saving}
                >
                  {form?.saving ? "Ukládám…" : "Uložit"}
                </Button>
                {form?.error && <span className="text-red-600">{form.error}</span>}
                {form?.success && <span className="text-green-600">{form.success}</span>}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
});

export default function RoundEvaluationQsoOverrides({ roundId, evaluationRunId }: Props) {
  const { t } = useTranslation("common");
  const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
  const [items, setItems] = useState<QsoResult[]>([]);
  const [overrides, setOverrides] = useState<Record<number, QsoOverride>>({});
  const [forms, setForms] = useState<Record<number, OverrideForm>>({});
  const [loading, setLoading] = useState(false);
  const [issueFilter, setIssueFilter] = useState("PROBLEMS");
  const [searchCallInput, setSearchCallInput] = useState("");
  const [searchMatchedInput, setSearchMatchedInput] = useState("");
  const [searchCall, setSearchCall] = useState("");
  const [searchMatchedId, setSearchMatchedId] = useState("");
  const [selectedLogId, setSelectedLogId] = useState("");
  const [logOptions, setLogOptions] = useState<LogEntry[]>([]);
  const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
  const [bulkStatus, setBulkStatus] = useState("AUTO");
  const [bulkMatchedId, setBulkMatchedId] = useState("");
  const [bulkReason, setBulkReason] = useState("");
  const [bulkLoading, setBulkLoading] = useState(false);
  const [bulkMessage, setBulkMessage] = useState<string | null>(null);
  const [bulkError, setBulkError] = useState<string | null>(null);
  const [page, setPage] = useState(1);
  const [lastPage, setLastPage] = useState(1);
  const perPage = 100;
  const [matcherTarget, setMatcherTarget] = useState<QsoResult | null>(null);
  const [matcherQuery, setMatcherQuery] = useState("");
  const [matcherItems, setMatcherItems] = useState<LogQso[]>([]);
  const [matcherPage, setMatcherPage] = useState(1);
  const [matcherLastPage, setMatcherLastPage] = useState(1);
  const [matcherLoading, setMatcherLoading] = useState(false);
  const [matcherError, setMatcherError] = useState<string | null>(null);

  const hasMissingLocator = (item: QsoResult) => {
    const working = item.working_qso;
    if (!working) return false;
    const errors = working.errors || [];
    return !working.loc_norm || !working.rloc_norm || errors.includes("INVALID_LOCATOR") || errors.includes("INVALID_RLOCATOR");
  };

  const problemLabel = useMemo(() => {
    return (item: QsoResult) => {
      if (issueFilter === "MISSING_LOCATOR" && hasMissingLocator(item)) return "MISSING_LOCATOR";
      if (item.error_code) return item.error_code;
      if (item.is_nil) return "NIL";
      if (item.is_duplicate) return "DUP";
      if (item.is_busted_call) return "BUSTED_CALL";
      if (item.is_busted_exchange) return "BUSTED_EXCHANGE";
      if (item.is_time_out_of_window) return "OUT_OF_WINDOW";
      return "—";
    };
  }, [issueFilter]);

  const errorDetailLabel = useMemo(() => {
    return (item: QsoResult) => {
      if (!item.error_detail) return null;
      const key = `qso_error_detail_${item.error_detail.toLowerCase()}`;
      return t(key) ?? item.error_detail;
    };
  }, [t]);

  const problemTooltip = useMemo(() => {
    return (item: QsoResult) => {
      const details: string[] = [];
      if (item.error_detail) {
        const key = `qso_error_detail_${item.error_detail.toLowerCase()}`;
        details.push(
          `${t("qso_problem_reason") ?? "Důvod"}: ${t(key) ?? item.error_detail}`
        );
      }
      const errors = item.working_qso?.errors ?? [];
      if (errors.length > 0) {
        details.push(`${t("qso_problem_errors") ?? "Chyby"}: ${errors.join(", ")}`);
      }
      return details.length > 0 ? details : null;
    };
  }, [t]);

  useEffect(() => {
    let active = true;
    (async () => {
      try {
        const res = await axios.get<PaginatedResponse<QsoOverride>>("/api/qso-overrides", {
          headers: { Accept: "application/json" },
          params: { evaluation_run_id: evaluationRunId, per_page: 500 },
          withCredentials: true,
        });
        if (!active) return;
        const map: Record<number, QsoOverride> = {};
        res.data.data.forEach((item) => {
          map[item.log_qso_id] = item;
        });
        setOverrides(map);
      } catch {
        if (!active) return;
        setOverrides({});
      }
    })();
    return () => {
      active = false;
    };
  }, [evaluationRunId]);

  useEffect(() => {
    let active = true;
    if (!roundId) return undefined;
    (async () => {
      try {
        const res = await axios.get<PaginatedResponse<LogEntry>>("/api/logs", {
          headers: { Accept: "application/json" },
          params: { round_id: roundId, per_page: 500 },
          withCredentials: true,
        });
        if (!active) return;
        setLogOptions(res.data.data ?? []);
      } catch {
        if (!active) return;
        setLogOptions([]);
      }
    })();
    return () => {
      active = false;
    };
  }, [roundId]);

  useEffect(() => {
    let active = true;
    (async () => {
      try {
        setLoading(true);
        const params: Record<string, unknown> = {
          evaluation_run_id: evaluationRunId,
          per_page: perPage,
          page,
        };
        if (issueFilter === "PROBLEMS") params.only_problems = true;
        if (issueFilter === "OK_ONLY") params.only_ok = true;
        if (issueFilter === "NOT_IN_COUNTERPART_LOG") params.error_code = "NOT_IN_COUNTERPART_LOG";
        if (issueFilter === "NO_COUNTERPART_LOG") params.error_code = "NO_COUNTERPART_LOG";
        if (issueFilter === "UNIQUE") params.error_code = "UNIQUE";
        if (issueFilter === "DUP") params.error_code = "DUP";
        if (issueFilter === "BUSTED_CALL") params.error_code = "BUSTED_CALL";
        if (issueFilter === "BUSTED_RST") params.error_code = "BUSTED_RST";
        if (issueFilter === "BUSTED_SERIAL") params.error_code = "BUSTED_SERIAL";
        if (issueFilter === "BUSTED_LOCATOR") params.error_code = "BUSTED_LOCATOR";
        if (issueFilter === "TIME_MISMATCH") params.error_code = "TIME_MISMATCH";
        if (issueFilter === "OUT_OF_WINDOW") params.is_time_out_of_window = true;
        if (issueFilter === "MISSING_LOCATOR") params.missing_locator = true;
        if (searchCall) params.call_like = searchCall;
        if (searchMatchedId) params.matched_qso_id = searchMatchedId;
        if (selectedLogId) params.log_id = selectedLogId;

        const res = await axios.get<PaginatedResponse<QsoResult>>("/api/qso-results", {
          headers: { Accept: "application/json" },
          params,
          withCredentials: true,
        });
        if (!active) return;
        setItems(res.data.data);
        setLastPage(res.data.last_page ?? 1);
      } catch {
        if (!active) return;
        setItems([]);
      } finally {
        if (active) setLoading(false);
      }
    })();
    return () => {
      active = false;
    };
  }, [evaluationRunId, issueFilter, page, searchCall, searchMatchedId, selectedLogId]);

  useEffect(() => {
    if (!items.length) return;
    setForms((prev) => {
      const next = { ...prev };
      for (const item of items) {
        const override = overrides[item.log_qso_id];
        if (!next[item.log_qso_id]) {
          next[item.log_qso_id] = {
            status: override?.forced_status ?? "AUTO",
            matchedId: override?.forced_matched_log_qso_id ? String(override.forced_matched_log_qso_id) : "",
            reason: override?.reason ?? "",
            saving: false,
            error: null,
            success: null,
          };
        }
      }
      return next;
    });
  }, [items, overrides]);

  const applySearch = () => {
    setSearchCall(searchCallInput.trim());
    setSearchMatchedId(searchMatchedInput.trim());
    setPage(1);
  };

  const clearSearch = () => {
    setSearchCallInput("");
    setSearchMatchedInput("");
    setSearchCall("");
    setSearchMatchedId("");
    setPage(1);
  };

  const activeFilterSummary = useMemo(() => {
    const parts: string[] = [];
    const issueLabel = issueOptions.find((opt) => opt.value === issueFilter)?.label;
    if (issueLabel) parts.push(`Filtr: ${issueLabel}`);
    if (searchCall) parts.push(`Callsign: ${searchCall}`);
    if (searchMatchedId) parts.push(`Matched ID: ${searchMatchedId}`);
    if (selectedLogId) {
      const log = logOptions.find((item) => String(item.id) === selectedLogId);
      if (log) {
        const locator = log.pwwlo || log.locator || "—";
        const sixh = log.sixhr_category ? " 6H" : "";
        parts.push(
          `Log: ${log.pcall ?? "—"} | ${log.psect ?? "—"} ${log.pband ?? ""} | ${
            log.power_category ?? "—"
          }${sixh} | ${locator}`
        );
      } else {
        parts.push(`Log: ${selectedLogId}`);
      }
    }
    return parts.length ? parts.join(" | ") : null;
  }, [issueFilter, searchCall, searchMatchedId, selectedLogId, logOptions]);

  const filteredItems = useMemo(() => items, [items]);

  const handleFieldChange = useCallback(
    (logQsoId: number, field: keyof OverrideForm, value: string) => {
      setForms((prev) => ({
        ...prev,
        [logQsoId]: {
          ...prev[logQsoId],
          [field]: value,
          error: null,
          success: null,
        },
      }));
    },
    []
  );

  const commitReason = useCallback((logQsoId: number, reason: string) => {
    setForms((prev) => ({
      ...prev,
      [logQsoId]: {
        ...prev[logQsoId],
        reason,
        error: null,
        success: null,
      },
    }));
  }, []);

  const toggleSelected = useCallback((logQsoId: number, checked: boolean) => {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (checked) {
        next.add(logQsoId);
      } else {
        next.delete(logQsoId);
      }
      return next;
    });
  }, []);

  const toggleSelectAll = useCallback(
    (checked: boolean) => {
      if (!checked) {
        setSelectedIds(new Set());
        return;
      }
      const next = new Set<number>();
      filteredItems.forEach((item) => {
        next.add(item.log_qso_id);
      });
      setSelectedIds(next);
    },
    [filteredItems]
  );

  const runBulkUpdate = async (ids: number[], status: string, matchedId: string, reason: string) => {
    if (!ids.length) return;
    if (!reason.trim()) {
      setBulkError("Doplň důvod změny.");
      return;
    }
    setBulkLoading(true);
    setBulkMessage(null);
    setBulkError(null);

    try {
      for (const logQsoId of ids) {
        const override = overrides[logQsoId];
        const hasStatus = status && status !== "AUTO";
        const hasMatched = matchedId !== "";
        const hasAny = hasStatus || hasMatched;

        if (!hasAny && !override) {
          continue;
        }

        const payload = {
          evaluation_run_id: evaluationRunId,
          log_qso_id: logQsoId,
          forced_status: status || "AUTO",
          forced_matched_log_qso_id: matchedId ? Number(matchedId) : null,
          reason: reason.trim(),
        };

        if (override) {
          const res = await axios.put<QsoOverride>(`/api/qso-overrides/${override.id}`, payload, {
            headers: { Accept: "application/json" },
            withCredentials: true,
            withXSRFToken: true,
          });
          setOverrides((prev) => ({ ...prev, [logQsoId]: res.data }));
        } else {
          const res = await axios.post<QsoOverride>("/api/qso-overrides", payload, {
            headers: { Accept: "application/json" },
            withCredentials: true,
            withXSRFToken: true,
          });
          setOverrides((prev) => ({ ...prev, [logQsoId]: res.data }));
        }
      }

      setBulkMessage("Hromadná změna uložena.");
      setSelectedIds(new Set());
      setBulkReason("");
    } catch (e: any) {
      const msg = e?.response?.data?.message || "Nepodařilo se uložit hromadné změny.";
      setBulkError(msg);
    } finally {
      setBulkLoading(false);
    }
  };

  const applyBulkStatus = async () => {
    await runBulkUpdate(Array.from(selectedIds), bulkStatus, bulkMatchedId, bulkReason);
  };

  const applyBulkPreset = async (status: string, ids: number[]) => {
    if (!ids.length) return;
    setSelectedIds(new Set(ids));
    setBulkStatus(status);
    setBulkMatchedId("");
    await runBulkUpdate(ids, status, "", bulkReason);
  };

  const openMatcher = useCallback(
    (item: QsoResult) => {
      setMatcherTarget(item);
      setMatcherQuery(item.log_qso?.dx_call ?? "");
      setMatcherItems([]);
      setMatcherPage(1);
      setMatcherLastPage(1);
      setMatcherError(null);
      onOpen();
    },
    [onOpen]
  );

  const fetchMatcher = async (target: QsoResult, pageOverride?: number) => {
    const query = matcherQuery.trim();
    if (!query) {
      setMatcherError("Zadej volací znak pro vyhledání.");
      return;
    }
    const pageToUse = pageOverride ?? matcherPage;
    setMatcherLoading(true);
    setMatcherError(null);
    try {
      const res = await axios.get<PaginatedResponse<LogQso>>("/api/log-qsos", {
        headers: { Accept: "application/json" },
        params: {
          round_id: roundId,
          call_like: query,
          exclude_log_qso_id: target.log_qso_id,
          exclude_log_id: target.log_qso?.log_id,
          per_page: 20,
          page: pageToUse,
        },
        withCredentials: true,
      });
      setMatcherItems(res.data.data ?? []);
      setMatcherLastPage(res.data.last_page ?? 1);
    } catch (e: any) {
      const msg = e?.response?.data?.message || "Nepodařilo se načíst QSO.";
      setMatcherError(msg);
      setMatcherItems([]);
      setMatcherLastPage(1);
    } finally {
      setMatcherLoading(false);
    }
  };

  useEffect(() => {
    if (!isOpen) {
      setMatcherTarget(null);
    }
  }, [isOpen]);

  const saveOverride = useCallback(
    async (logQsoId: number, payloadForm: OverridePayload) => {
      const override = overrides[logQsoId];
      const hasStatus = payloadForm.status && payloadForm.status !== "AUTO";
      const hasMatched = payloadForm.matchedId !== "";
      const hasAny = hasStatus || hasMatched;
      const reason = payloadForm.reason.trim();
      const baseline = {
        status: override?.forced_status ?? "AUTO",
        matchedId: override?.forced_matched_log_qso_id ? String(override.forced_matched_log_qso_id) : "",
      };
      const hasChanges =
        payloadForm.status !== baseline.status ||
        payloadForm.matchedId !== baseline.matchedId;

      setForms((prev) => {
        const current = prev[logQsoId] ?? {
          status: payloadForm.status,
          matchedId: payloadForm.matchedId,
          reason: payloadForm.reason,
          saving: false,
          error: null,
          success: null,
        };
        return {
          ...prev,
          [logQsoId]: {
            ...current,
            status: payloadForm.status,
            matchedId: payloadForm.matchedId,
            reason: payloadForm.reason,
            saving: true,
            error: null,
            success: null,
          },
        };
      });

      try {
        if (!hasChanges && !override) {
          setForms((prev) => ({
            ...prev,
            [logQsoId]: { ...prev[logQsoId], saving: false, success: "Bez změn." },
          }));
          return;
        }

        if (!hasChanges && override) {
          setForms((prev) => ({
            ...prev,
            [logQsoId]: { ...prev[logQsoId], saving: false, success: "Bez změn." },
          }));
          return;
        }

        if (!hasAny && override) {
          await axios.delete(`/api/qso-overrides/${override.id}`, {
            headers: { Accept: "application/json" },
            withCredentials: true,
            withXSRFToken: true,
          });
          setOverrides((prev) => {
            const next = { ...prev };
            delete next[logQsoId];
            return next;
          });
          setForms((prev) => ({
            ...prev,
            [logQsoId]: { ...prev[logQsoId], saving: false, success: "Uloženo." },
          }));
          return;
        }

        if (!reason) {
          setForms((prev) => ({
            ...prev,
            [logQsoId]: { ...prev[logQsoId], saving: false, error: "Doplň důvod změny." },
          }));
          return;
        }

        const payload = {
          evaluation_run_id: evaluationRunId,
          log_qso_id: logQsoId,
          forced_status: payloadForm.status || "AUTO",
          forced_matched_log_qso_id: payloadForm.matchedId ? Number(payloadForm.matchedId) : null,
          reason,
        };

        if (override) {
          const res = await axios.put<QsoOverride>(`/api/qso-overrides/${override.id}`, payload, {
            headers: { Accept: "application/json" },
            withCredentials: true,
            withXSRFToken: true,
          });
          setOverrides((prev) => ({ ...prev, [logQsoId]: res.data }));
        } else {
          const res = await axios.post<QsoOverride>("/api/qso-overrides", payload, {
            headers: { Accept: "application/json" },
            withCredentials: true,
            withXSRFToken: true,
          });
          setOverrides((prev) => ({ ...prev, [logQsoId]: res.data }));
        }

        setForms((prev) => ({
          ...prev,
          [logQsoId]: { ...prev[logQsoId], saving: false, success: "Uloženo." },
        }));
      } catch (e: any) {
        const msg = e?.response?.data?.message || "Nepodařilo se uložit override.";
        setForms((prev) => ({
          ...prev,
          [logQsoId]: { ...prev[logQsoId], saving: false, error: msg },
        }));
      }
    },
    [evaluationRunId, overrides]
  );

  if (!roundId) return null;

  return (
    <div className="pt-2 border-t border-divider">
      <div className="font-semibold text-sm mb-2">Ruční zásahy po matchingu</div>
      <div className="text-xs text-foreground-500 mb-2">
        Změny se projeví po kliknutí na „Pokračovat“. Slouží pro úpravy NIL/DUP/busted a párování.
      </div>
      <div className="mb-1 flex flex-wrap items-end gap-2 text-xs">
        <Select
          aria-label="Filtr"
          size="sm"
          variant="bordered"
          label="Filtr"
          className="w-80"
          selectedKeys={new Set([issueFilter])}
          onSelectionChange={(keys) => {
            const value = getFirstSelection(keys);
            if (!value) return;
            setIssueFilter(value);
            setPage(1);
          }}
          selectionMode="single"
          disallowEmptySelection={true}
        >
          {issueOptions.map((opt) => (
            <SelectItem key={opt.value} textValue={opt.label}>
              {opt.label}
            </SelectItem>
          ))}
        </Select>
        <Select
          aria-label="Log"
          size="sm"
          variant="bordered"
          label="Log"
          className="w-80"
          selectedKeys={new Set([selectedLogId || "ALL"])}
          onSelectionChange={(keys) => {
            const value = getFirstSelection(keys);
            const next = value === "ALL" ? "" : value;
            setSelectedLogId(next);
            setPage(1);
          }}
          selectionMode="single"
          disallowEmptySelection={true}
        >
          <SelectItem key="ALL" textValue="Všechny logy">
            Všechny logy
          </SelectItem>
          {logOptions.map((log) => {
            const locator = log.pwwlo || log.locator || "—";
            const sixh = log.sixhr_category ? " 6H" : "";
            const label = `${log.pcall ?? "—"} | ${log.psect ?? "—"} ${log.pband ?? ""} | ${
              log.power_category ?? "—"
            }${sixh} | ${locator}`;
            return (
              <SelectItem key={String(log.id)} textValue={label}>
                {label}
              </SelectItem>
            );
          })}
        </Select>
        <Input
          type="text"
          size="sm"
          variant="bordered"
          label="Callsign"
          value={searchCallInput}
          onChange={(e) => setSearchCallInput(e.target.value)}
          placeholder="OK1* / OL?ABC"
          className="w-48"
          onKeyDown={(e) => {
            if (e.key === "Enter") applySearch();
          }}
        />
        <Input
          type="text"
          size="sm"
          variant="bordered"
          label="Matched ID"
          value={searchMatchedInput}
          onChange={(e) => setSearchMatchedInput(e.target.value)}
          placeholder="12345"
          className="w-28"
          onKeyDown={(e) => {
            if (e.key === "Enter") applySearch();
          }}
        />
        <Button type="button" size="sm" variant="bordered" onPress={applySearch}>
          Hledat
        </Button>
        <Button type="button" size="sm" variant="light" onPress={clearSearch}>
          Vyčistit
        </Button>
      </div>
      <div className="mb-1 text-[11px] text-foreground-500">
        Filtr se aplikuje průběžně, hledání spustíš tlačítkem „Hledat“.
      </div>
      {activeFilterSummary && (
        <div className="mb-1 text-[11px] text-foreground-600">{activeFilterSummary}</div>
      )}
      <div className="mb-1 flex flex-wrap items-end gap-2 text-xs">
        <Select
          aria-label="Bulk status"
          size="sm"
          variant="bordered"
          label="Status"
          className="w-36"
          selectedKeys={new Set([bulkStatus])}
          onSelectionChange={(keys) => {
            const value = getFirstSelection(keys);
            if (value) setBulkStatus(value);
          }}
          selectionMode="single"
          disallowEmptySelection={true}
        >
          {statusOptions.map((opt) => (
            <SelectItem key={opt.value} textValue={opt.label}>
              {opt.label}
            </SelectItem>
          ))}
        </Select>
        <Input
          type="number"
          size="sm"
          variant="bordered"
          label="Matched ID"
          className="w-28"
          value={bulkMatchedId}
          onChange={(e) => setBulkMatchedId(e.target.value)}
          placeholder="matched id"
        />
        <Input
          type="text"
          size="sm"
          variant="bordered"
          label="Důvod"
          className="w-64"
          value={bulkReason}
          onChange={(e) => setBulkReason(e.target.value)}
          placeholder="Krátce popiš, proč zasahuješ."
        />
        <Button
          type="button"
          size="sm"
          color="primary"
          onPress={applyBulkStatus}
          isDisabled={!selectedIds.size || bulkLoading}
        >
          {bulkLoading ? "Ukládám…" : "Použít na vybrané"}
        </Button>
        {bulkMessage && <span className="text-green-600">{bulkMessage}</span>}
        {bulkError && <span className="text-red-600">{bulkError}</span>}
      </div>
      <div className="mb-1 text-xs">
        <Checkbox
          size="sm"
          isSelected={selectedIds.size > 0 && selectedIds.size === filteredItems.length}
          onValueChange={(checked) => toggleSelectAll(checked)}
        >
          Vybrat vše na stránce
        </Checkbox>
      </div>
      {loading && <div className="text-xs text-foreground-600">Načítám QSO…</div>}
      {!loading && filteredItems.length === 0 && (
        <div className="text-xs text-foreground-600">Žádné položky k úpravě.</div>
      )}
      {filteredItems.length > 0 && (
        <div className="space-y-2">
          {filteredItems.map((item) => (
            <QsoOverrideRow
              key={item.id}
              item={item}
              form={forms[item.log_qso_id]}
              override={overrides[item.log_qso_id]}
              selected={selectedIds.has(item.log_qso_id)}
              onToggleSelected={toggleSelected}
              onOpenMatcher={openMatcher}
              onFieldChange={handleFieldChange}
              onReasonCommit={commitReason}
              onSave={saveOverride}
              problemLabel={problemLabel}
              errorDetailLabel={errorDetailLabel}
              problemTooltip={problemTooltip}
              t={t}
            />
          ))}
          <div className="flex items-center gap-2 text-xs">
            <Button
              type="button"
              size="sm"
              variant="bordered"
              onPress={() => setPage((p) => Math.max(1, p - 1))}
              isDisabled={page <= 1}
            >
              Předchozí
            </Button>
            <span>
              Strana {page} / {lastPage}
            </span>
            <Button
              type="button"
              size="sm"
              variant="bordered"
              onPress={() => setPage((p) => Math.min(lastPage, p + 1))}
              isDisabled={page >= lastPage}
            >
              Další
            </Button>
          </div>
        </div>
      )}
      <Modal isOpen={isOpen} onOpenChange={onOpenChange} size="5xl" scrollBehavior="inside">
        <ModalContent>
          <ModalHeader className="flex flex-col gap-1">Vyhledat protistanici</ModalHeader>
          <ModalBody>
            {matcherTarget && (
              <div className="text-xs text-foreground-500 mb-2">
                <div>
                  QSO: {matcherTarget.log_qso?.my_call ?? "—"} →{" "}
                  {matcherTarget.log_qso?.dx_call ?? "—"} | {matcherTarget.log_qso?.band ?? "—"}{" "}
                  | {matcherTarget.log_qso?.time_on ?? "—"}
                </div>
                <div>Můj loc: {matcherTarget.log_qso?.my_locator ?? "—"}</div>
                <div>
                  Aktuální matched: {matcherTarget.matched_qso_id ?? "AUTO"}
                </div>
              </div>
            )}
            <div className="flex flex-wrap items-end gap-2 text-xs mb-2">
              <Input
                size="sm"
                variant="bordered"
                label="Volací znak"
                value={matcherQuery}
                onChange={(e) => setMatcherQuery(e.target.value)}
                placeholder="OK1* / OL?ABC"
                className="min-w-[220px]"
                onKeyDown={(e) => {
                  if (e.key === "Enter" && matcherTarget) {
                    fetchMatcher(matcherTarget, 1);
                  }
                }}
              />
              <Button
                size="sm"
                color="primary"
                onPress={() => matcherTarget && fetchMatcher(matcherTarget, 1)}
                isDisabled={!matcherTarget || matcherLoading}
              >
                {matcherLoading ? "Hledám…" : "Hledat"}
              </Button>
            </div>
            {matcherError && <div className="text-xs text-red-600 mb-2">{matcherError}</div>}
            {matcherItems.length === 0 && !matcherLoading && (
              <div className="text-xs text-foreground-500 mb-2">Žádné výsledky.</div>
            )}
            {matcherItems.length > 0 && (
              <Table
                aria-label="Matches"
                isStriped
                className="text-xs"
                removeWrapper
              >
                <TableHeader>
                  <TableColumn>ID</TableColumn>
                  <TableColumn>Log</TableColumn>
                  <TableColumn>Call</TableColumn>
                  <TableColumn>Band</TableColumn>
                  <TableColumn>Loc</TableColumn>
                  <TableColumn>Serial</TableColumn>
                  <TableColumn>Time</TableColumn>
                  <TableColumn>Akce</TableColumn>
                </TableHeader>
                <TableBody items={matcherItems}>
                  {(row) => (
                    <TableRow key={row.id}>
                      <TableCell>{row.id}</TableCell>
                      <TableCell>{row.log_id}</TableCell>
                      <TableCell>
                        {row.my_call ?? "—"} → {row.dx_call ?? "—"}
                      </TableCell>
                      <TableCell>{row.band ?? "—"}</TableCell>
                      <TableCell>
                        {row.my_locator ?? "—"} / {row.rx_wwl ?? "—"}
                      </TableCell>
                      <TableCell>
                        {row.my_serial ?? "—"} / {row.dx_serial ?? "—"}
                      </TableCell>
                      <TableCell>{row.time_on ?? "—"}</TableCell>
                      <TableCell>
                        <Button
                          size="sm"
                          variant="bordered"
                          onPress={() => {
                            if (!matcherTarget) return;
                            handleFieldChange(
                              matcherTarget.log_qso_id,
                              "matchedId",
                              String(row.id)
                            );
                            onClose();
                          }}
                        >
                          Vybrat
                        </Button>
                      </TableCell>
                    </TableRow>
                  )}
                </TableBody>
              </Table>
            )}
            {matcherItems.length > 0 && (
              <div className="flex items-center gap-2 text-xs mt-2">
                <Button
                  size="sm"
                  variant="bordered"
                  onPress={() => {
                    if (!matcherTarget) return;
                    const next = Math.max(1, matcherPage - 1);
                    setMatcherPage(next);
                    fetchMatcher(matcherTarget, next);
                  }}
                  isDisabled={matcherPage <= 1 || matcherLoading}
                >
                  Předchozí
                </Button>
                <span>
                  Strana {matcherPage} / {matcherLastPage}
                </span>
                <Button
                  size="sm"
                  variant="bordered"
                  onPress={() => {
                    if (!matcherTarget) return;
                    const next = Math.min(matcherLastPage, matcherPage + 1);
                    setMatcherPage(next);
                    fetchMatcher(matcherTarget, next);
                  }}
                  isDisabled={matcherPage >= matcherLastPage || matcherLoading}
                >
                  Další
                </Button>
              </div>
            )}
          </ModalBody>
        </ModalContent>
      </Modal>
    </div>
  );
}
