-- 스파이크: base/fallback-plan.md의 Traceback 메커니즘 의사코드가
-- 실제 Luau에서 그대로 동작하는지 실측. 결과는 audit/fallback-xpcall-verification.md.
--   "xpcall 에러 핸들러 배선의 실측 — 에러 핸들러 안에서 클로저 업밸류에 쓴
--    값이 바깥에서 제대로 보이는지, debug.traceback이 에러 시점 스택을 정확히
--    찍는지"
-- luau fallback-xpcall-spike.luau 로 실행

local failures = 0
local function check(name, cond)
    if cond then
        print("[PASS] " .. name)
    else
        failures += 1
        print("[FAIL] " .. name)
    end
end

-- 문서의 Traceback 의사코드 그대로 옮김
local function Traceback(original, onError)
    return function(...)
        local trace: string? = nil
        local ok, resultOrErr = xpcall(original, function(err)
            trace = debug.traceback(nil, 2)
            return err
        end, ...)
        if ok then
            return resultOrErr
        end
        return onError(resultOrErr, trace)
    end
end

-- 1) 성공 경로 — onError 호출 안 되고 원래 값 그대로 통과하는지
do
    local Widget = function(props)
        return "OK:" .. props.name
    end
    local calledOnError = false
    local SafeWidget = Traceback(Widget, function(msg, trace)
        calledOnError = true
        return "ERR"
    end)
    local result = SafeWidget({ name = "widget1" })
    check("성공 경로 — 원래 반환값 그대로 통과", result == "OK:widget1")
    check("성공 경로 — onError 호출 안 됨", calledOnError == false)
end

-- 2) 실패 경로 — 업밸류 trace가 onError 호출 시점에 이미 채워져 있는지
do
    local Widget = function(props)
        error("boom: " .. props.name)
    end
    local capturedMsg, capturedTrace = nil, nil
    local SafeWidget = Traceback(Widget, function(msg, trace)
        capturedMsg = msg
        capturedTrace = trace
        return "PLACEHOLDER"
    end)
    local result = SafeWidget({ name = "widget2" })
    check("실패 경로 — onError의 반환값이 최종 결과", result == "PLACEHOLDER")
    check("실패 경로 — 에러 메시지가 onError에 전달됨", capturedMsg ~= nil and string.find(capturedMsg, "boom: widget2") ~= nil)
    check("실패 경로 — trace 업밸류가 onError 호출 시점에 nil이 아님(클로저 배선 정상)", capturedTrace ~= nil)
    if capturedTrace then
        print("  captured trace:\n" .. capturedTrace)
    end
end

-- 3) 깊은 중첩 호출에서도 traceback이 실패 지점까지 스택을 담는지
do
    local function level3(props)
        error("deep failure")
    end
    local function level2(props)
        return level3(props)
    end
    local function level1(props)
        return level2(props)
    end
    local capturedTrace = nil
    local SafeWidget = Traceback(level1, function(msg, trace)
        capturedTrace = trace
        return "PLACEHOLDER"
    end)
    SafeWidget({})
    local mentionsLevel3 = capturedTrace ~= nil and string.find(capturedTrace, "level3") ~= nil
    check("깊은 중첩 — traceback에 실패 지점 함수(level3)가 보임", mentionsLevel3)
    if capturedTrace then
        print("  nested trace:\n" .. capturedTrace)
    end
end

-- 4) 문자열이 아닌 에러 값(table)도 onError로 그대로 전달되는지
do
    local Widget = function(props)
        error({ code = 42, reason = "custom" })
    end
    local capturedMsg = nil
    local SafeWidget = Traceback(Widget, function(msg, trace)
        capturedMsg = msg
        return "PLACEHOLDER"
    end)
    SafeWidget({})
    check("비-문자열 에러 값(table)도 onError에 그대로 전달됨", type(capturedMsg) == "table" and capturedMsg.code == 42)
end

-- 5) onError 자체가 커링된 클로저인 경우(문서의 "추가 상태 필요하면 커링" 관용구)
do
    local Widget = function(props)
        error("ctx-fail")
    end
    local function makeErrorHandler(context)
        return function(message, trace)
            return "PLACEHOLDER(" .. context .. "):" .. message
        end
    end
    local SafeWidget = Traceback(Widget, makeErrorHandler("someContext"))
    local result = SafeWidget({})
    -- error("ctx-fail")의 기본 level(=1)이 "파일:줄: " 접두를 붙이므로
    -- message가 정확히 "ctx-fail"이 아님 — 아래 6번에서 별도로 실측/확정.
    check("커링된 onError 관용구가 그대로 동작(접두 포함 메시지가 이어붙여짐)",
        string.find(result, "^PLACEHOLDER%(someContext%):.*ctx%-fail$") ~= nil)
end

-- 6) error(msg)의 기본 동작 — onError가 받는 message에 "파일:줄: " 접두가
--    자동으로 붙는지(레벨 지정 없이 호출한 경우), error(msg, 0)으로 접두를
--    끌 수 있는지
do
    local defaultLevelMsg = nil
    do
        local Widget = function()
            error("no-level-specified")
        end
        local SafeWidget = Traceback(Widget, function(msg) return msg end)
        defaultLevelMsg = SafeWidget()
    end
    check("error(msg) 기본 호출 — onError가 받는 message에 위치 접두(\"파일:줄: \")가 자동으로 붙음",
        defaultLevelMsg ~= defaultLevelMsg:match("^no%-level%-specified$")
            and string.find(defaultLevelMsg, "no%-level%-specified$") ~= nil
            and defaultLevelMsg ~= "no-level-specified")
    print("  defaultLevelMsg = " .. tostring(defaultLevelMsg))

    local zeroLevelMsg = nil
    do
        local Widget = function()
            error("no-level-specified", 0)
        end
        local SafeWidget = Traceback(Widget, function(msg) return msg end)
        zeroLevelMsg = SafeWidget()
    end
    check("error(msg, 0) — 위치 접두 없이 순수 메시지만 onError에 전달됨",
        zeroLevelMsg == "no-level-specified")
    print("  zeroLevelMsg = " .. tostring(zeroLevelMsg))
end

-- 7) original에 여러 인자를 넘기는 vararg 경로(문서 시그니처 (Args...) -> Comp)
do
    local Widget = function(a, b, c)
        if c == nil then
            error("missing c")
        end
        return a + b + c
    end
    local SafeWidget = Traceback(Widget, function(msg, trace)
        return -1
    end)
    check("vararg 성공 경로", SafeWidget(1, 2, 3) == 6)
    check("vararg 실패 경로", SafeWidget(1, 2) == -1)
end

print("")
if failures == 0 then
    print("=== ALL PASS ===")
else
    print("=== " .. failures .. " FAILURE(S) ===")
end
