import argparse
import csv
import datetime
import json
import os
import shutil
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Dict, Tuple


# DeepL API 설정
DEEPL_API_KEY = "5b9840fc-3a85-4ab9-bc59-424add794a2f"
# 무료 요금제는 https://api-free.deepl.com/v2/translate,
# Pro(유료) 요금제는 https://api.deepl.com/v2/translate 를 사용해야 합니다.
# 제공해주신 키 형식상 Pro 키로 보이므로 기본값을 Pro 엔드포인트로 설정합니다.
DEEPL_API_BASE = "https://api.deepl.com"
DEEPL_ENDPOINT = f"{DEEPL_API_BASE}/v2/translate"
DEEPL_GLOSSARIES_ENDPOINT = f"{DEEPL_API_BASE}/v3/glossaries"

# Glossary 디렉토리 (KO→각 언어별 TSV 파일)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
BASE_DIR = os.path.join(PROJECT_DIR, "translate")
GLOSSARY_DIR = os.path.join(BASE_DIR, "glossary")

# target_lang(DeepL 코드) → glossary TSV 파일명 매핑
# 참고: DeepL Glossary API는 zh-Hant를 지원하지 않음. ZH-HANT는 ZH와 동일 glossary(zh) 사용
GLOSSARY_FILES: Dict[str, str] = {
    "ZH": "ko_zh.tsv",
    "ZH-HANT": "ko_zh.tsv",  # zh-Hant 미지원 → ZH와 동일 파일/glossary 사용
    "EN": "ko_en.tsv",
    "FR": "ko_fr.tsv",
    "DE": "ko_de.tsv",
    "JA": "ko_ja.tsv",
    "PT-PT": "ko_pt.tsv",
    "ES": "ko_es.tsv",
    "TR": "ko_tr.tsv",
    "RU": "ko_ru.tsv",
    "VI": "ko_vi.tsv",
    "MS": "ko_ms.tsv",
    "TH": "ko_th.tsv",
    "ID": "ko_id.tsv",
}

# Glossary API용 언어 코드 (소문자). ZH-HANT는 API에서 zh만 지원하므로 zh 사용
GLOSSARY_LANG_CODES: Dict[str, str] = {
    "ZH": "zh",
    "ZH-HANT": "zh",
    "EN": "en",
    "FR": "fr",
    "DE": "de",
    "JA": "ja",
    "PT-PT": "pt",
    "ES": "es",
    "TR": "tr",
    "RU": "ru",
    "VI": "vi",
    "MS": "ms",
    "TH": "th",
    "ID": "id",
}


# CSV 컬럼명과 DeepL 타깃 언어 코드 매핑
KEY_COL = "Key"
KOREAN_COL = "Korean(ko)"
SHARED_COMMENTS_COL = "Shared Comments"

# CSV에서 "비어 있음"을 의미하는 값들 (실제 번역 대상)
BLANK_VALUES = {"", "-"}

LANG_COLUMNS: Dict[str, str] = {
    "Chinese (Simplified)(zh)": "ZH",        # 간체 중국어
    "Chinese (Traditional)(zh-TW)": "ZH-HANT",  # 번체 중국어
    "English(en)": "EN",
    "French(fr)": "FR",
    "German(de)": "DE",
    "Japanese(ja)": "JA",
    # DeepL 은 PT 일반 코드 대신 PT-PT / PT-BR 을 사용하므로, 여기서는 PT-PT 로 사용
    "Portuguese(pt)": "PT-PT",
    "Spanish(es)": "ES",
    "Turkish(tr)": "TR",
    "Russian(ru)": "RU",
    "Vietnamese(vi)": "VI",
    "Malay(ms)": "MS",
    "Thai(th)": "TH",
    "Indonesian(id)": "ID",
}

# 각 언어 컬럼에 대응하는 Comments 컬럼명 (예: "English(en)" -> "English(en) Comments")
COMMENTS_COL_SUFFIX = " Comments"


TRANSLATION_CACHE: Dict[Tuple[str, str, str, str], str] = {}
GLOSSARY_CACHE: Dict[str, str] = {}  # target_lang -> glossary_id


def ensure_dir(path: str) -> str:
    """디렉토리가 없으면 생성하고 경로를 반환."""
    os.makedirs(path, exist_ok=True)
    return path


