-- ShortHotkey.lua (EllesmereUI_koKR)
-- 액션바 버튼 단축키 텍스트에 사용자 축약 패턴만 단순 치환.
-- EllesmereUIActionBars 원본은 건드리지 않고 HotKey:SetText 후크만 사용.

-- ===========================================================================
-- 축약 패턴: 발견 시 → 변환 후 텍스트
-- ===========================================================================
-- 사용자 한글 패턴 + Ellesmere의 FormatHotkeyText가 만든 영어 축약 양쪽 다 매칭.
-- (Ellesmere는 GetBindingKey의 영어 내부 이름을 MwU/M3/C1/A2 형태로 줄여 넘기므로
--  한글 패턴만으론 매칭이 안 됨. 두 형식을 모두 사용자 단축어로 변환)
local PATTERNS = {
	-- 사용자 정의 한글 → 단축
	["마우스 휠 위로"]    = "휠▲",
	["마우스 휠 아래로"]  = "휠▼",
	["마우스 가운데 버튼"] = "휠★",
	["4번 마우스 버튼"]   = "마뒤",
	["5번 마우스 버튼"]   = "마앞",
	["Capslock"]   = "캡스",
	["Num Lock"]   = "넘버",
	["Delete"]     = "델",
	["스페이스 바"] = "스페",
	["숫자패드 "]   = "패드",
	["Page "]      = "페",
	["Down"]       = "▼",
	["Up"]         = "▲",
	-- Ctrl / Alt / Shift 축약 (소문자 a-/c-/s- 형태)
	["a%-"] = "알",
	["c%-"] = "컨",
	["s%-"] = "쉬",

	-- Ellesmere FormatHotkeyText 출력 → 같은 단축어
	["MwU"]      = "휠▲",
	["MwD"]      = "휠▼",
	["M3"]       = "휠★",
	["M4"]       = "마뒤",
	["M5"]       = "마앞",
	["CAPSLOCK"] = "캡스",
	["NUMLOCK"]  = "넘락",
	["DELETE"]   = "델",
	["SPACE"]    = "스페",
	["PAGEUP"]   = "페▲",
	["PAGEDOWN"] = "페▼",

	-- 숫자패드 (Ellesmere 버그로 NUMPADPLUS 등이 NPLUS로 잘못 출력됨)
	["NPLUS"]      = "넘+",
	["NMINUS"]     = "넘-",
	["NDIVIDE"]    = "넘/",
	["NMULTIPLY"]  = "넘*",
	["NDECIMAL"]   = "넘.",
	-- N0~N9 → 넘0~넘9
	["N0"] = "넘0", ["N1"] = "넘1", ["N2"] = "넘2", ["N3"] = "넘3", ["N4"] = "넘4",
	["N5"] = "넘5", ["N6"] = "넘6", ["N7"] = "넘7", ["N8"] = "넘8", ["N9"] = "넘9",
}

-- Ellesmere 모디파이어 접두어 (대문자, 하이픈 없음): SHIFT-→S, CTRL-→C, ALT-→A
-- 단순 gsub로 "S"를 변환하면 SPACE/SHIFT 등 일반 단어의 알파벳도 바뀌므로
-- 문자열 시작 위치에서, 그리고 뒤에 다른 글자가 있을 때만 변환.
local MOD_HEAD = { S = "쉬", C = "컨", A = "알" }

local function ConvertModifiers(text)
	if not text or #text < 2 then return text end
	local out = ""
	while #text > 1 do
		local ch = text:sub(1, 1)
		local k = MOD_HEAD[ch]
		if k then
			out = out .. k
			text = text:sub(2)
		else
			break
		end
	end
	return out .. text
end

local function Shorten(text)
	if not text or text == "" then return text end
	-- 1단계: 키워드 치환 (SPACE/PAGEUP/MwU 등). 모디파이어 단축자보다 먼저 처리해
	-- 일반 키 이름의 첫 글자가 잘못 변환되는 것을 방지.
	for k, v in pairs(PATTERNS) do
		text = text:gsub(k, v)
	end
	-- 2단계: 시작 위치의 모디파이어 접두어를 한글로 변환.
	return ConvertModifiers(text)
end

-- ===========================================================================
-- 후크 대상 버튼 prefix
-- ===========================================================================
-- 출처: EllesmereUIActionBars.lua
--   line 1373: local name = "EABButton" .. slot  → 액션바 슬롯 1~180
--   line 1361: btn = _G["StanceButton" .. index]  → 블리자드 기본
--   BAR_CONFIG: PetBar는 PetActionButton(블리자드 기본) 사용
local PREFIXES = {
	{ "EABButton",       180 }, -- Ellesmere 자체 액션바 슬롯 1~180
	{ "StanceButton",     12 }, -- 자세 바 (블리자드 기본 사용)
	{ "PetActionButton",  12 }, -- 펫 바 (블리자드 기본 사용)
}

