/**
 * Home Next-Actions
 *
 * `GET /api/:org/home-next-actions`
 *
 * Returns the still-pending next actions for the `/$org` home page:
 * - `prompts`: onboarding/icebreaker prompts from the Studio Pack agents and
 *   the org's configured default home agents. Each opens a new thread with the
 *   named agent and autosends the resolved MCP prompt as the first message.
 * - `threads`: the caller's threads in `requires_action` status (an agent
 *   asked a question or a tool needs approval). Each reopens that thread.
 *
 * Server-side filtering keeps the row pared down as the user makes progress.
 */

import { Hono } from "hono";
import { slugify } from "@decocms/mcp-utils/aggregate";
import { getHomeTiles, WellKnownOrgMCPId } from "@decocms/shared/sdk";
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
import type { StudioContext } from "@/core/studio-context";
import { createVirtualClientFrom } from "@/mcp-clients/virtual-mcp";
import { getPrompts } from "@/tools/guides";
import {
  STUDIO_PACK_AGENTS,
  resolveStudioPackChecklist,
} from "@/tools/virtual/studio-pack";

/** Per default-home agent, cap prompts so a chatty agent can't flood the row. */
const MAX_PROMPTS_PER_AGENT = 10;
/** Bound a slow/unreachable agent so it can't hang the home request. */
const LIST_PROMPTS_TIMEOUT_MS = 5000;

type Variables = {
  studioContext: StudioContext;
};
interface PromptEntry {
  agentId: string;
  agentName: string;
  agentIcon: string | null;
  /**
   * Gateway-namespaced prompt name — matches what `prompts/list` returns for
   * this agent's MCP client so the resulting mention chip's id lines up with
   * the slash-command flow (enables click-to-edit on the chip). Empty string
   * marks an agent-only fallback card (no prompt to autosend — click just
   * opens a fresh thread with the agent).
   */
  promptName: string;
  title: string;
  description: string;
  hasArguments: boolean;
  arguments?: Prompt["arguments"];
  _meta?: Prompt["_meta"];
}

/**
 * Tile UI for a home agent. Rendered as an MCP UI iframe inside the agent's
 * tile on `/$org`. The frontend opens an MCP client to `connectionId` (the
 * connection that owns the resource, not the virtual MCP gateway) so the
 * iframe can call tools by their bare names.
 */
interface TileEntry {
  agentId: string;
  agentName: string;
  agentIcon: string | null;
  tileId?: string;
  connectionId: string;
  resourceUri: string;
  toolName?: string;
  toolInput?: Record<string, unknown>;
  minHeight?: number;
  maxHeight?: number;
}

function indexPromptsByName() {
  const all = getPrompts();
  const byName = new Map<string, (typeof all)[number]>();
  for (const p of all) byName.set(p.name, p);
  return byName;
}

/**
 * Prompts + tile UIs from the org's configured default home agents (custom
 * virtual MCPs). Prompts are listed live from each agent's gateway; tile UIs
 * come from `metadata.ui.homeTile` on the virtual MCP and are surfaced as a
 * separate `tiles` array so the frontend can render iframes independently of
 * the prompt row. Studio Pack agent ids are skipped (prompts surfaced via the
 * static path; Studio Pack agents don't declare tile UIs today). Agents with
 * no prompts (or an unreachable gateway) still get one fallback card so every
 * configured default home agent shows up on the home row.
 */
