--
-- Shop (PocketOS Store app) -- "shitty amazon"
--
-- Browse a catalog (JSON hosted anywhere, e.g. next to your store
-- manifest), buy items with LINKED PAY (the sub-account you enrolled
-- in the Bank app), and ping the shopkeeper's pocket over chat.
--
-- Catalog format (shop-catalog.json):
-- {
--   "shop_name": "Petroid's Emporium",
--   "shop_account": "<numismatics account uuid to pay>",
--   "notify_id": <shopkeeper pocket computer id, or 0>,
--   "items": [
--     { "id": "dirt", "name": "Dirt x64", "price": 1, "desc": "a stack of dirt" }
--   ]
-- }
-- price is in COGS (1 cog = 64 spurs).
--
-- Delivery: physical/mail -- the shopkeeper sees the payment receipt
-- (bank backend logs it) plus a chat ping on their pocket.
--

local core = core
local cpair = core.cpair
local ALIGN = core.ALIGN

local container = pocketos.container
local os_config = pocketos.os_config

local unpackf = unpack or table.unpack

--#region CONFIG

local DEFAULT_CATALOG_URL = "https://computercraft.petroid.xyz/pocketos/files/store-apps/shop-catalog.json"

local cfg = pocketos.config.load()
cfg.catalog_url = cfg.catalog_url or DEFAULT_CATALOG_URL
pocketos.config.save(cfg)

--#endregion

--#region BANK PROTOCOL (linked pay only)

local modem = peripheral.find("modem")
local bank_channel = os_config.get("BankChannel") or 54123
local req_counter = 0

local function bank_request(op, args, timeout)
    timeout = timeout or 3
    if not modem then return nil, "no modem" end

    pcall(modem.open, bank_channel)

    req_counter = req_counter + 1
    local payload = {
        t = "pkt.bank.req/1",
        id = req_counter,
        src = os.getComputerID(),
        op = op,
        args = args or {},
    }

    local ok, err = pcall(modem.transmit, bank_channel, bank_channel, textutils.serialiseJSON(payload))
    if not ok then return nil, err end

    local deadline = os.epoch("local") / 1000 + timeout
    local replay = {}

    while os.epoch("local") / 1000 < deadline do
        local wake = os.startTimer(0.25)
        local ev = { os.pullEvent() }
        os.cancelTimer(wake)

        if ev[1] == "modem_message" and ev[3] == bank_channel then
            local okp, msg = pcall(textutils.unserialiseJSON, ev[5])
            if okp and type(msg) == "table" and msg.t == "pkt.bank.res/1" and msg.id == req_counter then
                for _, e in ipairs(replay) do os.queueEvent(unpackf(e)) end
                if msg.ok then
                    return msg.data
                else
                    return nil, msg.err or "backend error"
                end
            end
            table.insert(replay, ev)
        elseif ev[1] == "timer" then
            pocketos.tcd.handle(ev[2])
        else
            table.insert(replay, ev)
        end
    end

    for _, e in ipairs(replay) do os.queueEvent(unpackf(e)) end
    return nil, "timeout waiting for bank backend"
end

--#endregion

--#region CATALOG FETCH

local function fetch_catalog(url)
    local ok, code = pcall(function ()
        local req = http.get(url, nil, true)
        if not req then return nil end
        local body = req.getResponseCode() == 200 and req.readAll() or nil
        req.close()
        return body
    end)

    if not ok or not code then return nil, "catalog fetch failed" end

    local okp, parsed = pcall(textutils.unserialiseJSON, code)
    if not okp or type(parsed) ~= "table" or type(parsed.items) ~= "table" then
        return nil, "catalog is not valid JSON"
    end
    return parsed
end

--#endregion

--#region CHAT PING (notify the shopkeeper)

local chat_channel = os_config.get("PingChannel") or 54124
local catalog_cache = nil

local function ping_shopkeeper(shop_name, item_name)
    if not modem then return end
    local catalog = catalog_cache
    if not catalog or not catalog.notify_id or catalog.notify_id == 0 then return end

    pcall(modem.open, chat_channel)

    local payload = {
        t = "pkt.chat/1",
        from = os.getComputerID(),
        from_name = os_config.get("UserName") or "Player",
        to = catalog.notify_id,
        to_name = shop_name,
        text = "ORDER: " .. item_name .. " (paid)",
    }

    pcall(modem.transmit, chat_channel, chat_channel, textutils.serialiseJSON(payload))
end

--#endregion

--#region UI

