const commonFields = [
    {
        key: "uid_mode",
        label: "用户标识",
        type: "select",
        options: [
            ["", "按协议默认"],
            ["user_id", "内部用户 ID"],
            ["display_user_id", "用户短号 / 展示 ID"],
        ],
    },
    { key: "default_lang", label: "默认语言", placeholder: "按协议默认", type: "string" },
    {
        key: "token_ttl_seconds",
        label: "Token 有效期",
        min: 1,
        placeholder: "86400",
        type: "integer",
        unit: "秒",
    },
];

const catalogKeys = ["game_urls", "game_names", "game_icons", "game_covers", "game_level_uids"];

const globalConfigKeys = [
    "bridge_script_url",
    "bridge_script_version",
    "bridge_script_sha256",
    "bridgeScriptUrl",
    "bridgeScriptVersion",
    "bridgeScriptSha256",
    "bridge_script_hash",
];

// 单一注册表同时驱动选择器、字段、顶层接入语义和序列化类型，避免页面与提交逻辑各维护一份厂商分支。
export const adapterDefinitions = {
    yomi_v4: {
        label: "小游 Yomi V4",
        apiUrlLabel: "Auth / Gateway URL",
        apiUrlRequired: true,
        callbackSecretLabel: "Yomi AppSecret",
        ipWhitelist: true,
        catalog: true,
        catalogPrimary: "name",
        fields: [
            { key: "game_level_uid", label: "默认场次 ID", min: 0, type: "integer" },
            { key: "plat_payload", label: "平台透传参数", type: "string" },
        ],
    },
    leadercc_v1: {
        label: "灵仙 LeaderCC V1",
        apiUrl: false,
        callbackSecretLabel: "灵仙签名 Key",
        ipWhitelist: false,
        catalog: false,
        fields: [],
    },
    zeeone_v1: {
        label: "ZeeOne V1",
        apiUrlLabel: "默认 H5 URL",
        callbackSecretLabel: "ZeeOne 签名 Secret",
        ipWhitelist: false,
        catalog: true,
        fields: [
            { key: "merchant_id", label: "商户 ID", min: 1, required: true, type: "integer" },
            { key: "platform_id", label: "平台 ID", min: 1, required: true, type: "integer" },
            { key: "app_id", label: "回调 App ID", placeholder: "默认 M{商户ID}_P{平台ID}", type: "string" },
            { key: "game_mode", label: "游戏模式", type: "string" },
            { key: "extra", label: "启动透传参数", type: "string" },
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
    },
    baishun_v1: {
        label: "百顺 BAISHUN V1",
        apiUrlLabel: "厂商 API / 默认 H5 URL",
        callbackSecretLabel: "百顺 AppKey",
        ipWhitelist: true,
        catalog: true,
        fields: [
            { key: "app_id", label: "App ID", min: 1, required: true, type: "integer" },
            { key: "app_channel", label: "App Channel", required: true, type: "string" },
            { key: "game_list_url", label: "游戏列表 URL", type: "string" },
            { key: "game_list_type", label: "游戏列表类型", min: 1, placeholder: "1", type: "integer" },
            { key: "provider_api_base_url", label: "厂商 API Base URL", type: "string" },
            { key: "game_mode", label: "游戏模式", placeholder: "3", type: "string" },
            { key: "scene_mode", label: "场景模式", min: 0, type: "integer" },
            { key: "currency_icon", label: "货币图标 URL", type: "string" },
            { key: "gsp", label: "GSP", min: 0, type: "integer" },
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
        advancedFields: [
            { key: "user_type", label: "用户类型", min: 0, type: "integer" },
            { key: "release_cond", label: "放行条件", min: 0, type: "integer" },
            {
                key: "is_old_user",
                label: "历史用户",
                type: "selectBoolean",
                options: [
                    ["", "不传"],
                    ["true", "是"],
                    ["false", "否"],
                ],
            },
            { key: "extend", label: "扩展参数", type: "string" },
        ],
    },
    vivagames_v1: {
        label: "VIVAGAMES V1",
        apiUrlLabel: "默认 H5 URL",
        callbackSecretLabel: "VIVAGAMES AppKey",
        ipWhitelist: true,
        catalog: true,
        fields: [
            { key: "app_id", label: "App ID", min: 1, required: true, type: "integer" },
            { key: "app_secret", label: "App Secret", type: "string" },
            { key: "coin_type", label: "币种类型", min: 0, type: "integer" },
            { key: "amount_rate", label: "金额倍率", min: 1, placeholder: "1", type: "integer" },
            {
                key: "bgm_switch",
                label: "背景音乐",
                type: "selectNumber",
                options: [
                    ["", "按协议默认"],
                    ["1", "开启"],
                    ["0", "关闭"],
                ],
            },
            { key: "gsp", label: "GSP", min: 1, placeholder: "101", type: "integer" },
            { key: "currency_icon", label: "货币图标 URL", type: "string" },
            { key: "metadata", label: "启动 Metadata", type: "string" },
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
    },
    reyou_v1: {
        label: "热游 Reyou V1",
        apiUrlLabel: "默认 H5 URL",
        callbackSecretLabel: "热游签名 Key",
        ipWhitelist: true,
        catalog: true,
        fields: [
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
    },
    zgame_v1: {
        label: "ZGame V1",
        apiUrlLabel: "默认 H5 URL",
        callbackSecretLabel: "ZGame 签名 Secret",
        fixedPlatformCode: "zgame",
        fixedPlatformName: "ZGame",
        ipWhitelist: true,
        catalog: true,
        fields: [
            {
                className: "wide",
                key: "callback_base",
                label: "公网回调 Base URL",
                placeholder: "https://api.example.com/api/v1/zgame/callback",
                type: "string",
            },
            { key: "merchant_payload", label: "商户透传参数", type: "string" },
            { key: "level", label: "默认等级", min: 0, type: "integer" },
            { key: "gender", label: "默认性别", min: 0, type: "integer" },
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
    },
    amg_v1: {
        label: "AMG V1",
        apiUrlLabel: "默认 H5 URL",
        callbackSecretLabel: "AMG PKCS#8 私钥（Base64）",
        callbackSecretPlaceholder: "粘贴厂商提供的单行 Base64 私钥",
        fixedPlatformCode: "amg",
        fixedPlatformName: "AMG",
        ipWhitelist: true,
        catalog: true,
        numericGameID: true,
        fields: [
            { key: "app_key", label: "AMG App Key", required: true, type: "string" },
            {
                key: "coin_scale",
                label: "金币精度",
                type: "selectNumber",
                options: [
                    ["", "默认 1"],
                    ["1", "1（整数金币）"],
                    ["10", "10（1 位小数）"],
                    ["100", "100（2 位小数）"],
                    ["1000", "1,000（3 位小数）"],
                    ["10000", "10,000（4 位小数）"],
                    ["100000", "100,000（5 位小数）"],
                    ["1000000", "1,000,000（6 位小数）"],
                ],
            },
            { key: "ext", label: "启动扩展参数", type: "string" },
            { key: "loading", label: "Loading 参数", type: "string" },
            {
                className: "wide",
                key: "launch_url_template",
                label: "启动 URL 模板",
                placeholder: "https://game.example/{provider_game_id}",
                type: "string",
            },
        ],
        advancedFields: [
            { key: "mini", label: "Mini 参数", min: 0, type: "integer" },
            { key: "app_audio", label: "App Audio 参数", min: 0, type: "integer" },
            { key: "debug", label: "Debug 参数", min: 0, type: "integer" },
            { key: "use_lang", label: "Use Lang 参数", min: 0, type: "integer" },
        ],
    },
};

export const adapterTypeOptions = Object.entries(adapterDefinitions).map(([value, definition]) => [
    value,
    definition.label,
]);

export const writableBridgeAdapterTypes = new Set(Object.keys(adapterDefinitions));

const aliasesByKey = {
    app_id: ["appId"],
    app_channel: ["appChannel"],
    platform_id: ["platform"],
    callback_base: ["callbackBase"],
    provider_api_base_url: ["providerApiBaseUrl"],
    game_list_url: ["gameListUrl"],
    game_list_type: ["gameListType"],
};

export function adapterDefinition(adapterType) {
    return adapterDefinitions[adapterType] || adapterDefinitions.yomi_v4;
}

export function adapterCommonFields() {
    return commonFields;
}

export function adapterSecretState(form, editing = false) {
    const adapterType = String(form.adapterType || "").trim() || "yomi_v4";
    const adapterChanged =
        editing && Boolean(form.initialAdapterType) && String(form.initialAdapterType).trim() !== adapterType;
    const configuredSecretUsable = editing && !adapterChanged && Boolean(form.callbackSecretSet);
    return {
        adapterChanged,
        configuredSecretUsable,
        required:
            adapterChanged || ((String(form.status || "").trim() || "active") === "active" && !configuredSecretUsable),
    };
}

export function normalizeAdapterCallbackSecret(form, editing = false) {
    const callbackSecret = String(form.callbackSecret || "").trim();
    if (adapterSecretState(form, editing).required && !callbackSecret) {
        const definition = adapterDefinition(String(form.adapterType || "").trim());
        throw new Error(`请填写${definition.callbackSecretLabel || "回调密钥"}`);
    }
    return callbackSecret;
}

export function readAdapterConfig(raw) {
    const value = String(raw || "").trim();
    if (!value) {
        return {};
    }
    try {
        const parsed = JSON.parse(value);
        return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
    } catch {
        return null;
    }
}

export function adapterConfigFieldValue(raw, field) {
    const entries = parseObjectEntries(raw);
    if (!entries) {
        return "";
    }
    const rawValue = findFieldRawValue(entries, field, aliasesByKey[field.key] || []);
    const value = decodeRawValue(rawValue);
    if (value === undefined || value === null) {
        return "";
    }
    if (field.type === "selectBoolean") {
        return value === true || value === "true" ? "true" : value === false || value === "false" ? "false" : "";
    }
    return String(value);
}

export function updateAdapterConfigField(raw, field, inputValue) {
    const entries = parseObjectEntries(raw);
    if (!entries) {
        // 非法原始 JSON 必须由高级编辑器先修正；此时拒绝结构化 patch，避免把无法解析的历史配置覆盖成新对象。
        return String(raw || "{}");
    }
    const rawValue =
        inputValue === "" || inputValue === null || inputValue === undefined
            ? undefined
            : encodeFieldRawValue(field, inputValue);
    const next = replaceObjectEntry(entries, field.key, aliasesByKey[field.key] || [], rawValue);
    return serializeObjectEntries(next, true);
}

export function normalizeAdapterConfigJson(raw, adapterType) {
    let entries = parseObjectEntries(raw);
    if (!entries) {
        throw new Error("适配器高级配置必须是 JSON 对象");
    }
    const activeFields = new Map(
        [
            ...commonFields,
            ...(adapterDefinition(adapterType).fields || []),
            ...(adapterDefinition(adapterType).advancedFields || []),
        ].map((field) => [field.key, field]),
    );
    for (const field of activeFields.values()) {
        const aliases = aliasesByKey[field.key] || [];
        const rawValue = findFieldRawValue(entries, field, aliases);
        if (rawValue === undefined) {
            continue;
        }
        const value = decodeRawValue(rawValue);
        // 只迁移当前厂商实际识别的历史别名；其他 key 可能属于未来协议，必须按原始 JSON token 保留。
        entries = replaceObjectEntry(
            entries,
            field.key,
            aliases,
            value === "" || value === null ? undefined : encodeFieldRawValue(field, value, rawValue),
        );
    }
    return serializeObjectEntries(entries);
}

export function migrateAdapterConfigJson(raw, previousAdapterType, nextAdapterType) {
    let entries = parseObjectEntries(raw);
    if (!entries || previousAdapterType === nextAdapterType) {
        return String(raw || "{}");
    }
    const preservedKeys = new Set([...commonFields.map((field) => field.key), ...catalogKeys, ...globalConfigKeys]);
    const previous = adapterDefinition(previousAdapterType);
    for (const field of [...(previous.fields || []), ...(previous.advancedFields || [])]) {
        if (!preservedKeys.has(field.key)) {
            entries = replaceObjectEntry(entries, field.key, aliasesByKey[field.key] || [], undefined);
        }
    }
    return serializeObjectEntries(entries, true);
}

export function adapterCatalogRows(raw) {
    const entries = parseObjectEntries(raw);
    if (!entries) {
        return [];
    }
    const maps = Object.fromEntries(catalogKeys.map((key) => [key, readRawObjectMap(entries, key)]));
    const ids = [];
    const seen = new Set();
    for (const key of catalogKeys) {
        for (const id of Object.keys(maps[key])) {
            const normalized = String(id || "").trim();
            if (normalized && !seen.has(normalized)) {
                seen.add(normalized);
                ids.push(normalized);
            }
        }
    }
    return ids.map((providerGameId) => ({
        providerGameId,
        gameName: stringValue(maps.game_names[providerGameId]),
        gameUrl: stringValue(maps.game_urls[providerGameId]),
        iconUrl: stringValue(maps.game_icons[providerGameId]),
        coverUrl: stringValue(maps.game_covers[providerGameId]),
        gameLevelUid: numberInputValue(maps.game_level_uids[providerGameId]),
    }));
}

export function updateAdapterCatalog(raw, rows) {
    let entries = parseObjectEntries(raw);
    if (!entries) {
        return String(raw || "{}");
    }
    const providerGameIDs = (rows || []).map((row) => String(row.providerGameId || "").trim()).filter(Boolean);
    if (new Set(providerGameIDs).size !== providerGameIDs.length) {
        // JSON map 无法表达重复 key；拒绝改写底层配置，让表单保留两行并通过原生校验提示用户修正。
        return String(raw || "{}");
    }
    const gameURLs = new Map();
    const gameNames = new Map();
    const gameIcons = new Map();
    const gameCovers = new Map();
    const gameLevelUIDs = new Map();
    for (const row of rows || []) {
        const providerGameID = String(row.providerGameId || "").trim();
        if (!providerGameID) {
            continue;
        }
        assignTrimmed(gameURLs, providerGameID, row.gameUrl);
        assignTrimmed(gameNames, providerGameID, row.gameName);
        assignTrimmed(gameIcons, providerGameID, row.iconUrl);
        assignTrimmed(gameCovers, providerGameID, row.coverUrl);
        const levelUID = positiveIntegerRawValue(row.gameLevelUid);
        if (levelUID) {
            gameLevelUIDs.set(providerGameID, levelUID);
        }
    }
    entries = replaceObjectEntry(entries, "game_urls", [], serializeStringMap(gameURLs));
    entries = replaceObjectEntry(entries, "game_names", [], serializeStringMap(gameNames));
    entries = replaceObjectEntry(entries, "game_icons", [], serializeStringMap(gameIcons));
    entries = replaceObjectEntry(entries, "game_covers", [], serializeStringMap(gameCovers));
    entries = replaceObjectEntry(entries, "game_level_uids", [], serializeRawMap(gameLevelUIDs));
    return serializeObjectEntries(entries, true);
}

// 结构化编辑只重写目标顶层字段。未知字段的 raw JSON token（尤其 int64）必须原样保留，不能经过 Number。
function encodeFieldRawValue(field, value, fallbackRawValue) {
    if (field.type === "integer" || field.type === "selectNumber") {
        return integerRawValue(value) || fallbackRawValue || JSON.stringify(String(value).trim());
    }
    if (field.type === "selectBoolean") {
        return value === true || value === "true" ? "true" : "false";
    }
    return JSON.stringify(String(value).trim());
}

function parseObjectEntries(raw) {
    const source = String(raw || "").trim() || "{}";
    try {
        const parsed = JSON.parse(source);
        if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
            return null;
        }
    } catch {
        return null;
    }
    const entries = [];
    let index = skipWhitespace(source, 1);
    while (index < source.length && source[index] !== "}") {
        if (source[index] !== '"') {
            return null;
        }
        const keyEnd = scanJSONString(source, index);
        const key = JSON.parse(source.slice(index, keyEnd));
        index = skipWhitespace(source, keyEnd);
        if (source[index] !== ":") {
            return null;
        }
        const valueStart = skipWhitespace(source, index + 1);
        const valueEnd = scanJSONValue(source, valueStart);
        entries.push({ key, rawValue: source.slice(valueStart, valueEnd) });
        index = skipWhitespace(source, valueEnd);
        if (source[index] === ",") {
            index = skipWhitespace(source, index + 1);
        } else if (source[index] !== "}") {
            return null;
        }
    }
    return entries;
}

function scanJSONString(source, start) {
    let index = start + 1;
    while (index < source.length) {
        if (source[index] === "\\") {
            index += 2;
        } else if (source[index] === '"') {
            return index + 1;
        } else {
            index += 1;
        }
    }
    return source.length;
}

function scanJSONValue(source, start) {
    if (source[start] === '"') {
        return scanJSONString(source, start);
    }
    if (source[start] === "{" || source[start] === "[") {
        const stack = [source[start]];
        let index = start + 1;
        while (index < source.length && stack.length > 0) {
            if (source[index] === '"') {
                index = scanJSONString(source, index);
                continue;
            }
            if (source[index] === "{" || source[index] === "[") {
                stack.push(source[index]);
            } else if (source[index] === "}" || source[index] === "]") {
                stack.pop();
            }
            index += 1;
        }
        return index;
    }
    let index = start;
    while (index < source.length && !/[\s,}\]]/.test(source[index])) {
        index += 1;
    }
    return index;
}

function skipWhitespace(source, start) {
    let index = start;
    while (index < source.length && /\s/.test(source[index])) {
        index += 1;
    }
    return index;
}

function findEntryRawValue(entries, key, aliases) {
    for (const candidate of [key, ...aliases]) {
        for (let index = entries.length - 1; index >= 0; index -= 1) {
            if (entries[index].key === candidate) {
                return entries[index].rawValue;
            }
        }
    }
    return undefined;
}

function findFieldRawValue(entries, field, aliases) {
    const canonical = findEntryRawValue(entries, field.key, []);
    if (
        aliases.length === 0 ||
        (canonical !== undefined && !fieldValueFallsBackToAlias(field, decodeRawValue(canonical)))
    ) {
        return canonical;
    }
    const alias = findEntryRawValue(entries, aliases[0], aliases.slice(1));
    return alias === undefined ? canonical : alias;
}

function fieldValueFallsBackToAlias(field, value) {
    if (value === undefined || value === null) {
        return true;
    }
    if (field.type === "integer" || field.type === "selectNumber") {
        const normalized = integerRawValue(value);
        return normalized === "0";
    }
    return typeof value === "string" && value.trim() === "";
}

function decodeRawValue(rawValue) {
    if (rawValue === undefined) {
        return undefined;
    }
    const value = String(rawValue).trim();
    if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value)) {
        return value;
    }
    return JSON.parse(value);
}

