import { toDate, getDaysBetween } from '../../../utils/dateUtils.js';
export { addDays, isSameDay, isWeekend } from '../../../utils/dateUtils.js';

// Re-export generic date utilities from shared utils so existing consumers
// that import from "GanttChart.utils" continue to work.
/**
 * Day-offset of `date` from `rangeStart` (0-based).
 */
function getDayOffset(date, rangeStart) {
    return Math.round((toDate(date).getTime() - toDate(rangeStart).getTime()) / 86400000);
}
/**
 * Generate every date between rangeStart and rangeEnd (inclusive).
 */
function generateDayColumns(rangeStart, rangeEnd) {
    const days = [];
    const current = new Date(rangeStart);
    current.setHours(0, 0, 0, 0);
    const end = new Date(rangeEnd);
    end.setHours(0, 0, 0, 0);
    while (current <= end) {
        days.push(new Date(current));
        current.setDate(current.getDate() + 1);
    }
    return days;
}
function generateMonthSpans(days) {
    if (days.length === 0)
        return [];
    const spans = [];
    let currentLabel = formatMonthLabel(days[0]);
    let startIndex = 0;
    for (let i = 1; i < days.length; i++) {
        const label = formatMonthLabel(days[i]);
        if (label !== currentLabel) {
            spans.push({ label: currentLabel, startIndex, span: i - startIndex });
            currentLabel = label;
            startIndex = i;
        }
    }
    spans.push({
        label: currentLabel,
        startIndex,
        span: days.length - startIndex,
    });
    return spans;
}
function formatMonthLabel(date) {
    return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
/**
 * Auto-detect date range from task data with padding.
 */
function autoDateRange(tasks, paddingDays = 7) {
    if (tasks.length === 0) {
        const now = new Date();
        now.setHours(0, 0, 0, 0);
        const end = new Date(now);
        end.setDate(end.getDate() + 30);
        return { start: now, end };
    }
    let min = Infinity;
    let max = -Infinity;
    for (const t of tasks) {
        const s = toDate(t.startDate).getTime();
        const e = toDate(t.endDate).getTime();
        if (s < min)
            min = s;
        if (e > max)
            max = e;
    }
    const start = new Date(min);
    start.setDate(start.getDate() - paddingDays);
    start.setHours(0, 0, 0, 0);
    const end = new Date(max);
    end.setDate(end.getDate() + paddingDays);
    end.setHours(0, 0, 0, 0);
    return { start, end };
}
/**
 * Compute group summary: earliest start, latest end, completed count.
 */
function computeGroupSummary(group, tasks) {
    const groupTasks = tasks.filter((t) => t.groupId === group.id);
    if (groupTasks.length === 0) {
        const now = new Date();
        return { earliestStart: now, latestEnd: now, completedCount: 0 };
    }
    let earliest = Infinity;
    let latest = -Infinity;
    let completed = 0;
    for (const t of groupTasks) {
        const s = toDate(t.startDate).getTime();
        const e = toDate(t.endDate).getTime();
        if (s < earliest)
            earliest = s;
        if (e > latest)
            latest = e;
        if (t.completed)
            completed++;
    }
    return {
        earliestStart: new Date(earliest),
        latestEnd: new Date(latest),
        completedCount: completed,
    };
}
/**
 * Format a short day label (e.g. "15", "Mon 15").
 */
function formatDayLabel(date, zoom) {
    if (zoom === "day") {
        const weekday = date.toLocaleDateString("en-US", { weekday: "short" });
        return `${weekday} ${date.getDate()}`;
    }
    if (zoom === "year") {
        return date.getDate() === 1
            ? date.toLocaleDateString("en-US", { month: "short" })
            : "";
    }
    return `${date.getDate()}`;
}
/**
 * Format duration as "X days".
 */
function formatDuration(startDate, endDate) {
    const days = getDaysBetween(startDate, endDate);
    return `${days}d`;
}
/**
 * Format a date as "Jan 15" style short label.
 */
function formatDateShort(date) {
    const d = toDate(date);
    return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
/**
 * Calculate an SVG path string for a dependency arrow from one task bar to another.
 *
 * Two routing modes:
 * - Forward: target left >= source right + minGap → clean L-shaped path through midpoint
 * - Overlap/backward: bars overlap horizontally → route below both bars
 */
function calculateDependencyPath(fromLeft, fromWidth, fromTop, fromHeight, toLeft, toTop, toHeight) {
    const startX = fromLeft + fromWidth;
    const startY = fromTop + fromHeight / 2;
    const endX = toLeft;
    const endY = toTop + toHeight / 2;
    const minGap = 24;
    // Forward: enough horizontal space for a clean L-path
    if (endX >= startX + minGap) {
        if (Math.abs(endY - startY) < 1) {
            // Same row — straight line
            return { d: `M ${startX} ${startY} H ${endX}`, isBackward: false };
        }
        const midX = startX + (endX - startX) / 2;
        const r = Math.min(5, Math.abs(endY - startY) / 2, (endX - startX) / 4);
        const goDown = endY > startY;
        const d = [
            `M ${startX} ${startY}`,
            `H ${midX - r}`,
            `Q ${midX} ${startY} ${midX} ${startY + (goDown ? r : -r)}`,
            `V ${endY + (goDown ? -r : r)}`,
            `Q ${midX} ${endY} ${midX + r} ${endY}`,
            `H ${endX}`,
        ].join(" ");
        return { d, isBackward: false };
    }
    // Overlap or close proximity: route below both bars
    const gap = 14;
    const exitX = startX + gap;
    const entryX = endX - gap;
    // Go below the lower of the two bars so we never cross through them
    const lowerBottom = Math.max(fromTop + fromHeight, toTop + toHeight);
    const routeY = lowerBottom + gap;
    const d = [
        `M ${startX} ${startY}`,
        `H ${exitX}`,
        `V ${routeY}`,
        `H ${entryX}`,
        `V ${endY}`,
        `H ${endX}`,
    ].join(" ");
    // Only visually mark as backward (dashed) if target truly starts before source
    return { d, isBackward: endX < fromLeft };
}
/**
 * Build all dependency arrow paths for visible tasks.
 */
function buildDependencyArrows(tasks, rows, dayWidth, rowHeight, rangeStart, activeDrag) {
    var _a;
    const taskMap = new Map(tasks.map((t) => [t.id, t]));
    const rowIndexMap = new Map();
    rows.forEach((row, i) => {
        if (row.type === "task")
            rowIndexMap.set(row.task.id, i);
    });
    const arrows = [];
    const barHeight = rowHeight - 12;
    for (const task of tasks) {
        if (!((_a = task.dependencies) === null || _a === void 0 ? void 0 : _a.length))
            continue;
        const toIdx = rowIndexMap.get(task.id);
        if (toIdx === undefined)
            continue;
        for (const depId of task.dependencies) {
            const dep = taskMap.get(depId);
            if (!dep)
                continue;
            const fromIdx = rowIndexMap.get(depId);
            if (fromIdx === undefined)
                continue;
            const fromOffset = getDayOffset(dep.startDate, rangeStart);
            const fromSpan = getDaysBetween(dep.startDate, dep.endDate);
            let fromLeft = fromOffset * dayWidth;
            let fromWidth = Math.max(fromSpan * dayWidth, dayWidth);
            const fromTop = fromIdx * rowHeight + (rowHeight - barHeight) / 2;
            const toOffset = getDayOffset(task.startDate, rangeStart);
            let toLeft = toOffset * dayWidth;
            const toTop = toIdx * rowHeight + (rowHeight - barHeight) / 2;
            // Apply drag offsets for real-time arrow following
            if (activeDrag) {
                // Source task (dep) is being dragged
                if (activeDrag.taskId === depId) {
                    if (activeDrag.mode === "move") {
                        fromLeft += activeDrag.deltaX;
                    }
                    else if (activeDrag.mode === "resize-left") {
                        fromLeft += activeDrag.deltaX;
                        fromWidth -= activeDrag.deltaX;
                    }
                    else if (activeDrag.mode === "resize-right") {
                        fromWidth += activeDrag.deltaX;
                    }
                }
                // Target task is being dragged
                if (activeDrag.taskId === task.id) {
                    if (activeDrag.mode === "move") {
                        toLeft += activeDrag.deltaX;
                    }
                    else if (activeDrag.mode === "resize-left") {
                        toLeft += activeDrag.deltaX;
                    }
                    // resize-right doesn't move the left edge (target start)
                }
            }
            const result = calculateDependencyPath(fromLeft, fromWidth, fromTop, barHeight, toLeft, toTop, barHeight);
            arrows.push({
                id: `${depId}->${task.id}`,
                d: result.d,
                isBackward: result.isBackward,
            });
        }
    }
    return arrows;
}
// ---------------------------------------------------------------------------
// Expected progress
// ---------------------------------------------------------------------------
/**
 * Calculate expected progress (0-100) based on elapsed time.
 */
function calculateExpectedProgress(startDate, endDate) {
    const start = toDate(startDate).getTime();
    const end = toDate(endDate).getTime();
    const now = new Date().setHours(0, 0, 0, 0);
    if (now <= start)
        return 0;
    if (now >= end)
        return 100;
    return Math.round(((now - start) / (end - start)) * 100);
}

export { autoDateRange, buildDependencyArrows, calculateDependencyPath, calculateExpectedProgress, computeGroupSummary, formatDateShort, formatDayLabel, formatDuration, generateDayColumns, generateMonthSpans, getDayOffset, getDaysBetween, toDate };
