-- =========================================================
--  WezTerm Configuration  (v2 - 검증본)
--  Windows / macOS / Linux 공통  |  한국어 + 영어 최적화
--
--  설치 위치 (세 OS 모두 홈 디렉토리):
--    Windows : %USERPROFILE%\.wezterm.lua
--    macOS   : /Users/<사용자명>/.wezterm.lua
--    Linux   : /home/<사용자명>/.wezterm.lua
--
--  검증 기준: wezterm.org 공식 문서 (config/default-keys, fonts,
--             keys, window_decorations, files)
-- =========================================================

local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()

-- ---------------------------------------------------------
-- 1. 플랫폼 판별
-- ---------------------------------------------------------
local triple = wezterm.target_triple
local is_windows = triple:find('windows') ~= nil
local is_mac = triple:find('darwin') ~= nil
local is_linux = triple:find('linux') ~= nil

-- 파일 존재 확인.
-- [주의] wezterm.run_child_process 는 프로그램이 없을 때 false 를 반환하지 않고
--        Lua 에러를 던진다("No such file or directory"). 설정 최상위에서
--        이를 쓰면 창 대신 Configuration Error 가 뜬다. 따라서 사용하지 않는다.
--        io.open 은 실패 시 nil 을 돌려주므로 안전하고 프로세스도 띄우지 않는다.
local function file_exists(path)
  local ok, f = pcall(io.open, path, 'r')
  if ok and f then f:close(); return true end
  return false
end

-- WezTerm 빌드 날짜 비교 (예: '20240203-110809-...' -> 20240203)
-- 신버전 전용 옵션을 구버전에서 켜면 설정 전체가 로드 실패하므로 게이트가 필요하다.
local BUILD = tonumber(tostring(wezterm.version):match('^(%d%d%d%d%d%d%d%d)') or '0') or 0
local function build_at_least(d) return BUILD >= d end

-- 여러 후보 경로 중 처음 존재하는 것을 반환 (없으면 nil)
local function first_existing(paths)
  for _, p in ipairs(paths) do
    if file_exists(p) then return p end
  end
  return nil
end

-- ---------------------------------------------------------
-- 2. 폰트 : 영문 주폰트 + 한글 폴백
--    WezTerm은 지정한 목록 뒤에 Nerd Font Symbols 등
--    자체 기본 폴백을 자동 추가하므로 따로 나열할 필요 없음
-- ---------------------------------------------------------
local cjk_font
if is_windows then
  cjk_font = 'Malgun Gothic'        -- 윈도우 기본 탑재
elseif is_mac then
  cjk_font = 'Apple SD Gothic Neo'  -- macOS 기본 탑재
else
  cjk_font = 'Noto Sans Mono CJK KR'
end

-- [정정] 이전 버전에서 CJK 폰트에 scale=1.33 을 넣었으나 이는 잘못이었다.
--   한글 x_adv 가 셀폭보다 작은 것은 결함이 아니라 폰트의 의도된 좌우 여백이다.
--   1.33 배로 강제 확대하면 글리프가 픽셀 격자에서 벗어나 힌팅이 깨지고
--   화면이 뿌옇게 흐려진다. 크기 조절은 scale 이 아니라 font_size 로 해야 한다.
config.font = wezterm.font_with_fallback {
  -- 'JetBrains Mono' 는 WezTerm 내장 폰트라 설치가 필요 없고,
  -- Nerd Font 아이콘은 내장 'Symbols Nerd Font Mono' 가 자동 폴백된다.
  { family = 'JetBrains Mono' },
  { family = 'D2Coding' },   -- 한글 고정폭. 설치돼 있으면 우선 사용
  { family = cjk_font },     -- OS 기본 한글 폰트
  'Noto Color Emoji',
}

-- 크기: 기본 12 에서 한 단계 낮춘 11 을 기본값으로 둔다.
-- 크게 쓰고 싶으면 이 값만 12~13 으로 올릴 것 (scale 은 건드리지 말 것)
config.font_size = is_mac and 13.0 or 11.0
config.line_height = 1.1
config.cell_width = 1.0

-- CJK 폰트는 cap-height 지표가 부정확하므로 자동 스케일링을 끈다
config.use_cap_height_to_scale_fallback_fonts = false
config.warn_about_missing_glyphs = false

-- 글자 선명도.
--   Windows/Linux LCD 모니터에서는 서브픽셀 렌더링(HorizontalLcd)이 가장 또렷하다.
--   색 번짐(빨강/파랑 테두리)이 보이면 'Normal' 로 바꿀 것.
--   macOS 는 Retina 라 Normal 이 자연스럽다.
if is_mac then
  config.freetype_load_target = 'Normal'
  config.freetype_render_target = 'Normal'
