'use strict';

var obsidian = require('obsidian');

/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
}

const reKey = /\[\^(.+?(?=\]))\]/gi;
const reDefinition = /\[\^(.+)\]\:/;
// https://stackoverflow.com/a/1830844
function isNumeric(value) {
    return !isNaN(value - parseFloat(value));
}
function tidyFootnotes(editor) {
    let markers = [];
    let definitions = new Map();
    let firstDefinitionLine = -1;
    let definitionsIndexed = new Map();
    // Iterate through each line
    const lineCount = editor.lineCount();
    let prevKey = '';
    for (let i = 0; i < lineCount; i++) {
        const line = editor.getLine(i);
        let isDefinition = false;
        let match;
        if (prevKey.length) {
            const hasIndent = /^[ \t]/.test(line);
            const isLastLine = i === (lineCount - 1);
            if (hasIndent || (line.length === 0 && !isLastLine)) {
                // Append line to the previous footnote definition
                const value = definitions.get(prevKey);
                definitions.set(prevKey, value + "\n" + line);
                markers[markers.length - 1].length++;
                continue;
            }
            else {
                prevKey = '';
            }
        }
        // Look for footnote definition
        while ((match = reDefinition.exec(line)) !== null) {
            if (match.length < 1)
                return;
            isDefinition = true;
            // Remember definition and where it is
            let key = match[1];
            let value = line.substring(match[0].length);
            definitions.set(key, value);
            prevKey = key;
            let marker = {
                key,
                line: i,
                index: 0,
                length: 0,
                isDefinition: true
            };
            markers.push(marker);
            // Remember first definition line to insert combined list later
            if (firstDefinitionLine === -1) {
                firstDefinitionLine = i;
            }
            break;
        }
        if (isDefinition)
            continue;
        // Look for footnote key
        while ((match = reKey.exec(line)) !== null) {
            if (match.length < 1)
                return;
            // Remember where footnote key is
            let key = match[1];
            let marker = {
                key,
                line: i,
                index: match.index,
                length: match[0].length,
                isDefinition: false
            };
            markers.push(marker);
            if (!definitionsIndexed.has(key)) {
                // Add key into index
                definitionsIndexed.set(key, {
                    key,
                    newKey: key,
                    isNumber: isNumeric(key),
                    value: ''
                });
            }
        }
    }
    // Assign definition to key in index
    // If definition has no key, it will be appended with its current key
    definitions.forEach((value, key) => {
        definitionsIndexed.set(key, {
            key,
            newKey: key,
            isNumber: isNumeric(key),
            value
        });
    });
    // Re-index numbers and construct combined definitions output
    let count = 1;
    let definitionsStr = '';
    definitionsIndexed.forEach((definition, marker) => {
        let key = definition.key;
        if (definition.isNumber) {
            const current = definitionsIndexed.get(marker);
            key = count.toString();
            definitionsIndexed.set(marker, Object.assign(Object.assign({}, current), { newKey: key }));
            count++;
        }
        definitionsStr += `[^${key}]:${definition.value}\n`;
    });
    const markersCount = markers.length;
    for (let i = markersCount - 1; i >= 0; i--) {
        const marker = markers[i];
        const markerLine = marker.line;
        if (marker.isDefinition) {
            let rangeStart, rangeEnd;
            const lineEnd = markerLine + 1 + marker.length;
            if (lineEnd === editor.lineCount()) {
                // Replace from previous to current line to fix CodeMirror 6 error
                rangeStart = { line: markerLine, ch: 0 };
                rangeEnd = { line: lineEnd - 1, ch: Infinity };
            }
            else {
                // Replace from current to next line
                rangeStart = { line: markerLine, ch: 0 };
                rangeEnd = { line: lineEnd, ch: 0 };
            }
            if (markerLine === firstDefinitionLine) {
                // Replace first definition line with list of indexed definitions
                editor.replaceRange(definitionsStr, rangeStart, rangeEnd);
                continue;
            }
            // Remove line(s)
            editor.replaceRange('', rangeStart, rangeEnd);
            continue;
        }
        // Check if key has changed
        const definition = definitionsIndexed.get(marker.key);
        const newKey = definition.newKey;
        if (marker.key === newKey)
            continue;
        // Replace footnote key in line with the new one
        const line = editor.getLine(markerLine);
        const prefix = line.substring(0, marker.index);
        const newMarker = `[^${newKey}]`;
        const suffix = line.substr(marker.index + marker.length);
        const newLine = prefix + newMarker + suffix;
        editor.replaceRange(newLine, { line: markerLine, ch: 0 }, { line: markerLine, ch: Infinity });
    }
    if (firstDefinitionLine == -1) {
        // If there are no definitions, add definitions list at the end
        const lineCount = editor.lineCount();
        editor.replaceRange("\n\n" + definitionsStr, { line: lineCount, ch: 0 }, { line: lineCount, ch: Infinity });
    }
    // console.log(markers, definitions, definitionsIndexed, definitionsStr);
}

class TidyFootnotes extends obsidian.Plugin {
    onload() {
        return __awaiter(this, void 0, void 0, function* () {
            this.addCommand({
                id: 'tidy-footnotes',
                name: 'Tidy Footnotes',
                checkCallback: (checking) => {
                    // Ensure the active view is a Markdown editor
                    const view = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
                    if (checking)
                        return !!view;
                    if (!view || view.sourceMode == undefined)
                        return false;
                    let editor = view.editor;
                    tidyFootnotes(editor);
                }
            });
        });
    }
}

module.exports = TidyFootnotes;
