import { distance } from "fastest-levenshtein";
export async function* noTopLevelKeywordsMidline(lines, topLevelKeywords) {
    for await (const line of lines) {
        for (const keyword of topLevelKeywords) {
            const indexOf = line.indexOf(`${keyword} `);
            if (indexOf >= 0 && line.slice(indexOf - 1, indexOf).trim() !== "") {
                yield line.slice(0, indexOf);
                break;
            }
        }
        yield line;
    }
}
export async function* avoidPathLine(stream, comment) {
    // Snippets are inserted as comments with a line at the start '// Path: <PATH>'.
    // Sometimes the model with copy this pattern, which is unwanted
    for await (const line of stream) {
        // Also filter lines that are empty comments
        if (line.startsWith(`${comment} Path: `) || line.trim() === comment) {
            continue;
        }
        yield line;
    }
}
export async function* streamWithNewLines(stream) {
    let firstLine = true;
    for await (const nextLine of stream) {
        if (!firstLine) {
            yield "\n";
        }
        firstLine = false;
        yield nextLine;
    }
}
const bracketEnding = [")", "]", "}", ";"];
function isBracketEnding(line) {
    return line
        .trim()
        .split("")
        .some((char) => bracketEnding.includes(char));
}
function commonPrefixLength(a, b) {
    let i = 0;
    while (i < a.length && i < b.length && a[i] === b[i]) {
        i++;
    }
    return i;
}
export async function* stopAtSimilarLine(stream, line) {
    const trimmedLine = line.trim();
    const lineIsBracketEnding = isBracketEnding(trimmedLine);
    for await (const nextLine of stream) {
        if (nextLine === line) {
            break;
        }
        if (lineIsBracketEnding && trimmedLine.trim() === nextLine.trim()) {
            yield nextLine;
            continue;
        }
        let lineQualifies = nextLine.length > 4 && trimmedLine.length > 4;
        if (lineQualifies &&
            (commonPrefixLength(nextLine.trim(), trimmedLine.trim()) > 8 ||
                distance(nextLine.trim(), trimmedLine) / trimmedLine.length < 0.1)) {
            break;
        }
        yield nextLine;
    }
}
const LINES_TO_STOP_AT = ["# End of file.", "<STOP EDITING HERE"];
export async function* stopAtLines(stream) {
    for await (const line of stream) {
        if (LINES_TO_STOP_AT.some((stopAt) => line.trim().includes(stopAt))) {
            break;
        }
        yield line;
    }
}
const LINES_TO_SKIP = ["</START EDITING HERE>"];
export async function* skipLines(stream) {
    for await (const line of stream) {
        if (!LINES_TO_SKIP.some((skipAt) => line.startsWith(skipAt))) {
            yield line;
        }
    }
}
function shouldRemoveLineBeforeStart(line) {
    return (line.trimStart().startsWith("```") ||
        line.trim() === "[CODE]" ||
        line.trim() === "" ||
        line.trim() === "<START EDITING HERE>");
}
function shouldChangeLineAndStop(line) {
    if (line.trimStart() === "```") {
        return line;
    }
    if (line.includes("[/CODE]")) {
        return line.split("[/CODE]")[0].trimEnd();
    }
    return undefined;
}
export async function* filterCodeBlockLines(rawLines) {
    let seenValidLine = false;
    let waitingToSeeIfLineIsLast = undefined;
    for await (const line of rawLines) {
        // Filter out starting ```
        if (!seenValidLine) {
            if (shouldRemoveLineBeforeStart(line)) {
                continue;
            }
            seenValidLine = true;
        }
        // Filter out ending ```
        if (typeof waitingToSeeIfLineIsLast !== "undefined") {
            yield waitingToSeeIfLineIsLast;
            waitingToSeeIfLineIsLast = undefined;
        }
        const changedEndLine = shouldChangeLineAndStop(line);
        if (typeof changedEndLine === "string") {
            yield changedEndLine;
            return;
        }
        if (line.startsWith("```")) {
            waitingToSeeIfLineIsLast = line;
        }
        else {
            yield line;
        }
    }
}
function isEnglishFirstLine(line) {
    line = line.trim().toLowerCase();
    if (line.endsWith(":") && !line.trimStart().startsWith("def")) {
        return true;
    }
    if (line.startsWith("here is") ||
        line.startsWith("here's") ||
        line.startsWith("sure, here") ||
        line.startsWith("sure thing") ||
        line.startsWith("sure!") ||
        line.startsWith("to fill") ||
        line.startsWith("the code should")) {
        return true;
    }
    return false;
}
export async function* filterEnglishLinesAtStart(lines) {
    let i = 0;
    let wasEnglishFirstLine = false;
    for await (const line of lines) {
        if (i === 0 && line.trim() === "") {
            continue;
        }
        if (i === 0) {
            if (isEnglishFirstLine(line)) {
                wasEnglishFirstLine = true;
                i++;
                continue;
            }
        }
        else if (i === 1 && wasEnglishFirstLine && line.trim() === "") {
            i++;
            continue;
        }
        i++;
        yield line;
    }
}
function isEnglishPostExplanation(line) {
    const lower = line.toLowerCase();
    return (lower.startsWith("explanation:") ||
        lower.startsWith("here is") ||
        lower.startsWith("here's how") ||
        lower.startsWith("the above"));
}
export async function* filterEnglishLinesAtEnd(lines) {
    let finishedCodeBlock = false;
    for await (const line of lines) {
        if (line.trim() === "```") {
            finishedCodeBlock = true;
        }
        if (finishedCodeBlock && isEnglishPostExplanation(line)) {
            break;
        }
        yield line;
    }
}
export async function* fixCodeLlamaFirstLineIndentation(lines) {
    let isFirstLine = true;
    for await (const line of lines) {
        if (isFirstLine && line.startsWith("  ")) {
            yield line.slice(2);
            isFirstLine = false;
        }
        else {
            yield line;
        }
    }
}
function isUselessLine(line) {
    const trimmed = line.trim().toLowerCase();
    return trimmed === "" || trimmed === "```" || trimmed.startsWith("// end");
}
export async function* filterLeadingAndTrailingNewLineInsertion(diffLines) {
    let isFirst = true;
    let buffer = [];
    for await (const diffLine of diffLines) {
        const isBlankLineInsertion = diffLine.type === "new" && isUselessLine(diffLine.line);
        if (isFirst && isBlankLineInsertion) {
            isFirst = false;
            continue;
        }
        isFirst = false;
        if (isBlankLineInsertion) {
            buffer.push(diffLine);
        }
        else {
            if (diffLine.type === "old") {
                buffer = [];
            }
            else {
                while (buffer.length > 0) {
                    yield buffer.shift();
                }
            }
            yield diffLine;
        }
    }
}
export async function* stopAtRepeatingLines(lines) {
    const repeatedLines = [];
    for await (const line of lines) {
        if (repeatedLines.length === 0) {
            repeatedLines.push(line);
        }
        else if (repeatedLines.length < 3) {
            if (repeatedLines[repeatedLines.length - 1] === line) {
                repeatedLines.push(line);
            }
            else {
                while (repeatedLines.length > 0) {
                    yield repeatedLines.shift();
                }
                yield line;
            }
        }
        else {
            yield repeatedLines[0];
            return;
        }
    }
    while (repeatedLines.length > 0) {
        yield repeatedLines.shift();
    }
}
