const IS_BOLD = 1;
const IS_ITALIC = 2;
const IS_STRIKETHROUGH = 4;
const IS_CODE = 16;
const ADMONITION_TO_GFM = {
    note: 'NOTE',
    tip: 'TIP',
    warning: 'WARNING',
    danger: 'CAUTION'
};
function lexicalToMarkdown(state, options = {}) {
    const warnings = [];
    const root = resolveRoot(state);
    if (null == root) return {
        markdown: '',
        warnings
    };
    const blocks = serializeBlocks(root.children ?? [], {
        options,
        warnings
    });
    return {
        markdown: joinBlocks(blocks),
        warnings
    };
}
function resolveRoot(state) {
    if (null == state) return null;
    let value = state;
    if ('string' == typeof value) try {
        value = JSON.parse(value);
    } catch  {
        return null;
    }
    if ('object' != typeof value || null === value) return null;
    const obj = value;
    const root = obj.root ?? obj;
    return 'root' === root.type ? root : null;
}
function serializeBlocks(nodes, ctx) {
    const out = [];
    for (const node of nodes){
        const block = serializeBlock(node, ctx);
        if (null != block && block.length > 0) out.push(block);
    }
    return out;
}
function joinBlocks(blocks) {
    return blocks.join('\n\n');
}
function serializeBlock(node, ctx) {
    switch(node.type){
        case 'paragraph':
            {
                const text = serializeInline(node.children ?? [], ctx);
                return text.trim().length > 0 ? text : null;
            }
        case 'heading':
            {
                const tag = 'string' == typeof node.tag ? node.tag : 'h2';
                const level = Math.min(Math.max(Number(tag.slice(1)) || 2, 1), 6);
                return `${'#'.repeat(level)} ${serializeInline(node.children ?? [], ctx)}`;
            }
        case 'list':
            return serializeList(node, ctx, 0);
        case 'quote':
            {
                const inner = serializeInline(node.children ?? [], ctx);
                return prefixLines(inner, '> ');
            }
        case 'code':
            return serializeCode(node);
        case 'table':
            return serializeTable(node, ctx);
        case 'horizontalrule':
            return '---';
        case 'admonition':
            return serializeAdmonition(node, ctx);
        case 'youtube':
            return 'string' == typeof node.videoID && node.videoID.length > 0 ? `[YouTube video](https://www.youtube.com/watch?v=${node.videoID})` : null;
        case 'vimeo':
            return 'string' == typeof node.videoID && node.videoID.length > 0 ? `[Vimeo video](https://vimeo.com/${node.videoID})` : null;
        case 'layout-container':
            return joinBlocks(serializeBlocks(node.children ?? [], ctx));
        case 'layout-item':
            return joinBlocks(serializeBlocks(node.children ?? [], ctx));
        case 'inline-image':
            return serializeImage(node, ctx);
        default:
            if (node.children && node.children.length > 0) {
                ctx.warnings.push({
                    kind: 'unknown-node',
                    detail: `unknown block node '${node.type ?? '?'}' — serialized children only`
                });
                return joinBlocks(serializeBlocks(node.children, ctx));
            }
            ctx.warnings.push({
                kind: 'unknown-node',
                detail: `unknown leaf node '${node.type ?? '?'}' — dropped`
            });
            return null;
    }
}
function serializeList(node, ctx, depth) {
    const listType = 'number' === node.listType ? 'number' : node.listType;
    const lines = [];
    let ordinal = 1;
    for (const item of node.children ?? []){
        if ('listitem' !== item.type) continue;
        const inlineChildren = (item.children ?? []).filter((c)=>'list' !== c.type);
        const nestedLists = (item.children ?? []).filter((c)=>'list' === c.type);
        const marker = 'number' === listType ? `${ordinal}. ` : 'check' === listType ? `- [${true === item.checked ? 'x' : ' '}] ` : '- ';
        ordinal += 1;
        const text = serializeInline(inlineChildren, ctx);
        const indent = '  '.repeat(depth);
        if (text.trim().length > 0 || 0 === nestedLists.length) lines.push(`${indent}${marker}${text}`);
        for (const nested of nestedLists)lines.push(serializeList(nested, ctx, depth + 1));
    }
    return lines.join('\n');
}
function serializeCode(node) {
    const language = 'string' == typeof node.language ? node.language : '';
    const parts = [];
    for (const child of node.children ?? [])if ('linebreak' === child.type) parts.push('\n');
    else if ('string' == typeof child.text) parts.push(child.text);
    const body = parts.join('');
    const longestRun = body.match(/`+/g)?.reduce((m, r)=>Math.max(m, r.length), 0) ?? 0;
    const fence = '`'.repeat(Math.max(3, longestRun + 1));
    return `${fence}${language}\n${body}\n${fence}`;
}
function serializeTable(node, ctx) {
    const rows = (node.children ?? []).filter((c)=>'tablerow' === c.type);
    if (0 === rows.length) {
        ctx.warnings.push({
            kind: 'empty-table',
            detail: 'table with no rows dropped'
        });
        return null;
    }
    const toCells = (row)=>(row.children ?? []).filter((c)=>'tablecell' === c.type).map((cell)=>serializeInline(cell.children ?? [], ctx).replace(/\|/g, '\\|').trim());
    const [first, ...rest] = rows;
    const header = toCells(first);
    const lines = [
        `| ${header.join(' | ')} |`,
        `| ${header.map(()=>'---').join(' | ')} |`,
        ...rest.map((row)=>`| ${toCells(row).join(' | ')} |`)
    ];
    return lines.join('\n');
}
function serializeAdmonition(node, ctx) {
    const gfmType = ADMONITION_TO_GFM[String(node.admonitionType ?? 'note')] ?? 'NOTE';
    const title = 'string' == typeof node.title && node.title.length > 0 ? node.title : null;
    const body = joinBlocks(serializeBlocks(node.children ?? [], ctx));
    const parts = [
        `[!${gfmType}]`
    ];
    if (title) parts.push(`**${title}**`);
    if (body.length > 0) parts.push(body);
    return prefixLines(parts.join('\n\n'), '> ');
}
function serializeInline(nodes, ctx) {
    const parts = [];
    let i = 0;
    while(i < nodes.length){
        const node = nodes[i];
        switch(node.type){
            case 'text':
            case 'code-highlight':
                {
                    const format = Number(node.format ?? 0);
                    let text = String(node.text ?? '');
                    while(i + 1 < nodes.length && nodes[i + 1].type === node.type && Number(nodes[i + 1].format ?? 0) === format){
                        i += 1;
                        text += String(nodes[i].text ?? '');
                    }
                    parts.push(wrapFormats(text, format));
                    break;
                }
            case 'linebreak':
                parts.push('\\\n');
                break;
            case 'link':
            case 'autolink':
                parts.push(serializeLink(node, ctx));
                break;
            case 'inline-image':
                parts.push(serializeImage(node, ctx));
                break;
            default:
                if (node.children && node.children.length > 0) parts.push(serializeInline(node.children, ctx));
                else if ('string' == typeof node.text) parts.push(escapeText(node.text));
                else ctx.warnings.push({
                    kind: 'unknown-node',
                    detail: `unknown inline node '${node.type ?? '?'}' — dropped`
                });
        }
        i += 1;
    }
    return parts.join('');
}
function wrapFormats(rawText, format) {
    if (0 === rawText.length) return '';
    if (format & IS_CODE) {
        const longestRun = rawText.match(/`+/g)?.reduce((m, r)=>Math.max(m, r.length), 0) ?? 0;
        const fence = '`'.repeat(longestRun + 1);
        return `${fence}${rawText}${fence}`;
    }
    const leading = rawText.match(/^\s*/)?.[0] ?? '';
    const trailing = rawText.match(/\s*$/)?.[0] ?? '';
    const core = rawText.slice(leading.length, rawText.length - trailing.length);
    if (0 === core.length) return rawText;
    let text = escapeText(core);
    if (format & IS_BOLD) text = `**${text}**`;
    if (format & IS_ITALIC) text = `*${text}*`;
    if (format & IS_STRIKETHROUGH) text = `~~${text}~~`;
    return `${leading}${text}${trailing}`;
}
function escapeText(text) {
    return text.replace(/([\\`*_[\]])/g, '\\$1');
}
function serializeLink(node, ctx) {
    const attrs = node.attributes ?? {};
    const text = serializeInline(node.children ?? [], ctx);
    const url = resolveLinkUrl(attrs, ctx);
    if (null == url) return text;
    return `[${text}](${url})`;
}
function resolveLinkUrl(attrs, ctx) {
    if ('internal' === attrs.linkType) {
        const document = attrs.document ?? {};
        if (false === document._resolved) {
            ctx.warnings.push({
                kind: 'unresolved-link',
                detail: `internal link to ${String(attrs.targetDocumentId ?? '?')} unresolved`
            });
            return null;
        }
        const documentPath = 'string' == typeof document.path ? document.path : void 0;
        const custom = ctx.options.resolveInternalUrl?.({
            targetDocumentId: 'string' == typeof attrs.targetDocumentId ? attrs.targetDocumentId : void 0,
            targetCollectionPath: 'string' == typeof attrs.targetCollectionPath ? attrs.targetCollectionPath : void 0,
            documentPath
        });
        if (null != custom) return custom;
        if (null == documentPath) return null;
        if (documentPath.startsWith('/')) return documentPath;
        if ('string' == typeof attrs.targetCollectionPath && attrs.targetCollectionPath.length > 0) return `/${attrs.targetCollectionPath}/${documentPath}`;
        return null;
    }
    return 'string' == typeof attrs.url && attrs.url.length > 0 ? attrs.url : null;
}
function serializeImage(node, ctx) {
    const src = 'string' == typeof node.src ? node.src : '';
    if (0 === src.length) return '';
    const alt = 'string' == typeof node.altText ? node.altText : '';
    const image = `![${alt.replace(/[[\]]/g, '')}](${src})`;
    const caption = node.caption;
    if (true === node.showCaption && caption?.editorState != null) {
        const captionRoot = resolveRoot(caption.editorState);
        if (null != captionRoot) {
            const captionText = serializeBlocks(captionRoot.children ?? [], ctx).join(' ').trim();
            if (captionText.length > 0) return `${image}\n*${captionText}*`;
        }
    }
    return image;
}
function prefixLines(text, prefix) {
    return text.split('\n').map((line)=>line.length > 0 ? `${prefix}${line}` : prefix.trimEnd()).join('\n');
}
export { lexicalToMarkdown };
