import React, { useEffect, useMemo, useState } from "react";
import axios from "axios";
import { Button, Card, CardBody, CardHeader } from "@heroui/react";
import FileDropZone from "@/components/FileDropZone";
import HeaderFormFields from "@/components/RoundFileUpload/HeaderFormFields";
import UploadMessages from "@/components/RoundFileUpload/UploadMessages";
import { useUserStore } from "@/stores/userStore";
import { useContestStore } from "@/stores/contestStore";
import { useTranslation } from "react-i18next";
import {
  EMAIL_REGEX,
  PWWLO_REGEX,
  computeQsoCountWarning,
  extractHeaderMap,
  hasEdiHeader,
  hasPowerKeyword,
  normalizeSpowe,
  parsePSect,
  updateContentWithHeaders,
  validateCallsign,
  validateCallsignList,
  isMultiOp,
  isSingleOp,
  buildIaruPsect,
} from "@/utils/ediValidation";
import { validateEdi } from "@/utils/ediFileValidation";
import { type EdiHeaderForm } from "@/types/edi";
import { useRoundMeta } from "@/hooks/useRoundMeta";

type RoundFileUploadProps = {
    roundId: number | null;
    startTime?: string | null;
    logsDeadline?: string | null;
    onUploaded?: () => void;
};

// Lokátor, email a callsign regexy pro konzistentní validaci všech vstupů
const isWithinWindow = (_start?: string | null, deadline?: string | null) => {
    if (!deadline) return false;
    const endDate = new Date(deadline);
    if (Number.isNaN(endDate.getTime())) return false;

    const now = new Date();
    return now <= endDate;
};

const FILE_NAME_RULES = [
    { code: "01", operator: "SO", time: null, band: "145mhz" },
    { code: "02", operator: "MO", time: null, band: "145mhz" },
    { code: "03", operator: "SO", time: null, band: "435mhz" },
    { code: "04", operator: "MO", time: null, band: "435mhz" },
    { code: "05", operator: "SO", time: null, band: "1.3ghz" },
    { code: "06", operator: "MO", time: null, band: "1.3ghz" },
    { code: "07", operator: "SO", time: null, band: "2.3ghz" },
    { code: "08", operator: "MO", time: null, band: "2.3ghz" },
    { code: "09", operator: "SO", time: null, band: "3.4ghz" },
    { code: "10", operator: "MO", time: null, band: "3.4ghz" },
    { code: "11", operator: "SO", time: null, band: "5.7ghz" },
    { code: "12", operator: "MO", time: null, band: "5.7ghz" },
    { code: "13", operator: "SO", time: null, band: "10ghz" },
    { code: "14", operator: "MO", time: null, band: "10ghz" },
    { code: "15", operator: "SO", time: null, band: "24ghz" },
    { code: "16", operator: "MO", time: null, band: "24ghz" },
    { code: "17", operator: "SO", time: null, band: "47ghz" },
    { code: "18", operator: "MO", time: null, band: "47ghz" },
    { code: "19", operator: "SO", time: null, band: "76ghz" },
    { code: "20", operator: "MO", time: null, band: "76ghz" },
    { code: "21", operator: "SO", time: null, band: "120ghz" },
    { code: "22", operator: "MO", time: null, band: "120ghz" },
    { code: "23", operator: "SO", time: null, band: "134ghz" },
    { code: "24", operator: "MO", time: null, band: "134ghz" },
    { code: "25", operator: "SO", time: null, band: "248ghz" },
    { code: "26", operator: "MO", time: null, band: "248ghz" },
    { code: "50", operator: "SO", time: null, band: "50mhz" },
    { code: "51", operator: "MO", time: null, band: "50mhz" },
    { code: "61", operator: "SO", time: "6H", band: "145mhz" },
    { code: "62", operator: "MO", time: "6H", band: "145mhz" },
    { code: "63", operator: "SO", time: "6H", band: "435mhz" },
    { code: "64", operator: "MO", time: "6H", band: "435mhz" },
] as const;

const FILE_NAME_CODE_MAP = new Map(
    FILE_NAME_RULES.map((rule) => [
        `${rule.time ?? ""}|${rule.operator}|${rule.band}`,
        rule.code,
    ])
);

const normalizeBandKey = (value: string) =>
    value.trim().toLowerCase().replace(",", ".").replace(/\s+/g, "");

