SkyPhoneCompatibility = {}

function SkyPhoneCompatibility.RegisterExportAlias(resource_name, export_name, handler)
    assert(type(resource_name) == "string" and resource_name ~= "", "Export alias resource must be a non-empty string")
    assert(type(export_name) == "string" and export_name ~= "", "Export alias name must be a non-empty string")
    assert(type(handler) == "function", "Export alias handler must be a function")

    AddEventHandler(("__cfx_export_%s_%s"):format(resource_name, export_name), function(set_callback)
        set_callback(function(...)
            local debug_custom_app = SkyPhoneApps and SkyPhoneApps.Debug
            local invoking_resource = GetInvokingResource and GetInvokingResource() or nil
            if debug_custom_app then
                debug_custom_app(
                    "export",
                    "call provider=%s export=%s invoking_resource=%s argument_count=%s",
                    resource_name,
                    export_name,
                    tostring(invoking_resource),
                    select("#", ...)
                )
            end

            local results = table.pack(handler(...))
            if debug_custom_app then
                debug_custom_app(
                    "export",
                    "result provider=%s export=%s invoking_resource=%s success=%s error=%s",
                    resource_name,
                    export_name,
                    tostring(invoking_resource),
                    tostring(results[1]),
                    tostring(results[2])
                )
            end
            return table.unpack(results, 1, results.n)
        end)
    end)
end

SkyPhoneCompatibility.Providers = {
    lb = "lb_phone",
    seventeen = "17mov",
    high = "high_phone",
    quasar = "quasar_v3",
    yseries = "yseries",
}

local function normalize_at_resource_url(owner_resource, url, error_prefix)
    if type(url) ~= "string" or url == "" then
        return nil, "invalid_" .. error_prefix
    end
    if url:sub(1, 7) == "http://" then
        return nil, "insecure_" .. error_prefix
    end
    if url:sub(1, 1) ~= "@" then
        return url
    end

    local url_owner, url_path = url:match("^@([^/]+)/(.+)$")
    if url_owner ~= owner_resource or not url_path then
        return nil, error_prefix .. "_owner_mismatch"
    end
    return owner_resource .. "/" .. url_path
end

local function resolve_lb_asset_resource(owner_resource, app_data)
    local ui = app_data.ui
    if type(ui) ~= "string" then
        return nil
    end

    local explicit_ui_resource = ui:match("^https://cfx%-nui%-([^/]+)/")
        or ui:match("^nui://([^/]+)/")
    local asset_resource = explicit_ui_resource or ui:match("^([%w][%w._-]*)/")
    if not asset_resource or asset_resource == owner_resource then
        return nil
    end

    if explicit_ui_resource or app_data.resource == asset_resource then
        return asset_resource
    end

    local icon = app_data.icon
    local icon_resource = type(icon) == "string" and (
        icon:match("^https://cfx%-nui%-([^/]+)/") or icon:match("^nui://([^/]+)/")
    ) or nil
    if icon_resource == asset_resource then
        return asset_resource
    end

    return nil
end

local function build_locale_maps(app_name, locales)
    local names = {}
    local descriptions = {}

    if type(locales) == "table" then
        for locale, locale_data in pairs(locales) do
            if type(locale) == "string" and type(locale_data) == "table" then
                if type(locale_data.label) == "string" and locale_data.label ~= "" then
                    names[locale] = locale_data.label
                end
                if type(locale_data.description) == "string" and locale_data.description ~= "" then
                    descriptions[locale] = locale_data.description
                end
            end
        end
    end

    if not next(names) then
        return app_name, app_name
    end
    for locale, label in pairs(names) do
        if not descriptions[locale] then
            descriptions[locale] = label
        end
    end
    return names, descriptions
end

function SkyPhoneCompatibility.CopyQuasarData(app_data)
    local copy = {}
    for key, value in pairs(app_data) do
        if key == "iframe" and type(value) == "table" then
            copy.iframe = {}
            for iframe_key, iframe_value in pairs(value) do
                copy.iframe[iframe_key] = iframe_value
            end
        else
            copy[key] = value
        end
    end
    return copy
end

