/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

const PREFIX = "inference";
const METRICS = [
  `${PREFIX}-${PIPELINE_READY_LATENCY}`,
  `${PREFIX}-${INITIALIZATION_LATENCY}`,
  `${PREFIX}-${MODEL_RUN_LATENCY}`,
  `${PREFIX}-${TOTAL_MEMORY_USAGE}`,
];
const journal = {};
for (let metric of METRICS) {
  journal[metric] = [1];
}

const perfMetadata = {
  owner: "GenAI Team",
  name: "browser_ml_suggest_inference.js",
  description: "Template test for ML suggest Inference Model",
  options: {
    default: {
      perfherder: true,
      perfherder_metrics: [
        {
          name: "inference-pipeline-ready-latency",
          unit: "ms",
          shouldAlert: false,
        },
        {
          name: "inference-initialization-latency",
          unit: "ms",
          shouldAlert: false,
        },
        {
          name: "inference-model-run-latency",
          unit: "ms",
          shouldAlert: false,
        },
        {
          name: "inference-total-memory-usage",
          unit: "ms",
          shouldAlert: false,
        },
      ],
      verbose: true,
      manifest: "perftest.toml",
      manifest_flavor: "browser-chrome",
      try_platform: ["linux", "mac", "win"],
    },
  },
};

requestLongerTimeout(10);

const CUSTOM_INTENT_OPTIONS = {
  taskName: "text-classification",
  featureId: "suggest-intent-classification",
  engineId: "ml-suggest-intent",
  modelId: "Mozilla/mobilebert-uncased-finetuned-LoRA-intent-classifier",
  modelHubUrlTemplate: "{model}/{revision}",
  dtype: "q8",
  modelRevision: "main",
  numThreads: 2,
  timeoutMS: -1,
};

const CUSTOM_NER_OPTIONS = {
  taskName: "token-classification",
  featureId: "suggest-NER",
  engineId: "ml-suggest-ner",
  modelId: "Mozilla/distilbert-uncased-NER-LoRA",
  modelHubUrlTemplate: "{model}/{revision}",
  dtype: "q8",
  modelRevision: "main",
  numThreads: 2,
  timeoutMS: -1,
};

const ROOT_URL =
  "chrome://mochitests/content/browser/toolkit/components/ml/tests/browser/data/suggest";
const YELP_KEYWORDS_DATA = `${ROOT_URL}/yelp_val_keywords_data.json`;
const YELP_VAL_DATA = `${ROOT_URL}/yelp_val_generated_data.json`;
const NER_VAL_DATA = `${ROOT_URL}/named_entity_val_generated_data.json`;

async function get_val_data(inputDataPath) {
  const response = await fetch(inputDataPath);
  if (!response.ok) {
    throw new Error(
      `Failed to fetch data: ${response.statusText} from ${inputDataPath}`
    );
  }
  return response.json();
}

async function read_data_by_type(type) {
  let data;
  if (type === "YELP_KEYWORDS_DATA") {
    data = await get_val_data(YELP_KEYWORDS_DATA);
    return data[0].subjects;
  } else if (type === "YELP_VAL_DATA") {
    data = await get_val_data(YELP_VAL_DATA);
    return data.queries;
  } else if (type === "NER_VAL_DATA") {
    data = await get_val_data(NER_VAL_DATA);
    return data.queries;
  }
  return [];
}

// Utility to write results to a local JSON file using IOUtils
async function writeResultsToFile(results, type) {
  try {
    const json = JSON.stringify(results, null, 2);
    const OUTPUT_FILE_PATH = normalizePathForOS(
      `${
        Services.dirsvc.get("DfltDwnld", Ci.nsIFile).path
      }/ML_output_${type}.json`
    );
    await IOUtils.writeUTF8(OUTPUT_FILE_PATH, json);
    console.log("Results successfully written to:", OUTPUT_FILE_PATH);
  } catch (error) {
    console.error("Failed to write results to file:", error);
    Assert.ok(false, "Failed to write results to file");
  }
}

async function perform_inference(queries, type) {
  // Ensure MLSuggest is initialized
  await MLSuggest.initialize();
  const results = [];
  for (const query of queries) {
    const suggestion = await MLSuggest.makeSuggestions(query);
    const res = {
      query,
      intent: suggestion.intent,
      city: suggestion.location.city,
      state: suggestion.location.state,
    };
    results.push(res);
  }

  Assert.strictEqual(
    results.length,
    queries.length,
    "results size should be equal to queries size."
  );
  // Write results to a file
  await writeResultsToFile(results, type);
}

const runInference2 = async () => {
  ChromeUtils.defineESModuleGetters(this, {
    MLSuggest: "moz-src:///browser/components/urlbar/private/MLSuggest.sys.mjs",
  });

  // Override INTENT and NER options within MLSuggest
  MLSuggest.INTENT_OPTIONS = CUSTOM_INTENT_OPTIONS;
  MLSuggest.NER_OPTIONS = CUSTOM_NER_OPTIONS;

  const modelHubRootUrl = Services.env.get("MOZ_MODELS_HUB");
  if (!modelHubRootUrl) {
    throw new Error(
      "MOZ_MODELS_HUB is not set, you need to run with --hooks toolkit/components/ml/tests/tools/hook_local_hub.py"
    );
  }
  info(`ModelHubRootUrl: ${modelHubRootUrl}`);
  const { cleanup } = await perfSetup({
    prefs: [
      ["browser.ml.modelHubRootUrl", modelHubRootUrl],
      ["javascript.options.wasm_lazy_tiering", true],
    ],
  });

  const TYPES = ["YELP_KEYWORDS_DATA", "YELP_VAL_DATA", "NER_VAL_DATA"];
  for (const type of TYPES) {
    info(`processing ${type} now`);
    // Read data for the current type
    const queries = await read_data_by_type(type);
    if (!queries) {
      info(`No queries found for type: ${type}`);
      continue;
    }
    // Run inference for each query
    await perform_inference(queries, type);
  }
  await MLSuggest.shutdown();
  await EngineProcess.destroyMLEngine();
  await cleanup();
};

/**
 * Tests remote ml model
 */
add_task(async function test_ml_generic_pipeline() {
  await runInference2();
  reportMetrics(journal);
});
