// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: GPL-3.0-or-later

import { Type, Kind, type Static, type TObject, type TProperties } from "@sinclair/typebox";

const CustomModelSchema = Type.Object({
  provider: Type.String(),
  api: Type.String(),
  base_url: Type.String(),
  id: Type.String(),
  name: Type.String(),
  reasoning: Type.Boolean(),
  input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
  cost: Type.Object({
    input: Type.Number(),
    output: Type.Number(),
    cache_read: Type.Optional(Type.Number()),
    cache_write: Type.Optional(Type.Number()),
  }),
  context_window: Type.Number(),
  max_tokens: Type.Number(),
  api_key: Type.Optional(Type.String()),
  headers: Type.Optional(Type.Record(Type.String(), Type.String())),
  compat: Type.Optional(
    Type.Object({
      supports_store: Type.Optional(Type.Boolean()),
      supports_developer_role: Type.Optional(Type.Boolean()),
      supports_reasoning_effort: Type.Optional(Type.Boolean()),
      supports_usage_in_streaming: Type.Optional(Type.Boolean()),
      max_tokens_field: Type.Optional(Type.Union([Type.Literal("max_tokens"), Type.Literal("max_completion_tokens")])),
      requires_tool_result_name: Type.Optional(Type.Boolean()),
      requires_assistant_after_tool_result: Type.Optional(Type.Boolean()),
      requires_thinking_as_text: Type.Optional(Type.Boolean()),
      requires_mistral_tool_ids: Type.Optional(Type.Boolean()),
      thinking_format: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("zai")])),
    })
  ),
});

export const ConfigSchema = Type.Object({
  defaults: Type.Object({
    model: Type.String(),
    cleanup: Type.Boolean(),
    kagi_session_token: Type.Optional(Type.String()),
    tabstack_api_key: Type.Optional(Type.String()),
  }),
  web: Type.Object({
    model: Type.Optional(Type.String()),
    system_prompt_path: Type.Optional(Type.String()),
    kagi_session_token: Type.Optional(Type.String()),
    tabstack_api_key: Type.Optional(Type.String()),
  }),
  repo: Type.Object({
    model: Type.Optional(Type.String()),
    system_prompt_path: Type.Optional(Type.String()),
    default_depth: Type.Optional(Type.Number({ minimum: 1 })),
    blob_limit: Type.Optional(Type.String()),
  }),
  custom_models: Type.Optional(Type.Record(Type.String(), CustomModelSchema)),
});

/** Deep-partial version of ConfigSchema for validating TOML override files. */
export function partialObject<T extends TProperties>(schema: TObject<T>) {
  const partial: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(schema.properties)) {
    const v = value as any;
    const inner = v[Kind] === 'Object' && v.properties ? partialObject(v) : v;
    partial[key] = Type.Optional(inner as any);
  }
  return Type.Object(partial as any);
}

export const PartialConfigSchema = Type.Object({
  defaults: Type.Optional(partialObject(ConfigSchema.properties.defaults)),
  web: Type.Optional(partialObject(ConfigSchema.properties.web)),
  repo: Type.Optional(partialObject(ConfigSchema.properties.repo)),
  custom_models: Type.Optional(Type.Record(Type.String(), CustomModelSchema)),
});

export type RumiloConfig = Static<typeof ConfigSchema>;
export type CustomModelConfig = Static<typeof CustomModelSchema>;