else
  config.freetype_load_target = 'Light'
  config.freetype_render_target = 'HorizontalLcd'
end

config.adjust_window_size_when_changing_font_size = false

-- ---------------------------------------------------------
-- 3. 한국어 처리 (★ 핵심)
-- ---------------------------------------------------------
-- 한글 입력기(IME) 활성화
config.use_ime = true

-- macOS 는 한글 파일명을 NFD(자모 분리)로 저장한다.
-- 이 옵션이 없으면 ls 결과가 "ㅎㅏㄴㄱㅡㄹ" 처럼 분리되어 보인다.
config.normalize_output_to_unicode_nfc = true

-- 동아시아 애매폭 문자(─ │ ┌ ● ★ 등)를 2칸으로 처리할지 여부.
-- tree / htop / 박스 그리기가 어긋나면 true 로 바꿔 볼 것.
-- 반대로 이미 잘 나오는데 true 로 두면 오히려 어긋나므로 기본은 false.
config.treat_east_asian_ambiguous_width_as_wide = false

-- ---------------------------------------------------------
-- 4. 외관
-- ---------------------------------------------------------
-- =========================================================
--  밝은 테마 (눈에 편한 따뜻한 오프화이트)
--
--  [배경 선택 근거]
--    순백(#ffffff, 휘도 255)은 눈부심이 크다. 여기서는 휘도 238의
--    따뜻한 미색을 쓴다. 밝지만 눈이 덜 피로하다.
--
--  [ANSI 색을 직접 정의한 이유]
--    내장 밝은 테마 20종을 전수 측정한 결과 ANSI 색 최저 대비가
--    최고 2.11, 대부분 1.0~1.6 으로 WCAG AA(4.5)에 한참 못 미쳤다.
--    (밝은 배경 위에 밝은 색을 올리니 구조적으로 안 나온다)
--    그래서 Gruvbox Light 색상을 기준으로 색조·채도는 유지한 채
--    명도만 낮춰 모든 색이 4.5 이상이 되도록 재계산했다.
--    bright 계열은 7.0 이상으로 맞춰 굵은 글씨가 8/8 구분된다.
-- =========================================================
local P = {
  bg        = '#f6eeda',   -- 배경 (휘도 238)
  fg        = '#3c3836',   -- 전경 (대비 10.03)
  accent    = '#1d69ce',   -- 파랑 강조 (4.58)
  warn      = '#8e6516',   -- 주황 강조 (4.51)
  tab_bar   = '#e7e0cd',   -- 탭바 배경
  tab_ina   = '#6d6258',   -- 비활성 탭 글자 (4.51)
  popup_bg  = '#fbf7ee',   -- 팝업 배경 (글자 대비 10.84)
  thumb     = '#b1ab9d',   -- 스크롤바
  sel_bg    = '#c6d1d7',   -- 선택 영역 (글자 대비 7.46)
  dim       = '#5a5048',   -- 보조 텍스트 (탭바 위 대비 5.96)
  chip      = '#b5a98c',   -- 상태줄 칩 (탭바와 구분 1.77 / 글자 대비 4.98)
}

config.color_schemes = {
  ['Warm Light AA'] = {
    background = P.bg,
    foreground = P.fg,
    cursor_bg = P.accent,
    cursor_fg = P.bg,
    cursor_border = P.accent,
    selection_bg = P.sel_bg,
    selection_fg = P.fg,

    --        검정      빨강      초록      노랑      파랑      자홍      청록      흰색
    ansi    = { '#282828','#cc241d','#707013','#8e6516','#3d7578','#9c5676','#4e7650','#5f5750' },
    brights = { '#6d6258','#9b1b16','#52520e','#684a10','#2d5558','#723f56','#39563a','#3c3836' },
  },
}
config.color_scheme = 'Warm Light AA'

-- 투명도는 밝은 테마에서 특히 대비를 크게 깎는다. 불투명 유지.
config.window_background_opacity = 1.0

-- 세 OS 모두 TITLE|RESIZE 로 통일한다.
--   RESIZE 단독            -> 타이틀바 없음. 탭 1개면 탭바도 숨겨져 창 이동 불가
--   INTEGRATED_BUTTONS 단독 -> 창 버튼이 탭바에 들어가므로 위와 같은 문제 발생
config.window_decorations = 'TITLE|RESIZE'

-- [렌더링] 화면 찢김(가로 흰줄)과 랙의 주원인은 GPU 어댑터 선택 실패다.
--   노트북처럼 GPU 가 여러 개인 환경에서 WezTerm 이 소프트웨어 렌더러나
--   엉뚱한 어댑터를 잡으면 프레임이 밀리고 가로줄이 생긴다.
--   우선순위: Dx12/Metal > Vulkan > OpenGL, 외장 > 내장 (Cpu 는 제외)
local function pick_gpu()
  local ok, gpus = pcall(function() return wezterm.gui.enumerate_gpus() end)
  if not ok or type(gpus) ~= 'table' then return nil end
  local backend_rank = { Dx12 = 40, Metal = 40, Vulkan = 30, Gl = 10 }
  local device_rank  = { DiscreteGpu = 3, IntegratedGpu = 2, Other = 1, Cpu = 0 }
  local best, best_score = nil, 0
  for _, g in ipairs(gpus) do
    local d = device_rank[tostring(g.device_type)] or 0
    if d > 0 then
      local score = (backend_rank[tostring(g.backend)] or 0) + d
      if score > best_score then best, best_score = g, score end
    end
  end
  return best
end

local gpu = pick_gpu()
if gpu then
  config.front_end = 'WebGpu'
  config.webgpu_preferred_adapter = gpu
  config.webgpu_power_preference = 'HighPerformance'
else
  config.front_end = 'OpenGL'
end

config.max_fps = 120
config.enable_kitty_keyboard = false

if is_mac then
  config.macos_window_background_blur = 0
end

config.window_padding = { left = 12, right = 12, top = 10, bottom = 8 }
config.window_close_confirmation = 'AlwaysPrompt'
-- 스크롤백은 패널마다 따로 잡힌다. 분할을 자주 하면 그만큼 메모리와
-- 초기화 비용이 늘어난다. 10000 줄이면 실사용에 충분하다.
config.scrollback_lines = 10000
config.enable_scroll_bar = true
config.default_cursor_style = 'SteadyBar'
config.cursor_blink_rate = 0
config.cursor_blink_ease_in = 'Constant'
config.cursor_blink_ease_out = 'Constant'
config.audible_bell = 'Disabled'

-- 밝은 테마에서는 밝기를 낮추면 오히려 대비가 살짝 올라간다(10.03 -> 8.95).
-- 채도만 조금 빼서 비활성 패널을 '바래 보이게' 하는 편이 자연스럽다.
config.inactive_pane_hsb = { saturation = 0.75, brightness = 0.97 }
config.hide_mouse_cursor_when_typing = true

-- ---- 사용 편의 ----
-- 탭바 위에서 마우스 휠 -> 탭 전환
config.mouse_wheel_scrolls_tabs = true

-- 다른 창에서 넘어오며 클릭할 때, 그 클릭이 터미널로 전달되지 않게 한다
config.swallow_mouse_click_on_window_focus = true

-- 셸만 돌고 있으면 탭/창을 닫을 때 확인창을 띄우지 않는다.
-- 실행 중인 작업(vim, ssh 등)이 있을 때만 물어본다.
config.skip_close_confirmation_for_processes_named = {
  'bash', 'sh', 'zsh', 'fish', 'tmux',
  'cmd.exe', 'pwsh.exe', 'powershell.exe',
}

-- 비밀번호 입력 중이면 커서 모양으로 알려준다
config.detect_password_input = true

-- 키를 누르면 스크롤 위치를 맨 아래로 되돌린다
config.scroll_to_bottom_on_input = true

-- 파일을 창에 끌어다 놓으면 경로에 따옴표를 씌워준다 (공백/한글 경로 대응)
config.quote_dropped_files = is_windows and 'Windows' or 'Posix'

-- 팝업 UI 도 테마에 맞춘다. 지정 안 하면 밝은 회색 창이 튀어나온다.
config.command_palette_bg_color = P.popup_bg
config.command_palette_fg_color = P.fg
config.command_palette_font_size = is_mac and 13.0 or 11.0
config.command_palette_rows = 14
config.char_select_bg_color = P.popup_bg
config.char_select_fg_color = P.fg
config.char_select_font_size = is_mac and 13.0 or 11.0

-- 패널 전환 시 확대 상태 자동 해제 (확대된 채 이동하면 혼란)
config.unzoom_on_switch_pane = true

-- 상태 표시줄 갱신 주기(ms)
config.status_update_interval = 1000

-- 패키지 매니저로 설치했다면 앱 내부 업데이트 알림은 방해다
config.check_for_updates = false
config.show_update_window = false

-- 탭바
-- [중요] 탭별 닫기(x) 버튼은 fancy 탭바에서만 그려진다.
--   use_fancy_tab_bar = false(레트로)로 두면 닫기 버튼이 아예 없다.
--   show_close_tab_button_in_tabs 는 기본값이 true 라 따로 켤 필요는 없다.
config.use_fancy_tab_bar = true
config.tab_bar_at_bottom = false               -- 탭바를 창 위쪽에
config.hide_tab_bar_if_only_one_tab = false    -- 탭 1개여도 계속 표시
-- 탭 제목이 폭을 다 먹으면 오른쪽 닫기(x) 버튼이 잘려 안 보인다.
-- 제목은 짧게 자르고 탭 폭은 넉넉히 준다.
config.tab_max_width = 36

-- 닫기 버튼은 기본값이 true 지만, 구버전에는 이 옵션 자체가 없어
-- 그냥 쓰면 설정 로드가 통째로 실패한다. 지원하는 빌드에서만 명시한다.
if build_at_least(20240301) then
  config.show_close_tab_button_in_tabs = true
end
config.show_new_tab_button_in_tab_bar = true   -- + 버튼 (동작은 아래에서 분할로 바꿈)
config.show_tab_index_in_tab_bar = true
config.switch_to_last_active_tab_when_closing_tab = true  -- 탭 닫으면 직전 탭으로

-- fancy 탭바는 탭 '바깥' 영역을 window_frame 으로 그린다.
-- 여기를 안 맞추면 탭바만 회색으로 떠 보인다.
config.window_frame = {
  font = wezterm.font { family = 'JetBrains Mono', weight = 'Bold' },
  font_size = is_mac and 12.0 or 10.0,
  active_titlebar_bg = P.tab_bar,
  inactive_titlebar_bg = P.tab_bar,
  active_titlebar_fg = P.fg,
  inactive_titlebar_fg = P.dim,
  button_bg = P.tab_bar,
  button_fg = P.fg,
  button_hover_bg = P.accent,
  button_hover_fg = P.bg,
}

-- use_fancy_tab_bar = false 일 때 탭바는 테마 색을 따라가지 않으므로
-- ayu 팔레트에 맞춰 직접 지정한다 (지정하지 않으면 회색 기본값과 충돌)
config.colors = {
  -- 스크롤바 손잡이 (배경 대비 1.98 — 은은하게 보이는 정도)
  scrollbar_thumb = P.thumb,

  -- 패널 분할선. 지정 안 하면 밝은 배경에 묻혀 화면이 나뉜 게 안 보인다.
  split = P.accent,

  -- 한글 IME 조합 중 커서 색 (조합 중/확정 구분)
  compose_cursor = P.warn,

  -- 복사 모드 선택 강조
  copy_mode_active_highlight_bg = { Color = P.accent },
  copy_mode_active_highlight_fg = { Color = P.bg },
  copy_mode_inactive_highlight_bg = { Color = P.sel_bg },
  copy_mode_inactive_highlight_fg = { Color = P.fg },

  -- 퀵셀렉트(LEADER+e) 라벨
  quick_select_label_bg = { Color = P.warn },
  quick_select_label_fg = { Color = P.bg },
  quick_select_match_bg = { Color = P.sel_bg },
  quick_select_match_fg = { Color = P.fg },

  tab_bar = {
    -- [실측] 활성탭 글자 4.58 / 비활성탭 글자 4.51 / 배경 구분 4.02
    background = P.tab_bar,
    active_tab = {
      bg_color = P.accent,
      fg_color = P.bg,
      intensity = 'Bold',
    },
    inactive_tab = {
      bg_color = P.tab_bar,
      fg_color = P.tab_ina,
    },
    inactive_tab_hover = {
      bg_color = P.sel_bg,
      fg_color = P.fg,
      italic = false,
    },
    new_tab = {
      bg_color = P.tab_bar,
      fg_color = P.tab_ina,
    },
    new_tab_hover = {
      bg_color = P.accent,
      fg_color = P.bg,
      intensity = 'Bold',
    },
  },
}

-- ---------------------------------------------------------
-- 5. 기본 셸 / 실행 메뉴
-- ---------------------------------------------------------
if is_windows then
  -- PowerShell 7(pwsh)은 별도 설치 항목이다. 설치돼 있을 때만 기본 셸로 쓰고,
  -- 없으면 윈도우에 항상 있는 powershell.exe 로 폴백한다.
  local pwsh = first_existing {
    'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
    'C:\\Program Files (x86)\\PowerShell\\7\\pwsh.exe',
    (os.getenv('LOCALAPPDATA') or '') .. '\\Microsoft\\WindowsApps\\pwsh.exe',
    (os.getenv('ProgramFiles') or '') .. '\\PowerShell\\7\\pwsh.exe',
  }

  if pwsh then
    config.default_prog = { pwsh, '-NoLogo' }
  else
    config.default_prog = { 'powershell.exe', '-NoLogo' }
  end

  config.launch_menu = {
    { label = 'PowerShell 7', args = { 'pwsh.exe', '-NoLogo' } },
    { label = 'Windows PowerShell', args = { 'powershell.exe', '-NoLogo' } },
    { label = 'Command Prompt', args = { 'cmd.exe' } },
    { label = 'WSL', args = { 'wsl.exe', '~' } },
  }
elseif is_linux then
  config.launch_menu = {
    { label = 'bash', args = { 'bash', '-l' } },
    { label = 'zsh', args = { 'zsh', '-l' } },
  }
end

-- ---------------------------------------------------------
-- 6. 키 바인딩
--
--  설계 원칙:
--   (1) 창/탭/패널 조작은 전부 LEADER(Ctrl+A) 아래로 모은다.
--       -> 기본 키와 절대 충돌하지 않는 안전 지대
--   (2) 기본 키로 이미 되는 것은 다시 정의하지 않는다.
--       Ctrl+- / Ctrl+= / Ctrl+0  : 폰트 크기
--       Alt+Enter                 : 전체화면
--       Ctrl+Shift+C / V          : 복사 / 붙여넣기
--       Ctrl+Shift+L              : 디버그 오버레이 (문제 해결용, 덮지 말 것)
--       Ctrl+Shift+P              : 명령 팔레트
--       Ctrl+Shift+Tab / Ctrl+Tab : 탭 이동
--   (3) ALT+문자 는 쓰지 않는다. 셸의 readline / vim 이 Meta 로 사용
-- ---------------------------------------------------------
config.leader = { key = 'a', mods = 'CTRL', timeout_milliseconds = 1000 }

config.keys = {
  ---------------- 패널 분할 ----------------
  { key = '\\', mods = 'LEADER', action = act.SplitHorizontal { domain = 'CurrentPaneDomain' } },
  { key = '-',  mods = 'LEADER', action = act.SplitVertical { domain = 'CurrentPaneDomain' } },

  ---------------- 패널 이동 (vim 키) ----------------
  { key = 'h', mods = 'LEADER', action = act.ActivatePaneDirection 'Left' },
  { key = 'j', mods = 'LEADER', action = act.ActivatePaneDirection 'Down' },
  { key = 'k', mods = 'LEADER', action = act.ActivatePaneDirection 'Up' },
  { key = 'l', mods = 'LEADER', action = act.ActivatePaneDirection 'Right' },

  ---------------- 패널 크기 조정 ----------------
  { key = 'LeftArrow',  mods = 'LEADER', action = act.AdjustPaneSize { 'Left', 5 } },
  { key = 'DownArrow',  mods = 'LEADER', action = act.AdjustPaneSize { 'Down', 5 } },
  { key = 'UpArrow',    mods = 'LEADER', action = act.AdjustPaneSize { 'Up', 5 } },
  { key = 'RightArrow', mods = 'LEADER', action = act.AdjustPaneSize { 'Right', 5 } },

  ---------------- 패널 제어 ----------------
  { key = 'z', mods = 'LEADER', action = act.TogglePaneZoomState },
  { key = 'x', mods = 'LEADER', action = act.CloseCurrentPane { confirm = true } },
  { key = 'q', mods = 'LEADER', action = act.PaneSelect { alphabet = 'asdfghjkl' } },
  { key = 'o', mods = 'LEADER', action = act.RotatePanes 'Clockwise' },

  ---------------- 탭 ----------------
  { key = 'c', mods = 'LEADER', action = act.SpawnTab 'CurrentPaneDomain' },
  { key = 'n', mods = 'LEADER', action = act.ActivateTabRelative(1) },
  { key = 'p', mods = 'LEADER', action = act.ActivateTabRelative(-1) },
  -- '&' 같은 시프트 문자는 키보드 레이아웃에 따라 인식이 불안정하므로 'w' 사용
  { key = 'w', mods = 'LEADER', action = act.CloseCurrentTab { confirm = true } },
  { key = ',', mods = 'LEADER', action = act.PromptInputLine {
      description = '새 탭 이름:',
      action = wezterm.action_callback(function(window, _, line)
        if line and #line > 0 then
          window:active_tab():set_title(line)
        end
      end),
    },
  },

  ---------------- 검색 / 복사 / 선택 ----------------
  { key = '[', mods = 'LEADER', action = act.ActivateCopyMode },
  { key = ']', mods = 'LEADER', action = act.PasteFrom 'Clipboard' },
  { key = 'f', mods = 'LEADER', action = act.Search { CaseInSensitiveString = '' } },
  { key = 'e', mods = 'LEADER', action = act.QuickSelect },
  { key = 'u', mods = 'LEADER', action = act.CharSelect },

  ---------------- 유틸 ----------------
  { key = 'm', mods = 'LEADER', action = act.ShowLauncher },

  -- ---- 탭 관리 ----
  -- 탭이 많아지면 제목만으로 찾기 어렵다. 목록에서 골라 이동한다.
  { key = 't', mods = 'LEADER', action = act.ShowTabNavigator },
  -- 탭 순서 바꾸기 ('<' '>' 같은 시프트 문자는 레이아웃 의존이라 화살표 사용)
  { key = 'LeftArrow',  mods = 'LEADER|SHIFT', action = act.MoveTabRelative(-1) },
  { key = 'RightArrow', mods = 'LEADER|SHIFT', action = act.MoveTabRelative(1) },
  -- 탭 닫기(확인 있음) / 강제 닫기
  { key = 'X', mods = 'LEADER|SHIFT', action = act.CloseCurrentTab { confirm = false } },

  -- [UX] 크기 조절 모드. LEADER 는 1초 뒤 풀리므로 반복 조절이 불편하다.
  --   LEADER+r 로 모드에 들어가면 화살표/hjkl 을 계속 눌러 조절할 수 있고
  --   Esc 또는 Enter 로 빠져나온다. 진행 중에는 상태줄에 모드가 표시된다.
  { key = 'r', mods = 'LEADER', action = act.ActivateKeyTable {
      name = 'resize_pane', one_shot = false, timeout_milliseconds = 3000 } },
  { key = 'K', mods = 'LEADER|SHIFT', action = act.ClearScrollback 'ScrollbackAndViewport' },
  { key = 'R', mods = 'LEADER|SHIFT', action = act.ReloadConfiguration },
  { key = 'd', mods = 'LEADER', action = act.ShowDebugOverlay },

  ---------------- Ctrl+A 원본을 셸로 전달 ----------------
  -- Ctrl+A 를 두 번 누르면 '줄 맨 앞으로 이동'이 정상 동작
  { key = 'a', mods = 'LEADER|CTRL', action = act.SendKey { key = 'a', mods = 'CTRL' } },
}

-- LEADER + 1~9 로 탭 직접 이동
for i = 1, 9 do
  table.insert(config.keys, {
    key = tostring(i),
    mods = 'LEADER',
    action = act.ActivateTab(i - 1),
  })
end

-- ---------------------------------------------------------
-- 7. 마우스 : Ctrl(mac은 Cmd) + 클릭으로 링크 열기
-- ---------------------------------------------------------
local link_mod = is_mac and 'CMD' or 'CTRL'

config.mouse_bindings = {
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = link_mod,
    action = act.OpenLinkAtMouseCursor,
  },
  -- 누를 때의 클릭 이벤트가 셸로 새어나가지 않도록 차단
  {
    event = { Down = { streak = 1, button = 'Left' } },
    mods = link_mod,
    action = act.Nop,
  },
}