function replaceObjectEntry(entries, key, aliases, rawValue) {
    const replacedKeys = new Set([key, ...aliases]);
    const firstIndex = entries.findIndex((entry) => replacedKeys.has(entry.key));
    const next = entries.filter((entry) => !replacedKeys.has(entry.key));
    if (rawValue === undefined || rawValue === null || rawValue === "") {
        return next;
    }
    next.splice(firstIndex < 0 ? next.length : Math.min(firstIndex, next.length), 0, { key, rawValue });
    return next;
}

function serializeObjectEntries(entries, pretty = false) {
    if (!pretty) {
        return `{${entries.map((entry) => `${JSON.stringify(entry.key)}:${entry.rawValue}`).join(",")}}`;
    }
    if (entries.length === 0) {
        return "{}";
    }
    const body = entries
        .map((entry) => `  ${JSON.stringify(entry.key)}: ${String(entry.rawValue).replace(/\n/g, "\n  ")}`)
        .join(",\n");
    return `{\n${body}\n}`;
}

export function formatAdapterConfigJson(raw) {
    const entries = parseObjectEntries(raw);
    if (!entries) {
        throw new Error("适配器高级配置必须是 JSON 对象");
    }
    return serializeObjectEntries(entries, true);
}

function integerRawValue(value) {
    const text = String(value).trim();
    if (!/^-?(?:0|[1-9]\d*)$/.test(text)) {
        return "";
    }
    try {
        return BigInt(text).toString();
    } catch {
        return "";
    }
}

function positiveIntegerRawValue(value) {
    const normalized = integerRawValue(value);
    if (!normalized) {
        return "";
    }
    return BigInt(normalized) > 0n ? normalized : "";
}

function readRawObjectMap(entries, key) {
    const nestedEntries = parseObjectEntries(findEntryRawValue(entries, key, []) || "{}");
    const value = Object.create(null);
    for (const entry of nestedEntries || []) {
        value[entry.key] = decodeRawValue(entry.rawValue);
    }
    return value;
}

function stringValue(value) {
    return value === undefined || value === null ? "" : String(value);
}

function numberInputValue(value) {
    return positiveIntegerRawValue(value);
}

function assignTrimmed(target, key, value) {
    const normalized = String(value || "").trim();
    if (normalized) {
        target.set(key, normalized);
    }
}

function serializeStringMap(value) {
    if (value.size === 0) {
        return undefined;
    }
    return `{${[...value].map(([key, item]) => `${JSON.stringify(key)}:${JSON.stringify(item)}`).join(",")}}`;
}

function serializeRawMap(value) {
    if (value.size === 0) {
        return undefined;
    }
    return `{${[...value].map(([key, item]) => `${JSON.stringify(key)}:${item}`).join(",")}}`;
}