-- 추가기능 토글 게이트: 전역 SV(EllesmereUIKorAdditionsDB.hotkeyShorten)를
-- 직접 읽음. nil/true=켬, false 만 끔. 후크는 1회 설치돼 영구지만 이 체크는
-- 매 SetText 마다 평가 → 토글 끄면 다음 단축키 갱신부터 원문 유지.
local function _shEnabled()
	local db = _G and _G.EllesmereUIKorAdditionsDB
	return not (db and db.hotkeyShorten == false)
end

local hooked = {}
-- ★ secure 버튼의 HotKey FontString 에 사용자 키(__sh_busy)를 직접 쓰면 그 버튼이 taint 되어
--   SetRaidTarget 등 보호 함수 매크로가 차단됨 → 재진입 가드를 외부 weak 테이블로 분리.
local _busy = setmetatable({}, { __mode = "k" })

local function HookHotKey(btn)
	local hk = btn and btn.HotKey
	if not hk or hooked[hk] then return end
	hooked[hk] = true

	hooksecurefunc(hk, "SetText", function(self, text)
		if _busy[self] or not _shEnabled() then return end
		local short = Shorten(text)
		if short and short ~= text then
			_busy[self] = true
			self:SetText(short)
			_busy[self] = nil
		end
	end)

	-- 현재 텍스트도 즉시 축약 (재진입 가드는 외부 weak 테이블 → secure frame taint 없음)
	local cur = hk:GetText()
	local short = _shEnabled() and Shorten(cur) or cur
	if short and short ~= cur then
		_busy[hk] = true
		hk:SetText(short)
		_busy[hk] = nil
	end
end

local function HookAll()
	-- 알려진 prefix만 스캔. _G 전체 순회는 보안 테이블 접근으로 taint를 유발해 제거.
	for _, info in ipairs(PREFIXES) do
		local prefix, lastSlot = info[1], info[2]
		for i = 1, lastSlot do
			HookHotKey(_G[prefix .. i])
		end
	end
	HookHotKey(_G["ExtraActionButton1"])
end

-- ===========================================================================
-- 이벤트: 버튼이 늦게 만들어지는 경우 대비해 여러 시점에 재스캔.
-- HookHotKey 자체는 hooked 테이블로 중복 후크를 막으므로 안전.
-- ===========================================================================
-- ===========================================================================
-- 쿨다운 매니저(CDM) 단축키 축약 — ShortHotkeyCDM.lua 를 여기로 통합.
-- (토글로 제어하므로 분리 불필요. PATTERNS/Shorten 재사용 = 중복 제거.)
--
-- EllesmereUICdmHooks.lua 는 Blizzard CooldownViewer(Essential/Utility/
-- BuffIcon/BuffBar) 를 후크하는 모드라 keybind FS(EllesmereUI 가 생성한
-- 오버레이)가 그 Blizzard 프레임에 붙는다. ECME_CDMBar_(자체 바 모드)도
-- 함께 스캔. Shorten 은 숫자/비-단축키엔 무변화 → 같이 후크해도 안전,
-- hooksecurefunc 사후훅이라 taint 무관.
-- ===========================================================================
local function _cdmEnabled()
	local db = _G and _G.EllesmereUIKorAdditionsDB
	return not (db and db.cdmHotkeyShorten == false)
end

-- WoW 11.x: Blizzard_CooldownViewer 의 keybind FontString 텍스트는
-- secret 값이라 비교/문자열연산(t=="", gsub) 시 taint 크래시. 모든
-- secret-위험 연산을 명명 헬퍼 1곳에 모아 호출부에서 pcall 로 흡수하고,
-- 한 번 실패한 FS(=Blizzard secret)는 __cimCdmSecret 로 영구 스킵한다.
-- (EllesmereUI 자체 ECME 바 FS 는 secret 아님 → 정상 축약.)
-- 명명 헬퍼라 호출당 클로저 0(핫패스 안전).
local function _cdmTry(self, t)
	if self.__cimCdmBusy or type(t) ~= "string" then return end
	if t == "" then return end                 -- secret 이면 여기서 에러→pcall 흡수
	if not _cdmEnabled() then return end
	if t == self.__cimCdmDst then return end
	local s
	if self.__cimCdmSrc == t then
		s = self.__cimCdmDst
	else
		s = Shorten(t)
		self.__cimCdmSrc, self.__cimCdmDst = t, s
	end
	if not s or s == t then return end
	self.__cimCdmBusy = true
	self.SetText(self, s)
	self.__cimCdmBusy = false
end

local function _cdmInit(fs)
	local cur = fs:GetText()
	if not _cdmEnabled() or type(cur) ~= "string" or cur == "" then return end
	local s = Shorten(cur)
	if s ~= cur then
		fs.__cimCdmBusy = true
		fs.SetText(fs, s)
		fs.__cimCdmBusy = false
	end
end

