import { readdir } from "node:fs/promises";
import path from "node:path";
import { appendEvent } from "../observability/index.js";
import { createId, ensureDir, nowIso, readJsonFile, sanitizeText, withFileLock, writeJsonFile } from "../../shared/index.js";
import { ensureTeamRuntime, teamRoot } from "./store.js";
import { sendTeamBroadcast } from "./mailbox.js";
import { LAB_REPRESENTATIVE_AGENT } from "../../agents/identity.js";
export function teamMeetingsRoot(store, runId) {
    return path.join(teamRoot(store, runId), "meetings");
}
export function teamMeetingPath(store, runId, meetingId) {
    return path.join(teamMeetingsRoot(store, runId), `${meetingId}.json`);
}
export async function createTeamMeeting(store, runId, input) {
    const runtime = await ensureTeamRuntime(store, runId);
    const participants = normalizeParticipants(input.participants?.length ? input.participants : runtime.members.map((member) => String(member.name)));
    if (!participants.length)
        throw new Error("team meeting requires at least one participant");
    const knownMembers = new Set(runtime.members.map((member) => String(member.name)));
    const unknown = participants.filter((participant) => !knownMembers.has(participant));
    if (unknown.length)
        throw new Error(`team meeting participant not found: ${unknown.join(", ")}`);
    const now = nowIso();
    const meeting = {
        version: 1,
        meeting_id: createId("team_meeting"),
        run_id: runId,
        title: sanitizeText(input.title).trim(),
        agenda: input.agenda.map((item) => sanitizeText(item).trim()).filter(Boolean),
        status: "open",
        participants,
        created_by: sanitizeText(input.created_by).trim(),
        created_at: now,
        updated_at: now,
        metadata: input.metadata,
        posts: [],
    };
    if (!meeting.title)
        throw new Error("team meeting title is required");
    await ensureDir(teamMeetingsRoot(store, runId));
    await writeJsonFile(teamMeetingPath(store, runId, meeting.meeting_id), meeting);
    await appendEvent(store, runId, {
        type: "team_meeting_created",
        actor: meeting.created_by,
        summary: `${meeting.meeting_id}: ${meeting.title}`,
        metadata: { meeting_id: meeting.meeting_id, participants, agenda: meeting.agenda },
    });
    const recipients = participants.filter((participant) => participant !== meeting.created_by);
    if (recipients.length) {
        await sendTeamBroadcast(store, runId, {
            from: meeting.created_by,
            to: recipients,
            kind: "meeting",
            body: `Team meeting opened: ${meeting.title}\nAgenda:\n${meeting.agenda.map((item) => `- ${item}`).join("\n") || "- none"}`,
            metadata: { meeting_id: meeting.meeting_id, meeting_event: "created" },
        });
    }
    return meeting;
}
export async function createWorkspaceConflictMeeting(store, runId, conflict, input = {}) {
    const existing = (await listTeamMeetings(store, runId, { status: "open" }))
        .find((meeting) => meeting.metadata?.workspace_conflict_id === conflict.conflict_id);
    if (existing)
        return existing;
    const runtime = await ensureTeamRuntime(store, runId);
    const knownMembers = new Set(runtime.members.map((member) => String(member.name)));
    const creator = knownMembers.has(input.created_by ?? "") ? input.created_by : LAB_REPRESENTATIVE_AGENT;
    const participants = normalizeParticipants([creator, ...conflict.actors].filter((actor) => knownMembers.has(actor)));
    return createTeamMeeting(store, runId, {
        title: `Workspace conflict: ${conflict.path}`,
        created_by: creator,
        participants: participants.length ? participants : [creator],
        agenda: [
            conflict.summary,
            `Affected path: ${conflict.path}`,
            `Actors: ${conflict.actors.join(", ") || "unknown"}`,
            "Record a decision before resolving the workspace conflict.",
        ],
        metadata: {
            workspace_conflict_id: conflict.conflict_id,
            workspace_conflict_kind: conflict.kind,
            workspace_path: conflict.path,
        },
    });
}
export async function readTeamMeeting(store, runId, meetingId) {
    return readJsonFile(teamMeetingPath(store, runId, meetingId));
}
export async function listTeamMeetings(store, runId, options = {}) {
    let files = [];
    try {
        files = await readdir(teamMeetingsRoot(store, runId));
    }
    catch (error) {
        if (isMissingPathError(error))
            return [];
        throw error;
    }
    const meetings = await Promise.all(files
        .filter((file) => file.endsWith(".json"))
        .map((file) => readJsonFile(path.join(teamMeetingsRoot(store, runId), file))));
    return meetings
        .filter((meeting) => meeting !== undefined)
        .filter((meeting) => options.status === undefined || options.status === "all" || meeting.status === options.status)
        .filter((meeting) => !options.participant || meeting.participants.includes(options.participant))
        .sort((left, right) => left.created_at.localeCompare(right.created_at));
}
export async function postTeamMeeting(store, runId, input) {
    const file = teamMeetingPath(store, runId, input.meeting_id);
    const updated = await withFileLock(file, async () => {
        const meeting = await readJsonFile(file);
        if (!meeting)
            throw new Error(`team meeting not found: ${input.meeting_id}`);
        if (meeting.status !== "open")
            throw new Error(`team meeting is closed: ${input.meeting_id}`);
        const actor = sanitizeText(input.from).trim();
        if (!meeting.participants.includes(actor))
            throw new Error(`team meeting participant not found: ${actor}`);
        const now = nowIso();
        const post = {
            post_id: createId("team_post"),
            meeting_id: meeting.meeting_id,
            run_id: runId,
            from: actor,
            kind: input.kind ?? "message",
            body: sanitizeText(input.body).trim(),
            created_at: now,
            reply_to: optionalText(input.reply_to),
            action_owner: optionalText(input.action_owner),
            action_status: input.kind === "action_item" ? "open" : undefined,
        };
        if (!post.body)
            throw new Error("team meeting post body is required");
        const next = {
            ...meeting,
            updated_at: now,
            posts: [...meeting.posts, post],
        };
        await writeJsonFile(file, next);
        return next;
    });
    const post = updated.posts.at(-1);
    await appendEvent(store, runId, {
        type: "team_meeting_posted",
        actor: input.from,
        summary: `${updated.meeting_id}: ${post?.kind ?? "message"} from ${input.from}`,
        metadata: { meeting_id: updated.meeting_id, post_id: post?.post_id, kind: post?.kind },
    });
    if (post?.kind === "decision" || post?.kind === "action_item") {
        const recipients = updated.participants.filter((participant) => participant !== input.from);
        if (recipients.length) {
            await sendTeamBroadcast(store, runId, {
                from: input.from,
                to: recipients,
                kind: "meeting",
                body: `${post.kind === "decision" ? "Decision" : "Action item"} in ${updated.title}: ${post.body}`,
                metadata: { meeting_id: updated.meeting_id, post_id: post.post_id, meeting_event: post.kind },
            });
        }
    }
    return updated;
}
export async function closeTeamMeeting(store, runId, input) {
    const file = teamMeetingPath(store, runId, input.meeting_id);
    const updated = await withFileLock(file, async () => {
        const meeting = await readJsonFile(file);
        if (!meeting)
            throw new Error(`team meeting not found: ${input.meeting_id}`);
        if (meeting.status === "closed")
            return meeting;
        const now = nowIso();
        const summary = sanitizeText(input.summary).trim();
        const post = {
            post_id: createId("team_post"),
            meeting_id: meeting.meeting_id,
            run_id: runId,
            from: sanitizeText(input.closed_by).trim(),
            kind: "summary",
            body: summary || "Meeting closed.",
            created_at: now,
        };
        const next = {
            ...meeting,
            status: "closed",
            summary: post.body,
            closed_at: now,
            updated_at: now,
            posts: [...meeting.posts, post],
        };
        await writeJsonFile(file, next);
        return next;
    });
    await appendEvent(store, runId, {
        type: "team_meeting_closed",
        actor: input.closed_by,
        summary: `${updated.meeting_id}: ${updated.summary ?? "closed"}`,
        metadata: { meeting_id: updated.meeting_id, summary: updated.summary },
    });
    const recipients = updated.participants.filter((participant) => participant !== input.closed_by);
    if (recipients.length) {
        await sendTeamBroadcast(store, runId, {
            from: input.closed_by,
            to: recipients,
            kind: "meeting",
            body: `Team meeting closed: ${updated.title}\nSummary: ${updated.summary ?? "closed"}`,
            metadata: { meeting_id: updated.meeting_id, meeting_event: "closed" },
        });
    }
    return updated;
}
export function renderTeamMeetingContext(meetings, memberName, maxMeetings = 5) {
    const relevant = meetings
        .filter((meeting) => meeting.status === "open")
        .filter((meeting) => !memberName || meeting.participants.includes(memberName))
        .slice(-maxMeetings);
    if (!relevant.length)
        return undefined;
    return `<system-reminder>
[SEED team meetings]
${relevant.map(renderMeetingLine).join("\n")}

Use seed_team_meeting_status for full threads and seed_team_meeting_post to add decisions or action items.
</system-reminder>`;
}
export function compactTeamMeetingSummary(meetings, maxMeetings = 6) {
    const open = meetings.filter((meeting) => meeting.status === "open").slice(-maxMeetings);
    if (!open.length)
        return ["- none"];
    return open.map((meeting) => {
        const latestDecision = meeting.posts.filter((post) => post.kind === "decision").at(-1);
        const latestAction = meeting.posts.filter((post) => post.kind === "action_item").at(-1);
        const suffix = [
            latestDecision ? ` decision:${latestDecision.body.slice(0, 90)}` : "",
            latestAction ? ` action:${latestAction.body.slice(0, 90)}` : "",
        ].filter(Boolean).join("");
        return `- ${meeting.meeting_id} ${meeting.title} participants:${meeting.participants.join(",")}${suffix}`;
    });
}
function renderMeetingLine(meeting) {
    const decisions = meeting.posts.filter((post) => post.kind === "decision").slice(-2);
    const actions = meeting.posts.filter((post) => post.kind === "action_item").slice(-2);
    return [
        `- ${meeting.meeting_id}: ${meeting.title}`,
        `  participants: ${meeting.participants.join(", ")}`,
        `  agenda: ${meeting.agenda.join("; ") || "none"}`,
        ...(decisions.length ? decisions.map((post) => `  decision: ${post.body}`) : []),
        ...(actions.length ? actions.map((post) => `  action: ${post.action_owner ? `${post.action_owner}: ` : ""}${post.body}`) : []),
    ].join("\n");
}
function normalizeParticipants(values) {
    return [...new Set((values ?? []).map((value) => sanitizeText(value).trim()).filter(Boolean))];
}
function optionalText(value) {
    const text = sanitizeText(value ?? "").trim();
    return text || undefined;
}
function isMissingPathError(error) {
    return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
}
//# sourceMappingURL=meeting.js.map