def build_run_output_dir(output_root: str) -> str:
    """이번 실행 결과를 저장할 타임스탬프 디렉토리 경로를 생성."""
    timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    return ensure_dir(os.path.join(output_root, timestamp))


def resolve_previous_csv_path(previous_root: str | None, filename: str) -> str | None:
    """이전 번역 재사용 대상 CSV 파일 경로를 결정."""
    if not previous_root:
        return None

    if os.path.isdir(previous_root):
        candidate_path = os.path.join(previous_root, filename)
        return candidate_path if os.path.isfile(candidate_path) else None

    if os.path.isfile(previous_root) and os.path.basename(previous_root) == filename:
        return previous_root

    return None


def is_pass_comment(value: str | None) -> bool:
    """Comments 컬럼 값이 PASS 인지 확인."""
    return (value or "").strip().upper() == "PASS"


def get_language_comments_col(col_name: str) -> str:
    """언어 컬럼에 대응하는 Comments 컬럼명 반환."""
    return col_name + COMMENTS_COL_SUFFIX


def _api_request(
    url: str,
    method: str = "GET",
    data: Dict[str, object] | None = None,
) -> Dict[str, object]:
    """DeepL API 공통 요청."""
    headers = {
        "Authorization": f"DeepL-Auth-Key {DEEPL_API_KEY}",
        "User-Agent": "Springtime-Translate/1.0",
    }
    body = None
    if data is not None:
        body = json.dumps(data).encode("utf-8")
        headers["Content-Type"] = "application/json"

    req = urllib.request.Request(url, data=body, method=method, headers=headers)
    with urllib.request.urlopen(req, timeout=15) as resp:
        resp_body = resp.read().decode("utf-8")
        if not resp_body.strip():
            return {}
        return json.loads(resp_body)


def _normalize_glossary_entries(entries: str) -> str:
    """Glossary TSV 비교를 위해 줄바꿈/공백을 정규화."""
    normalized_lines = []
    for line in entries.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
        stripped = line.strip()
        if stripped:
            normalized_lines.append(stripped)
    return "\n".join(normalized_lines)


def _validate_glossary_entries(entries: str, glossary_path: str) -> bool:
    """Glossary TSV가 DeepL 형식에 맞는지 사전 검증."""
    for line_number, raw_line in enumerate(
        entries.replace("\r\n", "\n").replace("\r", "\n").split("\n"),
        start=1,
    ):
        line = raw_line.strip()
        if not line:
            continue

        if raw_line.count("\t") != 1:
            print(
                f"[WARN] 잘못된 glossary 형식: {glossary_path}:{line_number} "
                f"(탭 1개 필요, 현재 {raw_line.count(chr(9))}개)",
                file=sys.stderr,
            )
            return False

        source_text, target_text = raw_line.split("\t", 1)
        if not source_text.strip() or not target_text.strip():
            print(
                f"[WARN] 잘못된 glossary 형식: {glossary_path}:{line_number} "
                "(원문/번역문이 모두 필요)",
                file=sys.stderr,
            )
            return False

    return True


def _get_glossary_entries(glossary_id: str, source_lang: str, target_lang: str) -> str | None:
    """DeepL glossary 엔트리를 조회."""
    query = urllib.parse.urlencode(
        {
            "source_lang": source_lang.lower(),
            "target_lang": target_lang.lower(),
        }
    )
    url = f"{DEEPL_GLOSSARIES_ENDPOINT}/{glossary_id}/entries?{query}"
    result = _api_request(url, method="GET")
    dictionaries = result.get("dictionaries") or []
    if not dictionaries:
        return None
    return dictionaries[0].get("entries")


def _delete_glossary(glossary_id: str) -> bool:
    """DeepL glossary 삭제."""
    try:
        _api_request(f"{DEEPL_GLOSSARIES_ENDPOINT}/{glossary_id}", method="DELETE")
        return True
    except Exception as e:  # pylint: disable=broad-except
        print(f"[WARN] Glossary 삭제 실패: {glossary_id} ({e})", file=sys.stderr)
        return False


