import { createMetadataStore } from '../store.js';
import { layerExistsForEntity, mapEntityForResponse, matchesEntityNameOrId, paginate, readLayerFilesForEntity, sortByEntityId, } from '../utils.js';
const isFrameworkLayerFile = (path, framework) => {
    if (framework === 'vanilla') {
        return path.includes('/components/');
    }
    return path.includes(`/${framework}/`);
};
const isTestLayerFile = (path) => {
    const normalized = path.toLowerCase();
    return normalized.includes('.test.') || normalized.includes('.spec.');
};
const isIncludedLayerFile = (path, framework) => {
    if (isTestLayerFile(path)) {
        return false;
    }
    return path.includes('/components/') || isFrameworkLayerFile(path, framework);
};
const readInterfaceSnapshot = async (store, entity) => {
    const interfaceRef = entity.layers?.interface?.find((ref) => ref.path.endsWith('.json'));
    if (!interfaceRef) {
        return undefined;
    }
    const content = await store.readLayerFile(interfaceRef);
    return JSON.parse(content);
};
const hydrateInterfaceSnapshot = async (store, entity, options) => {
    if (!options.includeInterfaceSnapshot || entity.custom?.interfaceSnapshot) {
        return entity;
    }
    const interfaceSnapshot = await readInterfaceSnapshot(store, entity);
    if (!interfaceSnapshot) {
        return entity;
    }
    return {
        ...entity,
        custom: {
            ...entity.custom,
            interfaceSnapshot,
        },
    };
};
const getEntityTagName = (entity) => {
    const { custom } = entity;
    const snapshot = custom?.interfaceSnapshot;
    const tagName = snapshot?.tagName;
    return typeof tagName === 'string' ? tagName : undefined;
};
const matchesNameOrId = (entity, nameOrId) => matchesEntityNameOrId(entity, nameOrId, {
    extraCandidates: [getEntityTagName(entity)],
    prefix: 'component',
    // eslint-disable-next-line no-confusing-arrow
    prefixedCandidates: (input) => !input.startsWith('syn-') ? [`component:syn-${input}`] : [],
});
const normalize = (value) => value.trim().toLowerCase();
const matchesCluster = (entity, cluster) => {
    if (!cluster) {
        return true;
    }
    const requested = (Array.isArray(cluster) ? cluster : [cluster])
        .map(normalize)
        .filter(Boolean);
    if (requested.length === 0) {
        return true;
    }
    const clusters = entity.custom?.clusters;
    if (!Array.isArray(clusters) || clusters.length === 0) {
        return false;
    }
    const available = clusters
        .filter((candidate) => typeof candidate === 'string')
        .map(normalize);
    return requested.some((candidate) => available.includes(candidate));
};
/**
 * List components with optional filtering and pagination.
 * @param options Options for querying components, including filtering by status and tags, pagination, and layer/verbosity preferences.
 * @param storeOptions Options for configuring the metadata store.
 * @returns A promise that resolves to a public response containing the list of components and metadata.
 */
export const listComponents = async (options = {}, storeOptions = {}) => {
    const store = createMetadataStore(storeOptions);
    const index = await store.getIndex();
    const requestedLayer = options.layer ?? 'interface';
    const requestedVerbosity = options.verbosity ?? 'readable';
    const entities = await store.findEntities({
        kind: 'component',
        status: options.status,
        tags: options.tags,
    });
    const sorted = sortByEntityId(entities)
        .filter((entity) => matchesCluster(entity, options.cluster));
    const hasRequestedLayer = sorted.every((entity) => layerExistsForEntity(entity, requestedLayer));
    if (options.strictLayer && !hasRequestedLayer) {
        return {
            data: [],
            errors: [{
                    code: 'LAYER_NOT_AVAILABLE',
                    details: {
                        requestedLayer,
                    },
                    message: `Requested layer "${requestedLayer}" is not available for all component entities.`,
                }],
            meta: {
                builtAt: index.builtAt,
                requestedLayer,
                requestedVerbosity,
                resolvedLayer: requestedLayer,
                schemaVersion: index.version,
                total: sorted.length,
                warnings: ['strictLayer=true and requested layer is unavailable for part of result set'],
            },
        };
    }
    const resolvedLayer = hasRequestedLayer ? requestedLayer : 'full';
    const paged = paginate(sorted, options.limit, options.offset);
    const hydrated = await Promise.all(paged.map((entity) => hydrateInterfaceSnapshot(store, entity, options)));
    return {
        data: hydrated.map((entity) => mapEntityForResponse(entity, options)),
        meta: {
            builtAt: index.builtAt,
            requestedLayer,
            requestedVerbosity,
            resolvedLayer,
            schemaVersion: index.version,
            total: sorted.length,
            warnings: hasRequestedLayer ? undefined : [
                `Requested layer "${requestedLayer}" was unavailable; falling back to "full".`,
            ],
        },
    };
};
/**
 * Get metadata for a specific component by name or ID, with options for layer inclusion and verbosity.
 * @param nameOrId The name or ID of the component to retrieve metadata for.
 * @param options Options for querying the component, including filtering by status and tags, layer/verbosity preferences, and whether to include the interface snapshot.
 * @param storeOptions Options for configuring the metadata store.
 * @returns A promise that resolves to a public response containing the component metadata and related information, or null if not found.
 */
