import fs from "fs/promises";
import path from "path";
import { DIST_DIR, defaultMedusaRoutes, defaultMercurRoutes } from "./constants";
import { ensureDir } from "./fs";
import { getRoutes, type RouteInfo } from "./routes";
import { normalizeApiPath, normalizePathSep } from "./path";
import { toCamelCase } from "../utils/to-camel-case";

export { getRoutes, type RouteInfo } from "./routes";
export { recursiveReadDir, pathExists, ensureDir } from "./fs";
export { normalizeApiPath, normalizePathSep } from "./path";
export { DIST_DIR, ROUTE_FILE_PATTERN } from "./constants";

function generateImportPath(filePath: string): string {
    return `../../src/api/${filePath.replace(/\.ts$/, "")}`;
}

/**
 * Convert path segment to nested key
 * - ":param" → "$param"
 * - "kebab-case" → "camelCase"
 */
function toNestedKey(segment: string): string {
    if (segment.startsWith(":")) {
        return `$${toCamelCase(segment.slice(1))}`;
    }
    return toCamelCase(segment);
}

interface RouteNode {
    importType?: string;
    children: Map<string, RouteNode>;
}

function createRouteNode(): RouteNode {
    return { children: new Map() };
}

/**
 * Build a tree structure from flat routes
 */
function buildRouteTree(routeMap: Map<string, string>): RouteNode {
    const root = createRouteNode();

    for (const [route, importType] of routeMap) {
        const segments = route.split("/").filter(Boolean);
        let current = root;

        for (const segment of segments) {
            const key = toNestedKey(segment);
            if (!current.children.has(key)) {
                current.children.set(key, createRouteNode());
            }
            current = current.children.get(key)!;
        }

        current.importType = importType;
    }

    return root;
}

/**
 * Generate TypeScript type string from route tree
 */
function generateTypeFromTree(node: RouteNode, indent: string = "    "): string {
    const parts: string[] = [];

    // Add import type if this node is an endpoint
    if (node.importType) {
        parts.push(node.importType);
    }

    // Add children as nested object
    if (node.children.size > 0) {
        const childEntries: string[] = [];
        for (const [key, child] of node.children) {
            const childType = generateTypeFromTree(child, indent + "    ");
            childEntries.push(`${indent}    ${key}: ${childType}`);
        }
        parts.push(`{\n${childEntries.join(";\n")};\n${indent}}`);
    }

    // Combine with intersection if both endpoint and children exist
    if (parts.length === 0) {
        return "{}";
    }
    if (parts.length === 1) {
        return parts[0];
    }
    return parts.join(" & ");
}

export function generateRouteTypesFile(routes: RouteInfo[], importPathFn?: (filePath: string) => string): string {
    // Use Map to handle route deduplication (user routes override default routes)
    const routeMap = new Map<string, string>();

    // First, add default Medusa routes
    for (const [route, importType] of Object.entries(defaultMedusaRoutes)) {
        routeMap.set(route, importType);
    }

    // Second, add default Mercur routes
    for (const [route, importType] of Object.entries(defaultMercurRoutes)) {
        routeMap.set(route, importType);
    }

    const resolveImportPath = importPathFn ?? generateImportPath;

    // Then, override with user's custom routes (user takes priority)
    for (const route of routes) {
        const importPath = resolveImportPath(route.filePath);
        routeMap.set(route.route, `typeof import("${importPath}")`);
    }

    // Build nested tree structure
    const tree = buildRouteTree(routeMap);

    // Generate nested type
    const routesType = generateTypeFromTree(tree, "");

    return `// This file is generated automatically by Mercur CLI
// Do not edit this file manually

export type Routes = ${routesType};
`;
}

export async function writeRouteTypes(rootDir: string) {
    const entryFilePath = path.join(rootDir, DIST_DIR, "index.d.ts");
    const apiDir = path.join(rootDir, "src", "api");
    const entryDir = path.dirname(entryFilePath);

    await ensureDir(entryDir);

    const routes = await getRoutes(apiDir);
    const routeTypes = generateRouteTypesFile(routes);

    await fs.writeFile(entryFilePath, routeTypes, "utf-8");
}

export async function writeRegistryRouteTypes(rootDir: string, routeFilePaths: string[]) {
    const entryFilePath = path.join(rootDir, DIST_DIR, "index.ts");
    const entryDir = path.dirname(entryFilePath);

    await ensureDir(entryDir);

    const allRoutes: RouteInfo[] = routeFilePaths.map((filePath) => {
        const apiIndex = filePath.indexOf("/api/");
        const relativePath = apiIndex !== -1 ? filePath.slice(apiIndex + "/api/".length) : filePath;
        return {
            filePath,
            route: normalizeApiPath(normalizePathSep(relativePath)),
        };
    });

    const registryImportPath = (filePath: string) =>
        `../../src/${filePath.replace(/\.ts$/, "")}`;

    const routeTypes = generateRouteTypesFile(allRoutes, registryImportPath);

    await fs.writeFile(entryFilePath, routeTypes, "utf-8");
}