local function _HookCdmFS(fs)
	if not fs or fs.__cimCdmHK or not fs.SetText then return end
	if fs.IsForbidden and fs:IsForbidden() then return end
	fs.__cimCdmHK = true
	if not pcall(_cdmInit, fs) then
		fs.__cimCdmSecret = true
		fs.__cimCdmBusy = false
	end
	hooksecurefunc(fs, "SetText", function(self, t)
		if self.__cimCdmSecret then return end
		if not pcall(_cdmTry, self, t) then
			-- secret 텍스트(Blizzard CDM) → 영구 스킵·busy 해제
			self.__cimCdmSecret = true
			self.__cimCdmBusy = false
		end
	end)
end

local function _ScanCdmFrame(frame, depth)
	if not frame or depth > 8 then return end
	if frame.IsForbidden and frame:IsForbidden() then return end
	if frame.GetRegions then
		local ok, regs = pcall(function() return { frame:GetRegions() } end)
		if ok then
			for i = 1, #regs do
				local rg = regs[i]
				local oko, ot = pcall(function()
					return rg and rg.GetObjectType and rg:GetObjectType()
				end)
				if oko and ot == "FontString" then _HookCdmFS(rg) end
			end
		end
	end
	if frame.GetChildren then
		local ok, kids = pcall(function() return { frame:GetChildren() } end)
		if ok then
			for i = 1, #kids do _ScanCdmFrame(kids[i], depth + 1) end
		end
	end
end

local _CDM_FRAMES = {
	"EssentialCooldownViewer", "UtilityCooldownViewer",
	"BuffIconCooldownViewer", "BuffBarCooldownViewer",
}
local _cdmFound = false
local function _HookCdmBar(fr)
	_cdmFound = true
	_ScanCdmFrame(fr, 0)
	if not fr.__cimCdmShowHook and fr.HookScript then
		fr.__cimCdmShowHook = true
		pcall(fr.HookScript, fr, "OnShow", function(self) _ScanCdmFrame(self, 0) end)
	end
end
local function ApplyCdm()
	for i = 1, #_CDM_FRAMES do
		local fr = _G[_CDM_FRAMES[i]]
		if type(fr) == "table" and fr.GetChildren then _HookCdmBar(fr) end
	end
	if UIParent and UIParent.GetChildren then
		local ok, kids = pcall(function() return { UIParent:GetChildren() } end)
		if ok then
			for i = 1, #kids do
				local fr = kids[i]
				local okN, nm = pcall(function()
					return fr and fr.GetName and fr:GetName()
				end)
				if okN and type(nm) == "string" and nm:find("^ECME_CDMBar_") then
					_HookCdmBar(fr)
				end
			end
		end
	end
end

-- ===========================================================================
-- 이벤트: 버튼이 늦게 만들어지는 경우 대비해 여러 시점에 재스캔.
-- HookHotKey 자체는 hooked 테이블로 중복 후크를 막으므로 안전.
-- (액션바 + CDM 둘 다 한 핸들러에서.)
-- ===========================================================================
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LOGIN")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("UPDATE_BINDINGS")
f:RegisterEvent("PLAYER_REGEN_ENABLED")   -- 전투 종료: 새 CDM 아이콘 재스캔
f:SetScript("OnEvent", function()
	HookAll()
	ApplyCdm()
	if C_Timer and C_Timer.After then
		C_Timer.After(0, HookAll)
		C_Timer.After(0.5, HookAll)
		C_Timer.After(2.0, HookAll)
		C_Timer.After(0.5, ApplyCdm)
		C_Timer.After(2.0, ApplyCdm)
	end
end)

-- CDM 프레임이 늦게 생성될 수 있어 찾을 때까지 가벼운 유한 재시도(30초).
if C_Timer and C_Timer.NewTicker then
	local tries = 0
	local tk
	tk = C_Timer.NewTicker(1.0, function()
		tries = tries + 1
		ApplyCdm()
		if tries >= 30 then tk:Cancel() end
	end)
end

-- ===========================================================================
-- 추가기능 팝업 섹션 등록 (Core 가 로드된 경우에만). 토글은 EllesmereUIKorAdditionsDB
-- 의 hotkeyShorten / cdmHotkeyShorten 을 직접 읽으므로 onChange 불필요.
-- ===========================================================================
local NS = _G.EUIKorPlus
if NS and NS.RegisterSection then
	NS.RegisterSection({
		order = 20, col = 1, row = 3,
		title = "단축키 축약",
		desc  = "단축키 글자를 한글로 짧게 — 행동바·쿨다운 매니저 둘 다 적용 (쉬·컨·알·휠▲ 등).",
		build = function(popup, descFS)
			-- 체크 하나로 행동바·CDM 단축키 축약을 함께 켜고 끔.
			local sw = NS.MakeSwitch(descFS, -10, "단축키 축약 사용", "hotkeyShorten", function()
				NS.GetDB().cdmHotkeyShorten = (NS.GetDB().hotkeyShorten ~= false)
			end)
			sw:ClearAllPoints()
			sw:SetPoint("TOPLEFT", descFS, "BOTTOMLEFT", 0, -10)
		end,
	})
end