local function init_container()
    local btn_fg_bg  = cpair(colors.orange, colors.black)
    local btn_active = cpair(colors.white, colors.black)
    local label      = cpair(colors.lightGray, colors.black)

    local status = TextBox{parent=container, y=16, text="tap Refresh to load the catalog", fg_bg=label}

    local catalog_list = ListBox{parent=container, y=3, height=12, scroll_height=200,
        nav_fg_bg=cpair(colors.lightGray, colors.gray), nav_active=cpair(colors.white, colors.gray)}

    local function money(spurs)
        spurs = math.floor(tonumber(spurs) or 0)
        local cogs = math.floor(spurs / 64)
        local rem = spurs % 64
        if cogs > 0 and rem > 0 then
            return string.format("%dc %ds", cogs, rem)
        elseif cogs > 0 then
            return string.format("%dc", cogs)
        end
        return string.format("%ds", rem)
    end

    local function redraw_items()
        catalog_list.remove_all()

        if not catalog_cache then
            TextBox{parent=catalog_list, text="No catalog loaded.", fg_bg=cpair(colors.gray, colors._INHERIT)}
            TextBox{parent=catalog_list, text="Set the catalog URL below,", fg_bg=cpair(colors.gray, colors._INHERIT)}
            TextBox{parent=catalog_list, text="then tap Refresh.", fg_bg=cpair(colors.gray, colors._INHERIT)}
            return
        end

        local shop_name = catalog_cache.shop_name or "Shop"

        for _, item in ipairs(catalog_cache.items) do
            local price_spurs = math.floor((tonumber(item.price) or 0) * 64)

            local row = Div{parent=catalog_list, height=3}

            TextBox{parent=row, y=1, text=string.sub(item.name or item.id or "?", 1, 16),
                fg_bg=cpair(colors.white, colors._INHERIT)}
            TextBox{parent=row, x=17, y=1, text=money(price_spurs),
                fg_bg=cpair(colors.yellow, colors._INHERIT)}

            if item.desc then
                TextBox{parent=row, y=2, text=string.sub(item.desc, 1, 22), fg_bg=cpair(colors.gray, colors._INHERIT)}
            end

            PushButton{parent=row, y=3, text="Buy",
                fg_bg=cpair(colors.green, colors.black), active_fg_bg=btn_active,
                callback=function ()
                    if not catalog_cache.shop_account then
                        status.set_value("catalog has no shop_account")
                        return
                    end

                    status.set_value("paying " .. money(price_spurs) .. "...")

                    local receipt, err = bank_request("LINK_PAY",
                        { os.getComputerID(), catalog_cache.shop_account, price_spurs })

                    if receipt then
                        status.set_value("bought " .. (item.name or "?") .. "! check mail")
                        ping_shopkeeper(shop_name, item.name or item.id or "?")
                    else
                        status.set_value("failed: " .. tostring(err))
                    end
                end}

            local _ = Div{parent=catalog_list, height=1}
        end
    end

    -- title + refresh
    TextBox{parent=container, y=1, text="Shop", alignment=ALIGN.CENTER,
        fg_bg=cpair(colors.orange, colors._INHERIT)}

    PushButton{parent=container, x=1, y=2, text="Refresh", min_width=8,
        fg_bg=btn_fg_bg, active_fg_bg=btn_active,
        callback=function ()
            status.set_value("loading catalog...")

            local url = pocketos.config.load().catalog_url or DEFAULT_CATALOG_URL
            local catalog, err = fetch_catalog(url)

            if catalog then
                catalog_cache = catalog
                status.set_value((catalog.shop_name or "Shop") .. ": " .. #catalog.items .. " items")
            else
                catalog_cache = nil
                status.set_value(tostring(err))
            end

            redraw_items()
        end}

    -- catalog URL config
    TextBox{parent=container, y=17, text="Catalog URL:", fg_bg=label}
    local url_field = TextField{parent=container, y=18, width=20, max_len=200,
        fg_bg=cpair(colors.white, colors.gray)}
    url_field.set_value(cfg.catalog_url or DEFAULT_CATALOG_URL)

    PushButton{parent=container, x=1, y=15, text="Save URL", min_width=8,
        fg_bg=btn_fg_bg, active_fg_bg=btn_active,
        callback=function ()
            local url = url_field.get_value()
            if url and #url > 5 then
                local c = pocketos.config.load()
                c.catalog_url = url
                pocketos.config.save(c)
                cfg.catalog_url = url
                status.set_value("saved")
            end
        end}

    redraw_items()
end

init_container()
