#!/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";
import { z } from "zod";

// Import iolin data using new API pattern
import type { Entity } from "@zerospacegg/iolin";
import { allEntities } from "@zerospacegg/iolin/meta/all";
import type { Summary } from "@zerospacegg/iolin/meta/search-index";
import { getIndexedEntities, getSearchIndex } from "@zerospacegg/iolin/meta/search-index";
import { tags as tagsInfo } from "@zerospacegg/iolin/meta/tags";
import createDebug from "debug";

// Debug namespaces
const debug = createDebug("anrubic:server");
const debugLore = createDebug("anrubic:lore");
const debugPkl = createDebug("anrubic:pkl");
const debugError = createDebug("anrubic:error");

// TODO: Import clean mechanics service after porting to new iolin
// import { mechanicsService } from "./mechanics-service.js";

// Tagged entities mapping (tag -> entity IDs)
type TaggedEntities = Record<string, string[]>;
let taggedEntitiesCache: TaggedEntities | null = null;

// Lore index cache (independent of iolin)
let loreIndexCache: LoreIndex | null = null;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// TODO: Implement tag-to-entity mapping in new iolin API
async function getTaggedEntities(): Promise<TaggedEntities> {
  return {};
}

// Deprecated - use mechanicsService instead

async function getLoreIndex(): Promise<LoreIndex | null> {
  if (!loreIndexCache) {
    try {
      // Try dist/ first (production), then fall back to dev path
      const prodLorePath = path.join(__dirname, "zerospace_lore.md");
      const devLorePath = path.join(__dirname, "..", "..", "docs", "zerospace_lore.md");
      
      let loreFilePath = prodLorePath;
      try {
        fs.accessSync(prodLorePath);
        debugLore(`Using production lore file: ${prodLorePath}`);
      } catch {
        loreFilePath = devLorePath;
        debugLore(`Using development lore file: ${devLorePath}`);
      }
      
      debugLore(`Loading lore index from: ${loreFilePath}`);
      const startTime = Date.now();
      loreIndexCache = buildLoreIndex(loreFilePath);
      const buildTime = Date.now() - startTime;
      debugLore(
        `Lore index built in ${buildTime}ms (${loreIndexCache.metadata.totalChunks} chunks, ${loreIndexCache.metadata.totalTokens} tokens)`,
      );
    } catch (error) {
      debugError("Failed to load lore index:", error);
      return null;
    }
  }
  return loreIndexCache;
}

// TODO: Import Pkl evaluator after porting to new iolin
// import {
//   executeCombatAnalysis,
//   executeEmperorCalculation,
//   executeUnitComparison,
//   findCounters,
//   getPklHealth,
//   initializePkl,
//   isPklAvailable,
// } from "./pkl-evaluator.js";

// Import lore indexer (independent of iolin)
import path from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import { buildLoreIndex, LoreIndex, searchLore } from "./lore-indexer.js";

// Create the MCP server
const server = new Server(
  {
    name: "anrubic",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  },
);

// Initialize Pkl on startup
// TODO: PKL replaced with TypeScript in new iolin
// let pklInitialized = false;
// async function ensurePklInitialized() {
//   if (!pklInitialized) {
//     const result = await initializePkl();
//     pklInitialized = true;
//     debug("New iolin TypeScript system ready!");
//   }
// }

