import { env } from "../../config/env.js";

/**
 * Thin fetch wrapper around `services/tech-step-intent-service`'s HTTP
 * contract (`POST /v1/process`) — the microservice
 * {@link TechStepClassifierService} (`tech-step-matcher.ts`) delegates NER +
 * intent classification to, in place of the `node-nlp` `NlpManager` it used
 * to own directly. See that service's own README for the full contract and
 * why it never touches Postgres itself — it also owns its own training
 * corpus now (`training_data.py`), trained once at its own startup, so
 * `apps/api` never pushes anything to it; `process()` below is this
 * client's only method.
 *
 * Authenticated with `INTENT_SERVICE_SECRET` — the inverse direction of
 * `requireInternalWorker`'s `INTERNAL_WORKER_SECRET` (this time `apps/api`
 * is the caller, not the callee), but the same "one flat shared secret"
 * shape.
 */

/** One candidate mention one of the service's two `PhraseMatcher`s found — offsets `[start, end)`, same convention as `String.prototype.slice`. Mirrors `EntityPayload` (Python `schemas.py`). `kind` distinguishes a technique mention (`self._matcher`, the corpus-trained one) from a utensil mention (`self._utensil_matcher`, static — see `utensil_vocabulary.py`) — `tech-step-matcher.ts` resolves each against a different catalog (`TechStep`/`Utensil`). */
export interface IntentServiceEntity {
  uid: string;
  start: number;
  end: number;
  kind: "technique" | "utensil";
}

/** The full result of a `POST /v1/process` call — mirrors `ProcessResponse` (Python `schemas.py`). `intent` is `null` only when `locale` isn't one this service trains for, or `text` is blank; otherwise always a real `uid` (the Python service's `textcat` has no "None" sentinel, unlike node-nlp — see that service's README). */
export interface IntentServiceProcessResult {
  entities: IntentServiceEntity[];
  intent: string | null;
  score: number;
}

/**
 * Client for `services/tech-step-intent-service` — a real class (not a
 * plain object of functions) per this repo's service-style-logic
 * convention, even though it holds no state of its own: it's used as the
 * one shared {@link intentServiceClient} singleton below, same reasoning as
 * `TechStepClassifierService` itself.
 */
export class IntentServiceClient {
  /**
   * Performs a JSON request against the intent service and returns the
   * parsed body.
   *
   * @throws {Error} if the response status is not in the 2xx range, or the
   *   request itself fails (network error, service down) — left as a plain
   *   `Error` rather than a typed `HttpError`: this is an internal
   *   service-to-service call, not a request `apps/api`'s own HTTP layer
   *   needs to map to a client-facing status code (see
   *   `TechStepClassifierService.warmUp`'s retry in `server.ts` for how a
   *   failure here is actually handled).
   */
  private async _request<TResponseBody>(
    path: string,
    init: RequestInit = {},
  ): Promise<TResponseBody> {
    try {
      const response = await fetch(`${env.INTENT_SERVICE_BASE_URL}${path}`, {
        ...init,
        headers: {
          "Content-Type": "application/json",
          "X-Intent-Service-Secret": env.INTENT_SERVICE_SECRET,
          ...init.headers,
        },
      });
      if (!response.ok) {
        const body = await response.text().catch(() => "");
        throw new Error(`${init.method ?? "GET"} ${path} failed: ${response.status} ${body}`);
      }
      return (await response.json()) as TResponseBody;
    } catch (err) {
      // Rethrown as-is — every caller (`TechStepClassifierService`) already
      // wraps its own `await`s per the repo's try/catch convention; this is
      // just where the `await` itself has to sit inside one.
      throw err;
    }
  }

  /**
   * Equivalent to the old `NlpManager.process(locale, text)` — returns every
   * candidate technique mention (NER) plus the intent classifier's verdict
   * for `text` as a whole, whether `text` is a full step description or a
   * single clause `TechStepClassifierService` already cut out of one (this
   * service doesn't know or care which, exactly like `NlpManager` before
   * it).
   */
  public async process(locale: string, text: string): Promise<IntentServiceProcessResult> {
    try {
      return await this._request("/v1/process", {
        method: "POST",
        body: JSON.stringify({ locale, text }),
      });
    } catch (err) {
      throw err;
    }
  }
}

/** Single shared instance — stateless, no reason for more than one (same reasoning as `techStepClassifier`/`prisma`). */
export const intentServiceClient = new IntentServiceClient();
