import { useCallback } from "react";
import { useAtomSet, useAtomValue } from "@effect/atom-react";
import { Data, Effect, Option, Schema } from "effect";
import * as Atom from "effect/unstable/reactivity/Atom";
import { createFileRoute } from "@tanstack/react-router";

import { ResumeApprovalPage, ResumeApprovalPageView } from "../pages/resume-approval";
import { getExecutorServerAuthorizationHeader } from "../api/server-connection";
import type { ElicitationAction } from "../components/elicitation-approval";
import { useExecutorDocumentTitle } from "../lib/document-title";

const SearchParams = Schema.toStandardSchemaV1(
  Schema.Struct({
    mcp_session_id: Schema.optional(Schema.String),
  }),
);
const LocalMcpResumeCompleted = Schema.Struct({
  status: Schema.Literal("completed"),
  text: Schema.String,
  structured: Schema.Unknown,
  isError: Schema.Boolean,
});
const LocalMcpResumePaused = Schema.Struct({
  status: Schema.Literal("paused"),
  text: Schema.String,
  structured: Schema.Unknown,
});
const LocalMcpResumeResult = Schema.Union([LocalMcpResumeCompleted, LocalMcpResumePaused]);
const decodeLocalMcpResumeResult = Schema.decodeUnknownOption(LocalMcpResumeResult);

class LocalMcpResumeError extends Data.TaggedError("LocalMcpResumeError")<{
  readonly message: string;
}> {}

const McpPausedExecutionInfo = Schema.Struct({
  text: Schema.String,
  structured: Schema.Unknown,
});
const decodeMcpPausedExecutionInfo = Schema.decodeUnknownOption(McpPausedExecutionInfo);

// Paused-execution detail for the in-process / Cloudflare hosts, fetched
// session-scoped: the MCP paused execution lives in its session's engine, so it
// is resolved through `/api/mcp-sessions/:id/...`, not the session-less
// `/api/executions/:id` (which hits a different engine and can't see it). Cloud
// serves the equivalent through its own route + Durable Object RPC.
const mcpPausedExecutionAtom = Atom.family(
  (key: { readonly mcpSessionId: string; readonly executionId: string }) =>
    Atom.make(
      Effect.gen(function* () {
        // `/api/mcp-sessions/*` is bearer-gated. Attach the bearer (standalone
        // web reads it from localStorage; desktop injects it at the session
        // layer, so this is null and we send none) — otherwise this 401s on the
        // single-user local server and the page shows "unavailable".
        const authorization = getExecutorServerAuthorizationHeader();
        const response = yield* Effect.tryPromise({
          try: () =>
            fetch(
              `/api/mcp-sessions/${encodeURIComponent(key.mcpSessionId)}/executions/${encodeURIComponent(key.executionId)}`,
              authorization ? { headers: { authorization } } : undefined,
            ),
          catch: () => new LocalMcpResumeError({ message: "Failed to load the paused execution." }),
        });
        if (!response.ok) {
          return yield* new LocalMcpResumeError({
            message: `Paused execution unavailable (${response.status}).`,
          });
        }
        const body = yield* Effect.tryPromise({
          try: () => response.json(),
          catch: () => new LocalMcpResumeError({ message: "Paused response was not valid JSON." }),
        });
        const decoded = decodeMcpPausedExecutionInfo(body);
        if (Option.isNone(decoded)) {
          return yield* new LocalMcpResumeError({
            message: "Paused response had an unexpected shape.",
          });
        }
        return decoded.value;
      }),
    ),
);

type LocalMcpResumeInput = {
  readonly mcpSessionId: string;
  readonly executionId: string;
  readonly action: ElicitationAction;
  readonly content?: Record<string, unknown>;
  readonly persist?: string;
};

const resumeLocalMcpExecution = Atom.fn<LocalMcpResumeInput>()((input) =>
  Effect.gen(function* () {
    // `/api/mcp-sessions/*` is bearer-gated like the rest of /api. Attach the
    // bearer the same way the typed client does (standalone web reads it from
    // localStorage; on desktop the connection carries no auth and the main
    // process injects the header, so this is null and we send none).
    const authorization = getExecutorServerAuthorizationHeader();
    const response = yield* Effect.tryPromise({
      try: () =>
        fetch(
          `/api/mcp-sessions/${encodeURIComponent(input.mcpSessionId)}/executions/${encodeURIComponent(input.executionId)}/resume`,
          {
            method: "POST",
            headers: {
              "content-type": "application/json",
              ...(authorization ? { authorization } : {}),
            },
            body: JSON.stringify(
              input.action === "accept"
                ? {
                    action: input.action,
                    content: input.content ?? {},
                    ...(input.persist === undefined ? {} : { persist: input.persist }),
                  }
                : { action: input.action },
            ),
          },
        ),
      catch: () => new LocalMcpResumeError({ message: "Failed to submit approval." }),
    });

    if (!response.ok) {
      const body = yield* Effect.tryPromise({
        try: () => response.text(),
        catch: () => "",
      }).pipe(Effect.orElseSucceed(() => ""));
      return yield* new LocalMcpResumeError({
        message: body || `Approval request failed (${response.status}).`,
      });
    }

    const body = yield* Effect.tryPromise({
      try: () => response.json(),
      catch: () => new LocalMcpResumeError({ message: "Approval response was not valid JSON." }),
    });
    const result = decodeLocalMcpResumeResult(body);
    if (Option.isNone(result)) {
      return yield* new LocalMcpResumeError({
        message: "Approval response had an unexpected shape.",
      });
    }
    return result.value;
  }),
);

export const Route = createFileRoute("/{-$orgSlug}/resume/$executionId")({
  validateSearch: SearchParams,
  component: RouteComponent,
});

function RouteComponent() {
  useExecutorDocumentTitle("Resume");
  const { executionId } = Route.useParams();
  const { mcp_session_id: mcpSessionId } = Route.useSearch();
  if (mcpSessionId) {
    return <LocalMcpResumeApproval executionId={executionId} mcpSessionId={mcpSessionId} />;
  }
  return <ResumeApprovalPage executionId={executionId} />;
}

function LocalMcpResumeApproval(props: { executionId: string; mcpSessionId: string }) {
  const paused = useAtomValue(
    mcpPausedExecutionAtom({ mcpSessionId: props.mcpSessionId, executionId: props.executionId }),
  );
  const doResume = useAtomSet(resumeLocalMcpExecution, { mode: "promiseExit" });
  const resume = useCallback(
    (
      executionId: string,
      action: ElicitationAction,
      content?: Record<string, unknown>,
      persist?: string,
    ) =>
      doResume({
        mcpSessionId: props.mcpSessionId,
        executionId,
        action,
        content,
        persist,
      }),
    [doResume, props.mcpSessionId],
  );

  return (
    <ResumeApprovalPageView
      executionId={props.executionId}
      paused={paused}
      resume={resume}
      unavailableMessage="This paused execution is no longer available. It may have already been resumed, or the MCP session may have expired."
    />
  );
}
