
import { localize } from '../../../../nls.js';
import { deepClone } from '../../../../base/common/objects.js';
import { registerColor, asCssVariable } from '../../../../platform/theme/common/colorUtils.js';
import { foreground } from '../../../../platform/theme/common/colors/baseColors.js';
import { chartsBlue, chartsPurple } from '../../../../platform/theme/common/colors/chartsColors.js';
import '../../../../platform/theme/common/colors/editorColors.js';
import '../../../../platform/theme/common/colors/inputColors.js';
import '../../../../platform/theme/common/colors/listColors.js';
import '../../../../platform/theme/common/colors/menuColors.js';
import '../../../../platform/theme/common/colors/minimapColors.js';
import { badgeBackground } from '../../../../platform/theme/common/colors/miscColors.js';
import '../../../../platform/theme/common/colors/quickpickColors.js';
import '../../../../platform/theme/common/colors/searchColors.js';
import { SCMIncomingHistoryItemId, SCMOutgoingHistoryItemId } from '../common/history.js';
import { rot } from '../../../../base/common/numbers.js';
import { createElementNS, svgElem, $ } from '../../../../base/browser/dom.js';
import { PANEL_BACKGROUND } from '../../../common/theme.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { isMarkdownString, MarkdownString, isEmptyMarkdownString } from '../../../../base/common/htmlContent.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { findLastIdx } from '../../../../base/common/arraysFind.js';