export default function RoundFileUpload({ roundId, startTime, logsDeadline, onUploaded }: RoundFileUploadProps) {
    const user = useUserStore((s) => s.user);
    const selectedRound = useContestStore((s) => s.selectedRound);
    const { t } = useTranslation("common");
    // Stav hlaviček, validací a vedlejších efektů formuláře
    const [selectedFiles, setSelectedFiles] = useState<FileList | null>(null);
    const [headerForm, setHeaderForm] = useState<EdiHeaderForm>({
        TName: "",
        TDate: "",
        PCall: "",
        PWWLo: "",
        PSect: "",
        PBand: "",
        RHBBS: "",
        SPowe: "",
        SAnte: "",
        RCall: "",
        MOpe1: "",
        MOpe2: "",
    });
    const [uploading, setUploading] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [success, setSuccess] = useState<string | null>(null);
    const [uploadedOnce, setUploadedOnce] = useState(false);
    const [downloadEdited, setDownloadEdited] = useState(false);
    const [normalizedFileName, setNormalizedFileName] = useState<string | null>(null);
    const [qsoCountWarning, setQsoCountWarning] = useState<string | null>(null);
    const [qsoCallsignInfo, setQsoCallsignInfo] = useState<string | null>(null);
    const [pbandInfo, setPbandInfo] = useState<string | null>(null);
    const [spoweInfo, setSpoweInfo] = useState<string | null>(null);
    const [isEdiFile, setIsEdiFile] = useState(true);
    const [unsupportedInfo, setUnsupportedInfo] = useState<string | null>(null);
    const { bands, bandsError, bandsLoading, ediBands } = useRoundMeta(roundId, selectedRound);
    const bandNames = useMemo(() => new Set(bands.map((b) => b.name)), [bands]);
    const bandValue = headerForm.PBand.trim();
    const bandUnknown = bands.length > 0 && bandValue !== "" && !bandNames.has(bandValue);
    const bandMissing = bandValue === "";
    const normalizedPwwlo = headerForm.PWWLo.trim().toUpperCase();
    const pwwloEmpty = normalizedPwwlo === "";
    const pwwloFormatInvalid = !pwwloEmpty && !PWWLO_REGEX.test(normalizedPwwlo);
    const pwwloInvalid = pwwloEmpty || pwwloFormatInvalid;
    const rhbbsValue = headerForm.RHBBS.trim();
    const rhbbsEmpty = rhbbsValue === "";
    const rhbbsFormatInvalid = !rhbbsEmpty && !EMAIL_REGEX.test(rhbbsValue);
    const rhbbsInvalid = rhbbsEmpty || rhbbsFormatInvalid;
    const rhbbsWarning =
        selectedFiles && selectedFiles.length === 1 && rhbbsInvalid
            ? rhbbsEmpty
                ? (t("upload_warning_rhbbs_required") as string) || "RHBBS chybí."
                : (t("upload_warning_rhbbs_format") as string) || "RHBBS není ve formátu e-mailu."
            : null;
    const pCallValidation = useMemo(() => validateCallsign(headerForm.PCall), [headerForm.PCall]);
    const pCallEmpty = headerForm.PCall.trim() === "";
    const pCallInvalid = pCallEmpty || !pCallValidation.isValid;
    const rCallValidation = useMemo(() => validateCallsign(headerForm.RCall), [headerForm.RCall]);
    const tNameInvalid = headerForm.TName.trim() === "";
    const tDateError = (() => {
        const value = headerForm.TDate.trim();
        if (!value) return "TDate je povinné.";
        const parts = value.split(";");
        const invalidCount = parts.length !== 2;
        const invalidFormat = parts.some((p) => !/^\d{8}$/.test(p));
        const inverted =
            !invalidCount && !invalidFormat && parseInt(parts[0], 10) > parseInt(parts[1], 10);
        if (invalidCount || invalidFormat || inverted) {
            return "TDate musí být ve formátu YYYYMMDD;YYYYMMDD a první datum nesmí být větší.";
        }
        return null;
    })();
    const spoweParsed = useMemo(() => normalizeSpowe(headerForm.SPowe), [headerForm.SPowe]);
    const psectValue = headerForm.PSect.trim();
    const allowSixHour = !!selectedRound?.is_sixhr;
    const psectValidation = useMemo(
        // Formulář validujeme stejnými pravidly jako načtený soubor (STRICT režim)
        () => parsePSect(psectValue, { strict: true, allow6h: allowSixHour }),
        [psectValue, allowSixHour]
    );
    const psectMissing = psectValue === "";
    const psectHasErrors = !psectMissing && psectValidation.errors.length > 0;
    const spoweNumber = useMemo(() => {
        if (!spoweParsed.valid) return null;
        const parsed = parseFloat(spoweParsed.normalized);
        return Number.isFinite(parsed) ? parsed : null;
    }, [spoweParsed]);
    const bandAllowsPowerCategory = useMemo(() => {
        const band = bands.find((b) => b.name === bandValue);
        return !!band?.has_power_category;
    }, [bands, bandValue]);
    const shouldApplyLowPowerCategory =
        !psectValidation.isCheckLog && bandAllowsPowerCategory && spoweNumber !== null && spoweNumber <= 100;
    const psectTarget = useMemo(
        () => ({
            ...psectValidation,
            powerClass: shouldApplyLowPowerCategory ? "LP" : psectValidation.powerClass,
        }),
        [psectValidation, shouldApplyLowPowerCategory]
    );
    const psectCanonical = useMemo(() => buildIaruPsect(psectTarget), [psectTarget]);
    const psectNeedsFormat =
        !psectMissing &&
        !!psectCanonical &&
        psectValue.trim().toUpperCase().replace(/\s+/g, " ") !== psectCanonical.toUpperCase().replace(/\s+/g, " ");
    const shouldShowIaruAdjustButton =
        !!psectNeedsFormat && !!selectedFiles && selectedFiles.length === 1 && isEdiFile;
    const fileNameStatus = useMemo(() => {
        if (!selectedFiles || selectedFiles.length !== 1 || !isEdiFile) return null;
        if (psectValidation.isCheckLog) return null;
        const pcallRaw = headerForm.PCall.trim();
        const baseCall = pcallRaw.split("/")[0]?.replace(/[^A-Za-z0-9]/g, "").toUpperCase() ?? "";
        const bandKey = normalizeBandKey(headerForm.PBand);
        const operatorKey = psectValidation.operatorClass ?? "";
        const timeKey = psectValidation.timeClass ?? "";
        if (!baseCall || !bandKey || !operatorKey) {
            return {
                state: "pending",
                message:
                    (t("upload_filename_missing_fields") as string) ||
                    "Doplň PCall, PSect a PBand, aby šlo ověřit název souboru.",
            };
        }
        const code = FILE_NAME_CODE_MAP.get(`${timeKey}|${operatorKey}|${bandKey}`);
        if (!code) {
            return {
                state: "invalid",
                message:
                    (t("upload_filename_unknown_code") as string) ||
                    "Nelze určit prefix názvu souboru pro zvolenou kategorii a pásmo.",
            };
        }
        const expectedName = `${code}${baseCall}.edi`;
        const pattern = new RegExp(`^${code}${baseCall}[A-Za-z0-9._-]*\\.edi$`, "i");
        const isValid = pattern.test(selectedFiles[0].name);
        return {
            state: isValid ? "valid" : "invalid",
            expectedName,
            message: isValid
                ? (t("upload_filename_valid") as string) || "Název souboru odpovídá pravidlům."
                : (t("upload_filename_invalid", { expected: expectedName }) as string) ||
                  `Název souboru neodpovídá pravidlům. Doporučený tvar: ${expectedName}`,
        };
    }, [
        selectedFiles,
        isEdiFile,
        headerForm.PCall,
        headerForm.PBand,
        psectValidation.operatorClass,
        psectValidation.timeClass,
        psectValidation.isCheckLog,
        t,
    ]);

    const isAuth = Boolean(user);
    const windowOpen = useMemo(() => isWithinWindow(startTime, logsDeadline), [startTime, logsDeadline]);
    const canUpload = isAuth || windowOpen;
    const anonymousBlocked = !isAuth && uploadedOnce;
    const readFileAsText = (file: File) =>
        new Promise<string>((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : "");
            reader.onerror = () => reject(reader.error);
            reader.readAsText(file);
        });

    // Mapuje alternativní zápis PBand (EDI pásma) na název pásma v DB
    const resolvePBand = (value: string): { resolved: string; source: string } | null => {
        const lower = value.trim().toLowerCase();
        if (!lower) return null;
        const match = ediBands.find((b) => b.value.toLowerCase() === lower);
        if (!match) return null;
        const bandName = match.bands?.[0]?.name;
        if (!bandName) return null;
        if (bandNames.size && !bandNames.has(bandName)) return null;
        return { resolved: bandName, source: match.value };
    };

    const handleFiles = (files: FileList | null) => {
        if (!files || files.length === 0) {
            setSelectedFiles(null);
        } else if (!isAuth) {
      const firstFile = new DataTransfer();
      firstFile.items.add(files[0]);
      setSelectedFiles(firstFile.files);
      if (files.length > 1) {
        setError((t("upload_hint_anonymous_once") as string) || "Anonymně lze nahrát jen jeden soubor.");
      }
    } else {
      setSelectedFiles(files);
    }
    setSuccess(null);
    if (files && files.length <= 1) {
      setError(null);
    }
    setNormalizedFileName(null);
    setQsoCountWarning(null);
    setDownloadEdited(false);
        setSpoweInfo(null);
        setUnsupportedInfo(null);
        setIsEdiFile(true);
        setQsoCallsignInfo(null);

    if (files && files.length === 1) {
      const file = files[0];
      readFileAsText(file)
        .then((text) => {
          if (!hasEdiHeader(text)) {
            setIsEdiFile(false);
            setUnsupportedInfo((t("upload_file_not_supported") as string) || "");
            setSelectedFiles(null);
            return;
          }
          const earlyValidationErrors = validateEdi(text, file.name);
          const earlyQsoErrors = earlyValidationErrors.filter(
            (msg) => msg.includes("QSO #") && msg.toLowerCase().includes("volací znak")
          );
          if (earlyQsoErrors.length > 0) {
            const recommendation =
              "Doporučení: neplatné volací znaky v QSO oprav v souboru a zkus ho nahrát znovu.";
            setQsoCallsignInfo([...earlyQsoErrors, recommendation].join("\n"));
          }
          const headers = extractHeaderMap(text);
          setQsoCountWarning(computeQsoCountWarning(text, file.name));
                    const spoweParsedFromFile = normalizeSpowe(headers["SPowe"] ?? "");
                    const pbandMapping = resolvePBand(headers["PBand"] ?? "");
                    const mappedPBand = pbandMapping?.resolved ?? headers["PBand"] ?? "";
                    if (pbandMapping) {
                        setPbandInfo(
                            (t("upload_pband_mapped", { original: headers["PBand"], mapped: mappedPBand }) as string) ||
                            `PBand ${headers["PBand"]} -> ${mappedPBand}`
                        );
                    } else if (!mappedPBand) {
                        setPbandInfo((t("upload_pband_missing") as string) || "PBand není vyplněné v souboru, vyber pásmo ze seznamu.");
                    } else {
                        setPbandInfo(null);
                    }
                    if (spoweParsedFromFile.changed) {
                        setSpoweInfo(
                            (t("upload_spowe_normalized_from", {
                                from: headers["SPowe"] ?? "",
                                to: spoweParsedFromFile.normalized,
                            }) as string) || ""
                        );
                    } else {
                        setSpoweInfo(null);
                    }
                    setHeaderForm((prev) => ({
                        ...prev,
                        TName: headers["TName"] ?? "",
                        TDate: headers["TDate"] ?? "",
                        PCall: headers["PCall"] ?? "",
                        PWWLo: headers["PWWLo"] ?? "",
                        PSect: headers["PSect"] ?? "",
                        PBand: mappedPBand,
                        RHBBS: headers["RHBBS"] ?? "",
                        SPowe: spoweParsedFromFile.normalized,
                        SAnte: headers["SAnte"] ?? "",
                        RCall: headers["RCall"] ?? "",
                        MOpe1: headers["MOpe1"] ?? headers["Mope1"] ?? "",
                        MOpe2: headers["MOpe2"] ?? headers["Mope2"] ?? "",
                    }));
                })
                .catch(() => {
                    // leave editing disabled if reading fails
                    setHeaderForm((prev) => ({
                        ...prev,
                        TName: "",
                        TDate: "",
                        PCall: "",
                        PWWLo: "",
                        PSect: "",
                        PBand: "",
                        RHBBS: "",
                        SPowe: "",
                        SAnte: "",
                        RCall: "",
                        MOpe1: "",
                        MOpe2: "",
                    }));
                });
        } else {
            setQsoCountWarning(null);
            setQsoCallsignInfo(null);
            setHeaderForm((prev) => ({
                ...prev,
                TName: "",
                TDate: "",
                PCall: "",
                PWWLo: "",
                PSect: "",
                PBand: "",
                RHBBS: "",
                SPowe: "",
                SAnte: "",
                RCall: "",
                MOpe1: "",
                MOpe2: "",
            }));
            setPbandInfo(null);
        }
    };

  const markEdited = () => {
    setDownloadEdited(true);
  };

  const handlePbandChange = (value: string) => {
    setHeaderForm((prev) => ({ ...prev, PBand: value }));
    markEdited();
    setPbandInfo(null);
  };

  const handleSpoweChange = (value: string) => {
    const parsed = normalizeSpowe(value);
    setHeaderForm((prev) => ({ ...prev, SPowe: parsed.normalized }));
    if (parsed.changed) {
      setSpoweInfo(
        (t("upload_spowe_normalized_to", { value: parsed.normalized }) as string) ||
          `SPowe bylo normalizováno na "${parsed.normalized}".`
      );
    } else {
      setSpoweInfo(null);
    }
    markEdited();
  };

  const handleApplyPsectCanonical = () => {
    if (!psectCanonical) {
      setError(
        (t("upload_error_psect_normalize") as string) || "PSect nelze normalizovat, oprav chyby a zkus to znovu."
      );
      return;
    }
    setHeaderForm((prev) => ({ ...prev, PSect: psectCanonical }));
    setError(null);
  };

    // Reset formuláře při změně kola (např. v RoundDetail)
    useEffect(() => {
        setSelectedFiles(null);
        setSuccess(null);
        setError(null);
        setNormalizedFileName(null);
        setQsoCountWarning(null);
        setPbandInfo(null);
        setSpoweInfo(null);
        setUnsupportedInfo(null);
        setIsEdiFile(true);
        setQsoCallsignInfo(null);
        setHeaderForm({
            TName: "",
            TDate: "",
            PCall: "",
            PWWLo: "",
            PSect: "",
            PBand: "",
            RHBBS: "",
            SPowe: "",
            SAnte: "",
            RCall: "",
            MOpe1: "",
            MOpe2: "",
        });
    }, [roundId]);

    useEffect(() => {
        if (normalizedFileName) {
            setNormalizedFileName(null);
        }
    }, [headerForm.PCall, headerForm.PBand, headerForm.PSect, selectedFiles, normalizedFileName]);

    const uploadFiles = async () => {
        if (!roundId) {
            setError((t("upload_error_missing_round") as string) || "Chybí ID kola.");
            return;
        }
    if (!selectedFiles || selectedFiles.length === 0) {
      setError((t("upload_error_pick_file") as string) || "Vyber soubor.");
      return;
    }
    const isSingleUpload = selectedFiles.length === 1;
    if (bands.length > 0 && bandMissing) {
      if (isSingleUpload) {
        setError((t("upload_error_pick_band") as string) || "Vyber pásmo (PBand) ze seznamu.");
        return;
      }
    }
    if (bands.length > 0 && bandUnknown) {
      if (isSingleUpload) {
        setError(
                (t("upload_error_band_unknown", { band: bandValue }) as string) ||
                `PBand "${bandValue}" neodpovídá známým pásmům, vyber správnou hodnotu ze seznamu.`
            );
        return;
      }
        }
    if (pwwloInvalid) {
      if (isSingleUpload) {
        setError(
                pwwloEmpty
                    ? (t("upload_error_pwwlo_required") as string) || "PWWLo je povinné."
                    : (t("upload_error_pwwlo_format") as string) || "PWWLo musí mít 6 znaků ve formátu lokátoru (AA00AA)."
            );
        return;
      }
        }
    if (rhbbsInvalid && isSingleUpload) {
      setError(null);
    }
    if (tNameInvalid) {
      if (isSingleUpload) {
        setError((t("upload_error_tname_required") as string) || "TName je povinné.");
        return;
      }
    }
    if (pCallInvalid) {
      if (isSingleUpload) {
        setError(
                pCallEmpty
                    ? (t("upload_error_pcall_required") as string) || "PCall je povinné."
                    : (t("upload_error_pcall_format") as string) || "PCall musí být validní volací znak."
            );
        return;
      }
        }
    if (tDateError) {
      if (isSingleUpload) {
        setError(tDateError);
        return;
      }
    }
    if (psectMissing || psectHasErrors) {
      if (isSingleUpload) {
        setError(
                psectMissing
                    ? (t("upload_error_psect_required") as string) || "PSect je povinné."
                    : psectHasErrors
                        ? psectValidation.errors.join(" ") || (t("upload_error_psect_invalid") as string) || "PSect není validní."
                        : undefined
            );
        return;
      }
        }
    if (spoweInvalid) {
      if (isSingleUpload) {
        setError(
                spoweEmpty
                    ? (t("upload_error_spowe_required") as string) || "SPowe je povinné."
                    : spoweTooLong
                        ? (t("upload_error_spowe_length") as string) || "SPowe může mít maximálně 12 znaků."
                        : (t("upload_error_spowe_format") as string) || "SPowe musí být celé číslo (bez jednotek)."
            );
        return;
      }
        }
    if (!spoweInvalid && spoweOverLimit) {
      if (isSingleUpload) {
        setError(
                (t("upload_error_spowe_limit", {
                    value: spoweParsed.normalized,
                    category: psectValidation.powerClass ?? "-",
                    limit: powerLimit ?? "-",
                }) as string) || ""
            );
        return;
      }
        }
    if (santeInvalid) {
      if (isSingleUpload) {
        setError((t("upload_error_sante_required") as string) || "SAnte je povinné.");
        return;
      }
    }
    if (rcallInvalid) {
      if (isSingleUpload) {
        setError(
                headerForm.RCall.trim() === ""
                    ? (t("upload_error_rcall_required") as string) || "RCall je povinné pro zvolenou kategorii."
                    : (t("upload_error_rcall_format") as string) || "RCall musí být validní volací znak."
            );
        return;
      }
        }
    if (mopeInvalid) {
      if (isSingleUpload) {
        const invalidTokens =
                mopeValidation.invalidTokens.length > 0
                    ? ` Neplatné značky: ${mopeValidation.invalidTokens.join(", ")}.`
                    : "";
            const mopeMsg =
                (t("upload_error_mope_missing") as string) ||
                "Vyplň alespoň MOpe1 nebo MOpe2 (můžeš zadat více značek oddělených mezerou/středníkem/čárkou).";
            setError(`${mopeMsg}${invalidTokens}`);
            return;
      }
        }
        if (!canUpload || anonymousBlocked) {
            setError((t("upload_error_not_allowed") as string) || "Nahrávání není povoleno.");
            return;
        }

        setUploading(true);
        setError(null);
        setSuccess(null);

        try {
            await axios.get("/sanctum/csrf-cookie", { withCredentials: true });

      const filesArray = Array.from(selectedFiles);
            const results: string[] = [];
            const errors: string[] = [];
            const qsoCallsignWarnings: string[] = [];

            for (const file of filesArray) {
                const outputName =
                    filesArray.length === 1 && normalizedFileName ? normalizedFileName : file.name;
                let preparedFile = file;
                let editedContentForDownload: string | null = null;

                // klientská validace EDI + případná úprava hlavičky u jednoho souboru
                try {
                    let text = await readFileAsText(file);
                    const initialHeaders = extractHeaderMap(text);
                    const spoweParsedInitial = normalizeSpowe(initialHeaders["SPowe"] ?? "");
                    const pbandMapping = resolvePBand(initialHeaders["PBand"] ?? "");
                    if (pbandMapping) {
                        text = updateContentWithHeaders(text, { PBand: pbandMapping.resolved });
                        if (filesArray.length === 1) {
                            setPbandInfo(
                                (t("upload_pband_mapped", {
                                    original: initialHeaders["PBand"],
                                    mapped: pbandMapping.resolved,
                                }) as string) || ""
                            );
                        }
                    }
                    if (spoweParsedInitial.changed) {
                        text = updateContentWithHeaders(text, { SPowe: spoweParsedInitial.normalized });
                        if (filesArray.length === 1) {
                            setSpoweInfo(
                                `SPowe bylo normalizováno z "${initialHeaders["SPowe"] ?? ""}" na "${spoweParsedInitial.normalized}".`
                            );
                        }
                    }
          if (filesArray.length === 1) {
            const updatedContent = updateContentWithHeaders(text, headerForm);
                        text = updatedContent;
                        preparedFile = new File([updatedContent], outputName, { type: file.type || "text/plain" });
                        setQsoCountWarning(computeQsoCountWarning(updatedContent, file.name));
                        editedContentForDownload = updatedContent;
                    } else if (outputName !== file.name) {
                        preparedFile = new File([file], outputName, { type: file.type || "text/plain" });
                    }
                    const headers = extractHeaderMap(text);
                    const psect = (headers["PSect"] ?? "").trim();
                    const pband = (headers["PBand"] ?? "").trim();
                    const pcallValidation = validateCallsign(headers["PCall"] ?? "");
                    if (!pcallValidation.isValid) {
                        errors.push(
                            `${file.name}: ${(t("upload_error_pcall_format") as string) || "PCall musí být validní volací znak."}`
                        );
                        continue;
                    }
                    if (psect && (sectionNeedsSingle || sectionNeedsMulti)) {
                        const rcallValidation = validateCallsign(headers["RCall"] ?? "");
                        if (!rcallValidation.isValid) {
                            errors.push(
                                `${file.name}: ${(t("upload_error_rcall_format") as string) || "RCall musí být validní volací znak."}`
                            );
                            continue;
                        }
                    }
                    if (sectionNeedsMulti) {
                        const mope1Val = validateCallsignList(headers["MOpe1"] ?? "");
                        const mope2Val = validateCallsignList(headers["MOpe2"] ?? "");
                        const invalidTokens = [...mope1Val.invalidTokens, ...mope2Val.invalidTokens];
                        if (!mope1Val.hasValid && !mope2Val.hasValid) {
                            errors.push(
                                `${file.name}: ${(t("upload_error_mope_missing") as string) ||
                                "Vyplň alespoň MOpe1 nebo MOpe2 (můžeš zadat více značek oddělených mezerou/středníkem/čárkou)."
                                }`
                            );
                            continue;
                        }
                        if (invalidTokens.length > 0) {
                            errors.push(
                                `${file.name}: ${(t("upload_error_mope_invalid", { invalid: invalidTokens.join(", ") }) as string) ||
                                `Neplatné značky v MOpe1/MOpe2 (${invalidTokens.join(", ")}).`}`
                            );
                            continue;
                        }
                    }

                    const psectResult = parsePSect(psect, { strict: true, allow6h: allowSixHour });
                    if (psectResult.errors.length > 0) {
                        errors.push(`${file.name}: ${psectResult.errors.join(" ")}`);
                        continue;
                    }

                    if (psect && hasPowerKeyword(psect)) {
                        const bandMatch = bands.find((b) => b.name === pband);
                        const allowsPower = !!bandMatch?.has_power_category;
                        if (!allowsPower) {
                            errors.push(
                                `${file.name}: ${(t("upload_error_power_band_mismatch", {
                                    psect,
                                    band: pband || "?",
                                }) as string) || ""}`
                            );
                            continue;
                        }
                    }
                    const validationErrors = validateEdi(text, file.name);
                    const qsoCallsignErrors = validationErrors.filter(
                        (msg) => msg.includes("QSO #") && msg.toLowerCase().includes("volací znak")
                    );
                    const otherErrors = validationErrors.filter(
                        (msg) =>
                            !qsoCallsignErrors.includes(msg) &&
                            !msg.includes("Záznamy QSO nelze upravit")
                    );
                    if (qsoCallsignErrors.length > 0) {
                        qsoCallsignWarnings.push(
                            ...qsoCallsignErrors,
                            "Doporučení: neplatné volací znaky v QSO oprav v souboru a zkus ho nahrát znovu."
                        );
                    }
                    if (otherErrors.length > 0) {
                        errors.push(...otherErrors);
                        continue;
                    }
                } catch (readErr) {
                    errors.push(
                        `${file.name}: ${(t("upload_error_file_read") as string) || "Soubor se nepodařilo přečíst."}`
                    );
                    continue;
                }

                try {
                    const formData = new FormData();
                    formData.append("file", preparedFile);
                    formData.append("round_id", String(roundId));

                    const res = await axios.post("/api/files", formData, {
                        headers: { "Content-Type": "multipart/form-data", Accept: "application/json" },
                        withCredentials: true,
                        withXSRFToken: true,
                    });

                    const stored = res.data;
                    results.push(stored?.filename ?? outputName);

          if (filesArray.length === 1 && downloadEdited && editedContentForDownload) {
            const blob = new Blob([editedContentForDownload], { type: preparedFile.type || "text/plain" });
                        const url = URL.createObjectURL(blob);
                        const link = document.createElement("a");
                        link.href = url;
                        link.download = outputName;
                        document.body.appendChild(link);
                        link.click();
                        link.remove();
                        URL.revokeObjectURL(url);
                    }
                } catch (apiErr: any) {
                    const msg =
                        apiErr?.response?.data?.message ||
                        apiErr?.response?.data?.errors?.file?.[0] ||
                        `${file.name}: ${(t("upload_failed") as string) || "Nahrávání se nezdařilo."}`;
                    errors.push(msg);
                }
            }

            if (errors.length > 0) {
                setError(errors.join("\n"));
            }
            if (qsoCallsignWarnings.length > 0) {
                setQsoCallsignInfo(qsoCallsignWarnings.join("\n"));
            }

            if (results.length > 0) {
                setSuccess(
                    (t("files_uploaded", { files: results.join(", ") }) as string) ||
                    `Nahráno: ${results.join(", ")}`
                );
                setSelectedFiles(null);
                if (!isAuth) {
                    setUploadedOnce(true);
                }
                onUploaded?.();
            }
        } catch (e: any) {
            const message =
                e?.response?.data?.message ||
                e?.response?.data?.errors?.file?.[0] ||
                (t("upload_failed") as string) ||
                "Nahrávání se nezdařilo.";
            setError(message);
        } finally {
            setUploading(false);
        }
    };

    const helperText = (() => {
        if (isAuth) {
            return "";
        }
        if (!windowOpen) {
            return (
                (t("upload_hint_closed") as string) ||
                "Nahrávání je dostupné během závodu do uzávěrky logů, nebo se přihlas."
            );
        }
        if (uploadedOnce) {
            return (t("upload_hint_anonymous_once") as string) || "Anonymně lze nahrát jen jeden soubor.";
        }
        return (
            (t("upload_hint_anonymous") as string) ||
            "Anonymně lze nahrát jeden soubor během závodu do uzávěrky logů."
        );
    })();

    // Odvozené validace podle vstupů a zvolené kategorie
    const sectionNeedsSingle = isSingleOp(headerForm.PSect || "");
    const sectionNeedsMulti = isMultiOp(headerForm.PSect || "");
    const spoweRaw = headerForm.SPowe;
    const spoweEmpty = spoweParsed.normalized === "";
    const spoweTooLong = spoweRaw.trim().length > 12;
    const spoweInvalid = !spoweParsed.valid || spoweTooLong;
    const santeValue = headerForm.SAnte.trim();
    const santeTooLong = santeValue.length > 12;
    const santeInvalid = santeValue === "";
    const rcallInvalid =
        (sectionNeedsSingle || sectionNeedsMulti) &&
        (headerForm.RCall.trim() === "" || !rCallValidation.isValid);
    const mopeValidation = useMemo(() => {
        const first = validateCallsignList(headerForm.MOpe1);
        const second = validateCallsignList(headerForm.MOpe2);
        const invalidTokens = [...first.invalidTokens, ...second.invalidTokens];
        const hasAnyValid = first.hasValid || second.hasValid;
        const hasAny = first.tokens.length > 0 || second.tokens.length > 0;
        return { invalidTokens, hasAnyValid, hasAny };
    }, [headerForm.MOpe1, headerForm.MOpe2]);
    const mopeMissing = sectionNeedsMulti && !mopeValidation.hasAny;
    const mopeInvalidTokens =
        sectionNeedsMulti && mopeValidation.invalidTokens.length > 0;
    const mopeInvalid =
        sectionNeedsMulti &&
        (!mopeValidation.hasAnyValid || mopeValidation.invalidTokens.length > 0);
    const powerLimit = useMemo(() => {
        switch (psectValidation.powerClass) {
            case "QRP":
                return 5;
            case "N":
                return 10;
            case "LP":
                return 100;
            default:
                return null; // A/HP nebo chybějící
        }
    }, [psectValidation.powerClass]);
    const spoweOverLimit =
        powerLimit !== null &&
        spoweParsed.valid &&
        spoweParsed.normalized !== "" &&
        parseFloat(spoweParsed.normalized) > powerLimit;
    const spoweLimitError =
        powerLimit !== null && spoweOverLimit
            ? ((t("upload_error_spowe_limit", {
                value: spoweParsed.normalized,
                category: psectValidation.powerClass ?? "-",
                limit: powerLimit ?? "-",
            }) as string) || "")
            : null;

    if (!canUpload) return null;

    return (
        <Card>
            <CardHeader>
                <div className="flex flex-col">
                    <span className="text-lg font-semibold">
                        {t("upload_log_title") ?? "Nahrát log"}
                    </span>
                    <span className="text-sm text-foreground-500">{helperText}</span>
                </div>
            </CardHeader>
            <CardBody className="space-y-3">
                <div className="flex items-center gap-2 flex-wrap">
                    <FileDropZone
                        multiple={isAuth}
                        selectedFiles={selectedFiles}
                        label={(t("select_file") as string) ?? "Vyber soubor"}
                        hint={
                            isAuth
                                ? (t("upload_hint_bulk_auth") as string) ??
                                  "Hromadný upload je dostupný pouze pro přihlášené."
                                : (t("upload_file_drop_hint") as string) ??
                                  "Přetáhni EDI soubor sem nebo klikni pro výběr."
                        }
                        onFiles={handleFiles}
                    />
                </div>
                {selectedFiles && selectedFiles.length > 1 && (
                    <div className="flex flex-wrap items-center justify-between gap-3 text-sm text-foreground-500">
                        <span>Hromadný upload: hlavičku nelze upravovat, soubory se odešlou tak jak jsou.</span>
                        <Button
                            color="primary"
                            onPress={uploadFiles}
                            isDisabled={!canUpload || anonymousBlocked || uploading}
                            isLoading={uploading}
                            className="shrink-0"
                        >
                            {t("upload") ?? "Nahrát"}
                        </Button>
                    </div>
                )}
                {selectedFiles && selectedFiles.length === 1 && isEdiFile && (
                    <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                        {fileNameStatus && (
                            <div className="md:col-span-2 rounded-md border border-foreground-200 bg-foreground-50/40 p-3 text-sm">
                                <div className="flex items-start justify-between gap-3">
                                    <p
                                        className={
                                            fileNameStatus.state === "invalid"
                                                ? "text-danger-500"
                                                : "text-foreground-700"
                                        }
                                    >
                                        {fileNameStatus.message}
                                    </p>
                                    {fileNameStatus.state === "invalid" && fileNameStatus.expectedName && (
                                        <Button
                                            size="sm"
                                            variant="flat"
                                            onPress={() => setNormalizedFileName(fileNameStatus.expectedName ?? null)}
                                        >
                                            {t("upload_filename_normalize") ?? "Normalizovat název"}
                                        </Button>
                                    )}
                                </div>
                                {normalizedFileName && (
                                    <p className="mt-2 text-xs text-foreground-500">
                                        {(t("upload_filename_used", { name: normalizedFileName }) as string) ||
                                            `Při uploadu se použije: ${normalizedFileName}`}
                                    </p>
                                )}
                            </div>
                        )}
                        <HeaderFormFields
                            headerForm={headerForm}
                            setHeaderForm={setHeaderForm}
                            markEdited={markEdited}
                            onPbandChange={handlePbandChange}
                            onSpoweChange={handleSpoweChange}
                            onApplyPsectCanonical={handleApplyPsectCanonical}
                            t={t}
                            tNameInvalid={tNameInvalid}
                            tDateError={tDateError}
                            pCallEmpty={pCallEmpty}
                            pCallInvalid={pCallInvalid}
                            pwwloEmpty={pwwloEmpty}
                            pwwloFormatInvalid={pwwloFormatInvalid}
                            pwwloInvalid={pwwloInvalid}
                            psectMissing={psectMissing}
                            psectHasErrors={psectHasErrors}
                            psectValidation={psectValidation}
                            psectNeedsFormat={psectNeedsFormat}
                            psectCanonical={psectCanonical}
                            shouldShowIaruAdjustButton={shouldShowIaruAdjustButton}
                            bands={bands}
                            bandsLoading={bandsLoading}
                            bandsError={bandsError}
                            bandUnknown={bandUnknown}
                            bandMissing={bandMissing}
                            bandValue={bandValue}
                            pbandInfo={pbandInfo}
                            rhbbsWarning={rhbbsWarning}
                            spoweInvalid={spoweInvalid}
                            spoweEmpty={spoweEmpty}
                            spoweTooLong={spoweTooLong}
                            spoweOverLimit={spoweOverLimit}
                            spoweLimitError={spoweLimitError}
                            spoweInfo={spoweInfo}
                            santeInvalid={santeInvalid}
                            santeValue={santeValue}
                            santeTooLong={santeTooLong}
                            sectionNeedsSingle={sectionNeedsSingle}
                            sectionNeedsMulti={sectionNeedsMulti}
                            rcallInvalid={rcallInvalid}
                            mopeInvalid={mopeInvalid}
                        />
                        <div className="md:col-span-2 flex items-center justify-end gap-3">
                            <label className="flex items-center gap-2 text-sm cursor-pointer">
                                <input
                                    type="checkbox"
                                    checked={downloadEdited}
                                    onChange={(e) => setDownloadEdited(e.target.checked)}
                                    disabled={uploading}
                                />
                                <span>Chci stáhnout upravený soubor</span>
                            </label>
                            <Button
                                color="primary"
                                onPress={uploadFiles}
                                isDisabled={!canUpload || anonymousBlocked || uploading}
                                isLoading={uploading}
                                className="shrink-0"
                            >
                                {t("upload") ?? "Nahrát"}
                            </Button>
                        </div>
                    </div>
                )}
                <UploadMessages
                    isEdiFile={isEdiFile}
                    unsupportedInfo={unsupportedInfo}
                    qsoCountWarning={qsoCountWarning}
                    qsoCallsignInfo={qsoCallsignInfo}
                    rhbbsWarning={rhbbsWarning}
                    error={error}
                    success={success}
                />
            </CardBody>
        </Card>
    );
}
