import { lazy, useCallback, type ComponentProps, type ComponentType } from "react";
import { useAtomSet } from "@effect/atom-react";
import * as Exit from "effect/Exit";
import type {
  IntegrationPlugin,
  IntegrationQuickAddInput,
  IntegrationQuickAddResult,
} from "@executor-js/sdk/client";
import { placementFromHeaderPattern } from "@executor-js/react/lib/auth-placements";
import { slugifyNamespace } from "@executor-js/react/plugins/integration-identity";
import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";
import { mcpPresets } from "../sdk/presets";
import { addMcpServer, probeMcpEndpoint } from "./atoms";
import {
  mcpAuthMethodInputFromEditorValue,
  mcpDetectedAuthSeeds,
  mcpWireAuthInput,
} from "./auth-method-config";

const importAdd = () => import("./AddMcpIntegration");
const importEditSheet = () => import("./EditMcpIntegration");
const importAccounts = () => import("./McpAccountsPanel");

const LazyAddMcpIntegration = lazy(importAdd);
const LazyEditMcpSheet = lazy(importEditSheet);
const LazyMcpAccountsPanel = lazy(importAccounts);

type AddProps = ComponentProps<IntegrationPlugin["add"]>;

/** One-click add for a registry MCP row: probe, declare the auth methods the
 *  add page would have seeded (same policy — `mcpDetectedAuthSeeds`), and
 *  register. A failed probe means the configuration screen's job — it renders
 *  the failure with retry UX — so quick add reports `{ok: false}` instead of
 *  guessing. */
function useMcpQuickAdd(): (input: IntegrationQuickAddInput) => Promise<IntegrationQuickAddResult> {
  const doProbe = useAtomSet(probeMcpEndpoint, { mode: "promiseExit" });
  const doAddServer = useAtomSet(addMcpServer, { mode: "promiseExit" });
  return useCallback(
    async (input) => {
      const probeExit = await doProbe({ payload: { endpoint: input.url } });
      if (Exit.isFailure(probeExit)) return { ok: false, reason: "probe failed" };
      const probe = probeExit.value;
      const seeds = mcpDetectedAuthSeeds(probe, {
        placement: input.authHeader ? placementFromHeaderPattern(input.authHeader) : null,
        kind: input.authKind,
      });
      const methods = seeds.map((seed) =>
        mcpWireAuthInput(mcpAuthMethodInputFromEditorValue(seed.value)),
      );
      const slug = input.slug ? slugifyNamespace(input.slug) : undefined;
      const addExit = await doAddServer({
        payload: {
          transport: "remote" as const,
          name: input.name,
          endpoint: input.url,
          ...(slug ? { slug } : {}),
          ...(probe.instructions ? { description: probe.instructions } : {}),
          authenticationTemplate: methods.length > 0 ? methods : [{ kind: "none" as const }],
          // The probe reports when only legacy negotiation worked (a server
          // that echoes the modern revision but breaks its contract); pin it
          // so refreshes and tool calls use the same handshake.
          ...(probe.versionNegotiation === "legacy"
            ? { versionNegotiation: "legacy" as const }
            : {}),
        },
        reactivityKeys: integrationWriteKeys,
      });
      if (Exit.isFailure(addExit)) return { ok: false, reason: "add failed" };
      return { ok: true, slug: String(addExit.value.slug) };
    },
    [doProbe, doAddServer],
  );
}

export interface McpIntegrationPluginOptions {
  /**
   * Enable the stdio transport in the add-integration UI (tab + presets).
   *
   * Off by default — stdio is a high-risk transport on any server deployment
   * (see `dangerouslyAllowStdioMCP` on the server-side plugin). Only enable in
   * trusted local contexts where the server has the matching flag set.
   */
  readonly allowStdio?: boolean;
}

export const createMcpIntegrationPlugin = (
  options?: McpIntegrationPluginOptions,
): IntegrationPlugin => {
  const allowStdio = options?.allowStdio ?? false;

  const AddWithFlag: ComponentType<AddProps> = (props) => (
    <LazyAddMcpIntegration {...props} allowStdio={allowStdio} />
  );

  const base = allowStdio
    ? mcpPresets
    : mcpPresets.filter(
        (p) => !("transport" in p && (p as { transport?: string }).transport === "stdio"),
      );
  // Built-ins are registry-listed; a deployment's custom presets would not be.
  const presets = base.map((p) => ({ ...p, registryListed: true }));

  return {
    key: "mcp",
    label: "MCP",
    add: AddWithFlag,
    editSheet: LazyEditMcpSheet,
    accounts: LazyMcpAccountsPanel,
    presets,
    preload: () => {
      void importAdd();
      void importEditSheet();
      void importAccounts();
    },
    useQuickAdd: useMcpQuickAdd,
  };
};

/** @deprecated Use `createMcpIntegrationPlugin({ allowStdio })` instead. */
export const mcpIntegrationPlugin: IntegrationPlugin = createMcpIntegrationPlugin();