def _get_or_create_glossary(target_lang: str) -> str | None:
    """
    target_lang 에 해당하는 glossary 파일이 있으면
    DeepL API 로 glossary 를 생성/조회하여 glossary_id 반환.
    같은 이름의 glossary 가 이미 있어도 로컬 TSV 와 내용이 다르면 삭제 후 재생성.
    ZH-HANT는 API에서 zh-Hant 미지원이므로 ZH와 동일 glossary(zh) 사용.
    """
    if target_lang in GLOSSARY_CACHE:
        return GLOSSARY_CACHE[target_lang]

    filename = GLOSSARY_FILES.get(target_lang)
    if not filename:
        return None

    glossary_path = os.path.join(GLOSSARY_DIR, filename)
    if not os.path.isfile(glossary_path):
        return None

    try:
        with open(glossary_path, "r", encoding="utf-8") as f:
            entries = f.read().strip()
    except OSError:
        return None

    if not entries:
        return None

    if not _validate_glossary_entries(entries, glossary_path):
        return None

    # ZH-HANT는 zh-Hant 미지원 → ZH와 동일 glossary 사용 (glossary target=zh)
    glossary_lang_key = "ZH" if target_lang == "ZH-HANT" else target_lang
    glossary_name = f"Springtime-ko-{glossary_lang_key}"
    tgt_lang = GLOSSARY_LANG_CODES.get(target_lang, target_lang.lower())

    # 기존 glossary 목록에서 동일 이름 찾기
    try:
        result = _api_request(DEEPL_GLOSSARIES_ENDPOINT, method="GET")
        for g in result.get("glossaries", []):
            if g.get("name") == glossary_name:
                gid = g.get("glossary_id")
                if gid:
                    existing_entries = _get_glossary_entries(gid, "ko", tgt_lang)
                    if existing_entries is None:
                        print(
                            f"[WARN] 기존 glossary 엔트리 조회 실패, 재사용: {glossary_name}",
                            file=sys.stderr,
                        )
                        GLOSSARY_CACHE[target_lang] = gid
                        if glossary_lang_key == "ZH":
                            GLOSSARY_CACHE["ZH"] = gid
                            GLOSSARY_CACHE["ZH-HANT"] = gid
                        return gid

                    if _normalize_glossary_entries(existing_entries) == _normalize_glossary_entries(entries):
                        GLOSSARY_CACHE[target_lang] = gid
                        if glossary_lang_key == "ZH":
                            GLOSSARY_CACHE["ZH"] = gid
                            GLOSSARY_CACHE["ZH-HANT"] = gid
                        return gid

                    print(
                        f"[INFO] glossary 내용 변경 감지, 재생성: {glossary_name}",
                        file=sys.stderr,
                    )
                    if not _delete_glossary(gid):
                        GLOSSARY_CACHE[target_lang] = gid
                        if glossary_lang_key == "ZH":
                            GLOSSARY_CACHE["ZH"] = gid
                            GLOSSARY_CACHE["ZH-HANT"] = gid
                        return gid
                    break
    except Exception:  # pylint: disable=broad-except
        pass

    # 없으면 새로 생성
    try:
        create_data = {
            "name": glossary_name,
            "dictionaries": [
                {
                    "source_lang": "ko",
                    "target_lang": tgt_lang,
                    "entries": entries,
                    "entries_format": "tsv",
                }
            ],
        }
        result = _api_request(DEEPL_GLOSSARIES_ENDPOINT, method="POST", data=create_data)
        gid = result.get("glossary_id")
        if gid:
            GLOSSARY_CACHE[target_lang] = gid
            if glossary_lang_key == "ZH":
                GLOSSARY_CACHE["ZH"] = gid
                GLOSSARY_CACHE["ZH-HANT"] = gid
            return gid
    except urllib.error.HTTPError as e:
        response_body = e.read().decode("utf-8", errors="replace")
        print(
            f"[WARN] Glossary 생성 실패 ({target_lang}): HTTP {e.code} {response_body}",
            file=sys.stderr,
        )
    except Exception as e:  # pylint: disable=broad-except
        print(f"[WARN] Glossary 생성 실패 ({target_lang}): {e}", file=sys.stderr)

    return None


