
import { groupAdjacentBy } from '../../../base/common/arrays.js';
import { assertFn, checkAdjacentItems } from '../../../base/common/assert.js';
import { BugIndicatingError } from '../../../base/common/errors.js';
import { LineRange } from '../core/lineRange.js';
import { Position } from '../core/position.js';
import { Range } from '../core/range.js';
import { SingleTextEdit } from '../core/textEdit.js';

class LineRangeMapping {
    static inverse(mapping, originalLineCount, modifiedLineCount) {
        const result = [];
        let lastOriginalEndLineNumber = 1;
        let lastModifiedEndLineNumber = 1;
        for (const m of mapping) {
            const r = ( new LineRangeMapping(( new LineRange(lastOriginalEndLineNumber, m.original.startLineNumber)), ( new LineRange(lastModifiedEndLineNumber, m.modified.startLineNumber))));
            if (!r.modified.isEmpty) {
                result.push(r);
            }
            lastOriginalEndLineNumber = m.original.endLineNumberExclusive;
            lastModifiedEndLineNumber = m.modified.endLineNumberExclusive;
        }
        const r = ( new LineRangeMapping(( new LineRange(lastOriginalEndLineNumber, originalLineCount + 1)), ( new LineRange(lastModifiedEndLineNumber, modifiedLineCount + 1))));
        if (!r.modified.isEmpty) {
            result.push(r);
        }
        return result;
    }
    static clip(mapping, originalRange, modifiedRange) {
        const result = [];
        for (const m of mapping) {
            const original = m.original.intersect(originalRange);
            const modified = m.modified.intersect(modifiedRange);
            if (original && !original.isEmpty && modified && !modified.isEmpty) {
                result.push(( new LineRangeMapping(original, modified)));
            }
        }
        return result;
    }
    constructor(originalRange, modifiedRange) {
        this.original = originalRange;
        this.modified = modifiedRange;
    }
    toString() {
        return `{${( this.original.toString())}->${( this.modified.toString())}}`;
    }
    flip() {
        return ( new LineRangeMapping(this.modified, this.original));
    }
    join(other) {
        return ( new LineRangeMapping(this.original.join(other.original), this.modified.join(other.modified)));
    }
    get changedLineCount() {
        return Math.max(this.original.length, this.modified.length);
    }
    toRangeMapping() {
        const origInclusiveRange = this.original.toInclusiveRange();
        const modInclusiveRange = this.modified.toInclusiveRange();
        if (origInclusiveRange && modInclusiveRange) {
            return ( new RangeMapping(origInclusiveRange, modInclusiveRange));
        }
        else if (this.original.startLineNumber === 1 || this.modified.startLineNumber === 1) {
            if (!(this.modified.startLineNumber === 1 && this.original.startLineNumber === 1)) {
                throw ( new BugIndicatingError('not a valid diff'));
            }
            return ( new RangeMapping(( new Range(this.original.startLineNumber, 1, this.original.endLineNumberExclusive, 1)), ( new Range(this.modified.startLineNumber, 1, this.modified.endLineNumberExclusive, 1))));
        }
        else {
            return ( new RangeMapping(( new Range(
                this.original.startLineNumber - 1,
                Number.MAX_SAFE_INTEGER,
                this.original.endLineNumberExclusive - 1,
                Number.MAX_SAFE_INTEGER
            )), ( new Range(
                this.modified.startLineNumber - 1,
                Number.MAX_SAFE_INTEGER,
                this.modified.endLineNumberExclusive - 1,
                Number.MAX_SAFE_INTEGER
            ))));
        }
    }
    toRangeMapping2(original, modified) {
        if (isValidLineNumber(this.original.endLineNumberExclusive, original)
            && isValidLineNumber(this.modified.endLineNumberExclusive, modified)) {
            return ( new RangeMapping(( new Range(this.original.startLineNumber, 1, this.original.endLineNumberExclusive, 1)), ( new Range(this.modified.startLineNumber, 1, this.modified.endLineNumberExclusive, 1))));
        }
        if (!this.original.isEmpty && !this.modified.isEmpty) {
            return ( new RangeMapping(Range.fromPositions(( new Position(this.original.startLineNumber, 1)), normalizePosition(( new Position(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER)), original)), Range.fromPositions(( new Position(this.modified.startLineNumber, 1)), normalizePosition(( new Position(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER)), modified))));
        }
        if (this.original.startLineNumber > 1 && this.modified.startLineNumber > 1) {
            return ( new RangeMapping(Range.fromPositions(normalizePosition(( new Position(this.original.startLineNumber - 1, Number.MAX_SAFE_INTEGER)), original), normalizePosition(( new Position(this.original.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER)), original)), Range.fromPositions(normalizePosition(( new Position(this.modified.startLineNumber - 1, Number.MAX_SAFE_INTEGER)), modified), normalizePosition(( new Position(this.modified.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER)), modified))));
        }
        throw ( new BugIndicatingError());
    }
}
function normalizePosition(position, content) {
    if (position.lineNumber < 1) {
        return ( new Position(1, 1));
    }
    if (position.lineNumber > content.length) {
        return ( new Position(content.length, content[content.length - 1].length + 1));
    }
    const line = content[position.lineNumber - 1];
    if (position.column > line.length + 1) {
        return ( new Position(position.lineNumber, line.length + 1));
    }
    return position;
}
function isValidLineNumber(lineNumber, lines) {
    return lineNumber >= 1 && lineNumber <= lines.length;
}
class DetailedLineRangeMapping extends LineRangeMapping {
    static fromRangeMappings(rangeMappings) {
        const originalRange = LineRange.join(( rangeMappings.map(r => LineRange.fromRangeInclusive(r.originalRange))));
        const modifiedRange = LineRange.join(( rangeMappings.map(r => LineRange.fromRangeInclusive(r.modifiedRange))));
        return ( new DetailedLineRangeMapping(originalRange, modifiedRange, rangeMappings));
    }
    constructor(originalRange, modifiedRange, innerChanges) {
        super(originalRange, modifiedRange);
        this.innerChanges = innerChanges;
    }
    flip() {
        return ( new DetailedLineRangeMapping(this.modified, this.original, this.innerChanges?.map(c => c.flip())));
    }
    withInnerChangesFromLineRanges() {
        return ( new DetailedLineRangeMapping(this.original, this.modified, [this.toRangeMapping()]));
    }
}
class RangeMapping {
    static fromEdit(edit) {
        const newRanges = edit.getNewRanges();
        const result = ( edit.edits.map((e, idx) => ( new RangeMapping(e.range, newRanges[idx]))));
        return result;
    }
    static fromEditJoin(edit) {
        const newRanges = edit.getNewRanges();
        const result = ( edit.edits.map((e, idx) => ( new RangeMapping(e.range, newRanges[idx]))));
        return RangeMapping.join(result);
    }
    static join(rangeMappings) {
        if (rangeMappings.length === 0) {
            throw ( new BugIndicatingError('Cannot join an empty list of range mappings'));
        }
        let result = rangeMappings[0];
        for (let i = 1; i < rangeMappings.length; i++) {
            result = result.join(rangeMappings[i]);
        }
        return result;
    }
    static assertSorted(rangeMappings) {
        for (let i = 1; i < rangeMappings.length; i++) {
            const previous = rangeMappings[i - 1];
            const current = rangeMappings[i];
            if (!(previous.originalRange.getEndPosition().isBeforeOrEqual(current.originalRange.getStartPosition())
                && previous.modifiedRange.getEndPosition().isBeforeOrEqual(current.modifiedRange.getStartPosition()))) {
                throw ( new BugIndicatingError('Range mappings must be sorted'));
            }
        }
    }
    constructor(originalRange, modifiedRange) {
        this.originalRange = originalRange;
        this.modifiedRange = modifiedRange;
    }
    toString() {
        return `{${( this.originalRange.toString())}->${( this.modifiedRange.toString())}}`;
    }
    flip() {
        return ( new RangeMapping(this.modifiedRange, this.originalRange));
    }
    toTextEdit(modified) {
        const newText = modified.getValueOfRange(this.modifiedRange);
        return ( new SingleTextEdit(this.originalRange, newText));
    }
    join(other) {
        return ( new RangeMapping(
            this.originalRange.plusRange(other.originalRange),
            this.modifiedRange.plusRange(other.modifiedRange)
        ));
    }
}
function lineRangeMappingFromRangeMappings(alignments, originalLines, modifiedLines, dontAssertStartLine = false) {
    const changes = [];
    for (const g of groupAdjacentBy(( alignments.map(a => getLineRangeMapping(a, originalLines, modifiedLines))), (a1, a2) => a1.original.overlapOrTouch(a2.original)
        || a1.modified.overlapOrTouch(a2.modified))) {
        const first = g[0];
        const last = g[g.length - 1];
        changes.push(( new DetailedLineRangeMapping(
            first.original.join(last.original),
            first.modified.join(last.modified),
            ( g.map(a => a.innerChanges[0]))
        )));
    }
    assertFn(() => {
        if (!dontAssertStartLine && changes.length > 0) {
            if (changes[0].modified.startLineNumber !== changes[0].original.startLineNumber) {
                return false;
            }
            if (modifiedLines.length.lineCount - changes[changes.length - 1].modified.endLineNumberExclusive !== originalLines.length.lineCount - changes[changes.length - 1].original.endLineNumberExclusive) {
                return false;
            }
        }
        return checkAdjacentItems(changes, (m1, m2) => m2.original.startLineNumber - m1.original.endLineNumberExclusive === m2.modified.startLineNumber - m1.modified.endLineNumberExclusive &&
            m1.original.endLineNumberExclusive < m2.original.startLineNumber &&
            m1.modified.endLineNumberExclusive < m2.modified.startLineNumber);
    });
    return changes;
}
function getLineRangeMapping(rangeMapping, originalLines, modifiedLines) {
    let lineStartDelta = 0;
    let lineEndDelta = 0;
    if (rangeMapping.modifiedRange.endColumn === 1 && rangeMapping.originalRange.endColumn === 1
        && rangeMapping.originalRange.startLineNumber + lineStartDelta <= rangeMapping.originalRange.endLineNumber
        && rangeMapping.modifiedRange.startLineNumber + lineStartDelta <= rangeMapping.modifiedRange.endLineNumber) {
        lineEndDelta = -1;
    }
    if (rangeMapping.modifiedRange.startColumn - 1 >= modifiedLines.getLineLength(rangeMapping.modifiedRange.startLineNumber)
        && rangeMapping.originalRange.startColumn - 1 >= originalLines.getLineLength(rangeMapping.originalRange.startLineNumber)
        && rangeMapping.originalRange.startLineNumber <= rangeMapping.originalRange.endLineNumber + lineEndDelta
        && rangeMapping.modifiedRange.startLineNumber <= rangeMapping.modifiedRange.endLineNumber + lineEndDelta) {
        lineStartDelta = 1;
    }
    const originalLineRange = ( new LineRange(
        rangeMapping.originalRange.startLineNumber + lineStartDelta,
        rangeMapping.originalRange.endLineNumber + 1 + lineEndDelta
    ));
    const modifiedLineRange = ( new LineRange(
        rangeMapping.modifiedRange.startLineNumber + lineStartDelta,
        rangeMapping.modifiedRange.endLineNumber + 1 + lineEndDelta
    ));
    return ( new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, [rangeMapping]));
}
function lineRangeMappingFromChanges(changes) {
    const lineRangeMapping = [];
    for (const change of changes) {
        let originalRange;
        if (change.originalEndLineNumber === 0) {
            originalRange = ( new LineRange(change.originalStartLineNumber + 1, change.originalStartLineNumber + 1));
        }
        else {
            originalRange = ( new LineRange(change.originalStartLineNumber, change.originalEndLineNumber + 1));
        }
        let modifiedRange;
        if (change.modifiedEndLineNumber === 0) {
            modifiedRange = ( new LineRange(change.modifiedStartLineNumber + 1, change.modifiedStartLineNumber + 1));
        }
        else {
            modifiedRange = ( new LineRange(change.modifiedStartLineNumber, change.modifiedEndLineNumber + 1));
        }
        lineRangeMapping.push(( new LineRangeMapping(originalRange, modifiedRange)));
    }
    return lineRangeMapping;
}

export { DetailedLineRangeMapping, LineRangeMapping, RangeMapping, getLineRangeMapping, lineRangeMappingFromChanges, lineRangeMappingFromRangeMappings };
