#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// ============================================
// CONFIGURATION
// ============================================
const MCP_NAME = "basecamp";
const MCP_VERSION = "1.0.0";

// ============================================
// API CLIENT (OAuth 2.0)
// Basecamp 4 API uses: https://3.basecampapi.com/{account_id}/
// ============================================
class BasecampClient {
  private accessToken: string;
  private accountId: string;
  private baseUrl: string;
  private userAgent: string;

  constructor(accessToken: string, accountId: string, appIdentity: string) {
    this.accessToken = accessToken;
    this.accountId = accountId;
    this.baseUrl = `https://3.basecampapi.com/${accountId}`;
    this.userAgent = appIdentity; // Required: "AppName (contact@email.com)"
  }

  async request(endpoint: string, options: RequestInit = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.accessToken}`,
        "Content-Type": "application/json",
        "User-Agent": this.userAgent,
        ...options.headers,
      },
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Basecamp API error: ${response.status} - ${error}`);
    }

    const text = await response.text();
    return text ? JSON.parse(text) : { success: true };
  }

  async get(endpoint: string) {
    return this.request(endpoint, { method: "GET" });
  }

  async post(endpoint: string, data: any) {
    return this.request(endpoint, {
      method: "POST",
      body: JSON.stringify(data),
    });
  }

  async put(endpoint: string, data: any) {
    return this.request(endpoint, {
      method: "PUT",
      body: JSON.stringify(data),
    });
  }
}

// ============================================
// TOOL DEFINITIONS
// ============================================
const tools = [
  {
    name: "list_projects",
    description: "List all projects in the Basecamp account",
    inputSchema: {
      type: "object" as const,
      properties: {
        status: { 
          type: "string", 
          enum: ["active", "archived", "trashed"],
          description: "Filter by project status (default: active)" 
        },
      },
    },
  },
  {
    name: "get_project",
    description: "Get details of a specific project including its dock (tools)",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
      },
      required: ["project_id"],
    },
  },
  {
    name: "list_todos",
    description: "List to-dos from a to-do list in a project",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
        todolist_id: { type: "number", description: "To-do list ID (required)" },
        status: { 
          type: "string", 
          enum: ["active", "archived", "trashed"],
          description: "Filter by status" 
        },
        completed: { type: "boolean", description: "Filter by completion (true=completed, false=pending)" },
      },
      required: ["project_id", "todolist_id"],
    },
  },
  {
    name: "create_todo",
    description: "Create a new to-do in a to-do list",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
        todolist_id: { type: "number", description: "To-do list ID (required)" },
        content: { type: "string", description: "To-do content/title (required)" },
        description: { type: "string", description: "Rich text description (HTML)" },
        assignee_ids: { 
          type: "array", 
          items: { type: "number" },
          description: "Array of person IDs to assign" 
        },
        due_on: { type: "string", description: "Due date (YYYY-MM-DD)" },
        starts_on: { type: "string", description: "Start date (YYYY-MM-DD)" },
        notify: { type: "boolean", description: "Notify assignees (default: false)" },
      },
      required: ["project_id", "todolist_id", "content"],
    },
  },
  {
    name: "complete_todo",
    description: "Mark a to-do as complete",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
        todo_id: { type: "number", description: "To-do ID (required)" },
      },
      required: ["project_id", "todo_id"],
    },
  },
  {
    name: "list_messages",
    description: "List messages from a project's message board",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
        message_board_id: { type: "number", description: "Message board ID (required, get from project dock)" },
      },
      required: ["project_id", "message_board_id"],
    },
  },
  {
    name: "create_message",
    description: "Create a new message on a project's message board",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (required)" },
        message_board_id: { type: "number", description: "Message board ID (required)" },
        subject: { type: "string", description: "Message subject (required)" },
        content: { type: "string", description: "Message content in HTML (required)" },
        status: { 
          type: "string", 
          enum: ["active", "draft"],
          description: "Post status (default: active)" 
        },
        category_id: { type: "number", description: "Message type/category ID" },
      },
      required: ["project_id", "message_board_id", "subject", "content"],
    },
  },
  {
    name: "list_people",
    description: "List all people in the Basecamp account or a specific project",
    inputSchema: {
      type: "object" as const,
      properties: {
        project_id: { type: "number", description: "Project ID (optional - if provided, lists project members only)" },
      },
    },
  },
];