async function defaultHomeAgentNextActions(
  orgId: string,
  ctx: StudioContext,
  skipAgentIds: Set<string>,
): Promise<{ prompts: PromptEntry[]; tiles: TileEntry[] }> {
  const settings = await ctx.storage.organizationSettings.get(orgId);
  const ids = settings?.default_home_agents?.ids ?? [];
  const uniqueIds = [...new Set(ids)].filter((id) => !skipAgentIds.has(id));

  const perId = await Promise.all(
    uniqueIds.map(
      async (id): Promise<{ prompts: PromptEntry[]; tiles: TileEntry[] }> => {
        const virtualMcp = await ctx.storage.virtualMcps.findById(id);
        if (!virtualMcp) return { prompts: [], tiles: [] };

        const tiles: TileEntry[] = getHomeTiles(virtualMcp.metadata?.ui)
          .filter((t) => t?.resourceUri && t.connectionId)
          .map((t) => ({
            agentId: id,
            agentName: virtualMcp.title,
            agentIcon: virtualMcp.icon,
            tileId: t.tileId,
            connectionId: t.connectionId as string,
            resourceUri: t.resourceUri,
            toolName: t.toolName,
            toolInput: t.toolInput,
            minHeight: t.minHeight,
            maxHeight: t.maxHeight,
          }));

        // Quick-access agent card (no prompt). The home chip shows the
        // agent name as the title and its description as the subtitle, so
        // map the fields that way rather than hoisting the description up
        // into the title slot.
        const fallback = (): PromptEntry => ({
          agentId: id,
          agentName: virtualMcp.title,
          agentIcon: virtualMcp.icon,
          promptName: "",
          title: virtualMcp.title,
          description: virtualMcp.description || "Start a new chat",
          hasArguments: false,
        });

        try {
          const client = await createVirtualClientFrom(virtualMcp, ctx, false, {
            listTimeoutMs: LIST_PROMPTS_TIMEOUT_MS,
          });
          try {
            const { prompts } = await client.listPrompts();
            // `homePrompts: null/undefined` means "all prompts"; an
            // explicit `[]` means "none". The fallback "Start a new
            // chat" card only fires for the unspecified case so an
            // intentionally-empty curated list stays empty.
            const curated = virtualMcp.metadata?.ui?.homePrompts;
            const filtered = Array.isArray(curated)
              ? (curated
                  .map((name) => prompts.find((p) => p.name === name))
                  .filter(Boolean) as typeof prompts)
              : prompts;
            const entries = filtered
              .slice(0, MAX_PROMPTS_PER_AGENT)
              .map((prompt) => ({
                agentId: id,
                agentName: virtualMcp.title,
                agentIcon: virtualMcp.icon,
                promptName: prompt.name,
                title: prompt.title ?? prompt.name,
                description: prompt.description ?? "",
                hasArguments: (prompt.arguments?.length ?? 0) > 0,
                arguments: prompt.arguments,
                _meta: prompt._meta,
              }));
            const showFallback =
              entries.length === 0 && !Array.isArray(curated);
            return {
              prompts: showFallback ? [fallback()] : entries,
              tiles,
            };
          } finally {
            await client.close();
          }
        } catch (error) {
          console.error("[home-next-actions] failed to list agent prompts", {
            agentId: id,
            error,
          });
          return { prompts: [fallback()], tiles };
        }
      },
    ),
  );

  return {
    prompts: perId.flatMap((r) => r.prompts),
    tiles: perId.flatMap((r) => r.tiles),
  };
}

export function createHomeNextActionsRoutes() {
  const app = new Hono<{ Variables: Variables }>();

  app.get("/home-next-actions", async (c) => {
    const studio = c.get("studioContext");
    const orgId = studio.organization?.id;
    if (!orgId) return c.json({ error: "Organization required" }, 400);

    const promptByName = indexPromptsByName();

    const perAgent = await Promise.all(
      STUDIO_PACK_AGENTS.map(async (agent) => {
        const agentId = agent.getId(orgId);
        const [items, virtualMcp] = await Promise.all([
          resolveStudioPackChecklist(agent, { orgId, ctx: studio }),
          studio.storage.virtualMcps.findById(agentId),
        ]);
        return {
          agent,
          agentId,
          items,
          // `null` keeps default behaviour (all checklist items shown).
          curated: Array.isArray(virtualMcp?.metadata?.ui?.homePrompts)
            ? new Set(virtualMcp.metadata.ui.homePrompts)
            : null,
        };
      }),
    );

    // Studio Pack guide prompts are registered on the org's "self" MCP and
    // surfaced via the agent's passthrough gateway. We mirror the gateway's
    // namespacing here so the client sees the same `name` + `_meta` it would
    // get from `prompts/list` on the agent.
    const selfClientId = WellKnownOrgMCPId.SELF(orgId);
    const namespacePrefix = `${slugify(selfClientId)}_`;

    const prompts: PromptEntry[] = [];
    for (const { agent, items, curated } of perAgent) {
      for (const item of items) {
        if (item.completed) continue;
        const meta = promptByName.get(item.action.promptName);
        if (!meta) continue;
        const namespacedName = `${namespacePrefix}${meta.name}`;
        if (curated && !curated.has(namespacedName)) continue;
        const args = meta.arguments?.map((a) => ({
          name: a.name,
          description: a.description,
          required: a.required,
        }));
        prompts.push({
          agentId: agent.getId(orgId),
          agentName: agent.title,
          agentIcon: agent.icon,
          promptName: namespacedName,
          title: meta.title,
          description: meta.description,
          hasArguments: (args?.length ?? 0) > 0,
          arguments: args,
          _meta: { gatewayClientId: selfClientId },
        });
      }
    }

    const studioPackAgentIds = new Set(
      STUDIO_PACK_AGENTS.map((agent) => agent.getId(orgId)),
    );
    const { prompts: defaultPrompts, tiles } =
      await defaultHomeAgentNextActions(orgId, studio, studioPackAgentIds);
    prompts.push(...defaultPrompts);

    const settings = await studio.storage.organizationSettings.get(orgId);
    const pinnedIds = new Set(settings?.default_home_agents?.ids ?? []);
    const agentIdsWithCard = new Set(prompts.map((p) => p.agentId));
    for (const { agent, agentId } of perAgent) {
      if (!pinnedIds.has(agentId) || agentIdsWithCard.has(agentId)) continue;
      prompts.push({
        agentId,
        agentName: agent.title,
        agentIcon: agent.icon,
        promptName: "",
        title: agent.title,
        description: "Start a new chat",
        hasArguments: false,
      });
    }

    c.header("Cache-Control", "private, max-age=10");
    return c.json({ prompts, tiles });
  });

  return app;
}
