import json
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk


class JsonKoreanEditor:

    def __init__(self, root):
        self.root = root
        self.root.title("JSON koKR 번역 편집기")
        self.root.geometry("1200x800")

        self.data = []
        self.file_path = None
        self.current_index = None
        self.modified = set()

        self.create_ui()

        # 단축키
        self.root.bind("<Control-s>", self.save_json)
        self.root.bind("<Control-f>", self.focus_search)

    # =========================================================
    # UI
    # =========================================================

    def create_ui(self):

        # =====================================================
        # 상단 버튼 영역
        # =====================================================

        top_frame = tk.Frame(self.root)
        top_frame.pack(
            fill="x",
            padx=10,
            pady=10
        )

        tk.Button(
            top_frame,
            text="JSON 열기",
            command=self.open_json,
            width=12
        ).pack(
            side="left",
            padx=3
        )

        tk.Button(
            top_frame,
            text="저장",
            command=self.save_json,
            width=10
        ).pack(
            side="left",
            padx=3
        )

        tk.Button(
            top_frame,
            text="다른 이름으로 저장",
            command=self.save_as_json,
            width=16
        ).pack(
            side="left",
            padx=3
        )

        # 구분선
        tk.Label(
            top_frame,
            text="|"
        ).pack(
            side="left",
            padx=10
        )

        # =====================================================
        # 일괄 추가할 문자열
        # =====================================================

        tk.Label(
            top_frame,
            text="이름 뒤에 추가:"
        ).pack(
            side="left",
            padx=(0, 5)
        )

        self.suffix_entry = tk.Entry(
            top_frame,
            width=15,
            font=("Arial", 11)
        )

        self.suffix_entry.pack(
            side="left",
            padx=3
        )

        # 기본값
        self.suffix_entry.insert(
            0,
            ""
        )

        tk.Button(
            top_frame,
            text="일괄 추가",
            command=self.batch_add_suffix,
            width=12,
            bg="#FF9800",
            fg="white"
        ).pack(
            side="left",
            padx=5
        )

        # =====================================================
        # 파일명
        # =====================================================

        self.file_label = tk.Label(
            top_frame,
            text="파일이 열리지 않았습니다.",
            fg="gray"
        )

        self.file_label.pack(
            side="left",
            padx=15
        )

        # =====================================================
        # 검색 영역
        # =====================================================

        search_frame = tk.Frame(self.root)
        search_frame.pack(
            fill="x",
            padx=10,
            pady=(0, 8)
        )

        tk.Label(
            search_frame,
            text="검색:"
        ).pack(
            side="left"
        )

        self.search_entry = tk.Entry(
            search_frame,
            font=("Arial", 11)
        )

        self.search_entry.pack(
            side="left",
            fill="x",
            expand=True,
            padx=8
        )

        self.search_entry.bind(
            "<KeyRelease>",
            lambda event: self.refresh_list()
        )

        tk.Button(
            search_frame,
            text="검색 지우기",
            command=self.clear_search
        ).pack(
            side="left"
        )

        # =====================================================
        # 메인 영역
        # =====================================================

        main_frame = tk.Frame(self.root)
        main_frame.pack(
            fill="both",
            expand=True,
            padx=10,
            pady=5
        )

        # =====================================================
        # 왼쪽 리스트
        # =====================================================

        left_frame = tk.Frame(main_frame)
        left_frame.pack(
            side="left",
            fill="both",
            expand=True
        )

        columns = (
            "id",
            "key",
            "enUS",
            "koKR"
        )

        self.tree = ttk.Treeview(
            left_frame,
            columns=columns,
            show="headings"
        )

        self.tree.heading(
            "id",
            text="ID"
        )

        self.tree.heading(
            "key",
            text="Key"
        )

        self.tree.heading(
            "enUS",
            text="English"
        )

        self.tree.heading(
            "koKR",
            text="한국어 (koKR)"
        )

        self.tree.column(
            "id",
            width=70,
            anchor="center"
        )

        self.tree.column(
            "key",
            width=120
        )

        self.tree.column(
            "enUS",
            width=300
        )

        self.tree.column(
            "koKR",
            width=400
        )

        self.tree.pack(
            side="left",
            fill="both",
            expand=True
        )

        scrollbar = ttk.Scrollbar(
            left_frame,
            orient="vertical",
            command=self.tree.yview
        )

        scrollbar.pack(
            side="right",
            fill="y"
        )

        self.tree.configure(
            yscrollcommand=scrollbar.set
        )

        # 선택
        self.tree.bind(
            "<<TreeviewSelect>>",
            self.select_item
        )

        # 더블 클릭
        self.tree.bind(
            "<Double-1>",
            self.double_click_edit
        )

        # 수정된 항목
        self.tree.tag_configure(
            "modified",
            background="#FFF0B3"
        )

        # =====================================================
        # 오른쪽 편집 영역
        # =====================================================

        right_frame = tk.Frame(
            main_frame,
            width=350
        )

        right_frame.pack(
            side="right",
            fill="y",
            padx=(15, 0)
        )

        tk.Label(
            right_frame,
            text="항목 정보",
            font=("Arial", 14, "bold")
        ).pack(
            anchor="w",
            pady=(0, 10)
        )

        self.info_label = tk.Label(
            right_frame,
            text="항목을 선택하세요.",
            justify="left",
            anchor="w",
            wraplength=330
        )

        self.info_label.pack(
            fill="x"
        )

        # =====================================================
        # 번역 편집
        # =====================================================

        tk.Label(
            right_frame,
            text="koKR 번역",
            font=("Arial", 11, "bold")
        ).pack(
            anchor="w",
            pady=(25, 5)
        )

        self.translation_entry = tk.Entry(
            right_frame,
            font=("Arial", 13)
        )

        self.translation_entry.pack(
            fill="x",
            pady=5
        )

        self.translation_entry.bind(
            "<Return>",
            lambda event: self.apply_edit()
        )

        # =====================================================
        # 색상 코드 버튼 9개
        # =====================================================

        color_button_frame = tk.Frame(
            right_frame
        )

        color_button_frame.pack(
            fill="x",
            pady=(10, 5)
        )

        color_buttons = [
            ("■\n0", "ÿc;■", "#BF00FF"),   # 물약
            ("■\n1", "ÿc1■", "#FF6969"),   
            ("■\n2", "ÿc2■", "#00FF00"),   # 세트
            ("■\n3", "ÿc3■", "#8888FF"),   
            ("■\n4", "ÿc4■", "#D4C491"),   # 유니크
            ("■\n5", "ÿc5■", "#848484"),
            ("■\n6", "ÿc6■", "#000000"),
            ("■\n7", "ÿc7■", "#DBD096"),
            ("■\n8", "ÿc8■", "#FFBB00"),   # 동상,세계석
            ("■\n9", "ÿc9■", "#FFFF7F"),
        ]

        for label, code, color in color_buttons:

            button = tk.Button(
                color_button_frame,
                text=label,
                fg=color,
                bg="#FFFFFF",
                font=("Arial", 18, "bold"),
                width=1,
                height=1,
                relief="raised",
                command=lambda c=code: self.add_color_code(c)
            )

            button.pack(
                side="left",
                fill="x",
                expand=True,
                padx=2
            )

        # 버튼 설명
        tk.Label(
            right_frame,
            text="색상 코드를 이름 맨 뒤에 추가",
            fg="gray"
        ).pack(
            anchor="w",
            pady=(0, 5)
        )

        
        # =====================================================
        # 영문자 윗첨자 e a s c d 버튼
        # =====================================================

        english_superscript_button_frame = tk.Frame(
            right_frame
        )

        english_superscript_button_frame.pack(
            fill="x",
            pady=(5, 5)
        )

        english_superscript_chars = [
            "ᵉ",
            "ᵃ",
            "ˢ",
            "ᶜ",
            "ᵈ"
        ]

        for char in english_superscript_chars:

            button = tk.Button(
                english_superscript_button_frame,
                text=char,
                bg="#FFFFFF",
                font=("Arial", 14, "bold"),
                width=2,
                height=1,
                relief="raised",
                command=lambda c=char: self.add_special_char(c)
            )

            button.pack(
                side="left",
                fill="x",
                expand=True,
                padx=2
            )

        # 버튼 설명
        tk.Label(
            right_frame,
            text="영문자 윗첨자를 이름 맨 뒤에 추가",
            fg="gray"
        ).pack(
            anchor="w",
            pady=(0, 5)
        )

        # =====================================================
        # 윗첨자 ¹ ~ ⁶ 버튼
        # =====================================================

        superscript_button_frame = tk.Frame(
            right_frame
        )

        superscript_button_frame.pack(
            fill="x",
            pady=(5, 5)
        )

        superscript_chars = [
            "¹",
            "²",
            "³",
            "⁴",
            "⁵",
            "⁶"
        ]

        for char in superscript_chars:

            button = tk.Button(
                superscript_button_frame,
                text=char,
                bg="#FFFFFF",
                font=("Arial", 14, "bold"),
                width=2,
                height=1,
                relief="raised",
                command=lambda c=char: self.add_special_char(c)
            )

            button.pack(
                side="left",
                fill="x",
                expand=True,
                padx=2
            )

        # 버튼 설명
        tk.Label(
            right_frame,
            text="윗첨자를 이름 맨 뒤에 추가",
            fg="gray"
        ).pack(
            anchor="w",
            pady=(0, 5)
        )

        # =====================================================
        # 수정 적용
        # =====================================================

        tk.Button(
            right_frame,
            text="수정 적용",
            command=self.apply_edit,
            bg="#4CAF50",
            fg="white",
            height=2
        ).pack(
            fill="x",
            pady=10
        )

        # =====================================================
        # 이전 / 다음
        # =====================================================

        nav_frame = tk.Frame(right_frame)
        nav_frame.pack(
            fill="x",
            pady=5
        )

        tk.Button(
            nav_frame,
            text="◀ 이전",
            command=self.select_previous
        ).pack(
            side="left",
            fill="x",
            expand=True,
            padx=(0, 3)
        )

        tk.Button(
            nav_frame,
            text="다음 ▶",
            command=self.select_next
        ).pack(
            side="left",
            fill="x",
            expand=True,
            padx=(3, 0)
        )

        # =====================================================
        # 위치
        # =====================================================

        self.position_label = tk.Label(
            right_frame,
            text="0 / 0",
            fg="gray"
        )

        self.position_label.pack(
            pady=5
        )

        # =====================================================
        # 도움말
        # =====================================================

        help_text = (
            "단축키\n"
            "Ctrl + S : 저장\n"
            "Ctrl + F : 검색\n"
            "Enter : 수정 적용\n"
            "더블클릭 : 편집"
        )

        tk.Label(
            right_frame,
            text=help_text,
            justify="left",
            fg="gray"
        ).pack(
            anchor="w",
            pady=(30, 0)
        )

        # =====================================================
        # 하단 상태
        # =====================================================

        self.status_label = tk.Label(
            self.root,
            text="준비됨",
            anchor="w",
            relief="sunken"
        )

        self.status_label.pack(
            fill="x",
            side="bottom"
        )

    # =========================================================
    # JSON 열기
    # =========================================================

    def open_json(self):

        path = filedialog.askopenfilename(
            title="JSON 파일 선택",
            filetypes=[
                ("JSON files", "*.json"),
                ("All files", "*.*")
            ]
        )

        if not path:
            return

        try:

            # UTF-8 BOM 지원
            with open(
                path,
                "r",
                encoding="utf-8-sig"
            ) as f:

                self.data = json.load(f)

            if not isinstance(self.data, list):

                messagebox.showerror(
                    "JSON 오류",
                    "JSON의 최상위 구조가 배열([])이어야 합니다."
                )

                return

            self.file_path = path
            self.current_index = None
            self.modified.clear()

            self.file_label.config(
                text=path,
                fg="black"
            )

            self.refresh_list()

            self.status_label.config(
                text=f"{len(self.data):,}개의 항목을 불러왔습니다."
            )

        except json.JSONDecodeError as e:

            messagebox.showerror(
                "JSON 오류",
                f"JSON 파일을 읽을 수 없습니다.\n\n{e}"
            )

        except Exception as e:

            messagebox.showerror(
                "오류",
                str(e)
            )

    # =========================================================
    # 리스트 갱신
    # =========================================================

    def refresh_list(self):

        for item in self.tree.get_children():
            self.tree.delete(item)

        search_text = (
            self.search_entry
            .get()
            .strip()
            .lower()
        )

        visible_count = 0

        for index, item in enumerate(self.data):

            item_id = str(
                item.get("id", "")
            )

            key = str(
                item.get("Key", "")
            )

            en_us = str(
                item.get("enUS", "")
            )

            ko_kr = str(
                item.get("koKR", "")
            )

            combined = (
                item_id +
                " " +
                key +
                " " +
                en_us +
                " " +
                ko_kr
            ).lower()

            if search_text:
                if search_text not in combined:
                    continue

            tags = ()

            if index in self.modified:
                tags = ("modified",)

            self.tree.insert(
                "",
                "end",
                iid=str(index),
                values=(
                    item_id,
                    key,
                    en_us,
                    ko_kr
                ),
                tags=tags
            )

            visible_count += 1

        self.position_label.config(
            text=(
                f"표시: {visible_count:,} / "
                f"전체: {len(self.data):,}"
            )
        )

    # =========================================================
    # 항목 선택
    # =========================================================

    def select_item(self, event=None):

        selected = self.tree.selection()

        if not selected:
            return

        index = int(selected[0])

        self.current_index = index

        item = self.data[index]

        self.info_label.config(
            text=(
                f"ID : {item.get('id', '')}\n"
                f"Key : {item.get('Key', '')}\n"
                f"English : {item.get('enUS', '')}"
            )
        )

        self.translation_entry.delete(
            0,
            tk.END
        )

        self.translation_entry.insert(
            0,
            item.get("koKR", "")
        )

        self.position_label.config(
            text=(
                f"{index + 1:,} / "
                f"{len(self.data):,}"
            )
        )

    # =========================================================
    # 더블클릭 편집
    # =========================================================

    def double_click_edit(self, event):

        self.translation_entry.focus()

        self.translation_entry.select_range(
            0,
            tk.END
        )

    # =========================================================
    # 색상 코드 추가
    # =========================================================

    def add_color_code(self, code):

        if self.current_index is None:

            messagebox.showwarning(
                "알림",
                "먼저 항목을 선택하세요."
            )

            return

        # 현재 koKR
        current_name = str(
            self.data[
                self.current_index
            ].get(
                "koKR",
                ""
            )
        )

        # 맨 뒤에 색상 코드 추가
        new_name = current_name + code

        # 데이터 수정
        self.data[
            self.current_index
        ]["koKR"] = new_name

        # 수정된 항목으로 표시
        self.modified.add(
            self.current_index
        )

        # 입력창도 바로 갱신
        self.translation_entry.delete(
            0,
            tk.END
        )

        self.translation_entry.insert(
            0,
            new_name
        )

        # 리스트 갱신
        self.refresh_list()

        # 현재 항목 선택 유지
        current = self.current_index

        if self.tree.exists(
            str(current)
        ):

            self.tree.selection_set(
                str(current)
            )

            self.tree.focus(
                str(current)
            )

            self.tree.see(
                str(current)
            )

        self.status_label.config(
            text=(
                f"'{code}' 색상 코드를 "
                f"이름 뒤에 추가했습니다."
            )
        )

    # =========================================================
    # 윗첨자 추가
    # =========================================================

    def add_special_char(self, char):

        if self.current_index is None:

            messagebox.showwarning(
                "알림",
                "먼저 항목을 선택하세요."
            )

            return

        # 현재 koKR
        current_name = str(
            self.data[
                self.current_index
            ].get(
                "koKR",
                ""
            )
        )

        # 맨 뒤에 윗첨자 추가
        new_name = current_name + char

        # 데이터 수정
        self.data[
            self.current_index
        ]["koKR"] = new_name

        # 수정된 항목으로 표시
        self.modified.add(
            self.current_index
        )

        # 입력창 갱신
        self.translation_entry.delete(
            0,
            tk.END
        )

        self.translation_entry.insert(
            0,
            new_name
        )

        # 리스트 갱신
        self.refresh_list()

        # 현재 항목 선택 유지
        current = self.current_index

        if self.tree.exists(
            str(current)
        ):

            self.tree.selection_set(
                str(current)
            )

            self.tree.focus(
                str(current)
            )

            self.tree.see(
                str(current)
            )

        self.status_label.config(
            text=(
                f"'{char}' 윗첨자를 "
                f"이름 뒤에 추가했습니다."
            )
        )

    # =========================================================
    # 수정 적용
    # =========================================================

    def apply_edit(self):

        if self.current_index is None:

            messagebox.showwarning(
                "알림",
                "먼저 항목을 선택하세요."
            )

            return

        new_translation = (
            self.translation_entry.get()
        )

        self.data[
            self.current_index
        ]["koKR"] = new_translation

        self.modified.add(
            self.current_index
        )

        current = self.current_index

        self.refresh_list()

        if self.tree.exists(
            str(current)
        ):

            self.tree.selection_set(
                str(current)
            )

            self.tree.focus(
                str(current)
            )

            self.tree.see(
                str(current)
            )

        self.status_label.config(
            text=(
                f"{current + 1}번 항목 수정 완료"
            )
        )

    # =========================================================
    # 이전
    # =========================================================

    def select_previous(self):

        if not self.data:
            return

        if self.current_index is None:
            index = 0
        else:
            index = max(
                0,
                self.current_index - 1
            )

        self.select_index(index)

    # =========================================================
    # 다음
    # =========================================================

    def select_next(self):

        if not self.data:
            return

        if self.current_index is None:
            index = 0
        else:
            index = min(
                len(self.data) - 1,
                self.current_index + 1
            )

        self.select_index(index)

    # =========================================================
    # 특정 항목 선택
    # =========================================================

    def select_index(self, index):

        if not self.tree.exists(
            str(index)
        ):

            # 검색 해제
            self.search_entry.delete(
                0,
                tk.END
            )

            self.refresh_list()

        if self.tree.exists(
            str(index)
        ):

            self.tree.selection_set(
                str(index)
            )

            self.tree.focus(
                str(index)
            )

            self.tree.see(
                str(index)
            )

            self.current_index = index

            self.select_item()

            self.translation_entry.focus()

    # =========================================================
    # 검색 포커스
    # =========================================================

    def focus_search(self, event=None):

        self.search_entry.focus()

        self.search_entry.select_range(
            0,
            tk.END
        )

    # =========================================================
    # 검색 지우기
    # =========================================================

    def clear_search(self):

        self.search_entry.delete(
            0,
            tk.END
        )

        self.refresh_list()

        self.search_entry.focus()

    # =========================================================
    # 문자열 일괄 추가
    # =========================================================

    def batch_add_suffix(self):

        if not self.data:

            messagebox.showwarning(
                "알림",
                "먼저 JSON 파일을 열어주세요."
            )

            return

        # 사용자가 입력한 문자열
        suffix = self.suffix_entry.get()

        if not suffix:

            messagebox.showwarning(
                "알림",
                "추가할 문자열을 입력해주세요."
            )

            self.suffix_entry.focus()

            return

        # =====================================================
        # 적용 대상 찾기
        # =====================================================

        targets = []

        for index, item in enumerate(self.data):

            name = str(
                item.get("koKR", "")
            )

            # 빈 값 제외
            if not name:
                continue

            # 이미 같은 문자열로 끝나는 경우 제외
            if name.endswith(suffix):
                continue

            targets.append(index)

        # =====================================================
        # 대상 없음
        # =====================================================

        if not targets:

            messagebox.showinfo(
                "일괄 적용",
                (
                    f"'{suffix}'를 추가할 "
                    "항목이 없습니다."
                )
            )

            return

        # =====================================================
        # 미리보기
        # =====================================================

        preview_lines = []

        for index in targets[:10]:

            name = str(
                self.data[index].get(
                    "koKR",
                    ""
                )
            )

            preview_lines.append(
                f"{name} → {name}{suffix}"
            )

        preview = "\n".join(
            preview_lines
        )

        if len(targets) > 10:
            preview += (
                f"\n\n... 외 "
                f"{len(targets) - 10:,}개"
            )

        # =====================================================
        # 확인
        # =====================================================

        result = messagebox.askyesno(
            "문자열 일괄 추가",
            (
                f"총 {len(targets):,}개의 "
                "항목에\n\n"
                f"'{suffix}'\n\n"
                "를 추가합니다.\n\n"
                "예시:\n"
                f"{preview}\n\n"
                "계속하시겠습니까?"
            )
        )

        if not result:
            return

        # =====================================================
        # 실제 적용
        # =====================================================

        for index in targets:

            name = str(
                self.data[index].get(
                    "koKR",
                    ""
                )
            )

            self.data[index]["koKR"] = (
                name + suffix
            )

            self.modified.add(index)

        self.refresh_list()

        self.status_label.config(
            text=(
                f"{len(targets):,}개 항목에 "
                f"'{suffix}' 추가 완료"
            )
        )

        messagebox.showinfo(
            "완료",
            (
                f"{len(targets):,}개의 항목에\n"
                f"'{suffix}'를 추가했습니다."
            )
        )

    # =========================================================
    # JSON 저장
    # =========================================================

    def save_json(self, event=None):

        if not self.data:

            messagebox.showwarning(
                "알림",
                "저장할 데이터가 없습니다."
            )

            return

        if not self.file_path:

            self.save_as_json()

            return

        try:

            # UTF-8 BOM 유지
            with open(
                self.file_path,
                "w",
                encoding="utf-8-sig"
            ) as f:

                json.dump(
                    self.data,
                    f,
                    ensure_ascii=False,
                    indent=2
                )

            count = len(self.modified)

            self.modified.clear()

            self.refresh_list()

            self.status_label.config(
                text=(
                    f"저장 완료 - "
                    f"{count:,}개 항목 변경"
                )
            )

            messagebox.showinfo(
                "저장 완료",
                "JSON 파일을 저장했습니다."
            )

        except Exception as e:

            messagebox.showerror(
                "저장 오류",
                str(e)
            )

    # =========================================================
    # 다른 이름으로 저장
    # =========================================================

    def save_as_json(self):

        if not self.data:

            messagebox.showwarning(
                "알림",
                "저장할 데이터가 없습니다."
            )

            return

        path = filedialog.asksaveasfilename(
            title="JSON 파일 저장",
            defaultextension=".json",
            filetypes=[
                ("JSON files", "*.json"),
                ("All files", "*.*")
            ]
        )

        if not path:
            return

        try:

            with open(
                path,
                "w",
                encoding="utf-8-sig"
            ) as f:

                json.dump(
                    self.data,
                    f,
                    ensure_ascii=False,
                    indent=2
                )

            self.file_path = path

            self.modified.clear()

            self.file_label.config(
                text=path,
                fg="black"
            )

            self.refresh_list()

            self.status_label.config(
                text="다른 이름으로 저장 완료"
            )

            messagebox.showinfo(
                "저장 완료",
                "JSON 파일을 저장했습니다."
            )

        except Exception as e:

            messagebox.showerror(
                "저장 오류",
                str(e)
            )


# =============================================================
# 프로그램 시작
# =============================================================

if __name__ == "__main__":

    root = tk.Tk()

    app = JsonKoreanEditor(root)

    root.mainloop()
