import { parseAgentEnv } from "@code/env/agent";
import { defineTool } from "@flue/runtime";
import { ConvexHttpClient } from "convex/browser";
import { makeFunctionReference } from "convex/server";
import * as v from "valibot";

// ---------------------------------------------------------------------------
// Agent-gated Convex function references for the routing loop.
// ---------------------------------------------------------------------------

const listEvidenceRef = makeFunctionReference<
  "query",
  { readonly organizationId: string; readonly token: string },
  {
    messageId: string;
    rawText: string;
    createdAt: number;
    submissionId: string | null;
  }[]
>("signalRouting:listEvidence");

const createSignalRef = makeFunctionReference<
  "mutation",
  {
    readonly organizationId: string;
    readonly projectId?: string;
    messageIds: string[];
    readonly problemStatement: {
      readonly title: string;
      readonly summary: string;
      readonly desiredOutcome: string;
      constraints: string[];
    };
    readonly processedByAgentInstanceId: string;
    readonly token: string;
  },
  { signalId: string }
>("signalRouting:createSignal");

const listSignalsRef = makeFunctionReference<
  "query",
  {
    readonly organizationId: string;
    readonly projectId?: string;
    readonly token: string;
  },
  {
    _id: string;
    createdAt: number;
    problemStatement: {
      title: string;
      summary: string;
      desiredOutcome: string;
      constraints: string[];
    };
    projectId: string | null;
  }[]
>("signalRouting:listSignals");

const listActiveIssuesRef = makeFunctionReference<
  "query",
  {
    readonly organizationId: string;
    readonly projectId: string;
    readonly token: string;
  },
  {
    _id: string;
    number: number;
    title: string;
    body: string;
    status: string;
    projectId: string;
    updatedAt: number;
  }[]
>("signalRouting:listActiveIssues");

const attachSignalToIssueRef = makeFunctionReference<
  "mutation",
  {
    readonly organizationId: string;
    readonly signalId: string;
    readonly issueId: string;
    readonly token: string;
  },
  { attachmentId: string; alreadyAttached: boolean }
>("signalRouting:attachSignalToIssue");

const createIssueFromSignalRef = makeFunctionReference<
  "mutation",
  {
    readonly organizationId: string;
    readonly signalId: string;
    readonly token: string;
  },
  { issueId: string; projectId: string }
>("signalRouting:createIssueFromSignal");

const beginIssueRef = makeFunctionReference<
  "mutation",
  {
    readonly organizationId: string;
    readonly issueId: string;
    readonly token: string;
  },
  "queued" | "working"
>("signalRouting:beginIssue");

const getProjectContextRef = makeFunctionReference<
  "query",
  {
    readonly organizationId: string;
    readonly projectId: string;
    readonly token: string;
  },
  {
    project: {
      _id: string;
      name: string;
      description: string | null;
      organizationId: string;
    };
    contextDocuments: {
      kind: string;
      path: string;
      content: string;
      revision: number;
    }[];
  }
>("signalRouting:getProjectContext");

const listProjectsRef = makeFunctionReference<
  "query",
  {
    readonly organizationId: string;
    readonly token: string;
  },
  {
    _id: string;
    name: string;
    description: string | null;
    organizationId: string;
  }[]
>("signalRouting:listProjects");

// ---------------------------------------------------------------------------
// Tool factory: creates all routing tools bound to one agent instance.
//
// The `instanceId` is the organization ID (established by the authenticated
// route middleware). The tool never accepts organization ID as free input;
// it always comes from the bound instance. This is the hard tenancy boundary.
// ---------------------------------------------------------------------------

