import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
initStderrTee("waitlist");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { waitlistScan } from "./tools/waitlist-scan.js";
import { waitlistPersist } from "./tools/waitlist-persist.js";
import { waitlistList } from "./tools/waitlist-list.js";
import { waitlistReview } from "./tools/waitlist-review.js";
import { waitlistSetup } from "./tools/waitlist-setup.js";
import { waitlistHeal } from "./tools/waitlist-heal.js";
import { closeDriver } from "./lib/neo4j.js";
// Cast to `any` to avoid TS2589. TypeScript hits its 50-level instantiation
// limit when accumulating typed tool signatures on a generic McpServer.
// Individual tool handlers retain full type safety in their own files.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const server = new McpServer({
    name: "maxy-waitlist",
    version: "0.1.0",
});
const accountId = process.env.ACCOUNT_ID;
if (!accountId) {
    throw new Error("ACCOUNT_ID environment variable is required");
}
// ---------------------------------------------------------------------------
// waitlist-scan
// ---------------------------------------------------------------------------
server.tool("waitlist-scan", "Scan public agent conversations and return structured transcripts. Used as step 1 of the waitlist extraction workflow — gathers conversation data for the LLM extraction step. Returns JSON with conversation IDs and user messages.", {}, async () => {
    try {
        const result = await waitlistScan(accountId);
        return {
            content: [{ type: "text", text: JSON.stringify(result) }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Scan failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// waitlist-persist
// ---------------------------------------------------------------------------
server.tool("waitlist-persist", "Persist LLM extraction results as Person nodes. Used as step 3 of the waitlist extraction workflow — takes structured JSON from the LLM step and creates/updates Person nodes idempotently via MERGE. Validates emails against source conversations (hallucination guard), merges discovery notes, computes embeddings.", {
    extractions: z.unknown().describe("JSON array of extraction results from the LLM step"),
    conversations: z.unknown().describe("Conversation data from the scan step (for email validation)"),
}, async (params) => {
    try {
        const result = await waitlistPersist({
            accountId,
            extractions: params.extractions,
            conversations: params.conversations,
        });
        const parts = [`Processed ${result.processed} extractions`];
        if (result.created > 0)
            parts.push(`${result.created} new entries created`);
        if (result.updated > 0)
            parts.push(`${result.updated} entries updated`);
        if (result.flaggedForReview > 0)
            parts.push(`${result.flaggedForReview} flagged for review (email only)`);
        if (result.existing > 0)
            parts.push(`${result.existing} already existed`);
        if (result.withDiscoveryNotes > 0)
            parts.push(`${result.withDiscoveryNotes} with discovery notes`);
        if (result.filtered > 0)
            parts.push(`${result.filtered} filtered (invalid/hallucinated)`);
        if (result.errors > 0)
            parts.push(`${result.errors} errors`);
        if (result.processed === 0)
            parts.push("no extractions to process");
        return {
            content: [{ type: "text", text: parts.join(". ") + "." }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Persist failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// waitlist-list
// ---------------------------------------------------------------------------
server.tool("waitlist-list", "List waitlist entries with discovery notes. Returns Person nodes created by the extraction process (source=waitlist). Shows total count, how many need review, and customer discovery intelligence (business type, needs, challenges, interests) when available.", {
    status: z
        .string()
        .optional()
        .describe("Filter by status (waitlist, review, approved, rejected)"),
    since: z
        .string()
        .optional()
        .describe("ISO date — only entries created after this date"),
    limit: z
        .number()
        .int()
        .optional()
        .describe("Max results (default 50)"),
}, async (params) => {
    try {
        const result = await waitlistList({ ...params, accountId });
        if (result.entries.length === 0) {
            return {
                content: [{ type: "text", text: "No waitlist entries found." }],
            };
        }
        const header = `${result.total} waitlist entries (${result.reviewCount} needing review):\n\n`;
        const formatted = result.entries
            .map((e) => {
            let line = `${e.givenName}${e.familyName ? ` ${e.familyName}` : ""} <${e.email}> — ${e.status} (${e.createdOn})`;
            if (e.extractedOn)
                line += ` [extracted: ${e.extractedOn}]`;
            if (e.discoveryNotes) {
                try {
                    const notes = JSON.parse(e.discoveryNotes);
                    const parts = [];
                    if (notes.businessType)
                        parts.push(`business: ${notes.businessType}`);
                    if (notes.role)
                        parts.push(`role: ${notes.role}`);
                    if (notes.needs)
                        parts.push(`needs: ${notes.needs}`);
                    if (notes.challenges)
                        parts.push(`challenges: ${notes.challenges}`);
                    if (notes.interests)
                        parts.push(`interests: ${notes.interests}`);
                    if (notes.engagementQuality)
                        parts.push(`engagement: ${notes.engagementQuality}`);
                    if (notes.otherSignals)
                        parts.push(`signals: ${notes.otherSignals}`);
                    if (parts.length > 0)
                        line += `\n  Discovery: ${parts.join("; ")}`;
                }
                catch {
                    // Malformed discoveryNotes — skip display
                }
            }
            return line;
        })
            .join("\n");
        return {
            content: [{ type: "text", text: header + formatted }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `List failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// waitlist-review
// ---------------------------------------------------------------------------
server.tool("waitlist-review", "Update a waitlist entry and view discovery notes. Find by email, node ID, or name (partial, case-insensitive). Returns the entry's customer discovery intelligence (business type, needs, challenges, interests) alongside the status update. Use to resolve entries flagged for review (email-only, no name) by adding a name and setting the appropriate status. Also use to approve or reject entries. If status is omitted, the current status is preserved.", {
    email: z.string().optional().describe("Find entry by email address"),
    nodeId: z.string().optional().describe("Find entry by Neo4j node ID"),
    name: z.string().optional().describe("Find entry by name (partial match, case-insensitive)"),
    status: z
        .string()
        .optional()
        .describe("New status (e.g. waitlist, approved, rejected). Omit to keep current status."),
    givenName: z.string().optional().describe("Update first name (for review entries)"),
    familyName: z.string().optional().describe("Update last name"),
}, async (params) => {
    try {
        const result = await waitlistReview({ ...params, accountId });
        let text = `Updated ${result.email}: ${result.previousStatus} → ${result.newStatus}`;
        if (result.extractedOn)
            text += ` [extracted: ${result.extractedOn}]`;
        if (result.discoveryNotes) {
            try {
                const notes = JSON.parse(result.discoveryNotes);
                const parts = [];
                if (notes.businessType)
                    parts.push(`business: ${notes.businessType}`);
                if (notes.role)
                    parts.push(`role: ${notes.role}`);
                if (notes.needs)
                    parts.push(`needs: ${notes.needs}`);
                if (notes.challenges)
                    parts.push(`challenges: ${notes.challenges}`);
                if (notes.interests)
                    parts.push(`interests: ${notes.interests}`);
                if (notes.engagementQuality)
                    parts.push(`engagement: ${notes.engagementQuality}`);
                if (notes.otherSignals)
                    parts.push(`signals: ${notes.otherSignals}`);
                if (parts.length > 0)
                    text += `\nDiscovery: ${parts.join("; ")}`;
            }
            catch {
                // Malformed discoveryNotes — skip display
            }
        }
        return {
            content: [{ type: "text", text }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Review failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// waitlist-setup
// ---------------------------------------------------------------------------
server.tool("waitlist-setup", "Set up automatic waitlist extraction as a workflow. Creates a 3-step workflow (scan conversations → LLM extraction → persist Person nodes) and a recurring scheduled event that triggers it. The extraction prompt is visible via workflow-get and editable via workflow-update. Default schedule: every 4 hours. If the workflow already exists, returns its details. Cleans up any old-style extraction events automatically.", {
    recurrence: z
        .string()
        .optional()
        .describe("Cron expression (5-field: min hour dom mon dow). Default: '0 */4 * * *' (every 4 hours)"),
    timezone: z
        .string()
        .optional()
        .describe("IANA timezone (e.g. Europe/London). Defaults to server timezone."),
}, async (params) => {
    try {
        const result = await waitlistSetup({ ...params, accountId });
        if (result.alreadyExists) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Waitlist extraction already configured (workflow: ${result.workflowId}, event: ${result.eventId}, recurrence: ${result.recurrence}, next run: ${result.nextRun}). View the extraction prompt with workflow-get. To change the schedule, cancel the event with schedule-cancel then run waitlist-setup again.`,
                    },
                ],
            };
        }
        return {
            content: [
                {
                    type: "text",
                    text: `Waitlist extraction workflow created and scheduled: every ${result.recurrence} (workflow: ${result.workflowId}, event: ${result.eventId}, next run: ${result.nextRun}). View or edit the extraction prompt with workflow-get / workflow-update.`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Setup failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// waitlist-heal
// ---------------------------------------------------------------------------
server.tool("waitlist-heal", "Heal broken prompt patterns in the waitlist extraction workflow. Idempotent — safe to call on every heartbeat cycle. Reads the LLM step's prompt, replaces known broken patterns with the correct versions, and writes back only if something changed. Returns immediately with no changes when the prompt is already healthy.", {}, async () => {
    try {
        const result = await waitlistHeal({ accountId });
        if (result.healed) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Prompt healed for workflow ${result.workflowId}`,
                    },
                ],
            };
        }
        return {
            content: [
                {
                    type: "text",
                    text: result.workflowId
                        ? "No healing needed — prompt is healthy"
                        : "No waitlist workflow found — nothing to heal",
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Heal failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// Shutdown + start
// ---------------------------------------------------------------------------
process.on("SIGINT", async () => {
    await closeDriver();
    process.exit(0);
});
const transport = new StdioServerTransport();
await server.connect(transport);
//# sourceMappingURL=index.js.map