function defineStructure() {
    addColumn("Code");
    addColumn("Description");
    addColumn("codigoDescricao");
    addColumn("descricao");
    addColumn("sku");
    addColumn("brand");
    addColumn("ncmId");
    addColumn("categoria");
}

function onSync(lastSyncDate) {}

var GB_TOKEN_URL = "https://api.grupoboticario.com.br/global/v2/jwt-token/token";
var GB_STRATEGICS_URL = "https://api.grupoboticario.com.br/global/v1/franchising/gb-stores-data/product/classification/strategics";
var DEFAULT_GB_CLIENT_ID = "88ymKwAUNfu06sD85i0RiokCxWGSkFBkx9ytgI5y1ZKxX3OQ";
var DEFAULT_GB_CLIENT_SECRET = "YDFz43qAzL6ApNIKVCxu3dAmS9GWOqJbcc2aPnFDkmEaBXexSpsHGfcItg56i2dE";

function createDataset(fields, constraints, sortFields) {
    var dataset = DatasetBuilder.newDataset();
    dataset.addColumn("Code");
    dataset.addColumn("Description");
    dataset.addColumn("codigoDescricao");
    dataset.addColumn("descricao");
    dataset.addColumn("sku");
    dataset.addColumn("brand");
    dataset.addColumn("ncmId");
    dataset.addColumn("categoria");

    try {
        var filtro = parseConstraints(constraints);
        var clientService = fluigAPI.getAuthorizeClientService();
        var data = {
            companyId: String(getValue("WKCompany") || "1"),
            serviceCode: "GinsengAPI2",
            endpoint: "/dados_rgb_products",
            method: "get",
            timeoutService: "60000",
            params: {}
        };

        var vo = clientService.invoke(JSON.stringify(data));
        var statusHttp = vo ? String(vo.getHttpStatusResult() || "") : "";
        var retorno = vo ? String(vo.getResult() || "") : "";

        // Fallback: se o servico integrado estiver com endpoint/base incorreto, chama URL direta.
        if (statusHttp !== "200" || !retorno) {
            var direct = fetchDirect("https://api.grupoginseng.com.br/dados_rgb_products", 60000);
            statusHttp = String(direct.status || statusHttp);
            retorno = direct.body || retorno;
        }

        if (!retorno) {
            addDiagRow(dataset, "Sem retorno da API (HTTP " + statusHttp + ")");
            return dataset;
        }

        var obj = parseJsonSafe(retorno);
        if (!obj) {
            addDiagRow(dataset, "Falha no JSON da API (HTTP " + statusHttp + ")");
            return dataset;
        }

        var itens = extractItems(obj);
        if (!itens || !itens.length) {
            addDiagRow(dataset, "API sem itens (HTTP " + statusHttp + ")");
            return dataset;
        }

        var searchNorm = normalize(filtro.search);
        var maxRows = filtro.maxRows;
        var categoryMap = null;
        var categoryDiag = "";

        for (var i = 0; i < itens.length; i++) {
            var item = itens[i] || {};

            if (item.discontinued === true) {
                continue;
            }
            if (item.purchaseBlocked === true) {
                continue;
            }

            var code = trim(item.sku);
            var desc = trim(item.description);
            var codigoDescricao = code + " - " + desc;
            var brand = trim(item.brand);
            var categoria = trim(item.strategicDescription || item.strategic_description || item.categoria);
            if (!categoria) {
                if (categoryMap == null) {
                    var categorization = loadStrategicCategoryMap(constraints, 30000);
                    categoryMap = categorization.map || {};
                    categoryDiag = categorization.diag || "";
                }
                var strategicId = resolveStrategicId(item, code);
                categoria = resolveCategory(categoryMap, strategicId);
            }
            var blob = normalize([code, desc, brand, trim(item.id), categoria].join(" "));

            if (searchNorm && searchNorm.length >= 2 && blob.indexOf(searchNorm) === -1) {
                continue;
            }

            dataset.addRow([
                code,
                desc,
                codigoDescricao,
                desc,
                code,
                brand,
                trim(item.ncmId),
                categoria
            ]);

            if (maxRows > 0 && dataset.rowsCount >= maxRows) {
                break;
            }
        }

        if (dataset.rowsCount === 0) {
            var suffix = categoryDiag ? " | Categoria: " + categoryDiag : "";
            addDiagRow(dataset, "Sem produtos apos filtros (busca: " + filtro.search + ")" + suffix);
        }

    } catch (e) {
        addDiagRow(dataset, "Erro ao consultar API: " + e);
    }

    return dataset;
}

function onMobileSync(user) {}