const SWIMLANE_HEIGHT = 22;
const SWIMLANE_WIDTH = 11;
const SWIMLANE_CURVE_RADIUS = 5;
const CIRCLE_RADIUS = 4;
const CIRCLE_STROKE_WIDTH = 2;
const historyItemRefColor = registerColor("scmGraph.historyItemRefColor", chartsBlue, ( localize(13108, "History item reference color.")));
const historyItemRemoteRefColor = registerColor("scmGraph.historyItemRemoteRefColor", chartsPurple, ( localize(13109, "History item remote reference color.")));
const historyItemBaseRefColor = registerColor("scmGraph.historyItemBaseRefColor", "#EA5C00", ( localize(13110, "History item base reference color.")));
const historyItemHoverDefaultLabelForeground = registerColor(
    "scmGraph.historyItemHoverDefaultLabelForeground",
    foreground,
    ( localize(13111, "History item hover default label foreground color."))
);
const historyItemHoverDefaultLabelBackground = registerColor(
    "scmGraph.historyItemHoverDefaultLabelBackground",
    badgeBackground,
    ( localize(13112, "History item hover default label background color."))
);
const historyItemHoverLabelForeground = registerColor(
    "scmGraph.historyItemHoverLabelForeground",
    PANEL_BACKGROUND,
    ( localize(13113, "History item hover label foreground color."))
);
registerColor("scmGraph.historyItemHoverAdditionsForeground", {
    light: "#587C0C",
    dark: "#81B88B",
    hcDark: "#A1E3AD",
    hcLight: "#374E06"
}, ( localize(13114, "History item hover additions foreground color.")));
registerColor("scmGraph.historyItemHoverDeletionsForeground", {
    light: "#AD0707",
    dark: "#C74E39",
    hcDark: "#C74E39",
    hcLight: "#AD0707"
}, ( localize(13115, "History item hover deletions foreground color.")));
const colorRegistry = [registerColor("scmGraph.foreground1", "#FFB000", ( localize(13116, "Source control graph foreground color (1)."))), registerColor("scmGraph.foreground2", "#DC267F", ( localize(13117, "Source control graph foreground color (2)."))), registerColor("scmGraph.foreground3", "#994F00", ( localize(13118, "Source control graph foreground color (3)."))), registerColor("scmGraph.foreground4", "#40B0A6", ( localize(13119, "Source control graph foreground color (4)."))), registerColor("scmGraph.foreground5", "#B66DFF", ( localize(13120, "Source control graph foreground color (5).")))];
function getLabelColorIdentifier(historyItem, colorMap) {
    if (historyItem.id === SCMIncomingHistoryItemId) {
        return historyItemRemoteRefColor;
    } else if (historyItem.id === SCMOutgoingHistoryItemId) {
        return historyItemRefColor;
    } else {
        for (const ref of historyItem.references ?? []) {
            const colorIdentifier = colorMap.get(ref.id);
            if (colorIdentifier !== undefined) {
                return colorIdentifier;
            }
        }
    }
    return undefined;
}
function createPath(colorIdentifier, strokeWidth = 1) {
    const path = createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttribute("fill", "none");
    path.setAttribute("stroke-width", `${strokeWidth}px`);
    path.setAttribute("stroke-linecap", "round");
    path.style.stroke = asCssVariable(colorIdentifier);
    return path;
}
function drawCircle(index, radius, strokeWidth, colorIdentifier) {
    const circle = createElementNS("http://www.w3.org/2000/svg", "circle");
    circle.setAttribute("cx", `${SWIMLANE_WIDTH * (index + 1)}`);
    circle.setAttribute("cy", `${SWIMLANE_WIDTH}`);
    circle.setAttribute("r", `${radius}`);
    circle.style.strokeWidth = `${strokeWidth}px`;
    if (colorIdentifier) {
        circle.style.fill = asCssVariable(colorIdentifier);
    }
    return circle;
}
function drawDashedCircle(index, radius, strokeWidth, colorIdentifier) {
    const circle = createElementNS("http://www.w3.org/2000/svg", "circle");
    circle.setAttribute("cx", `${SWIMLANE_WIDTH * (index + 1)}`);
    circle.setAttribute("cy", `${SWIMLANE_WIDTH}`);
    circle.setAttribute("r", `${CIRCLE_RADIUS + 1}`);
    circle.style.stroke = asCssVariable(colorIdentifier);
    circle.style.strokeWidth = `${strokeWidth}px`;
    circle.style.strokeDasharray = "4,2";
    return circle;
}
function drawVerticalLine(x1, y1, y2, color, strokeWidth = 1) {
    const path = createPath(color, strokeWidth);
    path.setAttribute("d", `M ${x1} ${y1} V ${y2}`);
    return path;
}
function findLastIndex(nodes, id) {
    for (let i = nodes.length - 1; i >= 0; i--) {
        if (nodes[i].id === id) {
            return i;
        }
    }
    return -1;
}
function renderSCMHistoryItemGraph(historyItemViewModel) {
    const svg = createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.classList.add("graph");
    const historyItem = historyItemViewModel.historyItem;
    const inputSwimlanes = historyItemViewModel.inputSwimlanes;
    const outputSwimlanes = historyItemViewModel.outputSwimlanes;
    const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id);
    const circleIndex = inputIndex !== -1 ? inputIndex : inputSwimlanes.length;
    const circleColor = circleIndex < outputSwimlanes.length ? outputSwimlanes[circleIndex].color : circleIndex < inputSwimlanes.length ? inputSwimlanes[circleIndex].color : historyItemRefColor;
    let outputSwimlaneIndex = 0;
    for (let index = 0; index < inputSwimlanes.length; index++) {
        const color = inputSwimlanes[index].color;
        if (inputSwimlanes[index].id === historyItem.id) {
            if (index !== circleIndex) {
                const d = [];
                const path = createPath(color);
                d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`);
                d.push(
                    `A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * index} ${SWIMLANE_WIDTH}`
                );
                d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)}`);
                path.setAttribute("d", d.join(" "));
                svg.append(path);
            } else {
                outputSwimlaneIndex++;
            }
        } else {
            if (outputSwimlaneIndex < outputSwimlanes.length && inputSwimlanes[index].id === outputSwimlanes[outputSwimlaneIndex].id) {
                if (index === outputSwimlaneIndex) {
                    const path = drawVerticalLine(SWIMLANE_WIDTH * (index + 1), 0, SWIMLANE_HEIGHT, color);
                    svg.append(path);
                } else {
                    const d = [];
                    const path = createPath(color);
                    d.push(`M ${SWIMLANE_WIDTH * (index + 1)} 0`);
                    d.push(`V 6`);
                    d.push(
                        `A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 1 ${(SWIMLANE_WIDTH * (index + 1)) - SWIMLANE_CURVE_RADIUS} ${SWIMLANE_HEIGHT / 2}`
                    );
                    d.push(
                        `H ${(SWIMLANE_WIDTH * (outputSwimlaneIndex + 1)) + SWIMLANE_CURVE_RADIUS}`
                    );
                    d.push(
                        `A ${SWIMLANE_CURVE_RADIUS} ${SWIMLANE_CURVE_RADIUS} 0 0 0 ${SWIMLANE_WIDTH * (outputSwimlaneIndex + 1)} ${(SWIMLANE_HEIGHT / 2) + SWIMLANE_CURVE_RADIUS}`
                    );
                    d.push(`V ${SWIMLANE_HEIGHT}`);
                    path.setAttribute("d", d.join(" "));
                    svg.append(path);
                }
                outputSwimlaneIndex++;
            }
        }
    }
    for (let i = 1; i < historyItem.parentIds.length; i++) {
        const parentOutputIndex = findLastIndex(outputSwimlanes, historyItem.parentIds[i]);
        if (parentOutputIndex === -1) {
            continue;
        }
        const d = [];
        const path = createPath(outputSwimlanes[parentOutputIndex].color);
        d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`);
        d.push(
            `A ${SWIMLANE_WIDTH} ${SWIMLANE_WIDTH} 0 0 1 ${SWIMLANE_WIDTH * (parentOutputIndex + 1)} ${SWIMLANE_HEIGHT}`
        );
        d.push(`M ${SWIMLANE_WIDTH * parentOutputIndex} ${SWIMLANE_HEIGHT / 2}`);
        d.push(`H ${SWIMLANE_WIDTH * (circleIndex + 1)} `);
        path.setAttribute("d", d.join(" "));
        svg.append(path);
    }
    if (inputIndex !== -1) {
        const path = drawVerticalLine(
            SWIMLANE_WIDTH * (circleIndex + 1),
            0,
            SWIMLANE_HEIGHT / 2,
            inputSwimlanes[inputIndex].color
        );
        svg.append(path);
    }
    if (historyItem.parentIds.length > 0) {
        const path = drawVerticalLine(
            SWIMLANE_WIDTH * (circleIndex + 1),
            SWIMLANE_HEIGHT / 2,
            SWIMLANE_HEIGHT,
            circleColor
        );
        svg.append(path);
    }
    if (historyItemViewModel.kind === "HEAD") {
        const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 3, CIRCLE_STROKE_WIDTH, circleColor);
        svg.append(outerCircle);
        const innerCircle = drawCircle(circleIndex, CIRCLE_STROKE_WIDTH, CIRCLE_RADIUS);
        svg.append(innerCircle);
    } else if (historyItemViewModel.kind === "incoming-changes" || historyItemViewModel.kind === "outgoing-changes") {
        const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 3, CIRCLE_STROKE_WIDTH, circleColor);
        svg.append(outerCircle);
        const innerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH + 1);
        svg.append(innerCircle);
        const dashedCircle = drawDashedCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH - 1, circleColor);
        svg.append(dashedCircle);
    } else {
        if (historyItem.parentIds.length > 1) {
            const circleOuter = drawCircle(circleIndex, CIRCLE_RADIUS + 2, CIRCLE_STROKE_WIDTH, circleColor);
            svg.append(circleOuter);
            const circleInner = drawCircle(circleIndex, CIRCLE_RADIUS - 1, CIRCLE_STROKE_WIDTH, circleColor);
            svg.append(circleInner);
        } else {
            const circle = drawCircle(circleIndex, CIRCLE_RADIUS + 1, CIRCLE_STROKE_WIDTH, circleColor);
            svg.append(circle);
        }
    }
    svg.style.height = `${SWIMLANE_HEIGHT}px`;
    svg.style.width = `${SWIMLANE_WIDTH * (Math.max(inputSwimlanes.length, outputSwimlanes.length, 1) + 1)}px`;
    return svg;
}
function renderSCMHistoryGraphPlaceholder(columns, highlightIndex) {
    const elements = svgElem("svg", {
        style: {
            height: `${SWIMLANE_HEIGHT}px`,
            width: `${SWIMLANE_WIDTH * (columns.length + 1)}px`
        }
    });
    for (let index = 0; index < columns.length; index++) {
        const strokeWidth = index === highlightIndex ? 3 : 1;
        const path = drawVerticalLine(
            SWIMLANE_WIDTH * (index + 1),
            0,
            SWIMLANE_HEIGHT,
            columns[index].color,
            strokeWidth
        );
        elements.root.append(path);
    }
    return elements.root;
}
function toISCMHistoryItemViewModelArray(
    historyItems,
    colorMap = ( new Map()),
    currentHistoryItemRef,
    currentHistoryItemRemoteRef,
    currentHistoryItemBaseRef,
    addIncomingChanges,
    addOutgoingChanges,
    mergeBase
) {
    let colorIndex = -1;
    const viewModels = [];
    for (let index = 0; index < historyItems.length; index++) {
        const historyItem = historyItems[index];
        const kind = historyItem.id === currentHistoryItemRef?.revision ? "HEAD" : "node";
        const outputSwimlanesFromPreviousItem = viewModels.at(-1)?.outputSwimlanes ?? [];
        const inputSwimlanes = ( outputSwimlanesFromPreviousItem.map(i => deepClone(i)));
        const outputSwimlanes = [];
        let firstParentAdded = false;
        if (historyItem.parentIds.length > 0) {
            for (const node of inputSwimlanes) {
                if (node.id === historyItem.id) {
                    if (!firstParentAdded) {
                        outputSwimlanes.push({
                            id: historyItem.parentIds[0],
                            color: getLabelColorIdentifier(historyItem, colorMap) ?? node.color
                        });
                        firstParentAdded = true;
                    }
                    continue;
                }
                outputSwimlanes.push(deepClone(node));
            }
        }
        for (let i = firstParentAdded ? 1 : 0; i < historyItem.parentIds.length; i++) {
            let colorIdentifier;
            if (i === 0) {
                colorIdentifier = getLabelColorIdentifier(historyItem, colorMap);
            } else {
                const historyItemParent = historyItems.find(h => h.id === historyItem.parentIds[i]);
                colorIdentifier = historyItemParent ? getLabelColorIdentifier(historyItemParent, colorMap) : undefined;
            }
            if (!colorIdentifier) {
                colorIndex = rot(colorIndex + 1, colorRegistry.length);
                colorIdentifier = colorRegistry[colorIndex];
            }
            outputSwimlanes.push({
                id: historyItem.parentIds[i],
                color: colorIdentifier
            });
        }
        const references = ( (historyItem.references ?? []).map(ref => {
            let color = colorMap.get(ref.id);
            if (( colorMap.has(ref.id)) && color === undefined) {
                const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id);
                const circleIndex = inputIndex !== -1 ? inputIndex : inputSwimlanes.length;
                color = circleIndex < outputSwimlanes.length ? outputSwimlanes[circleIndex].color : circleIndex < inputSwimlanes.length ? inputSwimlanes[circleIndex].color : historyItemRefColor;
            }
            return {
                ...ref,
                color
            };
        }));
        references.sort((ref1, ref2) => compareHistoryItemRefs(
            ref1,
            ref2,
            currentHistoryItemRef,
            currentHistoryItemRemoteRef,
            currentHistoryItemBaseRef
        ));
        viewModels.push({
            historyItem: {
                ...historyItem,
                references
            },
            kind,
            inputSwimlanes,
            outputSwimlanes
        });
    }
    addIncomingOutgoingChangesHistoryItems(
        viewModels,
        currentHistoryItemRef,
        currentHistoryItemRemoteRef,
        addIncomingChanges,
        addOutgoingChanges,
        mergeBase
    );
    return viewModels;
}
function getHistoryItemIndex(historyItemViewModel) {
    const historyItem = historyItemViewModel.historyItem;
    const inputSwimlanes = historyItemViewModel.inputSwimlanes;
    const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id);
    return inputIndex !== -1 ? inputIndex : inputSwimlanes.length;
}
function addIncomingOutgoingChangesHistoryItems(
    viewModels,
    currentHistoryItemRef,
    currentHistoryItemRemoteRef,
    addIncomingChanges,
    addOutgoingChanges,
    mergeBase
) {
    if (currentHistoryItemRef?.revision !== currentHistoryItemRemoteRef?.revision && mergeBase) {
        if (addIncomingChanges && currentHistoryItemRemoteRef && currentHistoryItemRemoteRef.revision !== mergeBase) {
            const beforeHistoryItemIndex = findLastIdx(viewModels, vm => ( vm.outputSwimlanes.some(node => node.id === mergeBase)));
            const afterHistoryItemIndex = viewModels.findIndex(vm => vm.historyItem.id === mergeBase);
            if (beforeHistoryItemIndex !== -1 && afterHistoryItemIndex !== -1) {
                const incomingChangeMerged = viewModels[beforeHistoryItemIndex].historyItem.parentIds.length === 2 && viewModels[beforeHistoryItemIndex].historyItem.parentIds.includes(mergeBase);
                if (!incomingChangeMerged) {
                    viewModels[beforeHistoryItemIndex] = {
                        ...viewModels[beforeHistoryItemIndex],
                        inputSwimlanes: ( viewModels[beforeHistoryItemIndex].inputSwimlanes.map(node => {
                            return node.id === mergeBase && node.color === historyItemRemoteRefColor ? {
                                ...node,
                                id: SCMIncomingHistoryItemId
                            } : node;
                        })),
                        outputSwimlanes: ( viewModels[beforeHistoryItemIndex].outputSwimlanes.map(node => {
                            return node.id === mergeBase && node.color === historyItemRemoteRefColor ? {
                                ...node,
                                id: SCMIncomingHistoryItemId
                            } : node;
                        }))
                    };
                    const inputSwimlanes = ( viewModels[beforeHistoryItemIndex].outputSwimlanes.map(i => deepClone(i)));
                    const outputSwimlanes = ( viewModels[afterHistoryItemIndex].inputSwimlanes.map(i => deepClone(i)));
                    const displayIdLength = viewModels[0].historyItem.displayId?.length ?? 0;
                    const incomingChangesHistoryItem = {
                        id: SCMIncomingHistoryItemId,
                        displayId: "0".repeat(displayIdLength),
                        parentIds: [mergeBase],
                        author: currentHistoryItemRemoteRef?.name,
                        subject: ( localize(13121, "Incoming Changes")),
                        message: ""
                    };
                    viewModels.splice(afterHistoryItemIndex, 0, {
                        historyItem: incomingChangesHistoryItem,
                        kind: "incoming-changes",
                        inputSwimlanes,
                        outputSwimlanes
                    });
                }
            }
        }
        if (addOutgoingChanges && currentHistoryItemRef?.revision && currentHistoryItemRef.revision !== mergeBase) {
            const currentHistoryItemRefIndex = viewModels.findIndex(
                vm => vm.kind === "HEAD" && vm.historyItem.id === currentHistoryItemRef.revision
            );
            if (currentHistoryItemRefIndex !== -1) {
                const outgoingChangesHistoryItem = {
                    id: SCMOutgoingHistoryItemId,
                    displayId: viewModels[0].historyItem.displayId ? "0".repeat(viewModels[0].historyItem.displayId.length) : undefined,
                    parentIds: [currentHistoryItemRef.revision],
                    author: currentHistoryItemRef?.name,
                    subject: ( localize(13122, "Outgoing Changes")),
                    message: ""
                };
                const inputSwimlanes = viewModels[currentHistoryItemRefIndex].inputSwimlanes.slice(0);
                const outputSwimlanes = inputSwimlanes.slice(0).concat({
                    id: currentHistoryItemRef.revision,
                    color: historyItemRefColor
                });
                viewModels.splice(currentHistoryItemRefIndex, 0, {
                    historyItem: outgoingChangesHistoryItem,
                    kind: "outgoing-changes",
                    inputSwimlanes,
                    outputSwimlanes
                });
                viewModels[currentHistoryItemRefIndex + 1].inputSwimlanes.push({
                    id: currentHistoryItemRef.revision,
                    color: historyItemRefColor
                });
            }
        }
    }
}
function compareHistoryItemRefs(
    ref1,
    ref2,
    currentHistoryItemRef,
    currentHistoryItemRemoteRef,
    currentHistoryItemBaseRef
) {
    const getHistoryItemRefOrder = ref => {
        if (ref.id === currentHistoryItemRef?.id) {
            return 1;
        } else if (ref.id === currentHistoryItemRemoteRef?.id) {
            return 2;
        } else if (ref.id === currentHistoryItemBaseRef?.id) {
            return 3;
        } else if (ref.color !== undefined) {
            return 4;
        }
        return 99;
    };
    const ref1Order = getHistoryItemRefOrder(ref1);
    const ref2Order = getHistoryItemRefOrder(ref2);
    return ref1Order - ref2Order;
}
function toHistoryItemHoverContent(markdownRendererService, historyItem, includeReferences) {
    const disposables = ( new DisposableStore());
    if (historyItem.tooltip === undefined) {
        return {
            content: historyItem.message,
            disposables
        };
    }
    if (isMarkdownString(historyItem.tooltip)) {
        return {
            content: historyItem.tooltip,
            disposables
        };
    }
    const tooltipSections = historyItem.tooltip.slice();
    if (includeReferences && historyItem.references?.length) {
        const markdownString = ( new MarkdownString("", {
            supportHtml: true,
            supportThemeIcons: true
        }));
        for (const reference of historyItem.references) {
            const labelIconId = ThemeIcon.isThemeIcon(reference.icon) ? reference.icon.id : "";
            const labelBackgroundColor = reference.color ? asCssVariable(reference.color) : asCssVariable(historyItemHoverDefaultLabelBackground);
            const labelForegroundColor = reference.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(historyItemHoverDefaultLabelForeground);
            markdownString.appendMarkdown(
                `<span style="color:${labelForegroundColor};background-color:${labelBackgroundColor};border-radius:10px;">&nbsp;$(${labelIconId})&nbsp;`
            );
            markdownString.appendText(reference.name);
            markdownString.appendMarkdown("&nbsp;&nbsp;</span>");
        }
        markdownString.appendMarkdown(`\n\n---\n\n`);
        tooltipSections.splice(tooltipSections.length - 1, 0, markdownString);
    }
    const hoverContainer = $(".history-item-hover-container");
    for (const markdownString of tooltipSections) {
        if (isEmptyMarkdownString(markdownString)) {
            continue;
        }
        const renderedContent = markdownRendererService.render(markdownString);
        hoverContainer.appendChild(renderedContent.element);
        disposables.add(renderedContent);
    }
    return {
        content: hoverContainer,
        disposables
    };
}

export { SWIMLANE_HEIGHT, SWIMLANE_WIDTH, colorRegistry, compareHistoryItemRefs, getHistoryItemIndex, historyItemBaseRefColor, historyItemHoverDefaultLabelBackground, historyItemHoverDefaultLabelForeground, historyItemHoverLabelForeground, historyItemRefColor, historyItemRemoteRefColor, renderSCMHistoryGraphPlaceholder, renderSCMHistoryItemGraph, toHistoryItemHoverContent, toISCMHistoryItemViewModelArray };
