import { DEFAULT_KNOWLEDGE_SPACE_ID, getExplicitKnowledgeSpaceId, getKnowledgeSpaceId, isInKnowledgeSpaceScope, resolveKnowledgeSpaceScope, } from './spaces.js';
export function knowledgeSourceMatchesScope(source, scope = {}) {
    return isInKnowledgeSpaceScope(source, scope);
}
export function knowledgeNodeMatchesScope(node, scope = {}, lookup = {}) {
    const scopedSpaceId = resolveKnowledgeSpaceScope(scope);
    if (scopedSpaceId === null)
        return true;
    const relatedSpaces = relatedNodeSpaceIds(node, lookup);
    const ownSpace = getKnowledgeSpaceId(node);
    if (scopedSpaceId === DEFAULT_KNOWLEDGE_SPACE_ID
        && relatedSpaces.length === 0
        && (isUngroundedSemanticAnswerGapNode(node) || isUngroundedCatalogDerivedNode(node)))
        return false;
    if (scopedSpaceId === DEFAULT_KNOWLEDGE_SPACE_ID) {
        return ownSpace === scopedSpaceId && (relatedSpaces.length === 0 || relatedSpaces.every((spaceId) => spaceId === scopedSpaceId));
    }
    return ownSpace === scopedSpaceId || relatedSpaces.includes(scopedSpaceId);
}
export function knowledgeIssueMatchesScope(issue, scope = {}, lookup = {}) {
    const scopedSpaceId = resolveKnowledgeSpaceScope(scope);
    if (scopedSpaceId === null)
        return true;
    const relatedSpaces = relatedIssueSpaceIds(issue, lookup);
    const ownSpace = getKnowledgeSpaceId(issue);
    if (scopedSpaceId === DEFAULT_KNOWLEDGE_SPACE_ID
        && (isUngroundedSemanticAnswerGapIssue(issue, lookup) || isIssueLinkedOnlyToUngroundedAnswerGap(issue, lookup)))
        return false;
    if (scopedSpaceId === DEFAULT_KNOWLEDGE_SPACE_ID) {
        return ownSpace === scopedSpaceId && (relatedSpaces.length === 0 || relatedSpaces.every((spaceId) => spaceId === scopedSpaceId));
    }
    return ownSpace === scopedSpaceId || relatedSpaces.includes(scopedSpaceId);
}
function relatedIssueSpaceIds(issue, lookup) {
    const spaces = new Set();
    for (const sourceId of uniqueStrings([
        issue.sourceId,
        readString(issue.metadata.sourceId),
        ...readStringArray(issue.metadata.sourceIds),
    ])) {
        const sourceSpace = getExplicitKnowledgeSpaceId(lookupSource(lookup, sourceId));
        if (sourceSpace)
            spaces.add(sourceSpace);
    }
    const linkedNodeIds = uniqueStrings([
        issue.nodeId,
        readString(issue.metadata.nodeId),
        ...readStringArray(issue.metadata.linkedObjectIds),
        ...readStringArray(issue.metadata.subjectIds),
    ]);
    for (const nodeId of linkedNodeIds) {
        const node = lookupNode(lookup, nodeId);
        const nodeSpace = getExplicitKnowledgeSpaceId(node);
        if (nodeSpace)
            spaces.add(nodeSpace);
        if (node)
            for (const related of relatedNodeSpaceIds(node, lookup))
                spaces.add(related);
    }
    for (const edge of lookup.edges ?? []) {
        if (!edgeTouchesRecord(edge, 'issue', issue.id))
            continue;
        const edgeSpace = getExplicitKnowledgeSpaceId(edge);
        if (edgeSpace)
            spaces.add(edgeSpace);
        for (const related of relatedEdgeSpaceIds(edge, lookup))
            spaces.add(related);
    }
    return [...spaces];
}
function relatedNodeSpaceIds(node, lookup) {
    const spaces = new Set();
    for (const sourceId of uniqueStrings([
        node.sourceId,
        readString(node.metadata.sourceId),
        ...readStringArray(node.metadata.sourceIds),
    ])) {
        const sourceSpace = getExplicitKnowledgeSpaceId(lookupSource(lookup, sourceId));
        if (sourceSpace)
            spaces.add(sourceSpace);
    }
    for (const nodeId of uniqueStrings([
        ...readStringArray(node.metadata.linkedObjectIds),
        ...readStringArray(node.metadata.subjectIds),
    ])) {
        const linkedSpace = getExplicitKnowledgeSpaceId(lookupNode(lookup, nodeId));
        if (linkedSpace)
            spaces.add(linkedSpace);
    }
    for (const edge of lookup.edges ?? []) {
        if (!edgeTouchesRecord(edge, 'node', node.id))
            continue;
        const edgeSpace = getExplicitKnowledgeSpaceId(edge);
        if (edgeSpace)
            spaces.add(edgeSpace);
        for (const related of relatedEdgeSpaceIds(edge, lookup))
            spaces.add(related);
    }
    return [...spaces];
}
function relatedEdgeSpaceIds(edge, lookup) {
    const spaces = new Set();
    for (const endpoint of [
        { kind: edge.fromKind, id: edge.fromId },
        { kind: edge.toKind, id: edge.toId },
    ]) {
        if (endpoint.kind === 'source') {
            const sourceSpace = getExplicitKnowledgeSpaceId(lookupSource(lookup, endpoint.id));
            if (sourceSpace)
                spaces.add(sourceSpace);
        }
        if (endpoint.kind === 'node') {
            const node = lookupNode(lookup, endpoint.id);
            const nodeSpace = getExplicitKnowledgeSpaceId(node);
            if (nodeSpace)
                spaces.add(nodeSpace);
        }
    }
    return [...spaces];
}
function edgeTouchesRecord(edge, kind, id) {
    return (edge.fromKind === kind && edge.fromId === id) || (edge.toKind === kind && edge.toId === id);
}
function isUngroundedSemanticAnswerGapNode(node) {
    return node.kind === 'knowledge_gap'
        && readString(node.metadata.semanticKind) === 'gap'
        && readString(node.metadata.gapKind) === 'answer'
        && readString(node.metadata.visibility) === 'refinement'
        && uniqueStrings([
            node.sourceId,
            readString(node.metadata.sourceId),
            readString(node.metadata.nodeId),
            ...readStringArray(node.metadata.sourceIds),
            ...readStringArray(node.metadata.linkedObjectIds),
            ...readStringArray(node.metadata.subjectIds),
        ]).length === 0;
}
function isUngroundedSemanticAnswerGapIssue(issue, lookup) {
    return issue.code === 'knowledge.answer_gap'
        && (issue.metadata.semantic === true || readString(issue.metadata.semantic) === 'true')
        && uniqueStrings([
            issue.sourceId,
            readString(issue.metadata.sourceId),
            ...readStringArray(issue.metadata.sourceIds),
            ...readStringArray(issue.metadata.linkedObjectIds),
            ...readStringArray(issue.metadata.subjectIds),
        ]).length === 0
        && issueNodeIds(issue).every((nodeId) => {
            const node = lookupNode(lookup, nodeId);
            return node ? isUngroundedSemanticAnswerGapNode(node) : true;
        });
}
function isIssueLinkedOnlyToUngroundedAnswerGap(issue, lookup) {
    if (issue.code !== 'knowledge.answer_gap')
        return false;
    const nodeIds = issueNodeIds(issue);
    return nodeIds.length > 0 && nodeIds.every((nodeId) => {
        const node = lookupNode(lookup, nodeId);
        return Boolean(node && isUngroundedSemanticAnswerGapNode(node));
    });
}
function issueNodeIds(issue) {
    return uniqueStrings([
        issue.nodeId,
        readString(issue.metadata.nodeId),
    ]);
}
function isUngroundedCatalogDerivedNode(node) {
    if (node.sourceId || readString(node.metadata.sourceId) || readStringArray(node.metadata.sourceIds).length > 0)
        return false;
    if (readString(node.metadata.linkedObjectIds) || readStringArray(node.metadata.linkedObjectIds).length > 0)
        return false;
    if (readString(node.metadata.subjectIds) || readStringArray(node.metadata.subjectIds).length > 0)
        return false;
    if (node.kind === 'domain')
        return Boolean(readString(node.metadata.hostname));
    if (node.kind === 'bookmark_folder')
        return Boolean(readString(node.metadata.folderPath));
    if (node.kind !== 'topic')
        return false;
    const tag = readString(node.metadata.tag);
    return Boolean(tag && readOnlyMetadataKeys(node.metadata).every((key) => key === 'tag'));
}
function lookupSource(lookup, id) {
    if (!id)
        return null;
    return lookup.getSource?.(id) ?? lookup.sources?.get(id) ?? null;
}
function lookupNode(lookup, id) {
    if (!id)
        return null;
    return lookup.getNode?.(id) ?? lookup.nodes?.get(id) ?? null;
}
function readString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}
function readStringArray(value) {
    if (typeof value === 'string' && value.trim().length > 0)
        return [value.trim()];
    if (!Array.isArray(value))
        return [];
    return value.flatMap((entry) => readStringArray(entry));
}
function uniqueStrings(values) {
    return [...new Set(values.filter((entry) => Boolean(entry && entry.trim().length > 0)))];
}
function readOnlyMetadataKeys(metadata) {
    return Object.keys(metadata).filter((key) => !['knowledgeSpaceId', 'namespace'].includes(key));
}
