import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
import {
  intersectAllowlists,
  canonicalProviderId,
  isModelAllowedForProviderInEffective,
  isProviderAllowedInEffective,
  providerIdAliases
} from "./model-allowlist.js";
class AiModelFactoryError extends Error {
  constructor(code, message) {
    super(message);
    this.name = "AiModelFactoryError";
    this.code = code;
  }
}
function normalizeOverride(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}
function readGlobalProviderFromEnv(env, registry) {
  const candidates = [normalizeOverride(env.OM_AI_PROVIDER), normalizeOverride(env.OPENCODE_PROVIDER)];
  for (const candidate of candidates) {
    if (!candidate) continue;
    if (!registry.get) return providerIdAliases(candidate)[0] ?? candidate;
    for (const alias of providerIdAliases(candidate)) {
      if (registry.get(alias)) return alias;
    }
  }
  return null;
}
function readGlobalModelFromEnv(env) {
  return normalizeOverride(env.OM_AI_MODEL) ?? normalizeOverride(env.OPENCODE_MODEL);
}
function moduleModelEnvVarName(moduleId) {
  return `OM_AI_${moduleId.toUpperCase()}_MODEL`;
}
function legacyModuleModelEnvVarName(moduleId) {
  return `${moduleId.toUpperCase()}_AI_MODEL`;
}
function readModuleModelEnvOverride(env, moduleId) {
  return normalizeOverride(env[moduleModelEnvVarName(moduleId)]) ?? normalizeOverride(env[legacyModuleModelEnvVarName(moduleId)]);
}
function moduleProviderEnvVarName(moduleId) {
  return `OM_AI_${moduleId.toUpperCase()}_PROVIDER`;
}
function legacyModuleProviderEnvVarName(moduleId) {
  return `${moduleId.toUpperCase()}_AI_PROVIDER`;
}
function readModuleProviderEnvOverride(env, moduleId) {
  return normalizeOverride(env[moduleProviderEnvVarName(moduleId)]) ?? normalizeOverride(env[legacyModuleProviderEnvVarName(moduleId)]);
}
function normalizeProviderHint(providerId, registry) {
  if (!providerId) return null;
  const knownProviderIds = registry.list?.().map((provider) => provider.id) ?? [];
  if (knownProviderIds.length > 0) {
    return canonicalProviderId(providerId, knownProviderIds);
  }
  return providerIdAliases(providerId)[0] ?? providerId;
}
function resolveRequiredProvider(providerId, registry, env) {
  const resolved = registry.resolveFirstConfigured({ env, order: [providerId] });
  if (resolved?.id === providerId) return resolved;
  const direct = registry.get?.(providerId) ?? null;
  if (direct) return direct.isConfigured(env) ? direct : null;
  return null;
}
function requiredProviderMessage(providerId, registry, env) {
  const provider = registry.get?.(providerId) ?? null;
  const envKey = provider?.getConfiguredEnvKey?.(env);
  const credentialHint = envKey ? ` Set ${envKey} to use this provider.` : " Configure the matching provider API key to use this provider.";
  return `The resolved model is pinned to provider "${providerId}", but that provider is not configured.${credentialHint} The runtime refuses to send provider-specific model ids to a different provider.`;
}
function moduleBaseUrlEnvVarName(moduleId) {
  return `${moduleId.toUpperCase()}_AI_BASE_URL`;
}
function parseSlashShorthand(token, registry) {
  const slashIndex = token.indexOf("/");
  if (slashIndex < 0) return { providerHint: null, modelId: token };
  const before = token.slice(0, slashIndex);
  const after = token.slice(slashIndex + 1);
  if (!before || !after) return { providerHint: null, modelId: token };
  if (!registry.get) return { providerHint: null, modelId: token };
  const provider = registry.get(before);
  if (!provider) return { providerHint: null, modelId: token };
  return { providerHint: before, modelId: after };
}
function resolveAllowRuntimeOverride(input) {
  if (input.allowRuntimeOverride !== void 0) return input.allowRuntimeOverride !== false;
  if (input.allowRuntimeModelOverride !== void 0) return input.allowRuntimeModelOverride !== false;
  return true;
}
function createModelFactory(_container, deps = {}) {
  const registry = deps.registry ?? llmProviderRegistry;
  const env = deps.env ?? process.env;
  return {
    resolveModel(input) {
      const hasModule = typeof input.moduleId === "string" && input.moduleId.length > 0;
      const runtimeOverridesAllowed = resolveAllowRuntimeOverride(input);
      const requestModelRaw = runtimeOverridesAllowed ? normalizeOverride(input.requestOverride?.modelId ?? void 0) : null;
      const requestProviderRaw = runtimeOverridesAllowed ? normalizeOverride(input.requestOverride?.providerId ?? void 0) : null;
      const requestBaseUrlRaw = runtimeOverridesAllowed ? normalizeOverride(input.requestOverride?.baseURL ?? void 0) : null;
      const callerRaw = normalizeOverride(input.callerOverride);
      const tenantModelRaw = runtimeOverridesAllowed ? normalizeOverride(input.tenantOverride?.modelId ?? void 0) : null;
      const tenantProviderRaw = runtimeOverridesAllowed ? normalizeOverride(input.tenantOverride?.providerId ?? void 0) : null;
      const tenantBaseUrlRaw = runtimeOverridesAllowed ? normalizeOverride(input.tenantOverride?.baseURL ?? void 0) : null;
      const moduleModelRaw = hasModule ? readModuleModelEnvOverride(env, input.moduleId) : null;
      const agentModelRaw = normalizeOverride(input.agentDefaultModel);
      const globalModelRaw = readGlobalModelFromEnv(env);
      const requestModelParsed = requestModelRaw ? parseSlashShorthand(requestModelRaw, registry) : null;
      const callerParsed = callerRaw ? parseSlashShorthand(callerRaw, registry) : null;
      const tenantModelParsed = tenantModelRaw ? parseSlashShorthand(tenantModelRaw, registry) : null;
      const moduleModelParsed = moduleModelRaw ? parseSlashShorthand(moduleModelRaw, registry) : null;
      const agentModelParsed = agentModelRaw ? parseSlashShorthand(agentModelRaw, registry) : null;
      const globalModelParsed = globalModelRaw ? parseSlashShorthand(globalModelRaw, registry) : null;
      const providerOverrideRaw = normalizeOverride(input.providerOverride);
      const moduleProviderRaw = hasModule ? readModuleProviderEnvOverride(env, input.moduleId) : null;
      const agentDefaultProviderRaw = normalizeOverride(input.agentDefaultProvider);
      const globalProviderRaw = readGlobalProviderFromEnv(env, registry);
      const requestProviderHint = normalizeProviderHint(requestProviderRaw, registry);
      const providerOverrideHint = normalizeProviderHint(providerOverrideRaw, registry);
      const tenantProviderHint = normalizeProviderHint(tenantProviderRaw, registry);
      const moduleProviderHint = normalizeProviderHint(moduleProviderRaw, registry);
      const agentDefaultProviderHint = normalizeProviderHint(agentDefaultProviderRaw, registry);
      const providerHintCandidates = [
        requestModelParsed?.providerHint ?? null,
        requestProviderHint,
        callerParsed?.providerHint ?? null,
        providerOverrideHint,
        tenantModelParsed?.providerHint ?? null,
        tenantProviderHint,
        moduleModelParsed?.providerHint ?? null,
        moduleProviderHint,
        agentModelParsed?.providerHint ?? null,
        agentDefaultProviderHint,
        globalModelParsed?.providerHint ?? null,
        globalProviderRaw
      ];
      const orderHint = providerHintCandidates.find((hint) => hint !== null) ?? null;
      const order = orderHint ? [orderHint] : void 0;
      const pairPlainProviderIfWinning = (providerHint) => providerHint && providerHint === orderHint ? providerHint : null;
      let modelId;
      let source;
      let pairedProviderHint = null;
      if (requestModelParsed) {
        modelId = requestModelParsed.modelId;
        source = "request_override";
        pairedProviderHint = requestModelParsed.providerHint ?? pairPlainProviderIfWinning(requestProviderHint);
      } else if (callerParsed) {
        modelId = callerParsed.modelId;
        source = "caller_override";
        pairedProviderHint = callerParsed.providerHint ?? pairPlainProviderIfWinning(providerOverrideHint);
      } else if (tenantModelParsed) {
        modelId = tenantModelParsed.modelId;
        source = "tenant_override";
        pairedProviderHint = tenantModelParsed.providerHint ?? pairPlainProviderIfWinning(tenantProviderHint);
      } else if (moduleModelParsed) {
        modelId = moduleModelParsed.modelId;
        source = "module_env";
        pairedProviderHint = moduleModelParsed.providerHint ?? pairPlainProviderIfWinning(moduleProviderHint);
      } else if (agentModelParsed) {
        modelId = agentModelParsed.modelId;
        source = "agent_default";
        pairedProviderHint = agentModelParsed.providerHint ?? pairPlainProviderIfWinning(agentDefaultProviderHint);
      } else if (globalModelParsed) {
        modelId = globalModelParsed.modelId;
        source = "env_default";
        pairedProviderHint = globalModelParsed.providerHint ?? pairPlainProviderIfWinning(globalProviderRaw);
      } else {
        modelId = "";
        source = "provider_default";
      }
      const provider = pairedProviderHint ? resolveRequiredProvider(pairedProviderHint, registry, env) : registry.resolveFirstConfigured({ env, order });
      if (!provider) {
        if (pairedProviderHint) {
          throw new AiModelFactoryError(
            "no_provider_configured",
            requiredProviderMessage(pairedProviderHint, registry, env)
          );
        }
        throw new AiModelFactoryError(
          "no_provider_configured",
          "No LLM provider is configured. Set OM_AI_PROVIDER (or the legacy OPENCODE_PROVIDER) plus a matching API key such as OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY, then restart the app. See https://docs.openmercato.com/framework/ai-assistant/overview."
        );
      }
      const apiKey = provider.resolveApiKey(env);
      if (!apiKey) {
        throw new AiModelFactoryError(
          "api_key_missing",
          `LLM provider "${provider.id}" is advertised as configured but resolveApiKey() returned empty.`
        );
      }
      if (source === "provider_default") {
        modelId = provider.defaultModel;
      }
      const resolvedBaseURL = requestBaseUrlRaw ?? normalizeOverride(input.baseUrlOverride) ?? tenantBaseUrlRaw ?? (hasModule ? normalizeOverride(env[moduleBaseUrlEnvVarName(input.moduleId)]) : null) ?? normalizeOverride(input.agentDefaultBaseUrl) ?? void 0;
      const registryProviderIds = registry.list?.()?.map((p) => p.id) ?? [];
      const tenantProviderIds = input.tenantAllowlist ? Object.keys(input.tenantAllowlist.allowedModelsByProvider ?? {}) : [];
      const knownProviderIds = Array.from(
        /* @__PURE__ */ new Set([provider.id, ...registryProviderIds, ...tenantProviderIds])
      );
      const effectiveAllowlist = intersectAllowlists(
        env,
        knownProviderIds,
        input.tenantAllowlist ?? null
      );
      const allowlistResult = enforceAllowlist({
        env,
        registry,
        resolved: { provider, modelId },
        agentDefaultProvider: agentDefaultProviderRaw,
        agentDefaultModel: agentModelParsed?.modelId ?? agentModelRaw,
        effective: effectiveAllowlist
      });
      const finalProvider = allowlistResult.provider;
      const finalModelId = allowlistResult.modelId;
      const finalSource = allowlistResult.fallback ? "allowlist_fallback" : source;
      const finalApiKey = allowlistResult.fallback ? finalProvider.resolveApiKey(env) : apiKey;
      if (!finalApiKey) {
        throw new AiModelFactoryError(
          "api_key_missing",
          `LLM provider "${finalProvider.id}" is advertised as configured but resolveApiKey() returned empty.`
        );
      }
      const model = finalProvider.createModel({
        modelId: finalModelId,
        apiKey: finalApiKey,
        baseURL: resolvedBaseURL
      });
      return {
        model,
        modelId: finalModelId,
        providerId: finalProvider.id,
        source: finalSource,
        ...resolvedBaseURL !== void 0 ? { baseURL: resolvedBaseURL } : {},
        ...allowlistResult.fallback ? {
          allowlistFallback: {
            originalProviderId: provider.id,
            originalModelId: modelId,
            reason: allowlistResult.fallback
          }
        } : {}
      };
    }
  };
}
function enforceAllowlist(input) {
  const { registry, resolved, agentDefaultProvider, agentDefaultModel, effective } = input;
  let provider = resolved.provider;
  let modelId = resolved.modelId;
  let fallback = null;
  if (effective.providers !== null && !isProviderAllowedInEffective(effective, provider.id)) {
    const replacement = pickAllowedProvider({
      registry,
      agentDefaultProvider,
      effective
    });
    if (replacement) {
      const source = effective.tenantOverridesActive ? "the effective allowlist (env \u2229 tenant)" : "OM_AI_AVAILABLE_PROVIDERS";
      fallback = `Provider "${provider.id}" is not in ${source}; using "${replacement.id}" instead.`;
      console.warn(`[AI Model Factory] ${fallback}`);
      provider = replacement;
      modelId = pickAllowedModel({
        provider,
        preferred: agentDefaultModel,
        effective
      });
    }
  }
  if (!isModelAllowedForProviderInEffective(effective, provider.id, modelId)) {
    const replacementModel = pickAllowedModel({
      provider,
      preferred: agentDefaultModel,
      effective
    });
    if (replacementModel !== modelId) {
      const source = effective.tenantOverridesActive ? `the effective allowlist (env \u2229 tenant) for "${provider.id}"` : `OM_AI_AVAILABLE_MODELS_${provider.id.toUpperCase()}`;
      const reason = `Model "${modelId}" is not in ${source}; using "${replacementModel}" instead.`;
      console.warn(`[AI Model Factory] ${reason}`);
      fallback = fallback ? `${fallback} ${reason}` : reason;
      modelId = replacementModel;
    }
  }
  return { provider, modelId, fallback };
}
function pickAllowedProvider(input) {
  const { registry, agentDefaultProvider, effective } = input;
  if (agentDefaultProvider) {
    if (isProviderAllowedInEffective(effective, agentDefaultProvider)) {
      const provider = registry.get?.(agentDefaultProvider);
      if (provider && provider.isConfigured(process.env)) return provider;
    }
  }
  const allowed = effective.providers;
  if (!allowed) return null;
  for (const id of allowed) {
    const provider = registry.get?.(id);
    if (provider && provider.isConfigured(process.env)) return provider;
  }
  return null;
}
function pickAllowedModel(input) {
  const { provider, preferred, effective } = input;
  const allowed = effective.modelsByProvider[provider.id];
  if (allowed === void 0) {
    return preferred && preferred.length > 0 ? preferred : provider.defaultModel;
  }
  if (preferred && allowed.includes(preferred)) return preferred;
  if (allowed.includes(provider.defaultModel)) return provider.defaultModel;
  return allowed[0] ?? provider.defaultModel;
}
export {
  AiModelFactoryError,
  createModelFactory,
  parseSlashShorthand,
  resolveAllowRuntimeOverride
};
//# sourceMappingURL=model-factory.js.map