def get_context_for_language(
    row: Dict[str, str],
    col_name: str,
    fieldnames: list[str],
) -> str | None:
    """
    해당 언어 번역 시 사용할 context 를 반환.
    우선순위: 언어별 Comments (값 있음) > Shared Comments (값 있음) > None
    """
    # 1) 언어별 Comments 컬럼이 있고, 값이 비어있지 않으면 사용
    comments_col = get_language_comments_col(col_name)
    if comments_col in fieldnames:
        lang_comment = (row.get(comments_col) or "").strip()
        if lang_comment:
            return lang_comment

    # 2) Shared Comments 컬럼이 있고, 값이 비어있지 않으면 사용
    if SHARED_COMMENTS_COL in fieldnames:
        shared = (row.get(SHARED_COMMENTS_COL) or "").strip()
        if shared:
            return shared

    return None


def load_previous_rows(prev_path: str) -> Dict[str, Dict[str, str]]:
    """
    이전에 생성된 CSV 파일을 읽어서
    Key 기준으로 전체 row 를 딕셔너리에 저장해서 반환.
    """
    prev_rows: Dict[str, Dict[str, str]] = {}

    if not os.path.isfile(prev_path):
        return prev_rows

    try:
        with open(prev_path, "r", encoding="utf-8-sig", newline="") as f_prev:
            reader = csv.DictReader(f_prev)
            for row in reader:
                if row is None:
                    continue
                key = (row.get(KEY_COL) or "").strip()
                if not key:
                    continue
                prev_rows[key] = row
    except Exception as e:  # pylint: disable=broad-except
        print(f"[WARN] 이전 결과 파일 로드 실패: {prev_path} ({e})", file=sys.stderr)

    return prev_rows


def translate_text(
    text: str,
    target_lang: str,
    source_lang: str = "KO",
    context: str | None = None,
    glossary_id: str | None = None,
    retries: int = 3,
    sleep_sec: float = 1.0,
) -> str:
    """
    DeepL API 를 이용해 한국어(text)를 target_lang 으로 번역.
    context: 다의어 해석 개선용. glossary_id: 고유명사/용어 사전 적용.
    같은 (text, target_lang, context, glossary_id) 조합은 캐시에 저장해서 재사용.
    """
    if not text:
        return ""

    cache_key = (text, target_lang, context or "", glossary_id or "")
    if cache_key in TRANSLATION_CACHE:
        return TRANSLATION_CACHE[cache_key]

    data: Dict[str, object] = {
        "text": [text],
        "target_lang": target_lang,
        "source_lang": source_lang,
    }
    if context and context.strip():
        data["context"] = context.strip()
    if glossary_id:
        data["glossary_id"] = glossary_id

    encoded_data = json.dumps(data).encode("utf-8")
    request = urllib.request.Request(DEEPL_ENDPOINT, data=encoded_data, method="POST")
    request.add_header("Authorization", f"DeepL-Auth-Key {DEEPL_API_KEY}")
    request.add_header("Content-Type", "application/json")
    request.add_header("User-Agent", "Springtime-Translate/1.0")

    for attempt in range(retries):
        try:
            with urllib.request.urlopen(request, timeout=15) as response:
                resp_body = response.read().decode("utf-8")
                resp_json = json.loads(resp_body)
                translations = resp_json.get("translations") or []
                if not translations:
                    raise RuntimeError(f"Empty translations: {resp_body}")

                translated_text = translations[0].get("text", "")
                TRANSLATION_CACHE[cache_key] = translated_text
                return translated_text
        except urllib.error.HTTPError as e:  # pylint: disable=broad-except
            err_body = ""
            try:
                err_body = e.read().decode("utf-8", errors="replace")
            except Exception:  # pylint: disable=broad-except
                pass
            if attempt == retries - 1:
                print(
                    f"[DeepL Error] text='{text[:30]}...' target={target_lang} "
                    f"HTTP {e.code} {e.reason}",
                    file=sys.stderr,
                )
                if err_body:
                    print(f"[DeepL Response] {err_body}", file=sys.stderr)
                return text
            time.sleep(sleep_sec)
            continue
        except Exception as e:  # pylint: disable=broad-except
            # 마지막 시도에서만 에러 출력
            if attempt == retries - 1:
                print(
                    f"[DeepL Error] text='{text[:30]}...' "
                    f"target={target_lang} error={e}",
                    file=sys.stderr,
                )
                # 실패 시 원문을 그대로 반환
                return text
            time.sleep(sleep_sec)