// ============================================
// TOOL HANDLERS
// ============================================
async function handleTool(client: BasecampClient, name: string, args: any) {
  switch (name) {
    case "list_projects": {
      let endpoint = "/projects.json";
      if (args.status === "archived") {
        endpoint = "/projects/archive.json";
      } else if (args.status === "trashed") {
        endpoint = "/projects/trash.json";
      }
      return await client.get(endpoint);
    }
    case "get_project": {
      const { project_id } = args;
      return await client.get(`/projects/${project_id}.json`);
    }
    case "list_todos": {
      const { project_id, todolist_id, completed } = args;
      let endpoint = `/buckets/${project_id}/todolists/${todolist_id}/todos.json`;
      if (completed === true) {
        endpoint += "?completed=true";
      }
      return await client.get(endpoint);
    }
    case "create_todo": {
      const { project_id, todolist_id, content, description, assignee_ids, due_on, starts_on, notify } = args;
      const payload: any = { content };
      if (description) payload.description = description;
      if (assignee_ids) payload.assignee_ids = assignee_ids;
      if (due_on) payload.due_on = due_on;
      if (starts_on) payload.starts_on = starts_on;
      if (notify !== undefined) payload.notify = notify;
      return await client.post(`/buckets/${project_id}/todolists/${todolist_id}/todos.json`, payload);
    }
    case "complete_todo": {
      const { project_id, todo_id } = args;
      return await client.post(`/buckets/${project_id}/todos/${todo_id}/completion.json`, {});
    }
    case "list_messages": {
      const { project_id, message_board_id } = args;
      return await client.get(`/buckets/${project_id}/message_boards/${message_board_id}/messages.json`);
    }
    case "create_message": {
      const { project_id, message_board_id, subject, content, status, category_id } = args;
      const payload: any = { subject, content };
      if (status) payload.status = status;
      if (category_id) payload.category_id = category_id;
      return await client.post(`/buckets/${project_id}/message_boards/${message_board_id}/messages.json`, payload);
    }
    case "list_people": {
      const { project_id } = args;
      if (project_id) {
        return await client.get(`/projects/${project_id}/people.json`);
      }
      return await client.get("/people.json");
    }
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

// ============================================
// SERVER SETUP
// ============================================
async function main() {
  const accessToken = process.env.BASECAMP_ACCESS_TOKEN;
  const accountId = process.env.BASECAMP_ACCOUNT_ID;
  const appIdentity = process.env.BASECAMP_APP_IDENTITY || "MCPServer (mcp@example.com)";

  if (!accessToken) {
    console.error("Error: BASECAMP_ACCESS_TOKEN environment variable required");
    console.error("Obtain via OAuth 2.0 flow: https://github.com/basecamp/api/blob/master/sections/authentication.md");
    process.exit(1);
  }

  if (!accountId) {
    console.error("Error: BASECAMP_ACCOUNT_ID environment variable required");
    console.error("Find your account ID in the Basecamp URL: https://3.basecamp.com/{ACCOUNT_ID}/");
    process.exit(1);
  }

  const client = new BasecampClient(accessToken, accountId, appIdentity);

  const server = new Server(
    { name: `${MCP_NAME}-mcp`, version: MCP_VERSION },
    { capabilities: { tools: {} } }
  );

  server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools,
  }));

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;
    
    try {
      const result = await handleTool(client, name, args || {});
      return {
        content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        content: [{ type: "text", text: `Error: ${message}` }],
        isError: true,
      };
    }
  });

  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(`${MCP_NAME} MCP server running on stdio`);
}

main().catch(console.error);