function SkyPhoneCompatibility.BuildLbDefinition(owner_resource, app_data)
    if type(app_data) ~= "table" or type(app_data.identifier) ~= "string" then
        return nil, "invalid_app_data"
    end

    local ui, ui_error = normalize_at_resource_url(owner_resource, app_data.ui, "ui")
    if not ui then
        return nil, ui_error
    end

    return {
        schemaVersion = 1,
        id = app_data.identifier,
        name = app_data.name,
        description = app_data.description,
        developer = app_data.developer,
        category = app_data.game and "games" or "utilities",
        ui = ui,
        assetResource = resolve_lb_asset_resource(owner_resource, app_data),
        bridgeMode = "legacy",
        icon = app_data.icon,
        defaultInstalled = app_data.defaultApp or false,
        removable = not app_data.defaultApp,
        orientation = app_data.landscape and "landscape" or "portrait",
        onInstall = app_data.onInstall,
        onOpen = app_data.onOpen or app_data.onUse,
        onClose = app_data.onClose,
        compatibility = {
            provider = SkyPhoneCompatibility.Providers.lb,
            apiVersion = 1,
            fixBlur = app_data.fixBlur == true,
            resourceName = type(app_data.resource) == "string" and app_data.resource or owner_resource,
        },
    }
end

function SkyPhoneCompatibility.Build17MovDefinition(app_data)
    if type(app_data) ~= "table" or type(app_data.name) ~= "string" then
        return nil, "invalid_app_data"
    end

    return {
        schemaVersion = 1,
        id = app_data.name,
        name = app_data.label,
        description = app_data.description or app_data.label,
        category = "utilities",
        ui = app_data.ui,
        bridgeMode = "legacy",
        icon = app_data.icon,
        iconBackground = type(app_data.iconBackground) == "string" and app_data.iconBackground or nil,
        defaultInstalled = app_data.default or app_data.preInstalled or false,
        removable = not app_data.default,
        orientation = "portrait",
        compatibility = {
            provider = SkyPhoneCompatibility.Providers.seventeen,
            apiVersion = 1,
        },
    }
end

function SkyPhoneCompatibility.BuildHighDefinition(owner_resource, app_name, data, locales)
    if type(owner_resource) ~= "string" or type(app_name) ~= "string" or type(data) ~= "table" then
        return nil, "invalid_app_data"
    end
    if #app_name > 64 or not app_name:match("^[a-z0-9][a-z0-9._-]+$") then
        return nil, "invalid_app_id"
    end

    local external_url, url_error = normalize_at_resource_url(
        owner_resource,
        data.externalUrl,
        "external_url"
    )
    if not external_url then
        return nil, url_error
    end

    local name, description = build_locale_maps(app_name, locales)
    local icon = type(data.icon) == "table" and data.icon.imageUrl or data.icon
    return {
        schemaVersion = 1,
        id = app_name,
        name = name,
        description = description,
        developer = data.developer,
        category = "utilities",
        ui = external_url,
        bridgeMode = "legacy",
        icon = icon,
        iconBackground = type(data.icon) == "table" and data.icon.background or nil,
        defaultInstalled = data.preAdded or false,
        removable = data.removable ~= false,
        orientation = "portrait",
        compatibility = {
            provider = SkyPhoneCompatibility.Providers.high,
            apiVersion = 1,
        },
    }
end

function SkyPhoneCompatibility.BuildQuasarDefinition(app_data)
    local iframe_url = type(app_data) == "table"
        and type(app_data.iframe) == "table"
        and app_data.iframe.url
        or nil
    if type(app_data) ~= "table"
        or type(app_data.id) ~= "string"
        or type(app_data.label) ~= "string"
        or type(iframe_url) ~= "string"
    then
        return nil, "invalid_app_data"
    end

    return {
        schemaVersion = 1,
        id = app_data.id,
        name = app_data.label,
        description = app_data.description or app_data.label,
        category = "utilities",
        ui = iframe_url,
        bridgeMode = "legacy",
        icon = app_data.icon,
        defaultInstalled = app_data.defaultInstalled or false,
        removable = true,
        orientation = "portrait",
        compatibility = {
            provider = SkyPhoneCompatibility.Providers.quasar,
            apiVersion = 1,
        },
    }
end

function SkyPhoneCompatibility.BuildYSeriesDefinition(app_data)
    if type(app_data) ~= "table" or type(app_data.key) ~= "string" then
        return nil, "invalid_app_data"
    end

    local icon = app_data.icon
    if type(icon) == "table" then
        icon = icon.yos or icon.humanoid
    end

    return {
        schemaVersion = 1,
        id = app_data.key,
        name = app_data.name,
        description = app_data.description or app_data.name,
        category = app_data.game and "games" or "utilities",
        ui = app_data.ui,
        bridgeMode = "legacy",
        icon = icon,
        defaultInstalled = app_data.defaultApp or false,
        removable = true,
        orientation = "portrait",
        compatibility = {
            provider = SkyPhoneCompatibility.Providers.yseries,
            apiVersion = 1,
        },
    }
end
