-- squad-injector/apply_lineup_write.lua
-- FIFA Live Editor Lua script (v2 API).
--
-- TEMPLATE — fill in TARGET_TABLE / TARGET_FIELDS below using whatever
-- diff_snapshots.py reports after the snapshot/Freeze-Lineup/snapshot cycle
-- described in docs/foundational-xi-injection-test.md. This script cannot
-- run correctly until those are filled in; it's the second half of the
-- foundational test, not a standalone tool.
--
-- Once filled in, this replicates the discovered write programmatically
-- (no GUI/Formation Editor needed), which is what build-order step 2 (the
-- app->game bridge) needs to drive a squad push from outside the game.
--
-- Correct EditDBTableField usage (confirmed from FLE's own Lua API docs and
-- lua/scripts/99ovr_99pot.lua): mutate the cell's .value in place, then pass
-- THE CELL ITSELF (not table/index/field-name/value as separate args) to
-- EditDBTableField:
--
--   local rows = GetDBTableRows("players")
--   local row = rows[1]
--   row["overallrating"].value = "99"
--   local ok = EditDBTableField(row["overallrating"])

local IN_PATH = "C:\\FIFA 23 Live Editor\\openfut_test_xi.json"
local OUT_PATH = "C:\\FIFA 23 Live Editor\\openfut_apply_result.json"

-- ── FILL IN AFTER RUNNING diff_snapshots.py ──────────────────────────────────
local TARGET_TABLE = nil -- e.g. "teamsheet" — set this once the diff identifies it
local TARGET_FIELDS = {
    -- e.g. { name = "selected", value = function(entry) return "1" end },
    -- e.g. { name = "position",  value = function(entry) return entry.position end },
}

local function json_val(v)
    local t = type(v)
    if t == "nil" then return "null"
    elseif t == "boolean" then return tostring(v)
    elseif t == "number" then return string.format("%.6g", v)
    elseif t == "string" then
        return '"' .. v:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n') .. '"'
    elseif t == "table" then
        local n = #v
        if n > 0 then
            local parts = {}
            for i = 1, n do parts[i] = json_val(v[i]) end
            return "[" .. table.concat(parts, ",") .. "]"
        else
            local parts = {}
            for k, vv in pairs(v) do
                parts[#parts + 1] = json_val(tostring(k)) .. ":" .. json_val(vv)
            end
            return "{" .. table.concat(parts, ",") .. "}"
        end
    end
    return "null"
end

local function parse_json(s)
    local i = 1
    local function skip_ws() while s:sub(i, i):match("%s") do i = i + 1 end end
    local parse_value

    local function parse_string()
        i = i + 1
        local start = i
        local out = {}
        while s:sub(i, i) ~= '"' do
            if s:sub(i, i) == "\\" then
                out[#out + 1] = s:sub(start, i - 1)
                local esc = s:sub(i + 1, i + 1)
                out[#out + 1] = ({ n = "\n", t = "\t", ['"'] = '"', ["\\"] = "\\" })[esc] or esc
                i = i + 2
                start = i
            else
                i = i + 1
            end
        end
        out[#out + 1] = s:sub(start, i - 1)
        i = i + 1
        return table.concat(out)
    end

    local function parse_number()
        local start = i
        while s:sub(i, i):match("[%-%+%.eE0-9]") do i = i + 1 end
        return tonumber(s:sub(start, i - 1))
    end

    local function parse_array()
        i = i + 1
        local out = {}
        skip_ws()
        if s:sub(i, i) == "]" then i = i + 1; return out end
        while true do
            skip_ws()
            out[#out + 1] = parse_value()
            skip_ws()
            if s:sub(i, i) == "," then i = i + 1 else break end
        end
        skip_ws()
        i = i + 1
        return out
    end

    local function parse_object()
        i = i + 1
        local out = {}
        skip_ws()
        if s:sub(i, i) == "}" then i = i + 1; return out end
        while true do
            skip_ws()
            local key = parse_string()
            skip_ws()
            i = i + 1
            skip_ws()
            out[key] = parse_value()
            skip_ws()
            if s:sub(i, i) == "," then i = i + 1 else break end
        end
        skip_ws()
        i = i + 1
        return out
    end

    parse_value = function()
        skip_ws()
        local c = s:sub(i, i)
        if c == '"' then return parse_string()
        elseif c == "{" then return parse_object()
        elseif c == "[" then return parse_array()
        elseif c == "t" then i = i + 4; return true
        elseif c == "f" then i = i + 5; return false
        elseif c == "n" then i = i + 4; return nil
        else return parse_number()
        end
    end

    skip_ws()
    return parse_value()
end

local function read_file(path)
    local f = io.open(path, "r")
    if not f then return nil end
    local content = f:read("*a")
    f:close()
    return content
end

local function write_file(path, content)
    local f = io.open(path, "w")
    if not f then return false end
    f:write(content)
    f:close()
    return true
end

if not TARGET_TABLE or #TARGET_FIELDS == 0 then
    MessageBox("OpenFUT Apply", "TARGET_TABLE / TARGET_FIELDS are not filled in yet. " ..
        "Run the snapshot/Freeze-Lineup/snapshot/diff cycle first (see docs/foundational-xi-injection-test.md).")
    return
end

local raw = read_file(IN_PATH)
if not raw then
    MessageBox("OpenFUT Apply", "ERROR: could not read " .. IN_PATH)
    return
end

local ok_parse, request = pcall(parse_json, raw)
if not ok_parse or not request or not request.xi then
    MessageBox("OpenFUT Apply", "ERROR: could not parse " .. IN_PATH)
    return
end

local rows = GetDBTableRows(TARGET_TABLE) or {}
local result = {
    attempted_at = os.date("!%Y-%m-%dT%H:%M:%SZ"),
    is_career_mode = IsInCM(),
    target_table = TARGET_TABLE,
    rows_written = {},
    errors = {},
}

for _, entry in ipairs(request.xi) do
    local pid = entry.player_id
    local matched_row = nil
    for _, row in ipairs(rows) do
        local cell = row["playerid"] or row["player_id"]
        if cell and math.floor(cell.value) == math.floor(pid) then
            matched_row = row
            break
        end
    end

    if not matched_row then
        result.errors[#result.errors + 1] = "player_id " .. tostring(pid) .. " not found in " .. TARGET_TABLE
    else
        local row_result = { player_id = pid, writes = {} }
        for _, fdef in ipairs(TARGET_FIELDS) do
            local cell = matched_row[fdef.name]
            if cell then
                cell.value = fdef.value(entry)
                local ok = EditDBTableField(cell)
                row_result.writes[#row_result.writes + 1] = { field = fdef.name, value = cell.value, ok = ok }
            else
                row_result.writes[#row_result.writes + 1] = { field = fdef.name, ok = false, err = "field not found on row" }
            end
        end
        result.rows_written[#result.rows_written + 1] = row_result
    end
end

write_file(OUT_PATH, json_val(result))
Log("[apply] done, see " .. OUT_PATH)
MessageBox("OpenFUT Apply", "Rows written: " .. #result.rows_written .. " / " .. #request.xi ..
    "\nErrors: " .. #result.errors .. "\n\n" .. OUT_PATH)
