// Copyright 2026 Cisco Systems, Inc. and its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: Apache-2.0

package gateway

import (
	"encoding/json"
	"net/http"
)

// FormatAdapter owns the wire-format-specific translations for a given
// LLM chat API (e.g. OpenAI Chat Completions, Responses API, Anthropic
// Messages, Gemini generateContent, Bedrock Converse, Ollama native chat).
//
// Adapters are stateless, safe for concurrent use, and selected by the
// package-level adapterRegistry via Matches(path, provider). First-match
// wins, so the registry is ordered from most-specific to least-specific.
//
// Contract:
//   - InjectSystem prepends the notification-queue system message using
//     the wire format's canonical system-prompt slot. It returns a new
//     body; the input is not mutated.
//   - LaunderHistory strips prior synthetic assistant turns that were
//     generated by DefenseClaw (detected by the defenseClawBlockBanner
//     prefix and/or defenseClawBlockIDPrefix) from the conversation
//     history. It returns the new body and the count of stripped turns.
//   - WriteBlockResponse writes the full block response envelope (status,
//     headers, body). The caller must not touch w before calling it.
//
// Concurrency: adapterRegistry is populated once at package init and is
// read-only thereafter. Adapters themselves hold no mutable state.
type FormatAdapter interface {
	// Name is a short stable identifier (e.g. "openai-chat"). Used in
	// log lines, error strings, and the "unknown adapter" fallback.
	Name() string
	// InjectionSite is the stable, human-readable label written to
	// gateway.jsonl when InjectSystem succeeds (e.g.
	// "chat-completions/messages", "responses-api/instructions",
	// "anthropic/system"). Kept separate from Name so we can evolve
	// internal adapter names without breaking downstream log consumers
	// or regression tests.
	InjectionSite() string
	Matches(path, provider string) bool
	InjectSystem(raw json.RawMessage, content string) (json.RawMessage, error)
	LaunderHistory(raw json.RawMessage) (json.RawMessage, int, error)
	WriteBlockResponse(p *GuardrailProxy, w http.ResponseWriter, path, model string, stream bool, msg string)
}

// adapterRegistry is the ordered list of registered format adapters.
// Built by buildAdapterRegistry() at init time; read-only afterwards.
// First-match-wins — the order inside buildAdapterRegistry is the
// authoritative dispatch priority.
var adapterRegistry = buildAdapterRegistry()

// buildAdapterRegistry returns the canonical, ordered list of built-in
// FormatAdapters. Future phases add their adapters here in match-priority
// order (most specific → least specific); the OpenAI Chat Completions
// adapter is the fallback and must be last.
func buildAdapterRegistry() []FormatAdapter {
	// IMPORTANT: order == priority. Add new adapters ABOVE openaiChat.
	// The Chat Completions adapter is the catch-all for any OpenAI-
	// compatible shape (LiteLLM-fronted Mistral/Groq/Cohere/DeepSeek/
	// Perplexity/Together/xAI, vLLM, and most self-hosted endpoints).
	return []FormatAdapter{
		openaiResponsesAdapter{}, // /responses (most specific)
		anthropicAdapter{},       // /messages → top-level system, not messages[]
		geminiAdapter{},          // :generateContent / :streamGenerateContent
		bedrockConverseAdapter{}, // /model/.../converse(-stream) — Converse only
		ollamaAdapter{},          // /api/chat — native Ollama (LiteLLM-fronted lands on openai-chat)
		openaiChatAdapter{},      // catch-all: /chat/completions + OpenAI-compatible
	}
}

// adapterFor returns the first adapter whose Matches(path, provider)
// returns true. Returns nil when no adapter claims the request; callers
// should treat that as an unsupported format and either skip the
// operation or fall back to legacy provider-specific helpers.
func adapterFor(path, provider string) FormatAdapter {
	for _, a := range adapterRegistry {
		if a.Matches(path, provider) {
			return a
		}
	}
	return nil
}
