<%* 
/**
 * Template Name: Copy with Reference
 * Description: Copies selected text with a block reference. For multiple separate blocks (divided by empty lines), can be configured to use footnote style or append source link.
 * Limitation: May not handle code blocks or tables correctly if selection exactly matches block boundaries. Only works in Live Preview or Source mode. Code improvements welcome.
 * Version: 1.0
 * Author: Created via Claude
 * Source: https://forum.obsidian.md/t/grab-text-with-auto-generated-footnote-link-to-source/92600
 * Last Updated: 2024-12-01
 */

const APPEND_SOURCE_FOR_MULTIBLOCK = true;
// If true: appends "Source: [[note#^block-id]]" on new line for multi-block selections
// If false: adds footnote style "^[[[note#^block-id]]]" to bottom-most block

const editor = app.workspace.activeEditor.editor;
const isReadingView = app.workspace.activeEditor.getMode() === "preview";

if (isReadingView) {
    new Notice("Please switch to Live Preview or Source mode to use this script");
    return;
}

const selection = editor.getSelection();
if (!selection) {
    new Notice("No text selected");
    return;
}

const cursor = editor.getCursor();
const fromLine = editor.getCursor("from").line;
const toLine = editor.getCursor("to").line;
const currentFile = tp.file.find_tfile(tp.file.path(true));
const fileCache = app.metadataCache.getFileCache(currentFile);

function findNearestHeadingAbove(lineNumber) {
    const heading = fileCache?.headings
        ?.filter(h => h.position.start.line < lineNumber)
        .sort((a, b) => b.position.start.line - a.position.start.line)[0];
    
    if (heading) {
        const headingLine = editor.getLine(heading.position.start.line);
        const idMatch = headingLine.match(/\^([a-zA-Z0-9-]+)\s*$/);
        if (idMatch) heading.id = idMatch[1];
    }
    return heading;
}

function findExistingIds(fileCache) {
    const existingIds = new Set();
    fileCache?.sections?.forEach(section => { if (section.id) existingIds.add(section.id); });
    fileCache?.listItems?.forEach(item => { if (item.id) existingIds.add(item.id); });
    return existingIds;
}

function generateUniqueId(fileCache) {
    const existingIds = findExistingIds(fileCache);
    let newId;
    do {
        newId = Math.random().toString(36).substr(2, 6);
    } while (existingIds.has(newId));
    return newId;
}

function shouldInsertAfter(block) {
    if (block?.type) {
        return ["blockquote", "code", "table", "comment", "footnoteDefinition"].includes(block.type);
    }
    return false;
}

function extractExistingId(text) {
    const match = text.match(/\^([a-zA-Z0-9-]+)\s*$/);
    return match ? match[1] : null;
}

function isMultipleBlocks(fromLine, toLine, fileCache) {
    const sections = fileCache?.sections || [];
    let currentBlock = null;
    
    for (let i = 0; i < sections.length; i++) {
        const section = sections[i];
        if (section.position.start.line <= fromLine && section.position.end.line >= fromLine) {
            currentBlock = section;
            break;
        }
    }

    if (currentBlock) {
        return fromLine < currentBlock.position.start.line || toLine > currentBlock.position.end.line;
    }

    const lines = [];
    for (let i = fromLine; i <= toLine; i++) {
        const line = editor.getLine(i).trim();
        if (line === '') {
            const prevLine = i > 0 ? editor.getLine(i - 1).trim() : '';
            const nextLine = i < editor.lineCount() - 1 ? editor.getLine(i + 1).trim() : '';
            if (prevLine !== '' && nextLine !== '') {
                return true;
            }
        }
        lines.push(editor.getLine(i));
    }
    
    if (fromLine > 0) {
        const prevLine = editor.getLine(fromLine - 1).trim();
        if (prevLine === '' && lines[0].trim() !== '') return true;
    }
    
    if (toLine < editor.lineCount() - 1) {
        const nextLine = editor.getLine(toLine + 1).trim();
        if (nextLine === '' && lines[lines.length - 1].trim() !== '') return true;
    }

    return false;
}

const isMultiBlock = isMultipleBlocks(fromLine, toLine, fileCache);

let referenceText;
if (isMultiBlock) {
    const nearestHeading = findNearestHeadingAbove(fromLine);
    let sourceLink;
    let blockId;
    
    if (nearestHeading) {
        blockId = nearestHeading.id || generateUniqueId(fileCache);
        if (!nearestHeading.id) {
            const end = {
                ch: nearestHeading.position.end.col,
                line: nearestHeading.position.start.line
            };
            editor.replaceRange(` ^${blockId}`, end);
        }
        sourceLink = `[[${tp.file.title}#^${blockId}]]`;
    } else {
        sourceLink = `[[${tp.file.title}]]`;
    }
    
    if (APPEND_SOURCE_FOR_MULTIBLOCK) {
        referenceText = `${selection}\n\nSource: ${sourceLink}`;
    } else {
        referenceText = `${selection} ^[[[${tp.file.title}#^${blockId}]]]`;
    }
} else {
    const existingId = extractExistingId(selection);
    if (existingId) {
        referenceText = `${selection} ^[[[${tp.file.title}#^${existingId}]]]`;
    } else {
        let block = (fileCache?.sections || []).find(section => 
            section.position.start.line <= cursor.line && section.position.end.line >= cursor.line
        );

        if (block?.type === "list") {
            block = (fileCache?.listItems || []).find(item => 
                item.position.start.line <= cursor.line && item.position.end.line >= cursor.line
            );
        } else if (block?.type === "heading") {
            block = fileCache.headings.find(heading => 
                heading.position.start.line === block.position.start.line
            );
            if (block) {
                const headingLine = editor.getLine(block.position.start.line);
                const idMatch = headingLine.match(/\^([a-zA-Z0-9-]+)\s*$/);
                if (idMatch) block.id = idMatch[1];
            }
        }

        let blockId = block?.id || generateUniqueId(fileCache);
        if (!block?.id) {
            const spacer = shouldInsertAfter(block) ? "\n\n" : " ";
            const end = {
                ch: block?.position.end.col || editor.getLine(cursor.line).length,
                line: block?.position.end.line || cursor.line
            };
            editor.replaceRange(`${spacer}^${blockId}`, end);
        }
        referenceText = `${selection} ^[[[${tp.file.title}#^${blockId}]]]`;
    }
}

await navigator.clipboard.writeText(referenceText);
new Notice("Copied selection with reference");
tR = selection;
-%>