import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { ChevronDown, ChevronRight, CircleCheck, CircleDashed, CircleX, Loader2, MinusCircle, Pencil, Plus, Target, Trash2, } from "lucide-react";
import { cn } from "../../lib/utils";
import { MarkdownDisplay } from "../../components/MarkdownDisplay";
import { Button } from "../../components/ui/button";
import { Select } from "../../components/ui/select";
import { Input } from "../../components/ui/input";
import { KpiTile, formatPercent } from "./ProjectDashboard";
import { GoalDialog } from "./GoalDialog";
import { createSubgoal, deleteGoal, deleteSubgoal, updateGoal, updateSubgoal, } from "../services/goalService";
const CUSTOM_KEY = "__custom__";
const GOAL_STATUS_OPTIONS = [
    { value: "Open", label: "Open" },
    { value: "InProgress", label: "In progress" },
    { value: "Achieved", label: "Achieved" },
    { value: "Abandoned", label: "Abandoned" },
];
const SUBGOAL_STATUS_OPTIONS = [
    { value: "Pending", label: "Pending" },
    { value: "InProgress", label: "In progress" },
    { value: "Done", label: "Done" },
    { value: "Failed", label: "Failed" },
    { value: "Skipped", label: "Skipped" },
];
function goalGroupKey(goal) {
    return goal.sourceGoalTemplateId ?? CUSTOM_KEY;
}
function goalGroupLabel(goal) {
    if (!goal.sourceGoalTemplateId)
        return "Custom";
    return goal.sourceGoalTemplateName ?? "Removed definition";
}
function isItemSpanning(goal) {
    return !!goal.coverage && goal.coverage.hasMeasurableSubgoals;
}
function canEditPermission(permission) {
    return permission === "Owner" || permission === "Editor";
}
function getProjectPermission(permissions, projectId) {
    const key = projectId?.trim();
    if (!key)
        return undefined;
    const direct = permissions[key];
    if (direct)
        return direct;
    const normalizedKey = key.toLowerCase();
    return Object.entries(permissions).find(([candidate]) => candidate.trim().toLowerCase() === normalizedKey)?.[1];
}
export function ProjectGoalsPanel(props) {
    const { goals, catalog, loading, hasSubprojects, includeDescendants, onToggleIncludeDescendants, selectedProjectId, permissions, onChanged, } = props;
    const [dialogOpen, setDialogOpen] = useState(false);
    const [editingGoal, setEditingGoal] = useState(null);
    const canCreate = !!selectedProjectId &&
        canEditPermission(getProjectPermission(permissions, selectedProjectId));
    const groups = useMemo(() => {
        const byKey = new Map();
        for (const goal of goals) {
            const key = goalGroupKey(goal);
            const entry = byKey.get(key) ?? {
                key,
                label: goalGroupLabel(goal),
                done: 0,
                total: 0,
            };
            if (isItemSpanning(goal) && goal.coverage) {
                entry.done += goal.coverage.done;
                entry.total += goal.coverage.total;
            }
            else {
                entry.total += 1;
                if (goal.status === "Achieved")
                    entry.done += 1;
            }
            byKey.set(key, entry);
        }
        return Array.from(byKey.values()).sort((a, b) => a.label.localeCompare(b.label));
    }, [goals]);
    const sortedGoals = useMemo(() => [...goals].sort((a, b) => a.sortOrder - b.sortOrder), [goals]);
    const openCreate = () => {
        setEditingGoal(null);
        setDialogOpen(true);
    };
    const openEdit = (goal) => {
        setEditingGoal(goal);
        setDialogOpen(true);
    };
    return (_jsxs("div", { className: "flex min-h-0 flex-col gap-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [hasSubprojects ? (_jsxs("label", { className: "text-neutral-grey-50 flex items-center gap-2 text-xs", children: [_jsx("input", { type: "checkbox", checked: includeDescendants, onChange: (event) => onToggleIncludeDescendants(event.target.checked) }), "Include subprojects"] })) : (_jsx("span", {})), canCreate ? (_jsxs(Button, { onClick: openCreate, "data-testid": "taskboard-add-goal-button", children: [_jsx(Plus, { strokeWidth: 1, className: "mr-1 size-4", "aria-hidden": "true" }), "Add goal"] })) : null] }), loading && goals.length === 0 ? (_jsxs("div", { className: "text-neutral-grey-50 flex items-center gap-2 py-8 text-sm", children: [_jsx(Loader2, { strokeWidth: 1, className: "size-4 animate-spin", "aria-hidden": "true" }), "Loading goals\u2026"] })) : goals.length === 0 ? (_jsxs("div", { "data-testid": "taskboard-goals-empty", className: "border-border-default text-neutral-grey-50 rounded-md border border-dashed p-8 text-center text-sm", children: ["No goals yet.", " ", canCreate
                        ? "Add one, or let agents create them as they work."
                        : "Agents and editors can create goals for this project."] })) : (_jsxs(_Fragment, { children: [groups.length > 0 ? (_jsx("div", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3", children: groups.map((group) => {
                            const pct = group.total > 0 ? (group.done / group.total) * 100 : null;
                            return (_jsx(KpiTile, { icon: _jsx(Target, { strokeWidth: 1, className: "size-4", "aria-hidden": "true" }), label: group.label, value: `${group.done} / ${group.total}`, detail: pct === null ? "—" : `${formatPercent(pct)} complete`, testId: `goal-group-${group.key}` }, group.key));
                        }) })) : null, _jsx("div", { className: "flex flex-col gap-3", children: sortedGoals.map((goal) => (_jsx(GoalCard, { goal: goal, canEdit: canEditPermission(getProjectPermission(permissions, goal.projectId)), onEdit: () => openEdit(goal), onChanged: onChanged }, goal.goalId))) })] })), selectedProjectId ? (_jsx(GoalDialog, { open: dialogOpen, onOpenChange: setDialogOpen, projectId: selectedProjectId, catalog: catalog, goal: editingGoal, onSaved: onChanged })) : null] }));
}
function GoalCard({ goal, canEdit, onEdit, onChanged, }) {
    const [expanded, setExpanded] = useState(false);
    const [addingSubgoal, setAddingSubgoal] = useState(false);
    const [newSubgoalTitle, setNewSubgoalTitle] = useState("");
    const [busy, setBusy] = useState(false);
    const subgoals = goal.subgoals ?? [];
    const hasSubgoals = subgoals.length > 0;
    const coverage = goal.coverage;
    const pct = coverage && coverage.hasMeasurableSubgoals ? (coverage.percent ?? 0) : null;
    const changeStatus = async (status) => {
        setBusy(true);
        try {
            const result = await updateGoal({ goalId: goal.goalId, status });
            if (result.type !== "success") {
                toast.error(result.summary || "Failed to update status");
                return;
            }
            onChanged();
        }
        finally {
            setBusy(false);
        }
    };
    const remove = async () => {
        if (!window.confirm("Delete this goal and its subgoals?"))
            return;
        setBusy(true);
        try {
            const result = await deleteGoal(goal.goalId);
            if (result.type !== "success") {
                toast.error(result.summary || "Failed to delete goal");
                return;
            }
            onChanged();
        }
        finally {
            setBusy(false);
        }
    };
    const addSubgoal = async () => {
        if (!newSubgoalTitle.trim())
            return;
        setBusy(true);
        try {
            const result = await createSubgoal({
                goalId: goal.goalId,
                title: newSubgoalTitle.trim(),
            });
            if (result.type !== "success") {
                toast.error(result.summary || "Failed to add subgoal");
                return;
            }
            setNewSubgoalTitle("");
            setAddingSubgoal(false);
            setExpanded(true);
            onChanged();
        }
        finally {
            setBusy(false);
        }
    };
    return (_jsxs("div", { "data-testid": "taskboard-goal-card", className: "border-border-default rounded-md border bg-white p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2", children: [_jsx(Target, { strokeWidth: 1, className: "text-neutral-grey-50 mt-0.5 size-4 shrink-0", "aria-hidden": "true" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-neutral-grey-100 text-sm font-medium", children: goal.title }), !canEdit ? _jsx(GoalStatusBadge, { status: goal.status }) : null, _jsx("span", { className: "bg-neutral-grey-5 text-neutral-grey-50 rounded px-2 py-0.5 text-[11px]", children: goalGroupLabel(goal) })] }), goal.description ? (_jsx("div", { className: "text-neutral-grey-50 mt-1 text-xs", children: goal.description })) : null] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [pct !== null ? (_jsxs("span", { className: "text-neutral-grey-50 text-xs", children: [coverage?.done, " / ", coverage?.total] })) : null, canEdit ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "w-32", children: _jsx(Select, { value: goal.status, onValueChange: changeStatus, options: GOAL_STATUS_OPTIONS }) }), _jsx(IconButton, { label: "Edit goal", onClick: onEdit, disabled: busy, icon: _jsx(Pencil, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" }) }), _jsx(IconButton, { label: "Delete goal", onClick: remove, disabled: busy, icon: _jsx(Trash2, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" }) })] })) : null] })] }), pct !== null ? (_jsx("div", { className: "bg-neutral-grey-5 mt-3 h-1.5 w-full overflow-hidden rounded-full", children: _jsx("div", { className: cn("h-full", pct >= 100 ? "bg-feedback-green" : "bg-feedback-blue"), style: { width: `${Math.max(0, Math.min(100, pct))}%` } }) })) : null, _jsx(GoalDataView, { data: goal.data }), hasSubgoals ? (_jsxs("div", { className: "mt-3", children: [_jsxs("button", { type: "button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 flex items-center gap-1 text-xs", onClick: () => setExpanded((value) => !value), children: [expanded ? (_jsx(ChevronDown, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" })) : (_jsx(ChevronRight, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" })), expanded ? "Hide" : "Show", " ", subgoals.length, " subgoal", subgoals.length === 1 ? "" : "s"] }), expanded ? (_jsx("div", { className: "border-border-default mt-2 flex flex-col divide-y rounded-md border", children: [...subgoals]
                            .sort((a, b) => a.sortOrder - b.sortOrder)
                            .map((subgoal) => (_jsx(SubgoalRow, { subgoal: subgoal, canEdit: canEdit, onChanged: onChanged }, subgoal.subgoalId))) })) : null] })) : null, canEdit ? (_jsx("div", { className: "mt-3", children: addingSubgoal ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { autoFocus: true, "data-testid": "taskboard-subgoal-title-input", value: newSubgoalTitle, onChange: (e) => setNewSubgoalTitle(e.target.value), placeholder: "Subgoal title", onKeyDown: (e) => {
                                if (e.key === "Enter")
                                    void addSubgoal();
                                if (e.key === "Escape")
                                    setAddingSubgoal(false);
                            } }), _jsx(Button, { onClick: () => void addSubgoal(), disabled: busy || !newSubgoalTitle.trim(), "data-testid": "taskboard-subgoal-add-confirm", children: "Add" }), _jsx(Button, { variant: "outline", onClick: () => setAddingSubgoal(false), disabled: busy, children: "Cancel" })] })) : (_jsxs("button", { type: "button", "data-testid": "taskboard-add-subgoal-button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 flex items-center gap-1 text-xs", onClick: () => setAddingSubgoal(true), children: [_jsx(Plus, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" }), "Add subgoal"] })) })) : null] }));
}
function SubgoalRow({ subgoal, canEdit, onChanged, }) {
    const [showData, setShowData] = useState(false);
    const [busy, setBusy] = useState(false);
    const label = subgoal.title || subgoal.targetItemPath || "Subgoal";
    const changeStatus = async (status) => {
        setBusy(true);
        try {
            const result = await updateSubgoal({
                subgoalId: subgoal.subgoalId,
                status,
            });
            if (result.type !== "success") {
                toast.error(result.summary || "Failed to update subgoal");
                return;
            }
            onChanged();
        }
        finally {
            setBusy(false);
        }
    };
    const remove = async () => {
        setBusy(true);
        try {
            const result = await deleteSubgoal(subgoal.subgoalId);
            if (result.type !== "success") {
                toast.error(result.summary || "Failed to delete subgoal");
                return;
            }
            onChanged();
        }
        finally {
            setBusy(false);
        }
    };
    return (_jsxs("div", { className: "px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(SubgoalStatusIcon, { status: subgoal.status }), _jsx("span", { className: "text-neutral-grey-100 min-w-0 flex-1 truncate text-xs", children: label }), subgoal.data != null ? (_jsx("button", { type: "button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 text-[11px]", onClick: () => setShowData((value) => !value), children: showData ? "Hide data" : "Data" })) : null, canEdit ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "w-32", children: _jsx(Select, { value: subgoal.status, onValueChange: changeStatus, options: SUBGOAL_STATUS_OPTIONS }) }), _jsx(IconButton, { label: "Delete subgoal", onClick: remove, disabled: busy, icon: _jsx(Trash2, { strokeWidth: 1, className: "size-3.5", "aria-hidden": "true" }) })] })) : null] }), subgoal.targetItemPath ? (_jsx("div", { className: "text-neutral-grey-50 mt-0.5 ml-6 truncate text-[11px]", children: subgoal.targetItemPath })) : null, showData ? (_jsx("div", { className: "ml-6", children: _jsx(GoalDataView, { data: subgoal.data }) })) : null] }));
}
function IconButton({ label, icon, onClick, disabled, }) {
    return (_jsx("button", { type: "button", "aria-label": label, title: label, onClick: onClick, disabled: disabled, className: "border-border-default text-neutral-grey-50 hover:text-neutral-grey-100 hover:bg-neutral-grey-5 flex size-7 items-center justify-center rounded-md border disabled:opacity-50", children: icon }));
}
function GoalStatusBadge({ status }) {
    const map = {
        Achieved: "text-feedback-green",
        InProgress: "text-feedback-blue",
        Open: "text-neutral-grey-50",
        Abandoned: "text-neutral-grey-50",
    };
    return (_jsx("span", { className: cn("text-[11px] font-medium", map[status]), children: status }));
}
function SubgoalStatusIcon({ status }) {
    const className = "size-4 shrink-0";
    switch (status) {
        case "Done":
            return (_jsx(CircleCheck, { strokeWidth: 1, className: cn(className, "text-feedback-green"), "aria-label": "Done" }));
        case "InProgress":
            return (_jsx(Loader2, { strokeWidth: 1, className: cn(className, "text-feedback-blue"), "aria-label": "In progress" }));
        case "Failed":
            return (_jsx(CircleX, { strokeWidth: 1, className: cn(className, "text-feedback-red"), "aria-label": "Failed" }));
        case "Skipped":
            return (_jsx(MinusCircle, { strokeWidth: 1, className: cn(className, "text-neutral-grey-50"), "aria-label": "Skipped" }));
        default:
            return (_jsx(CircleDashed, { strokeWidth: 1, className: cn(className, "text-neutral-grey-50"), "aria-label": "Pending" }));
    }
}
// ---- Arbitrary data rendering (XSS-safe) ----
function asRecord(value) {
    return value && typeof value === "object" && !Array.isArray(value)
        ? value
        : null;
}
function asStringList(value) {
    if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
        return value;
    }
    return null;
}
function asMetrics(value) {
    if (!Array.isArray(value))
        return null;
    const out = [];
    for (const item of value) {
        const record = asRecord(item);
        if (!record)
            return null;
        out.push({
            label: String(record.label ?? ""),
            value: String(record.value ?? ""),
        });
    }
    return out;
}
function asLinks(value) {
    if (!Array.isArray(value))
        return null;
    const out = [];
    for (const item of value) {
        const record = asRecord(item);
        if (!record || typeof record.url !== "string")
            return null;
        if (!isSafeHttpUrl(record.url))
            continue;
        out.push({ label: String(record.label ?? record.url), url: record.url });
    }
    return out;
}
function isSafeHttpUrl(url) {
    try {
        const parsed = new URL(url, window.location.origin);
        return parsed.protocol === "http:" || parsed.protocol === "https:";
    }
    catch {
        return false;
    }
}
function GoalDataView({ data }) {
    const [showRaw, setShowRaw] = useState(false);
    if (data == null)
        return null;
    const record = asRecord(data);
    if (!record) {
        return (_jsx("pre", { className: "bg-neutral-grey-5 text-neutral-grey-100 mt-2 max-h-48 overflow-auto rounded-md p-2 text-[11px]", children: safeStringify(data) }));
    }
    const summary = typeof record.summary === "string" ? record.summary : null;
    const recommendations = asStringList(record.recommendations) ?? asStringList(record.items);
    const metrics = asMetrics(record.metrics);
    const links = asLinks(record.links);
    const knownKeys = new Set([
        "summary",
        "recommendations",
        "items",
        "metrics",
        "links",
    ]);
    const otherKeys = Object.keys(record).filter((key) => !knownKeys.has(key));
    const hasKnown = !!summary ||
        (recommendations && recommendations.length > 0) ||
        (metrics && metrics.length > 0) ||
        (links && links.length > 0);
    return (_jsxs("div", { className: "mt-2 flex flex-col gap-2", children: [summary ? (_jsx("div", { className: "text-neutral-grey-100 text-xs", children: _jsx(MarkdownDisplay, { source: summary }) })) : null, recommendations && recommendations.length > 0 ? (_jsx("ul", { className: "text-neutral-grey-100 ml-4 list-disc text-xs", children: recommendations.map((item, index) => (_jsx("li", { children: item }, index))) })) : null, metrics && metrics.length > 0 ? (_jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3", children: metrics.map((metric, index) => (_jsxs("div", { className: "bg-neutral-grey-5 rounded-md p-2 text-xs", children: [_jsx("div", { className: "text-neutral-grey-50 text-[10px] uppercase", children: metric.label }), _jsx("div", { className: "text-neutral-grey-100 font-medium", children: metric.value })] }, index))) })) : null, links && links.length > 0 ? (_jsx("div", { className: "flex flex-col gap-1 text-xs", children: links.map((link, index) => (_jsx("a", { href: link.url, target: "_blank", rel: "noopener noreferrer", className: "text-feedback-blue truncate hover:underline", children: link.label }, index))) })) : null, otherKeys.length > 0 || !hasKnown ? (_jsxs("div", { children: [_jsx("button", { type: "button", className: "text-neutral-grey-50 hover:text-neutral-grey-100 text-[11px]", onClick: () => setShowRaw((value) => !value), children: showRaw ? "Hide raw data" : "Show raw data" }), showRaw ? (_jsx("pre", { className: "bg-neutral-grey-5 text-neutral-grey-100 mt-1 max-h-48 overflow-auto rounded-md p-2 text-[11px]", children: safeStringify(data) })) : null] })) : null] }));
}
function safeStringify(value) {
    try {
        return JSON.stringify(value, null, 2);
    }
    catch {
        return String(value);
    }
}
//# sourceMappingURL=ProjectGoalsPanel.js.map