// lib/content-loader.ts
import fg from 'fast-glob';
import fs from 'fs/promises';
import matter from 'gray-matter';
import { z } from 'zod';
import path from 'path';

// Shared schemas used across multiple files
export const badge = z.object({
    content: z.string(),
    backgroundColor: z.string(),
    textColor: z.string(),
    icon: z.string().optional(),
});

// Create a union type for owners
export const ownerReference = z
    .union([
        // The ID of the user or team
        z.string(),
        // The full object with the ID and collection (keep compatibility with `reference`)
        z.object({
            id: z.string(),
            collection: z.enum(['users', 'teams']),
        }),
    ])
    .transform(
        // This transformation is needed to keep compatibility with `reference`.
        // The utilities `getTeams` and `getUsers` rely on this transformation.
        (lookup) => ({ id: typeof lookup === 'string' ? lookup : lookup.id })
    );



const resourcePointer = z.object({
    id: z.string(),
    version: z.string().optional().default('latest'),
    type: z.enum(['service', 'event', 'command', 'query', 'flow', 'channel', 'domain', 'user', 'team']),
});

const pointer = z.object({
    id: z.string(),
    version: z.string().optional().default('latest'),
});

const baseSchema = z.object({
    id: z.string(),
    name: z.string(),
    summary: z.string().optional(),
    version: z.string(),
    draft: z.union([z.boolean(), z.object({ title: z.string().optional(), message: z.string() })]).optional(),
    badges: z.array(badge).optional(),
    owners: z.array(ownerReference).optional(),
    schemaPath: z.string().optional(),
    sidebar: z
        .object({
            label: z.string().optional(),
            badge: z.string().optional(),
        })
        .optional(),
    repository: z
        .object({
            language: z.string().optional(),
            url: z.string().optional(),
        })
        .optional(),
    specifications: z
        .union([
            z.object({
                openapiPath: z.string().optional(),
                asyncapiPath: z.string().optional(),
            }),
            z.array(
                z.object({
                    type: z.enum(['openapi', 'asyncapi']),
                    path: z.string(),
                    name: z.string().optional(),
                })
            ),
        ])
        .optional(),
    hidden: z.boolean().optional(),
    editUrl: z.string().optional(),
    resourceGroups: z
        .array(
            z.object({
                id: z.string().optional(),
                title: z.string().optional(),
                items: z.array(resourcePointer),
                limit: z.number().optional().default(10),
                sidebar: z.boolean().optional().default(true),
            })
        )
        .optional(),
    styles: z
        .object({
            icon: z.string().optional(),
            node: z
                .object({
                    color: z.string().optional(),
                    label: z.string().optional(),
                })
                .optional(),
        })
        .optional(),
    deprecated: z
        .union([
            z.object({
                message: z.string().optional(),
                date: z.union([z.string(), z.date()]).optional(),
            }),
            z.boolean().optional(),
        ])
        .optional(),
    visualiser: z.boolean().optional(),
    // Used by eventcatalog
    versions: z.array(z.string()).optional(),
    latestVersion: z.string().optional(),
    catalog: z
        .object({
            path: z.string(),
            filePath: z.string(),
            astroContentFilePath: z.string(),
            publicPath: z.string(),
            type: z.string(),
        })
        .optional(),
});

const domainSchema = baseSchema.extend({
    services: z.array(pointer).optional(),
    domains: z.array(pointer).optional(),
    entities: z.array(pointer).optional(),
});

const messageSchema = baseSchema.extend({});

const serviceSchema = baseSchema.extend({
    sends: z.array(pointer).optional(),
    receives: z.array(pointer).optional(),
    entities: z.array(pointer).optional(),
});

