import { belongsToSpace, edgeIsActive, readRecord, readString, readStringArray } from './helpers.js';
export function readHomeGraphState(store, spaceId) {
    const sources = store.listSourcesInSpace(spaceId).filter((source) => source.status !== 'stale');
    const nodes = store.listNodesInSpace(spaceId).filter((node) => node.status !== 'stale');
    const sourceIds = new Set(sources.map((source) => source.id));
    const nodeIds = new Set(nodes.map((node) => node.id));
    const edges = store.listEdges().filter((edge) => (edgeIsActive(edge)
        && belongsToSpace(edge, spaceId)
        && (edge.fromKind !== 'source' || sourceIds.has(edge.fromId))
        && (edge.toKind !== 'source' || sourceIds.has(edge.toId))
        && (edge.fromKind !== 'node' || nodeIds.has(edge.fromId))
        && (edge.toKind !== 'node' || nodeIds.has(edge.toId))));
    const issues = store.listIssuesInSpace(spaceId).filter((issue) => ((!issue.sourceId || sourceIds.has(issue.sourceId))
        && (!issue.nodeId || nodeIds.has(issue.nodeId))));
    const extractions = store.listExtractionsForSources(sourceIds);
    return { spaceId, sources, nodes, edges, issues, extractions };
}
export function renderHomeGraphState(store, spaceId, title) {
    const state = readHomeGraphState(store, spaceId);
    return {
        spaceId,
        title,
        sources: state.sources,
        nodes: state.nodes,
        edges: state.edges,
        issues: state.issues,
    };
}
export function sourcesLinkedToNode(nodeId, state) {
    const sourceIds = sourceIdsLinkedToNodeThroughFacts(nodeId, state.edges);
    return state.sources.filter((source) => sourceIds.has(source.id));
}
export function collectLinkedObjects(results, state) {
    const nodeIds = new Set();
    const factDescribes = factDescribesIndex(state.edges);
    for (const result of results) {
        if (result.node)
            nodeIds.add(result.node.id);
        if (result.source) {
            for (const edge of state.edges) {
                if (!edgeIsActive(edge))
                    continue;
                if (edge.fromKind === 'source' && edge.fromId === result.source.id && edge.toKind === 'node') {
                    nodeIds.add(edge.toId);
                    for (const describedNodeId of factDescribes.get(edge.toId) ?? [])
                        nodeIds.add(describedNodeId);
                }
            }
            for (const id of sourceLinkedObjectIds(result.source)) {
                nodeIds.add(id);
            }
        }
    }
    return state.nodes.filter((node) => nodeIds.has(node.id));
}
function factDescribesIndex(edges) {
    const index = new Map();
    for (const edge of edges) {
        if (!edgeIsActive(edge)
            || edge.fromKind !== 'node'
            || edge.toKind !== 'node'
            || edge.relation !== 'describes')
            continue;
        const current = index.get(edge.fromId) ?? new Set();
        current.add(edge.toId);
        index.set(edge.fromId, current);
    }
    return index;
}
function sourceIdsLinkedToNodeThroughFacts(nodeId, edges) {
    const sourceIds = new Set();
    const factIds = new Set();
    for (const edge of edges) {
        if (!edgeIsActive(edge))
            continue;
        if (edge.fromKind === 'source' && edge.toKind === 'node' && edge.toId === nodeId)
            sourceIds.add(edge.fromId);
        if (edge.fromKind === 'node' && edge.toKind === 'source' && edge.fromId === nodeId)
            sourceIds.add(edge.toId);
        if (edge.fromKind === 'node' && edge.toKind === 'node' && edge.toId === nodeId && edge.relation === 'describes') {
            factIds.add(edge.fromId);
        }
    }
    for (const edge of edges) {
        if (!edgeIsActive(edge))
            continue;
        if (edge.fromKind === 'source' && edge.toKind === 'node' && factIds.has(edge.toId) && edge.relation === 'supports_fact') {
            sourceIds.add(edge.fromId);
        }
    }
    return sourceIds;
}
function sourceLinkedObjectIds(source) {
    const metadata = source.metadata ?? {};
    const discovery = readRecord(metadata.sourceDiscovery);
    return [
        ...readStringArray(metadata.linkedObjectIds),
        ...readStringArray(discovery.linkedObjectIds),
    ];
}
export function missingDevicePassportFields(device, sources, facts = []) {
    const hasManufacturer = typeof device.metadata.manufacturer === 'string'
        || factsContainField(facts, /\b(manufacturer|brand|vendor)\b/);
    const hasModel = typeof device.metadata.model === 'string'
        || factsContainField(facts, /\b(model|model number|model id)\b/);
    const hasBatteryType = factsContainField(facts, /\b(battery type|battery|cr2032|cr123|aaa|aa)\b/);
    return [
        hasManufacturer ? '' : 'manufacturer',
        hasModel ? '' : 'model',
        devicePassportNeedsBatteryField(device) && !hasBatteryType ? 'battery type' : '',
        sources.length > 0 ? '' : 'manual/source',
    ].filter(Boolean);
}
function factsContainField(facts, pattern) {
    return facts.some((fact) => pattern.test([
        fact.title,
        fact.summary,
        readString(fact.metadata.value),
        readString(fact.metadata.evidence),
        ...readStringArray(fact.metadata.labels),
    ].filter(Boolean).join(' ').toLowerCase()));
}
function devicePassportNeedsBatteryField(device) {
    if (typeof device.metadata.batteryType === 'string' && device.metadata.batteryType.trim().length > 0)
        return false;
    if (device.metadata.batteryPowered === false)
        return false;
    if (device.metadata.batteryPowered === true)
        return true;
    const text = [
        device.title,
        device.summary,
        ...device.aliases,
        typeof device.metadata.manufacturer === 'string' ? device.metadata.manufacturer : '',
        typeof device.metadata.model === 'string' ? device.metadata.model : '',
    ].join(' ').toLowerCase();
    if (/\b(tv|television|webos|display|monitor|receiver|soundbar|speaker|appliance|outlet|plug|switch|router|bridge|hub|coordinator|adapter|home assistant|integration|software|service|core|supervisor)\b/.test(text)) {
        return false;
    }
    return /\b(battery|button|keypad|leak sensor|motion sensor|contact sensor|door sensor|window sensor|remote|lock|thermostat|cr2032|cr123)\b/.test(text);
}
export function findHomeAssistantNode(nodes, kind, id) {
    return nodes.find((node) => node.kind === kind && (node.id === id
        || readHomeAssistantString(node, 'objectId') === id
        || readHomeAssistantString(node, 'deviceId') === id
        || readHomeAssistantString(node, 'areaId') === id));
}
export function inferHomeGraphSourceType(tags, fallback) {
    const normalized = new Set((tags ?? []).map((tag) => tag.toLowerCase()));
    if (normalized.has('manual'))
        return 'manual';
    if (normalized.has('receipt') || normalized.has('warranty'))
        return 'document';
    if (normalized.has('photo') || normalized.has('image'))
        return 'image';
    return fallback;
}
export function safeHomeGraphFilename(value) {
    return value.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '') || 'home-graph';
}
export function renderAskAnswer(query, results, mode) {
    if (results.length === 0) {
        return `No Home Graph knowledge matched "${query}".`;
    }
    const lines = results.slice(0, mode === 'detailed' ? 5 : mode === 'concise' ? 1 : 3).map((result) => {
        const detail = result.excerpt ?? result.summary ?? result.source?.description ?? result.source?.sourceUri ?? '';
        return detail ? `${result.title}: ${detail}` : result.title;
    });
    return mode === 'concise' ? lines[0] : lines.map((line) => `- ${line}`).join('\n');
}
export function edgeConnectsNode(edge, nodeId, relation, toId) {
    return edgeIsActive(edge)
        && edge.fromKind === 'node'
        && edge.fromId === nodeId
        && edge.toKind === 'node'
        && edge.toId === toId
        && edge.relation === relation;
}
function readHomeAssistantString(node, key) {
    const homeAssistant = readRecord(node.metadata.homeAssistant);
    const value = homeAssistant[key];
    return typeof value === 'string' ? value : undefined;
}