function parseConstraints(constraints) {
    var out = {
        search: "",
        maxRows: 0
    };

    if (!constraints) {
        return out;
    }

    for (var i = 0; i < constraints.length; i++) {
        var c = constraints[i];
        if (!c || !c.fieldName) continue;

        var name = String(c.fieldName);
        var lowerName = name.toLowerCase();
        var value = cleanSearchValue(c.initialValue);

        if (lowerName === "sqllimit") {
            var n = parseInt(trim(c.initialValue), 10);
            if (!isNaN(n) && n > 0) {
                out.maxRows = n;
            }
            continue;
        }

        if (
            value &&
            lowerName !== "metadata#id" &&
            lowerName !== "metadata#active" &&
            lowerName !== "sqllimit" &&
            lowerName !== "gbclientid" &&
            lowerName !== "gbclientsecret" &&
            lowerName !== "clientid" &&
            lowerName !== "clientsecret"
        ) {
            if (!out.search || value.length > out.search.length) {
                out.search = value;
            }
        }
    }

    return out;
}

function cleanSearchValue(v) {
    var s = trim(v);
    s = s.replace(/[%*_]/g, "");
    // Evita que flags comuns do Fluig virem termo de busca
    if (s === "true" || s === "false" || s === "on" || s === "off" || s === "1") {
        return "";
    }
    return trim(s);
}

function normalize(v) {
    return trim(v).toLowerCase();
}

function trim(v) {
    return String(v == null ? "" : v).trim();
}

function parseJsonSafe(text) {
    try {
        return JSON.parse(text);
    } catch (e) {
        return null;
    }
}

function extractItems(obj) {
    if (!obj) return [];
    if (obj instanceof Array) return obj;
    if (obj.data && obj.data instanceof Array) return obj.data;

    if (obj.content) {
        if (obj.content instanceof Array) return obj.content;
        if (typeof obj.content === "string") {
            var parsedContent = parseJsonSafe(obj.content);
            if (parsedContent && parsedContent.data && parsedContent.data instanceof Array) {
                return parsedContent.data;
            }
            if (parsedContent && parsedContent instanceof Array) {
                return parsedContent;
            }
        }
        if (obj.content.data && obj.content.data instanceof Array) return obj.content.data;
    }

    if (obj.result) {
        if (obj.result instanceof Array) return obj.result;
        if (typeof obj.result === "string") {
            var parsedResult = parseJsonSafe(obj.result);
            if (parsedResult && parsedResult.data && parsedResult.data instanceof Array) {
                return parsedResult.data;
            }
        }
    }

    return [];
}

function getConstraintValue(constraints, fieldName) {
    if (!constraints || !fieldName) return "";
    var target = String(fieldName).toLowerCase();
    for (var i = 0; i < constraints.length; i++) {
        var c = constraints[i];
        if (!c || !c.fieldName) continue;
        if (String(c.fieldName).toLowerCase() === target) {
            return c.initialValue;
        }
    }
    return "";
}

function resolveGbCredentials(constraints) {
    var clientId = trim(getConstraintValue(constraints, "gbClientId"));
    if (!clientId) clientId = trim(getConstraintValue(constraints, "clientId"));
    if (!clientId) {
        try { clientId = trim(java.lang.System.getenv("GB_CLIENT_ID")); } catch (e1) {}
    }
    if (!clientId) clientId = DEFAULT_GB_CLIENT_ID;

    var clientSecret = trim(getConstraintValue(constraints, "gbClientSecret"));
    if (!clientSecret) clientSecret = trim(getConstraintValue(constraints, "clientSecret"));
    if (!clientSecret) {
        try { clientSecret = trim(java.lang.System.getenv("GB_CLIENT_SECRET")); } catch (e2) {}
    }
    if (!clientSecret) clientSecret = DEFAULT_GB_CLIENT_SECRET;

    return {
        clientId: clientId,
        clientSecret: clientSecret
    };
}

function loadStrategicCategoryMap(constraints, timeoutMs) {
    var out = { map: {}, diag: "" };

    var creds = resolveGbCredentials(constraints);
    if (!creds.clientId || !creds.clientSecret) {
        out.diag = "credenciais ausentes";
        return out;
    }

    var tokenResp = requestGbToken(creds, timeoutMs || 30000);
    if (!tokenResp.token) {
        out.diag = tokenResp.diag || "falha ao obter token";
        return out;
    }

    var strategicsResp = requestStrategics(tokenResp.token, timeoutMs || 30000);
    var strategics = strategicsResp.items || [];
    if (!strategics.length) {
        out.diag = strategicsResp.diag || "sem classificacoes";
        return out;
    }

    for (var i = 0; i < strategics.length; i++) {
        var item = strategics[i] || {};
        var id = trim(item.strategicId || item.strategicID || item.id || item.code);
        var descricao = trim(item.description || item.descricao || item.category || item.name);
        if (!id || !descricao) continue;
        out.map[id] = descricao;
    }

    if (!hasOwnKeys(out.map)) {
        out.diag = "classificacoes sem strategicId/description";
    }

    return out;
}