export const getComponentMetadata = async (nameOrId, options = {}, storeOptions = {}) => {
    const store = createMetadataStore(storeOptions);
    const index = await store.getIndex();
    const requestedLayer = options.layer ?? 'interface';
    const requestedVerbosity = options.verbosity ?? 'readable';
    const entities = (await store.findEntities({
        kind: 'component',
        status: options.status,
        tags: options.tags,
    }));
    const sorted = sortByEntityId(entities)
        .filter((candidate) => matchesCluster(candidate, options.cluster));
    const entity = sorted.find((candidate) => matchesNameOrId(candidate, nameOrId));
    if (!entity) {
        return {
            data: null,
            errors: [{
                    code: 'NOT_FOUND',
                    details: {
                        nameOrId,
                    },
                    message: `Component "${nameOrId}" was not found.`,
                }],
            meta: {
                builtAt: index.builtAt,
                requestedLayer,
                requestedVerbosity,
                resolvedLayer: requestedLayer,
                schemaVersion: index.version,
                total: sorted.length,
            },
        };
    }
    const hasRequestedLayer = layerExistsForEntity(entity, requestedLayer);
    if (options.strictLayer && !hasRequestedLayer) {
        return {
            data: null,
            errors: [{
                    code: 'LAYER_NOT_AVAILABLE',
                    details: {
                        id: entity.id,
                        requestedLayer,
                    },
                    message: `Requested layer "${requestedLayer}" is not available for component "${entity.id}".`,
                }],
            meta: {
                builtAt: index.builtAt,
                requestedLayer,
                requestedVerbosity,
                resolvedLayer: requestedLayer,
                schemaVersion: index.version,
                total: sorted.length,
                warnings: ['strictLayer=true and requested layer is unavailable for the selected entity'],
            },
        };
    }
    const resolvedLayer = hasRequestedLayer ? requestedLayer : 'full';
    const hydrated = await hydrateInterfaceSnapshot(store, entity, options);
    return {
        data: mapEntityForResponse(hydrated, options),
        meta: {
            builtAt: index.builtAt,
            requestedLayer,
            requestedVerbosity,
            resolvedLayer,
            schemaVersion: index.version,
            total: sorted.length,
            warnings: hasRequestedLayer ? undefined : [
                `Requested layer "${requestedLayer}" was unavailable; falling back to "full".`,
            ],
        },
    };
};
/**
 * High-level helper for component usage payloads grouped by layer semantics.
 * - `full`: returns filtered source files (vanilla + requested framework)
 * - `examples`: returns markdown files from the examples layer
 * - `interface`: returns markdown files from the interface layer
 */
export const getDataForComponent = async (nameOrId, options = {}, storeOptions = {}) => {
    const requestedLayer = options.layer ?? 'full';
    const resolvedFramework = options.framework ?? 'vanilla';
    const metadata = await getComponentMetadata(nameOrId, {
        includeInterfaceSnapshot: requestedLayer === 'interface',
        includeLayerRefs: true,
        includeSources: false,
        layer: requestedLayer,
    }, storeOptions);
    if (!metadata.data) {
        return {
            ...metadata,
            data: null,
        };
    }
    const store = createMetadataStore(storeOptions);
    const layerFiles = await readLayerFilesForEntity(store, metadata.data, metadata.meta.resolvedLayer);
    const textLayerContent = layerFiles
        .filter(({ ref }) => ref.path.endsWith('.md'))
        .map(({ content, ref }) => ({
        content,
        path: ref.path,
    }))
        .toSorted((a, b) => a.path.localeCompare(b.path));
    const codeLayerContent = layerFiles
        .filter(({ ref }) => isIncludedLayerFile(ref.path, resolvedFramework))
        .map(({ content, ref }) => ({
        content,
        layer: ref.layer,
        path: ref.path,
    }))
        .toSorted((a, b) => a.path.localeCompare(b.path));
    const frameworkDetails = resolvedFramework === 'vanilla'
        ? undefined
        : metadata.data.custom?.frameworks?.[resolvedFramework];
    const basePayload = {
        component: metadata.data.id,
        framework: resolvedFramework,
        layer: metadata.meta.resolvedLayer,
        requestedFrameworkDetails: frameworkDetails,
        warnings: metadata.meta.warnings,
    };
    let data;
    switch (metadata.meta.resolvedLayer) {
        case 'examples':
            data = {
                ...basePayload,
                examples: textLayerContent,
            };
            break;
        case 'interface':
            data = {
                ...basePayload,
                interface: textLayerContent,
            };
            break;
        case 'rules':
            data = {
                ...basePayload,
                rules: textLayerContent,
            };
            break;
        default:
            data = {
                ...basePayload,
                relevantLayerCode: codeLayerContent,
            };
            break;
    }
    return {
        ...metadata,
        data,
    };
};
/**
 * Focused helper for component rules markdown.
 * Unlike `getDataForComponent(..., { layer: 'rules' })`, this helper requires the
 * rules layer and returns only the rules-specific payload shape.
 */
export const getRulesForComponent = async (nameOrId, storeOptions = {}) => {
    const metadata = await getComponentMetadata(nameOrId, {
        includeLayerRefs: true,
        includeSources: false,
        layer: 'rules',
        strictLayer: true,
    }, storeOptions);
    if (!metadata.data) {
        return {
            ...metadata,
            data: null,
        };
    }
    const store = createMetadataStore(storeOptions);
    const layerFiles = await readLayerFilesForEntity(store, metadata.data, 'rules');
    const rules = layerFiles
        .filter(({ ref }) => ref.path.endsWith('.md'))
        .map(({ content, ref }) => ({
        content,
        path: ref.path,
    }))
        .toSorted((a, b) => a.path.localeCompare(b.path));
    return {
        ...metadata,
        data: {
            component: metadata.data.id,
            layer: 'rules',
            rules,
            warnings: metadata.meta.warnings,
        },
    };
};