export async function getAllEventCatalogResources() {

    const eventCatalogDir = await getEventCatalogPath();

    const events = await fg(['**/events/*/index.(md|mdx)', '**/events/*/versioned/*/index.(md|mdx)'], {
        cwd: eventCatalogDir,
        absolute: true,
    }).then(paths => paths.map(path => ({
        path, collection: 'events', schema: messageSchema, editor: {
            type: 'event',
        }
    })));

    const commands = await fg(['**/commands/*/index.(md|mdx)', '**/commands/*/versioned/*/index.(md|mdx)'], {
        cwd: eventCatalogDir,
        absolute: true,
    }).then(paths => paths.map(path => ({
        path, collection: 'commands', schema: messageSchema, editor: {
            type: 'command',
        }
    })));

    const queries = await fg(['**/queries/*/index.(md|mdx)', '**/queries/*/versioned/*/index.(md|mdx)'], {
        cwd: eventCatalogDir,
        absolute: true,
    }).then(paths => paths.map(path => ({
        path, collection: 'queries', schema: messageSchema, editor: {
            type: 'query',
        }
    })));

    const services = await fg([
        'domains/*/services/*/index.(md|mdx)',
        'domains/*/services/*/versioned/*/index.(md|mdx)',

        // Capture subdomain folders
        'domains/*/subdomains/*/services/*/index.(md|mdx)',
        'domains/*/subdomains/*/services/*/versioned/*/index.(md|mdx)',

        // Capture services in the root
        'services/*/index.(md|mdx)', // ✅ Capture only services markdown files
        'services/*/versioned/*/index.(md|mdx)', // ✅ Capture versioned files inside services
    ], {
        cwd: eventCatalogDir,
        absolute: true,
    }).then(paths => paths.map(path => ({
        path, collection: 'services', schema: serviceSchema, editor: {
            type: 'service',
        }
    })));


    const allResources = [...events, ...commands, ...queries, ...services];

    const resources = await Promise.all(
        allResources.map(async ({ path, collection, schema, editor }) => {
            const raw = await fs.readFile(path, 'utf-8');
            const { data, content } = matter(raw);
            const parsed = schema.safeParse(data);

            if (!parsed.success) {
                throw new Error(`Invalid frontmatter in ${path}`);
            }

            return {
                id: parsed.data.id,
                data: {
                    ...parsed.data,
                },
                editor,
                // body: content,
                collection,
                filePath: path,
            };
        })
    );

    return resources;
}

export async function getStudioFilesFromEventCatalogDirectory(): Promise<{ id: string, creationDate: string }[]> {
    const eventCatalogDir = await getEventCatalogPath();

    const studioFiles = await fg(['**/*/*.ecstudio'], {
        cwd: eventCatalogDir,
        absolute: true,
    })

    const parsedStudioFiles = await Promise.all(studioFiles.map(async (path) => {
        try {
            const content = await fs.readFile(path, 'utf-8');
            const parsed = JSON.parse(content);
            return {
                id: parsed.id,
                creationDate: parsed.creationDate,
            }
        } catch (error) {
            console.error(`Error parsing ${path}:`, error);
            return null;
        }
    }));

    return parsedStudioFiles.filter(Boolean) as { id: string, creationDate: string }[];
}

export async function getStudioFileFromEventCatalogDirectoryById(id: string): Promise<any | null> {
    const eventCatalogDir = await getEventCatalogPath();

    const studioFiles = await fg(['**/*/*.ecstudio'], {
        cwd: eventCatalogDir,
        absolute: true,
    })

    const parsedStudioFiles = await Promise.all(studioFiles.map(async (path) => {
        try {
            const content = await fs.readFile(path, 'utf-8');
            const parsed = JSON.parse(content);
            return parsed
        } catch (error) {
            console.error(`Error parsing ${path}:`, error);
            return null;
        }
    }));

    const studioFile = parsedStudioFiles.find(file => file.id === id);

    if (!studioFile) {
        return null;
    }

    return studioFile;
}

export async function getEventCatalogPath(): Promise<string> {
    const eventCatalogDir = process.env.EVENTCATALOG_DIR || path.join(process.cwd(), 'eventcatalog');
    return eventCatalogDir;
}

export async function getStudioTemplates(): Promise<any[]> {
    const templateDir = process.env.TEMPLATE_DIR || process.cwd();
    
    try {
        const templateFiles = await fg(['**/*/*.ectemplate'], {
            cwd: templateDir,
            absolute: true,
        });

        const templates = await Promise.all(
            templateFiles.map(async (filePath) => {
                try {
                    const content = await fs.readFile(filePath, 'utf-8');
                    const template = JSON.parse(content);
                    const filename = path.basename(filePath, '.ectemplate');
                    
                    return {
                        id: filename,
                        ...template,
                        filePath
                    };
                } catch (error) {
                    console.error(`Error parsing template ${filePath}:`, error);
                    return null;
                }
            })
        );

        return templates.filter(Boolean);
    } catch (error) {
        console.error('Error loading templates:', error);
        return [];
    }
}