import type { GenerationAdapterRequest, NormalizedGenerationOutput } from "./ai-adapter-contract.js";
import { normalizeImageOutputToRatio } from "./image-output-normalizer.mjs";
import {
  AdapterContractError, type AdapterStartResult, type AdapterTransport, balanceSignalFromResponse,
  classifyAdapterError, dimensionsForRatio, mockPngBytes, validateAdapterRequest, validateMockContract,
  type ModelAdapter, supportedAdapterRatios,
} from "./model-adapter.js";

const expectedContract = {
  endpoint: "https://mock.invalid/gemini/pro",
  fixture_id: "gemini-pro-mock-v1",
  mode: "async",
  protocol_version: "gemini-pro-v1",
  response_shape: "operation.poll.candidates",
  supports_reference_images: true,
  supported_ratios: [...supportedAdapterRatios],
};

function defaultTransport(): AdapterTransport {
  return {
    async start(request) {
      const dimensions = dimensionsForRatio(request.ratio ?? "3:4");
      return { operation: { done: true, response: { candidates: [{ inline_data: { data: mockPngBytes("pro").toString("base64"), mime_type: "image/png" }, ...dimensions }] } } };
    },
    async poll() { throw new AdapterContractError("unexpected_poll"); },
  };
}

export class GeminiProAdapter implements ModelAdapter {
  readonly contractVersion = "gemini-pro-v1";
  readonly fixtureId = "gemini-pro-mock-v1";
  readonly modelId = "gemini-3-pro-image-preview";
  private readonly transport: AdapterTransport;

  constructor(input: { transport?: AdapterTransport } = {}) { this.transport = input.transport ?? defaultTransport(); }

  async start(request: GenerationAdapterRequest): Promise<AdapterStartResult> {
    try {
      validateAdapterRequest(request, this.modelId);
      const response = await this.transport.start({ operation: "start", modelId: this.modelId, prompt: request.prompt, ratio: request.ratio, referenceAssetIds: request.referenceAssetIds });
      return await this.interpret(response, request.configSnapshot.error_mapping_profile as Record<string, string> ?? {}, request.ratio);
    } catch (error) {
      const classified = error instanceof AdapterContractError
        ? { category: "gateway_contract_invalid" as const, sourceCategory: error.sourceCategory }
        : this.classifyError(error, {});
      return { ...classified, status: "failed" };
    }
  }

  async poll(upstreamJobReference: string): Promise<AdapterStartResult> {
    try {
      const response = await this.transport.poll({ operation: "poll", modelId: this.modelId, upstreamJobReference });
      return await this.interpret(response, {});
    } catch (error) {
      const classified = error instanceof AdapterContractError
        ? { category: "gateway_contract_invalid" as const, sourceCategory: error.sourceCategory }
        : this.classifyError(error, {});
      return { ...classified, status: "failed" };
    }
  }

  private async interpret(response: unknown, mappingProfile: Readonly<Record<string, string>>, ratio?: GenerationAdapterRequest["ratio"]): Promise<AdapterStartResult> {
    if (!response || typeof response !== "object" || !("operation" in response) || !response.operation || typeof response.operation !== "object") {
      return { category: "gateway_contract_invalid", sourceCategory: "response_shape_invalid", status: "failed" };
    }
    if ("status" in response && response.status === "failed") {
      const classified = this.classifyError("error" in response ? response.error : undefined, mappingProfile);
      return { ...classified, status: "failed" };
    }
    const operation = response.operation;
    if ("done" in operation && operation.done === false) {
      const reference = "name" in operation && typeof operation.name === "string" ? operation.name : undefined;
      return reference ? { status: "pending", upstreamJobReference: reference } : { category: "gateway_contract_invalid", sourceCategory: "upstream_reference_missing", status: "failed" };
    }
    try {
      const output = this.normalizeOutput("response" in operation ? operation.response : undefined);
      return { outputs: [ratio ? await normalizeImageOutputToRatio({ ...output, ratio }) : output], status: "completed" };
    } catch (error) {
      return { category: "gateway_contract_invalid", sourceCategory: error instanceof AdapterContractError ? error.sourceCategory : "response_shape_invalid", status: "failed" };
    }
  }

  normalizeOutput(response: unknown): NormalizedGenerationOutput {
    if (!response || typeof response !== "object" || !("candidates" in response) || !Array.isArray(response.candidates) || response.candidates.length !== 1) throw new AdapterContractError("response_single_image_required");
    const candidate = response.candidates[0];
    if (!candidate || typeof candidate !== "object" || !("inline_data" in candidate) || !candidate.inline_data || typeof candidate.inline_data !== "object") throw new AdapterContractError("response_shape_invalid");
    const data = "data" in candidate.inline_data && typeof candidate.inline_data.data === "string" ? Buffer.from(candidate.inline_data.data, "base64") : undefined;
    const mimeType = "mime_type" in candidate.inline_data && candidate.inline_data.mime_type === "image/png" ? "image/png" : undefined;
    const pixelHeight = "pixelHeight" in candidate && typeof candidate.pixelHeight === "number" ? candidate.pixelHeight : undefined;
    const pixelWidth = "pixelWidth" in candidate && typeof candidate.pixelWidth === "number" ? candidate.pixelWidth : undefined;
    if (!data || !data.length || !mimeType || !pixelHeight || !pixelWidth) throw new AdapterContractError("response_media_invalid");
    return { bytes: data, mimeType, pixelHeight, pixelWidth };
  }

  classifyError(error: unknown, mappingProfile: Readonly<Record<string, string>>) { return classifyAdapterError(error, mappingProfile); }
  checkBalanceSignal(response: unknown) { return balanceSignalFromResponse(response); }
  validateContract(routeProfile: Readonly<Record<string, unknown>>) { return validateMockContract(routeProfile, expectedContract, this.modelId, this.fixtureId, this.contractVersion); }
}

export { expectedContract as geminiProMockContract };