function requestGbToken(creds, timeoutMs) {
    var timeout = timeoutMs || 30000;
    var body = "client_id=" + urlEncode(creds.clientId) +
        "&client_secret=" + urlEncode(creds.clientSecret);

    var attempts = [
        {
            name: "post-query-body",
            method: "POST",
            url: GB_TOKEN_URL + "?grant_type=client_credentials",
            headers: {
                "Accept": "application/json",
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: body
        }
    ];

    var trace = [];
    for (var i = 0; i < attempts.length; i++) {
        var attempt = attempts[i];
        var resp = httpRequest(attempt.url, attempt.method, attempt.headers, attempt.body, timeout);
        trace.push(attempt.name + ":" + trim(resp.status));
        var parsed = normalizeApiBody(resp.body);
        var token = trim(parsed && (parsed.access_token || parsed.accessToken || parsed.token));
        if (token) {
            return { token: token, diag: "token ok (" + attempt.name + ")" };
        }
    }

    return { token: "", diag: "token falhou [" + trace.join(" | ") + "]" };
}

function requestStrategics(accessToken, timeoutMs) {
    var resp = httpRequest(
        GB_STRATEGICS_URL,
        "GET",
        {
            "Accept": "application/json",
            "Authorization": "Bearer " + trim(accessToken)
        },
        "",
        timeoutMs || 30000
    );

    var parsed = normalizeApiBody(resp.body);
    var items = extractItems(parsed);
    if ((!items || !items.length) && parsed && parsed.items instanceof Array) {
        items = parsed.items;
    }
    if ((!items || !items.length) && parsed && parsed.strategics instanceof Array) {
        items = parsed.strategics;
    }

    if (!items || !items.length) {
        return { items: [], diag: "strategics HTTP " + trim(resp.status) };
    }

    return { items: items, diag: "ok" };
}

function resolveStrategicId(item, fallbackCode) {
    var candidates = [
        item && item.strategicId,
        item && item.strategicID,
        item && item.classificationStrategicId,
        item && item.productStrategicId,
        item && item.estrategicId,
        item && item.strategyId,
        item && item.code,
        fallbackCode
    ];

    for (var i = 0; i < candidates.length; i++) {
        var value = trim(candidates[i]);
        if (value) return value;
    }

    return "";
}

function resolveCategory(map, strategicId) {
    var id = trim(strategicId);
    if (!id || !map) return "";

    if (map.hasOwnProperty(id)) {
        return trim(map[id]);
    }

    var asInt = parseInt(id, 10);
    if (!isNaN(asInt)) {
        var normalized = String(asInt);
        if (map.hasOwnProperty(normalized)) {
            return trim(map[normalized]);
        }
    }

    return "";
}

function normalizeApiBody(text) {
    var obj = parseJsonSafe(text);
    if (!obj) return null;

    if (obj.content && typeof obj.content === "string") {
        var parsedContent = parseJsonSafe(obj.content);
        if (parsedContent) return parsedContent;
    }

    if (obj.result && typeof obj.result === "string") {
        var parsedResult = parseJsonSafe(obj.result);
        if (parsedResult) return parsedResult;
    }

    return obj;
}

function hasOwnKeys(obj) {
    for (var k in obj) {
        if (obj.hasOwnProperty(k)) return true;
    }
    return false;
}

function urlEncode(value) {
    return String(java.net.URLEncoder.encode(String(value || ""), "UTF-8"));
}

function addDiagRow(dataset, msg) {
    dataset.addRow([
        "DEBUG",
        String(msg),
        String(msg),
        String(msg),
        "",
        "",
        "",
        ""
    ]);
}

function fetchDirect(url, timeoutMs) {
    return httpRequest(
        url,
        "GET",
        { "Accept": "application/json" },
        "",
        timeoutMs || 30000
    );
}

function httpRequest(url, method, headers, body, timeoutMs) {
    var conn = null;
    var reader = null;
    var writer = null;
    try {
        var URL = java.net.URL;
        var InputStreamReader = java.io.InputStreamReader;
        var BufferedReader = java.io.BufferedReader;
        var OutputStreamWriter = java.io.OutputStreamWriter;
        var StringBuilder = java.lang.StringBuilder;

        var httpMethod = String(method || "GET").toUpperCase();
        var timeout = timeoutMs || 30000;
        var payload = body == null ? "" : String(body);

        conn = new URL(url).openConnection();
        conn.setRequestMethod(httpMethod);
        conn.setConnectTimeout(timeout);
        conn.setReadTimeout(timeout);

        if (headers) {
            for (var h in headers) {
                if (!headers.hasOwnProperty(h)) continue;
                if (headers[h] == null || headers[h] === "") continue;
                conn.setRequestProperty(String(h), String(headers[h]));
            }
        }

        if (payload && (httpMethod === "POST" || httpMethod === "PUT" || httpMethod === "PATCH")) {
            conn.setDoOutput(true);
            writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            writer.write(payload);
            writer.flush();
        }

        var statusCode = conn.getResponseCode();
        var status = String(statusCode);
        var stream = (statusCode >= 200 && statusCode < 300) ? conn.getInputStream() : conn.getErrorStream();
        if (stream == null) {
            return { status: status, body: "" };
        }

        reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
        var sb = new StringBuilder();
        var line = null;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }

        return { status: status, body: String(sb.toString()) };
    } catch (e) {
        return { status: "", body: "" };
    } finally {
        try { if (writer) writer.close(); } catch (e1) {}
        try { if (reader) reader.close(); } catch (e2) {}
        try { if (conn) conn.disconnect(); } catch (e3) {}
    }
}