-- ---------------------------------------------------------
-- 6-2. 모달 키 테이블 : 크기 조절 모드
--   LEADER+r 로 진입. 화살표 또는 hjkl 을 반복해서 누를 수 있다.
--   Esc / Enter / q 로 빠져나오며, 3초간 입력이 없어도 자동 해제된다.
-- ---------------------------------------------------------
config.key_tables = {
  resize_pane = {
    { key = 'LeftArrow',  action = act.AdjustPaneSize { 'Left', 3 } },
    { key = 'h',          action = act.AdjustPaneSize { 'Left', 3 } },
    { key = 'DownArrow',  action = act.AdjustPaneSize { 'Down', 3 } },
    { key = 'j',          action = act.AdjustPaneSize { 'Down', 3 } },
    { key = 'UpArrow',    action = act.AdjustPaneSize { 'Up', 3 } },
    { key = 'k',          action = act.AdjustPaneSize { 'Up', 3 } },
    { key = 'RightArrow', action = act.AdjustPaneSize { 'Right', 3 } },
    { key = 'l',          action = act.AdjustPaneSize { 'Right', 3 } },
    { key = 'Escape', action = 'PopKeyTable' },
    { key = 'Enter',  action = 'PopKeyTable' },
    { key = 'q',      action = 'PopKeyTable' },
  },
}