server.setRequestHandler(ListToolsRequestSchema, async () => {
  // TODO: No PKL initialization needed with new iolin

  const basicTools = [
    {
      name: "get_tags",
      description: "Get all available tags for filtering and discovery",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    {
      name: "search_entities",
      description: "Search for game entities (units, buildings, etc.) by name or description",
      inputSchema: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "Search term to find entities",
          },
          limit: {
            type: "number",
            description: "Maximum number of results to return (default: 10)",
            default: 10,
          },
        },
        required: ["query"],
      },
    },
    {
      name: "find_by_tags",
      description: "Find entities that match specific tags",
      inputSchema: {
        type: "object",
        properties: {
          tags: {
            type: "array",
            items: { type: "string" },
            description: "Array of tags to search for",
          },
          matchAll: {
            type: "boolean",
            description: "Whether to match all tags (true) or any tag (false)",
            default: true,
          },
          limit: {
            type: "number",
            description: "Maximum number of results to return (default: 20)",
            default: 20,
          },
        },
        required: ["tags"],
      },
    },
    {
      name: "fetch_entity_by_id",
      description: "Fetch a specific entity by its ID",
      inputSchema: {
        type: "object",
        properties: {
          id: {
            type: "string",
            description: "Entity ID to fetch",
          },
        },
        required: ["id"],
      },
    },
    {
      name: "compare_units",
      description: "Compare two units side by side with basic stats",
      inputSchema: {
        type: "object",
        properties: {
          unitA: {
            type: "string",
            description: "Name or ID of first unit",
          },
          unitB: {
            type: "string",
            description: "Name or ID of second unit",
          },
        },
        required: ["unitA", "unitB"],
      },
    },
    {
      name: "get_faction_data",
      description: "Get comprehensive data for a specific faction",
      inputSchema: {
        type: "object",
        properties: {
          faction: {
            type: "string",
            description: "Faction name (e.g., 'grell', 'protectorate', 'legion')",
          },
        },
        required: ["faction"],
      },
    },
    {
      name: "get_tech_tree",
      description: "Get tech tree and building requirements for a faction",
      inputSchema: {
        type: "object",
        properties: {
          faction: {
            type: "string",
            description: "Faction name (optional, returns all if not specified)",
          },
        },
      },
    },
    {
      name: "all_ids",
      description: "Get all entity IDs, optionally filtered by type and/or faction",
      inputSchema: {
        type: "object",
        properties: {
          type: {
            type: "string",
            description: "Entity type filter (e.g., 'unit', 'building')",
          },
          faction: {
            type: "string",
            description: "Faction filter",
          },
        },
      },
    },
    {
      name: "check_lore",
      description: "Check which entities have lore data available and get lore statistics",
      inputSchema: {
        type: "object",
        properties: {
          includePreview: {
            type: "boolean",
            description: "Include short lore previews in results (default: false)",
            default: false,
          },
          type: {
            type: "string",
            description: "Filter by entity type (e.g., 'faction', 'unit', 'building')",
          },
          faction: {
            type: "string",
            description: "Filter by faction name",
          },
        },
      },
    },
    {
      name: "search_lore",
      description: "Search ZeroSpace lore for specific topics, characters, or concepts",
      inputSchema: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "Search query - can be single words, phrases, or concepts (e.g., 'blood magic', 'Leviathan', 'ancient history')",
          },
          limit: {
            type: "number",
            description: "Maximum number of results to return (default: 5)",
            default: 5,
          },
        },
        required: ["query"],
      },
    },
    {
      name: "get_lore_stats",
      description: "Get statistics about the lore database for debugging and exploration",
      inputSchema: {
        type: "object",
        properties: {},
        required: [],
      },
    },
    // TODO: Re-enable after fixing mechanics in iolin itself
    // {
    //   name: "get_mechanics",
    //   description: "Get ZeroSpace game mechanics documentation and explanations",
    //   inputSchema: {
    //     type: "object",
    //     properties: {
    //       term: {
    //         type: "string",
    //         description: "Optional specific mechanic term to retrieve (e.g., 'ABES', 'Biomass')",
    //       },
    //     },
    //     required: [],
    //   },
    // },
  ];

  // TODO: Add TypeScript-powered combat tools from new iolin later
  const pklTools: any[] = [];

  return {
    tools: [...basicTools, ...pklTools],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  // TODO: No PKL initialization needed with new iolin

  const { name, arguments: args } = request.params;

  try {
    switch (name) {

      case "get_tags": {
        const tagList = Object.entries(tagsInfo).map(([tag, info]: [string, any]) => ({
          tag,
          label: info.label,
          description: info.description,
          slug: info.slug,
        }));

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(tagList, null, 2),
            },
          ],
        };
      }

      case "search_entities": {
        const schema = z.object({
          query: z.string(),
          limit: z.number().optional().default(10),
        });
        const { query, limit } = schema.parse(args);

        const entities = allEntities();
        const results = entities
          .filter(
            (entity: any) =>
              entity.name.toLowerCase().includes(query.toLowerCase()) ||
              (entity.description &&
                entity.description.toLowerCase().includes(query.toLowerCase())),
          )
          .slice(0, limit);

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  query,
                  totalMatches: results.length,
                  results,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "find_by_tags": {
        const schema = z.object({
          tags: z.array(z.string()),
          matchAll: z.boolean().optional().default(true),
          limit: z.number().optional().default(20),
        });
        const { tags, matchAll, limit } = schema.parse(args);

        // Get the tagged entities mapping
        const taggedEntities = await getTaggedEntities();

        // Use the tagged entities mapping instead of checking entity.tags
        let matchingEntityIds: Set<string>;

        if (matchAll) {
          // For matchAll=true, find entities that have ALL tags
          const tagEntitySets = tags.map((tag) => new Set(taggedEntities[tag] || []));

          if (tagEntitySets.length === 0) {
            matchingEntityIds = new Set();
          } else {
            // Start with first tag's entities
            matchingEntityIds = new Set(tagEntitySets[0]);
            // Intersect with each subsequent tag's entities
            for (let i = 1; i < tagEntitySets.length; i++) {
              matchingEntityIds = new Set(
                [...matchingEntityIds].filter((id) => tagEntitySets[i].has(id)),
              );
            }
          }
        } else {
          // For matchAll=false, find entities that have ANY tag
          matchingEntityIds = new Set();
          for (const tag of tags) {
            const entityIdsForTag = taggedEntities[tag] || [];
            entityIdsForTag.forEach((id: string) => matchingEntityIds.add(id));
          }
        }

        // Convert to array and slice for limit
        const matchingIds = Array.from(matchingEntityIds).slice(0, limit);

        // Get the actual entities
        const allEntitiesMap = allEntities();
        const results = matchingIds.map((id) => allEntitiesMap.find(e => e.id === id)).filter(Boolean);

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  searchTags: tags,
                  matchAll,
                  totalMatches: matchingEntityIds.size,
                  resultsReturned: results.length,
                  results,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "fetch_entity_by_id": {
        const schema = z.object({
          id: z.string(),
        });
        const { id } = schema.parse(args);

        // Try to get full entity data first, fallback to summary if not found
        const fullEntity = allEntities().find(e => e.id === id);
        if (fullEntity) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(fullEntity, null, 2),
              },
            ],
          };
        }

        // Fallback to summary data if full entity not found
        const entity = allEntities().find(e => e.id === id);
        if (!entity) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify({ error: `Entity not found: ${id}` }, null, 2),
              },
            ],
          };
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(entity, null, 2),
            },
          ],
        };
      }

      case "compare_units": {
        const schema = z.object({
          unitA: z.string(),
          unitB: z.string(),
        });
        const { unitA, unitB } = schema.parse(args);

        // Simple comparison using JSON data
        const entities = allEntities();
        const findUnit = (name: string) =>
          entities.find(
            (entity: any) => entity.name.toLowerCase() === name.toLowerCase() || entity.id === name,
          );

        const unit1 = findUnit(unitA);
        const unit2 = findUnit(unitB);

        if (!unit1 || !unit2) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(
                  {
                    error: "One or both units not found",
                    unitA: unit1 ? "found" : "not found",
                    unitB: unit2 ? "found" : "not found",
                  },
                  null,
                  2,
                ),
              },
            ],
          };
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  comparison: "basic",
                  unitA: unit1,
                  unitB: unit2,
                  note: "PKL system replaced with TypeScript - basic comparison only",
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "get_faction_data": {
        const schema = z.object({
          faction: z.string(),
        });
        const { faction } = schema.parse(args);

        const entities: Entity[] = allEntities();
        const factionUnits = entities.filter(
          (e) => e.id.includes("/unit/") && e.faction === faction,
        );
        const factionBuildings = entities.filter(
          (e) => e.id.includes("/building/") && e.faction === faction,
        );

        if (factionUnits.length === 0 && factionBuildings.length === 0) {
          const availableFactions = [...new Set(entities.map((e) => e.faction).filter(Boolean))];
          return {
            content: [
              {
                type: "text",
                text: `No data found for faction "${faction}". Available factions: ${availableFactions.join(
                  ", ",
                )}`,
              },
            ],
          };
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  faction,
                  units: factionUnits,
                  buildings: factionBuildings,
                  totalEntities: factionUnits.length + factionBuildings.length,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "get_tech_tree": {
        const schema = z.object({
          faction: z.string().optional(),
        });
        const { faction } = schema.parse(args);

        const entities = allEntities();
        let buildingData = entities.filter((entity: any) => entity.type === "building");

        if (faction) {
          buildingData = buildingData.filter((entity: any) => entity.faction === faction);
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  faction: faction || "all",
                  buildings: buildingData,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "all_ids": {
        const schema = z.object({
          type: z.string().optional(),
          faction: z.string().optional(),
        });
        const { type, faction } = schema.parse(args);

        let entities = allEntities();

        if (type) {
          entities = entities.filter((entity: any) => entity.type === type);
        }

        if (faction) {
          entities = entities.filter((entity: any) => entity.faction === faction);
        }

        const ids = entities.map((entity: any) => entity.id);

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  filters: { type, faction },
                  totalIds: ids.length,
                  ids,
                },
                null,
                2,
              ),
            },
          ],
        };
      }

      case "check_lore": {
        const schema = z.object({
          includePreview: z.boolean().optional().default(false),
          type: z.string().optional(),
          faction: z.string().optional(),
        });
        const { includePreview, type, faction } = schema.parse(args);

        let entities = allEntities();

        // Filter by type if specified
        if (type) {
          entities = entities.filter((entity: any) => entity.type === type);
        }

        // Filter by faction if specified
        if (faction) {
          entities = entities.filter((entity: any) => entity.faction === faction);
        }

        // Separate entities with and without lore
        const entitiesWithLore = entities.filter((entity: any) => entity.hasLore === true);
        const entitiesWithoutLore = entities.filter((entity: any) => entity.hasLore !== true);

        // Group entities with lore by type
        const loreByType: Record<string, any[]> = entitiesWithLore.reduce(
          (acc: Record<string, any[]>, entity: any) => {
            if (!acc[entity.type]) {
              acc[entity.type] = [];
            }
            acc[entity.type].push(entity);
            return acc;
          },
          {},
        );

        // Create summary
        const summary = {
          totalEntities: entities.length,
          entitiesWithLore: entitiesWithLore.length,
          entitiesWithoutLore: entitiesWithoutLore.length,
          coverage:
            entities.length > 0
              ? Math.round((entitiesWithLore.length / entities.length) * 100)
              : 0,
          typeBreakdown: Object.keys(loreByType).reduce((acc: any, type: string) => {
            acc[type] = loreByType[type].length;
            return acc;
          }, {}),
        };

        // Format entities with lore
        const entitiesData = entitiesWithLore
          .map((entity: any) => ({
            id: entity.id,
            name: entity.name,
            faction: entity.faction,
            type: entity.type,
            subtype: entity.subtype,
            tier: entity.tier,
            slug: entity.slug,
            hasLore: entity.hasLore,
          }))
          .sort((a: any, b: any) => a.name.localeCompare(b.name));

        const response = {
          summary,
          entitiesByType: Object.keys(loreByType).reduce((acc: any, type: string) => {
            acc[type] = loreByType[type]
              .map((entity: any) => ({
                id: entity.id,
                name: entity.name,
                faction: entity.faction,
                subtype: entity.subtype,
                tier: entity.tier,
                hasLore: entity.hasLore,
              }))
              .sort((a: any, b: any) => a.name.localeCompare(b.name));
            return acc;
          }, {}),
          allEntitiesWithLore: entitiesData,
          entitiesWithoutLore: entitiesWithoutLore
            .map((entity: any) => ({
              id: entity.id,
              name: entity.name,
              faction: entity.faction,
              type: entity.type,
              subtype: entity.subtype,
              tier: entity.tier,
            }))
            .sort((a: any, b: any) => a.name.localeCompare(b.name)),
        };

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(response, null, 2),
            },
          ],
        };
      }

      case "search_lore": {
        const loreIndex = await getLoreIndex();
        if (!loreIndex) {
          return {
            content: [
              {
                type: "text",
                text: "Lore index not available - could not load lore data",
              },
            ],
          };
        }

        const schema = z.object({
          query: z.string(),
          limit: z.number().optional().default(5),
        });
        const { query, limit } = schema.parse(args);

        const results = searchLore(loreIndex, query, limit);

        if (results.length === 0) {
          return {
            content: [
              {
                type: "text",
                text: `No lore found for query: "${query}"`,
              },
            ],
          };
        }

        const response = {
          query,
          totalResults: results.length,
          results: results.map((result) => ({
            title: result.chunk.title,
            section: result.chunk.section,
            subsection: result.chunk.subsection,
            score: Math.round(result.score * 100) / 100,
            matchedTokens: result.matchedTokens,
            lineRange: `${result.chunk.startLine}-${result.chunk.endLine}`,
            content: result.chunk.content,
            context: result.context,
          })),
        };

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(response, null, 2),
            },
          ],
        };
      }

      case "get_lore_stats": {
        const loreIndex = await getLoreIndex();
        if (!loreIndex) {
          return {
            content: [
              {
                type: "text",
                text: "ERROR: Lore index not available",
              },
            ],
          };
        }

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(loreIndex.metadata, null, 2),
            },
          ],
        };
      }

      // TODO: Re-enable after porting mechanics service to new iolin
      // case "get_mechanics": {
      //   try {
      //     const { term } = request.params.arguments as { term?: string };
      //     // ... (mechanics service implementation)
      //   } catch (error) {
      //     debugError("Error in get_mechanics handler:", error);
      //     return { content: [{ type: "text", text: `ERROR: Mechanics service not available` }] };
      //   }
      // }

      default:
        throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: `Error: ${error instanceof Error ? error.message : String(error)}`,
        },
      ],
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  debug("Anrubic MCP server running with Pkl integration support!");
}

main().catch((error) => {
  debugError("Failed to start server:", error);
  process.exit(1);
});