def process_csv_file(
    input_path: str,
    output_path: str,
    show_spinner: bool = False,
    renew: bool = False,
    previous_rows: Dict[str, Dict[str, str]] | None = None,
    write_bom: bool = True,
) -> None:
    """
    한 개의 CSV 파일을 읽어서,
    - 각 row 의 Korean(ko) 컬럼을 기준으로
    - 나머지 언어 컬럼(위에 정의된 LANG_COLUMNS) 값이 비어 있을 경우 DeepL 로 번역해서 채운 뒤
    - 동일한 헤더 구조로 output_path 에 저장.
    """
    with open(input_path, "r", encoding="utf-8-sig", newline="") as f_in:
        reader = csv.DictReader(f_in)
        fieldnames = reader.fieldnames

        if not fieldnames:
            print(f"[WARN] 빈 헤더 또는 잘못된 CSV 형식: {input_path}", file=sys.stderr)
            return

        rows = []

        # 전체 행을 미리 리스트로 만들어 개수 파악 (스피너/진행 표시용)
        row_list = list(reader)
        total_rows = len(row_list)

        spinner_chars = ["-", "\\", "|", "/"]

        for idx, row in enumerate(row_list, start=1):
            if row is None:
                continue

            source_text = (row.get(KOREAN_COL) or "").strip()
            key_val = (row.get(KEY_COL) or "").strip()
            shared_pass = (
                SHARED_COMMENTS_COL in fieldnames
                and is_pass_comment(row.get(SHARED_COMMENTS_COL))
            )
            pass_languages = {
                col_name
                for col_name in LANG_COLUMNS.keys()
                if col_name in fieldnames
                and is_pass_comment(row.get(get_language_comments_col(col_name)))
            }

            # Shared Comments 가 PASS 이면 행 전체를 입력값 그대로 유지한다.
            if shared_pass:
                rows.append(row)
                if show_spinner and total_rows > 0:
                    spinner = spinner_chars[idx % len(spinner_chars)]
                    progress = f"{idx}/{total_rows}"
                    msg = (
                        f"\r[{spinner}] 번역 중(PASS 유지): "
                        f"{os.path.basename(input_path)} ({progress})"
                    )
                    print(msg, end="", flush=True)
                continue

            # 1) renew 옵션이 꺼져 있고, 이전 결과가 있으며
            #    같은 Key & 같은 한글 텍스트라면
            #    DeepL 호출 대신 이전 번역 결과를 그대로 사용
            reused_from_previous = False
            if not renew and previous_rows is not None and key_val:
                prev_row = previous_rows.get(key_val)
                if prev_row is not None:
                    prev_korean = (prev_row.get(KOREAN_COL) or "").strip()
                    if prev_korean == source_text and source_text:
                        # 이전 번역 결과 복사
                        for col_name in LANG_COLUMNS.keys():
                            if col_name in prev_row and col_name not in pass_languages:
                                row[col_name] = prev_row[col_name]
                        reused_from_previous = True

            if reused_from_previous:
                rows.append(row)

                # 스피너 + 진행률 출력만 업데이트
                if show_spinner and total_rows > 0:
                    spinner = spinner_chars[idx % len(spinner_chars)]
                    progress = f"{idx}/{total_rows}"
                    msg = (
                        f"\r[{spinner}] 번역 중(이전 결과 재사용): "
                        f"{os.path.basename(input_path)} ({progress})"
                    )
                    print(msg, end="", flush=True)
                continue

            # 2) 한국어 원문이 없으면 번역 스킵
            if not source_text:
                rows.append(row)
                # 진행 표시만 업데이트
                if show_spinner and total_rows > 0:
                    spinner = spinner_chars[idx % len(spinner_chars)]
                    progress = f"{idx}/{total_rows}"
                    msg = f"\r[{spinner}] 번역 중: {os.path.basename(input_path)} ({progress})"
                    print(msg, end="", flush=True)
                continue

            for col_name, target_lang in LANG_COLUMNS.items():
                if col_name not in fieldnames:
                    # 해당 언어 컬럼이 실제 파일에 없으면 건너뜀
                    continue

                # 언어별 Comments 가 PASS 이면 입력 CSV 값을 그대로 유지한다.
                if col_name in pass_languages:
                    continue

                current_val_raw = row.get(col_name) or ""
                current_val = current_val_raw.strip()

                # renew 가 꺼져 있을 때만 기존 값을 존중하고,
                # 켜져 있으면 기존 번역 여부와 상관없이 항상 다시 번역
                if not renew:
                    # 이미 값이 있고, 그 값이 단순한 placeholder("-")가 아니면 그대로 둠
                    # 빈 문자열("") 또는 "-" 인 경우는 "미번역"으로 보고 번역 실행
                    if current_val not in BLANK_VALUES and current_val != "":
                        continue

                context = get_context_for_language(row, col_name, fieldnames)
                glossary_id = _get_or_create_glossary(target_lang)
                translated = translate_text(
                    source_text,
                    target_lang=target_lang,
                    context=context,
                    glossary_id=glossary_id,
                )
                row[col_name] = translated

            rows.append(row)

            # 스피너 + 진행률 출력
            if show_spinner and total_rows > 0:
                spinner = spinner_chars[idx % len(spinner_chars)]
                progress = f"{idx}/{total_rows}"
                msg = f"\r[{spinner}] 번역 중: {os.path.basename(input_path)} ({progress})"
                print(msg, end="", flush=True)

    os.makedirs(os.path.dirname(output_path), exist_ok=True)

    output_encoding = "utf-8-sig" if write_bom else "utf-8"

    with open(output_path, "w", encoding=output_encoding, newline="") as f_out:
        writer = csv.DictWriter(f_out, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

    # 스피너 한 줄 마무리 후 줄바꿈
    if show_spinner:
        print("\r[✔] 번역 완료: {}".format(os.path.basename(input_path)))


def main():
    """
    --file/-f 파라미터로 파일 또는 디렉토리를 지정할 수 있습니다.
    - 지정하지 않으면 기본값: springtime-server/translate/cvs 디렉토리
    - 디렉토리이면: 내부의 모든 CSV 파일을 번역
    - 파일이면: 해당 CSV 파일만 번역
    출력은 항상 springtime-server/translate/output 아래에 생성됩니다.
    최신 번역본은 output/all 에 저장하고, 실행별 보관본은 output/YYYYMMDDHHMMSS 에 저장합니다.
    이전 번역 재사용은 기본적으로 output/all 에서 찾으며,
    --previous_output/-p 로 다른 디렉토리 또는 파일을 지정할 수 있습니다.
    """
    default_input_dir = os.path.join(BASE_DIR, "cvs")

    parser = argparse.ArgumentParser(description="DeepL을 사용한 CSV 번역 스크립트")
    parser.add_argument(
        "--file",
        "-f",
        dest="file",
        help="번역할 CSV 파일 또는 CSV 파일들이 있는 디렉토리 경로",
        default=None,
    )
    parser.add_argument(
        "--previous_output",
        "-p",
        dest="previous_output",
        help=(
            "이전에 생성된 번역 CSV 들이 있는 디렉토리 또는 단일 CSV 파일 경로. "
            "같은 파일이 있고, 같은 Key 의 한글 값이 동일하면 이전 번역 값을 그대로 재사용합니다."
        ),
        default=None,
    )
    parser.add_argument(
        "--renew",
        "-r",
        action="store_true",
        help=(
            "CSV 나 이전 결과에 기존 번역이 있어도 모두 무시하고, "
            "Korean(ko) 컬럼 기준으로 모든 언어를 다시 DeepL로 번역합니다."
        ),
    )
    parser.add_argument(
        "--no-bom",
        action="store_true",
        help="출력 CSV를 UTF-8 with BOM 대신 UTF-8로 저장합니다.",
    )
    args = parser.parse_args()

    target_path = args.file if args.file else default_input_dir
    target_path = os.path.abspath(target_path)

    output_root = ensure_dir(os.path.join(BASE_DIR, "output"))
    latest_output_root = ensure_dir(os.path.join(output_root, "all"))
    run_output_root = build_run_output_dir(output_root)

    # 이전 번역 결과를 찾는 기본 위치는 output/all 이며,
    # --previous_output/-p 로 명시된 값이 있으면 그것을 우선 사용
    previous_root: str | None = None
    if args.previous_output:
        previous_root = os.path.abspath(args.previous_output)
    else:
        previous_root = latest_output_root

    if os.path.isdir(target_path):
        input_dir = target_path
        csv_files = [
            f
            for f in os.listdir(input_dir)
            if os.path.isfile(os.path.join(input_dir, f))
            and f.lower().endswith(".csv")
        ]

        if not csv_files:
            print(f"[WARN] CSV 파일이 없습니다: {input_dir}", file=sys.stderr)
            return

        print(f"입력 디렉토리 : {input_dir}")
        print(f"출력 디렉토리 : {output_root}")
        print(f"최신본 디렉토리 : {latest_output_root}")
        print(f"실행본 디렉토리 : {run_output_root}")
        print(f"처리 대상 CSV : {len(csv_files)}개")

        for filename in csv_files:
            in_path = os.path.join(input_dir, filename)
            out_path = os.path.join(run_output_root, filename)
            latest_out_path = os.path.join(latest_output_root, filename)
            print(f" - 처리 중: {filename}")

            # renew 가 꺼져 있을 때만 이전 결과에서 같은 파일이 있으면 로드
            prev_rows_for_file: Dict[str, Dict[str, str]] | None = None
            if not args.renew and previous_root:
                cand_prev = resolve_previous_csv_path(previous_root, filename)
                if cand_prev:
                    prev_rows_for_file = load_previous_rows(cand_prev)

            process_csv_file(
                in_path,
                out_path,
                show_spinner=True,
                renew=args.renew,
                previous_rows=prev_rows_for_file,
                write_bom=not args.no_bom,
            )
            shutil.copyfile(out_path, latest_out_path)

            # 번역이 끝난 CSV 내용을 화면에 바로 표시
            try:
                print(f"\n===== 번역 결과 보기: {out_path} =====")
                with open(out_path, "r", encoding="utf-8-sig") as f_res:
                    print(f_res.read())
                print("===== 끝 =====\n")
            except Exception as e:  # pylint: disable=broad-except
                print(f"[WARN] 결과 파일 출력 중 오류: {out_path} ({e})", file=sys.stderr)

    elif os.path.isfile(target_path):
        if not target_path.lower().endswith(".csv"):
            print(f"[ERROR] CSV 파일이 아닙니다: {target_path}", file=sys.stderr)
            sys.exit(1)

        filename = os.path.basename(target_path)
        out_path = os.path.join(run_output_root, filename)
        latest_out_path = os.path.join(latest_output_root, filename)

        print(f"입력 파일     : {target_path}")
        print(f"출력 디렉토리 : {output_root}")
        print(f"최신본 디렉토리 : {latest_output_root}")
        print(f"실행본 디렉토리 : {run_output_root}")
        print("처리 대상 CSV : 1개 (단일 파일)")
        print(f" - 처리 중: {filename}")

        prev_rows_for_file: Dict[str, Dict[str, str]] | None = None
        if not args.renew and previous_root:
            cand_prev = resolve_previous_csv_path(previous_root, filename)
            if cand_prev:
                prev_rows_for_file = load_previous_rows(cand_prev)

        process_csv_file(
            target_path,
            out_path,
            show_spinner=True,
            renew=args.renew,
            previous_rows=prev_rows_for_file,
            write_bom=not args.no_bom,
        )
        shutil.copyfile(out_path, latest_out_path)

        # 번역이 끝난 CSV 내용을 화면에 바로 표시
        try:
            print(f"\n===== 번역 결과 보기: {out_path} =====")
            with open(out_path, "r", encoding="utf-8-sig") as f_res:
                print(f_res.read())
            print("===== 끝 =====\n")
        except Exception as e:  # pylint: disable=broad-except
            print(f"[WARN] 결과 파일 출력 중 오류: {out_path} ({e})", file=sys.stderr)

    else:
        print(f"[ERROR] 파일/디렉토리를 찾을 수 없습니다: {target_path}", file=sys.stderr)
        sys.exit(1)

    print("모든 CSV 번역 및 저장 완료.")
    print(f"최신본 디렉토리: {latest_output_root}")
    print(f"실행본 디렉토리: {run_output_root}")


if __name__ == "__main__":
    main()