-- ---------------------------------------------------------
-- 7-1. 상태 표시줄
--   [문제] LEADER(Ctrl+A)를 눌러도 화면에 아무 변화가 없어서
--          눌렸는지 알 수 없고, 모드에 들어가 있는지도 보이지 않는다.
--   [해결] 왼쪽에 LEADER/모드 상태를, 오른쪽에 작업공간과 시간을 표시한다.
-- ---------------------------------------------------------
wezterm.on('update-status', function(window, pane)
  local ok = pcall(function()
    ---------------- 왼쪽: 모드 표시 ----------------
    local left = {}
    local mode = window:active_key_table()

    if mode == 'resize_pane' then
      -- 크기 조절 모드: 주황색으로 강하게
      left = {
        { Background = { Color = P.warn } },
        { Foreground = { Color = P.bg } },
        { Attribute = { Intensity = 'Bold' } },
        { Text = ' 크기조절 hjkl/화살표  Esc 종료 ' },
      }
    elseif window:leader_is_active() then
      -- LEADER 대기 중: 청색
      left = {
        { Background = { Color = P.accent } },
        { Foreground = { Color = P.bg } },
        { Attribute = { Intensity = 'Bold' } },
        { Text = ' LEADER ' },
      }
    else
      left = { { Text = '' } }
    end
    window:set_left_status(wezterm.format(left))

    ---------------- 오른쪽: 작업공간 + 시간 ----------------
    local right = {}
    local ws = window:active_workspace()
    if ws and ws ~= 'default' then
      right[#right + 1] = { Foreground = { Color = P.accent } }
      right[#right + 1] = { Text = ' ' .. ws .. ' ' }
    end

    -- 확대된 패널이 있으면 알려준다 (확대 중인 걸 모르고 헤매는 일 방지)
    local tab = window:active_tab()
    for _, p in ipairs(tab:panes_with_info()) do
      if p.is_zoomed then
        right[#right + 1] = { Foreground = { Color = P.warn } }
        right[#right + 1] = { Attribute = { Intensity = 'Bold' } }
        right[#right + 1] = { Text = ' 확대중 ' }
        break
      end
    end

    -- [시인성] 이전에는 흐린 회색(#8a7f74)이라 탭바 위에서 대비 2.97 로
    --   거의 읽히지 않았다. 진한 글자(대비 8.80)에 배경 칩을 깔아 분리한다.
    right[#right + 1] = { Background = { Color = P.chip } }
    right[#right + 1] = { Foreground = { Color = P.fg } }
    right[#right + 1] = { Attribute = { Intensity = 'Bold' } }
    right[#right + 1] = { Text = wezterm.strftime('  %m/%d (%a) %H:%M  ') }

    window:set_right_status(wezterm.format(right))
  end)
  if not ok then
    window:set_left_status('')
    window:set_right_status('')
  end
end)

-- ---------------------------------------------------------
-- 7-2. 탭바의 [+] 버튼을 "화면 분할" 버튼으로 바꾼다
--
--   좌클릭 1회 : 좌우 반반 (2분할)
--   좌클릭 2회 : 균등 3분할
--   좌클릭 3회 : 그 이상은 새 탭 생성
--   우클릭     : 기본 동작(런처 메뉴) 유지
--
--   3분할을 균등하게 만들기 위해 두 번째 분할은 top_level=true 를 쓴다.
--   활성 패널만 쪼개면 50/25/25 가 되지만, 탭 전체를 기준으로 1/3 을 떼면
--   기존 50/50 이 2/3 안으로 밀려 들어가 33/33/33 이 된다.
-- ---------------------------------------------------------
wezterm.on('new-tab-button-click', function(window, pane, button, default_action)
  -- 좌클릭이 아니면 WezTerm 기본 동작에 맡긴다 (우클릭 = 런처 메뉴)
  if button ~= 'Left' then return end

  -- [성능 메모] 분할이 느리게 느껴지는 주된 원인은 이 핸들러가 아니라
  --   새 패널마다 셸 프로세스가 새로 뜨는 비용이다(측정: 핸들러 1회 0.004ms).
  --   Windows PowerShell 은 프로필 로딩 때문에 특히 느리다.
  --   본인 환경 측정:  Measure-Command { pwsh -NoLogo -Command exit }
  --   500ms 를 넘으면 프로필($PROFILE)을 정리하는 게 가장 효과적이다.
  --   아래 FAST_SPLIT 을 true 로 하면 분할된 패널은 프로필 없이 띄운다.
  local FAST_SPLIT = false

  local ok, err = pcall(function()
    local tab = window:active_tab()
    local count = #tab:panes()

    local spawn = nil
    if FAST_SPLIT and is_windows then
      spawn = { args = { config.default_prog[1], '-NoLogo', '-NoProfile' } }
    end

    -- 확대(zoom) 상태에서는 분할이 어색하므로 먼저 해제
    tab:set_zoomed(false)

    if count == 1 then
      pane:split { direction = 'Right', size = 0.5, args = spawn and spawn.args or nil }
    elseif count == 2 then
      pane:split { direction = 'Right', size = 1 / 3, top_level = true,
                   args = spawn and spawn.args or nil }
    else
      -- 3분할 이상이면 새 탭
      if default_action then
        window:perform_action(default_action, pane)
      else
        window:perform_action(act.SpawnTab 'CurrentPaneDomain', pane)
      end
    end
  end)

  if not ok then
    wezterm.log_error('new-tab-button-click 실패: ' .. tostring(err))
  end

  -- 처리했음을 알려 기본 동작이 중복 실행되지 않게 한다
  return false
end)

-- ---------------------------------------------------------
-- 8. 탭 제목 : 현재 디렉토리명 표시
--    current_working_dir 은 버전에 따라 Url 객체(userdata) 또는
--    문자열을 반환하므로 양쪽 모두 처리한다
-- ---------------------------------------------------------
wezterm.on('format-tab-title', function(tab)
  local pane = tab.active_pane
  local title = pane.title or 'shell'
  local cwd = pane.current_working_dir

  if cwd then
    local path
    if type(cwd) == 'string' then
      path = cwd
    else
      path = cwd.file_path or tostring(cwd)
    end
    local name = path:gsub('[/\\]+$', ''):match('([^/\\]+)$')
    if name and #name > 0 then
      title = name
    end
  end

  -- 한글은 화면에서 2칸을 차지하므로 열 폭 기준으로 자른다
  if wezterm.column_width(title) > 20 then
    title = wezterm.truncate_right(title, 19) .. '…'
  end

  -- 분할 개수 / 확대 / 미확인 출력 상태를 수집
  local panes = 1
  local zoomed, unseen = false, false
  if tab.panes then
    panes = #tab.panes
    for _, p in ipairs(tab.panes) do
      if p.is_zoomed then zoomed = true end
      if p.has_unseen_output and not tab.is_active then unseen = true end
    end
  end

  local suffix = panes > 1 and (' ' .. panes) or ''
  if zoomed then suffix = suffix .. ' Z' end

  local items = {}

  -- [UX] 비활성 탭에서 새 출력이 있으면 주황 점으로 알려준다.
  --   빌드/테스트를 다른 탭에서 돌릴 때 끝났는지 확인하러 갈 필요가 없다.
  if unseen then
    items[#items + 1] = { Foreground = { Color = P.warn } }
    items[#items + 1] = { Text = ' •' }
  else
    items[#items + 1] = { Text = ' ' }
  end

  items[#items + 1] = { Attribute = { Intensity = tab.is_active and 'Bold' or 'Normal' } }
  items[#items + 1] = { Text = string.format(' %d: %s%s ', tab.tab_index + 1, title, suffix) }

  return items
end)

-- ---------------------------------------------------------
-- 9. 시작 시 창 최대화
-- ---------------------------------------------------------
-- [주의] 이 핸들러가 등록되면 WezTerm 은 기본 창을 스스로 만들지 않고
--        이 함수에 위임한다. 여기서 예외가 나면 창이 아예 안 뜨거나
--        기본 창과 겹쳐 두 개가 뜰 수 있으므로 전부 pcall 로 감싼다.
wezterm.on('gui-startup', function(cmd)
  local ok, err = pcall(function()
    -- 이미 창이 있으면 새로 만들지 않는다 (중복 창 방지)
    local existing = wezterm.mux.all_windows()
    if existing and #existing > 0 then
      local gw = existing[1]:gui_window()
      if gw then gw:maximize() end
      return
    end

    local _, _, window = wezterm.mux.spawn_window(cmd or {})
    local gw = window:gui_window()
    if gw then gw:maximize() end
  end)
  if not ok then
    wezterm.log_error('gui-startup 실패: ' .. tostring(err))
  end
end)

return config

+ Recent posts