// The MCP SDK only sets windowsHide:true in Electron (checks `'type' in process`).
if (process.platform === "win32" && !("type" in process)) {
  Object.defineProperty(process, "type", { value: "saeeol-bun", configurable: true })
}

import { type Tool, type JSONSchema7, dynamicTool, jsonSchema } from "ai"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { type Tool as MCPToolDef, CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"
import { ConfigMCP } from "../config/mcp"
import { Config } from "@/config/config"
import * as Log from "@saeeol/core/util/log"
import { NamedError } from "@saeeol/core/util/error"
import z from "zod/v4"
import { InstallationVersion } from "@saeeol/core/installation/version"
import { withTimeout } from "@/util/timeout"
import { BusEvent } from "../bus/bus-event"
import { Effect, Schema } from "effect"
import { zod as effectZod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"

const log = Log.create({ service: "mcp" })

export const DEFAULT_TIMEOUT = 30_000

export function ensureDockerRm(cmd: string, args: string[]): string[] {
  const isDocker = cmd === "docker" || cmd === "podman"
  if (!isDocker) return args
  const runIdx = args.indexOf("run")
  if (runIdx < 0) return args
  if (args.includes("--rm")) return args
  const result = [...args]
  result.splice(runIdx + 1, 0, "--rm")
  return result
}

export const Resource = Schema.Struct({
  name: Schema.String, uri: Schema.String,
  description: Schema.optional(Schema.String), mimeType: Schema.optional(Schema.String),
  client: Schema.String,
}).annotate({ identifier: "McpResource" }).pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type Resource = Schema.Schema.Type<typeof Resource>

export const ToolsChanged = BusEvent.define("mcp.tools.changed", Schema.Struct({ server: Schema.String }))
export const BrowserOpenFailed = BusEvent.define("mcp.browser.open.failed", Schema.Struct({ mcpName: Schema.String, url: Schema.String }))
export const Failed = NamedError.create("MCPFailed", z.object({ name: z.string() }))

export type MCPClient = Client
export type McpEntry = NonNullable<Config.Info["mcp"]>[string]

const StatusConnected = Schema.Struct({ status: Schema.Literal("connected") }).annotate({ identifier: "MCPStatusConnected" })
const StatusDisabled = Schema.Struct({ status: Schema.Literal("disabled") }).annotate({ identifier: "MCPStatusDisabled" })
const StatusFailed = Schema.Struct({ status: Schema.Literal("failed"), error: Schema.String }).annotate({ identifier: "MCPStatusFailed" })
const StatusNeedsAuth = Schema.Struct({ status: Schema.Literal("needs_auth") }).annotate({ identifier: "MCPStatusNeedsAuth" })
const StatusNeedsReg = Schema.Struct({ status: Schema.Literal("needs_client_registration"), error: Schema.String }).annotate({ identifier: "MCPStatusNeedsClientRegistration" })

export const Status = Schema.Union([StatusConnected, StatusDisabled, StatusFailed, StatusNeedsAuth, StatusNeedsReg])
  .annotate({ identifier: "MCPStatus", discriminator: "status" })
  .pipe(withStatics((s) => ({ zod: effectZod(s) })))
export type Status = Schema.Schema.Type<typeof Status>

export const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_-]/g, "_")
export function remoteURL(key: string, value: string) {
  if (URL.canParse(value)) return new URL(value)
  log.warn("invalid remote mcp url", { key })
}
export function isMcpConfigured(entry: McpEntry): entry is ConfigMCP.Info {
  return typeof entry === "object" && entry !== null && "type" in entry
}

export function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number): Tool {
  const inputSchema = mcpTool.inputSchema
  const schema: JSONSchema7 = { ...(inputSchema as JSONSchema7), type: "object", properties: (inputSchema.properties ?? {}) as JSONSchema7["properties"], additionalProperties: false }
  return dynamicTool({
    description: mcpTool.description ?? "", inputSchema: jsonSchema(schema),
    execute: async (args: unknown) => client.callTool({ name: mcpTool.name, arguments: (args || {}) as Record<string, unknown> }, CallToolResultSchema, { resetTimeoutOnProgress: true, timeout }),
  })
}

export function defs(key: string, client: MCPClient, timeout?: number) {
  return Effect.tryPromise({ try: () => withTimeout(client.listTools(), timeout ?? DEFAULT_TIMEOUT), catch: (err) => (err instanceof Error ? err : new Error(String(err))) }).pipe(
    Effect.map((r) => r.tools),
    Effect.catch((err) => { log.error("failed to get tools from client", { key, error: err }); return Effect.succeed(undefined) }),
  )
}

export function fetchFromClient<T extends { name: string }>(clientName: string, client: Client, listFn: (c: Client) => Promise<T[]>, label: string) {
  return Effect.tryPromise({ try: () => listFn(client), catch: (e: any) => { log.error(`failed to get ${label}`, { clientName, error: e.message }); return e } }).pipe(
    Effect.map((items) => {
      const out: Record<string, T & { client: string }> = {}
      for (const item of items) out[sanitize(clientName) + ":" + sanitize(item.name)] = { ...item, client: clientName }
      return out
    }),
    Effect.orElseSucceed(() => undefined),
  )
}

export type { MCPToolDef }
export { SSEClientTransport }
export type Transport = StdioClientTransport | StreamableHTTPClientTransport | SSEClientTransport
export type TransportWithAuth = StreamableHTTPClientTransport | SSEClientTransport
export const pendingOAuthTransports = new Map<string, TransportWithAuth>()
export type PromptInfo = Awaited<ReturnType<MCPClient["listPrompts"]>>["prompts"][number]
export type ResourceInfo = Awaited<ReturnType<MCPClient["listResources"]>>["resources"][number]
export interface CreateResult { mcpClient?: MCPClient; status: Status; defs?: MCPToolDef[] }
export interface AuthResult { authorizationUrl: string; oauthState: string; client?: MCPClient }
export interface State { status: Record<string, Status>; clients: Record<string, MCPClient>; defs: Record<string, MCPToolDef[]> }
export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