export const createSignalRoutingTools = (
  instanceId: string,
  runtimeEnv: Record<string, string | undefined>
) => {
  const env = parseAgentEnv(runtimeEnv);
  const client = new ConvexHttpClient(env.CONVEX_URL);
  const token = env.FLUE_DB_TOKEN;
  // The instance id is the organization id; tools are scoped to it.
  const organizationId = instanceId;

  return [
    defineTool({
      description:
        "List projects in the current organization. Call this first to identify the project context the user is working in.",
      name: "list_projects",
      async run() {
        return await client.query(listProjectsRef, {
          organizationId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "Read the project name, description, and canonical context documents (README, product, design, tech, agents). Use this to understand the project before routing work.",
      input: v.object({
        projectId: v.string(),
      }),
      name: "get_project_context",
      async run({ input }) {
        return await client.query(getProjectContextRef, {
          organizationId,
          projectId: input.projectId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "List admitted user messages in the current conversation that have not yet been consumed by a Signal. These are the candidate evidence messages. Raw text is exact — never modify it.",
      name: "list_signal_evidence",
      async run() {
        return await client.query(listEvidenceRef, {
          organizationId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "Create a Signal from one or more admitted user messages plus a structured problem statement. Use ONLY when the conversation contains an actionable problem, request, blocker, or decision. Do NOT create Signals for casual chat, greetings, or exploration without a concrete problem. The problemStatement must faithfully represent the user's own words — do not invent or rewrite their intent.",
      input: v.object({
        messageIds: v.array(v.string()),
        problemStatement: v.object({
          constraints: v.array(v.string()),
          desiredOutcome: v.string(),
          summary: v.string(),
          title: v.string(),
        }),
        projectId: v.optional(v.string()),
      }),
      name: "create_signal",
      async run({ input }) {
        return await client.mutation(createSignalRef, {
          messageIds: input.messageIds,
          organizationId,
          problemStatement: input.problemStatement,
          ...(input.projectId === undefined
            ? {}
            : { projectId: input.projectId }),
          processedByAgentInstanceId: organizationId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "List recent Signals for a project (or organization-wide if no projectId). Use this to see what has already been captured.",
      input: v.object({
        projectId: v.optional(v.string()),
      }),
      name: "list_recent_signals",
      async run({ input }) {
        return await client.query(listSignalsRef, {
          organizationId,
          ...(input.projectId === undefined
            ? {}
            : { projectId: input.projectId }),
          token,
        });
      },
    }),

    defineTool({
      description:
        "List active (open, queued, working, needs-input) ProjectIssues for a project. Use this to find existing issues that a new Signal might relate to before deciding whether to attach or create a new one.",
      input: v.object({
        projectId: v.string(),
      }),
      name: "list_active_issues",
      async run({ input }) {
        return await client.query(listActiveIssuesRef, {
          organizationId,
          projectId: input.projectId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "Attach a Signal to an existing ProjectIssue. This links the signal's evidence to an open issue. Idempotent: repeating the same attachment is safe and returns the existing relation. Use when the signal's problem clearly relates to an existing active issue.",
      input: v.object({
        issueId: v.string(),
        signalId: v.string(),
      }),
      name: "attach_signal_to_issue",
      async run({ input }) {
        return await client.mutation(attachSignalToIssueRef, {
          issueId: input.issueId,
          organizationId,
          signalId: input.signalId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "Create a new ProjectIssue from a Signal. The signal must be project-scoped. This also auto-attaches the signal to the new issue. Use when the signal's problem does not match any existing active issue.",
      input: v.object({
        signalId: v.string(),
      }),
      name: "create_issue_from_signal",
      async run({ input }) {
        return await client.mutation(createIssueFromSignalRef, {
          organizationId,
          signalId: input.signalId,
          token,
        });
      },
    }),

    defineTool({
      description:
        "Begin working on a ProjectIssue by transitioning it to queued. Use only after the user explicitly confirms they want to start the issue. Returns the new status.",
      input: v.object({
        issueId: v.string(),
      }),
      name: "begin_issue",
      async run({ input }) {
        return await client.mutation(beginIssueRef, {
          issueId: input.issueId,
          organizationId,
          token,
        });
      },
    }),
  ];
};
