import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { stringify as stringifyYaml } from "yaml";
import { aggregateTargetMaterialDetections, detectTargetMaterialRefs, } from "../target-material-kind.js";
import { validateSourceObservationBoundary, } from "./source-observations.js";
import { loadReconstructSourceProfiles, } from "./source-profiles.js";
const CONCRETE_TARGET_MATERIAL_KINDS = new Set([
    "code",
    "spreadsheet",
    "document",
    "database",
]);
function isConcreteTargetMaterialKind(kind) {
    return CONCRETE_TARGET_MATERIAL_KINDS.has(kind);
}
function isoNow() {
    return new Date().toISOString();
}
async function writeYamlDocument(filePath, value) {
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    await fs.writeFile(filePath, stringifyYaml(value), "utf8");
}
function stableObservationId(args) {
    const digest = crypto
        .createHash("sha256")
        .update(`${args.targetMaterialKind}\n${path.resolve(args.sourceRef)}\n${args.location}`)
        .digest("hex")
        .slice(0, 16);
    return `obs_${args.targetMaterialKind}_${digest}`;
}
function supportForMaterial(args) {
    if (args.targetMaterialKind === "unknown") {
        return {
            support_status: "unknown",
            unsupported_reason: "runtime could not classify target material kind safely",
        };
    }
    if (args.targetMaterialKind === "mixed") {
        return {
            support_status: "partial_composite",
            unsupported_reason: "mixed target material kind is observed through per-ref source profiles; unsupported members are skipped with authority impact",
        };
    }
    if (args.selectedProfiles.length === 0) {
        return {
            support_status: "unsupported",
            unsupported_reason: "no reconstruct source profile exists for the detected target material kind",
        };
    }
    return {
        support_status: "partial",
        unsupported_reason: "source profile exists, but only minimal structural observation is implemented",
    };
}
function inventoryUnitForMaterial(kind) {
    switch (kind) {
        case "code":
            return "file_or_package_unit";
        case "spreadsheet":
            return "workbook_sheet_or_table_unit";
        case "document":
            return "section_heading_or_document_unit";
        case "database":
            return "schema_table_or_query_unit";
        case "mixed":
            return "per_member_material_unit";
        case "unknown":
            return "unknown_material_unit";
    }
}
function profileRefForKind(profiles, kind) {
    return profiles.find((profile) => profile.target_material_kind === kind)
        ?.profile_path ?? null;
}
function selectedProfileRefs(profiles, candidates) {
    const selected = [];
    for (const kind of candidates) {
        const profile = profiles.find((candidate) => candidate.target_material_kind === kind);
        if (!profile)
            continue;
        selected.push({
            target_material_kind: profile.target_material_kind,
            profile_ref: profile.profile_path,
            support_summary: profile.support_summary,
            scan_targets: profile.scan_targets,
        });
    }
    return selected;
}
async function textStats(ref) {
    try {
        const text = await fs.readFile(ref, "utf8");
        const excerptLimit = 6000;
        return {
            line_count: text.length === 0 ? 0 : text.split(/\r?\n/).length,
            char_count: text.length,
            content_excerpt: text.slice(0, excerptLimit),
            excerpt_truncated: text.length > excerptLimit,
        };
    }
    catch {
        return {
            line_count: null,
            char_count: null,
            content_excerpt: null,
            excerpt_truncated: false,
        };
    }
}
async function buildObservation(detection) {
    if (!detection.exists || !isConcreteTargetMaterialKind(detection.kind)) {
        return null;
    }
    const extension = path.extname(detection.ref).toLowerCase();
    const location = detection.ref;
    const stat = await fs.stat(detection.ref);
    const stats = stat.isFile() ? await textStats(detection.ref) : {
        line_count: null,
        char_count: null,
        content_excerpt: null,
        excerpt_truncated: false,
    };
    const observation = {
        observation_id: stableObservationId({
            targetMaterialKind: detection.kind,
            sourceRef: detection.ref,
            location,
        }),
        target_material_kind: detection.kind,
        adapter_id: `minimal-${detection.kind}-structure-observer`,
        source_ref: detection.ref,
        location,
        summary: `${detection.kind} material observed at ${path.basename(detection.ref)}`,
        structural_data: {
            basename: path.basename(detection.ref),
            extension: extension || null,
            path_kind: stat.isDirectory() ? "directory" : "file",
            size_bytes: stat.isFile() ? stat.size : null,
            line_count: stats.line_count,
            char_count: stats.char_count,
            content_excerpt: stats.content_excerpt,
            excerpt_truncated: stats.excerpt_truncated,
        },
    };
    const validation = validateSourceObservationBoundary(observation);
    if (!validation.valid) {
        throw new Error(`Invalid source observation boundary for ${detection.ref}: ${validation.violations.join("; ")}`);
    }
    return observation;
}
function stableFrontierRefId(unit) {
    const digest = crypto
        .createHash("sha256")
        .update(`${unit.target_material_kind}\n${path.resolve(unit.ref)}\n${unit.inventory_unit}`)
        .digest("hex")
        .slice(0, 16);
    return `frontier_initial_${digest}`;
}
function buildInitialSourceFrontier(args) {
    return {
        schema_version: "1",
        session_id: args.sessionId,
        created_at: isoNow(),
        frontier_id: "initial",
        source_refs: args.inventory.inventory_units
            .filter((unit) => unit.scan_status === "planned")
            .map((unit) => ({
            frontier_ref_id: stableFrontierRefId(unit),
            source_ref: unit.ref,
            target_material_kind: unit.target_material_kind,
            inventory_unit: unit.inventory_unit,
            profile_ref: unit.profile_ref,
            rationale: "Initial source frontier derived from runtime material inventory and selected source profile.",
        })),
        skipped_refs: args.inventory.inventory_units
            .filter((unit) => unit.scan_status === "skipped")
            .map((unit) => ({
            source_ref: unit.ref,
            target_material_kind: unit.target_material_kind,
            reason: unit.skip_reason ?? "skipped",
            authority_impact: "Semantic artifacts cannot use this ref as trusted evidence until a supported material profile and observation exist.",
        })),
    };
}
function buildInventoryUnits(args) {
    return args.detections.map((detection) => {
        const profileRef = profileRefForKind(args.profiles, detection.kind);
        const planned = detection.exists &&
            isConcreteTargetMaterialKind(detection.kind) &&
            profileRef !== null;
        return {
            ref: detection.ref,
            exists: detection.exists,
            target_material_kind: detection.kind,
            inventory_unit: inventoryUnitForMaterial(detection.kind),
            profile_ref: profileRef,
            scan_status: planned ? "planned" : "skipped",
            skip_reason: planned
                ? null
                : detection.exists
                    ? `no runnable source adapter for target_material_kind=${detection.kind}`
                    : "target ref does not exist",
        };
    });
}
export async function materializeReconstructPreparationArtifacts(params) {
    if (params.targetRefs.length === 0) {
        throw new Error("targetRefs must not be empty.");
    }
    const sessionRoot = path.resolve(params.sessionRoot);
    const sessionId = path.basename(sessionRoot);
    const targetRefs = params.targetRefs.map((ref) => path.resolve(ref));
    const profiles = await loadReconstructSourceProfiles(params.profilesRoot);
    const perRefDetections = await detectTargetMaterialRefs(targetRefs);
    const detection = aggregateTargetMaterialDetections(perRefDetections);
    const selectedProfiles = selectedProfileRefs(profiles, detection.target_material_kind === "mixed"
        ? detection.target_material_kind_candidates
        : [detection.target_material_kind]);
    const support = supportForMaterial({
        targetMaterialKind: detection.target_material_kind,
        selectedProfiles,
    });
    const targetMaterialProfile = {
        schema_version: "1",
        session_id: sessionId,
        created_at: isoNow(),
        target_refs: targetRefs,
        target_material_kind: detection.target_material_kind,
        target_material_kind_candidates: detection.target_material_kind_candidates,
        support_status: support.support_status,
        unsupported_reason: support.unsupported_reason,
        selected_source_profiles: selectedProfiles,
        detection: {
            owner: "runtime_heuristic",
            confidence: detection.confidence,
            confidence_basis: detection.confidence_basis,
            per_ref: detection.per_ref,
        },
    };
    const inventory = {
        schema_version: "1",
        session_id: sessionId,
        created_at: isoNow(),
        inventory_units: buildInventoryUnits({
            detections: detection.per_ref,
            profiles,
        }),
        scan_boundary: {
            filesystem_allowed_roots: params.filesystemAllowedRoots?.map((root) => path.resolve(root)) ?? [],
            source: "binding",
        },
    };
    const initialSourceFrontier = buildInitialSourceFrontier({
        sessionId,
        inventory,
    });
    const observations = [];
    const skippedRefs = [];
    for (const unit of inventory.inventory_units) {
        if (unit.scan_status === "skipped") {
            skippedRefs.push({
                ref: unit.ref,
                target_material_kind: unit.target_material_kind,
                reason: unit.skip_reason ?? "skipped",
            });
            continue;
        }
        const refDetection = detection.per_ref.find((candidate) => candidate.ref === unit.ref);
        if (!refDetection)
            continue;
        const observation = await buildObservation(refDetection);
        if (observation)
            observations.push(observation);
    }
    const sourceObservations = {
        schema_version: "1",
        session_id: sessionId,
        created_at: isoNow(),
        observations,
        skipped_refs: skippedRefs,
        validation_results: [
            "target_material_kind_valid",
            "source_profiles_resolved",
            "source_observation_boundary_valid",
        ],
    };
    const refs = {
        target_material_profile: path.join(sessionRoot, "target-material-profile.yaml"),
        source_inventory: path.join(sessionRoot, "source-inventory.yaml"),
        initial_source_frontier: path.join(sessionRoot, "initial-source-frontier.yaml"),
        source_observations: path.join(sessionRoot, "source-observations.yaml"),
    };
    await Promise.all([
        writeYamlDocument(refs.target_material_profile, targetMaterialProfile),
        writeYamlDocument(refs.source_inventory, inventory),
        writeYamlDocument(refs.initial_source_frontier, initialSourceFrontier),
        writeYamlDocument(refs.source_observations, sourceObservations),
    ]);
    return refs;
}
