import { parseFormula, renameFormulaSheetReferences, rewriteFormulaForStructuralTransform, serializeFormula, } from '@bilig/formula';
import { ErrorCode } from '@bilig/protocol';
const binaryChunkSize = 0x8000;
const chartPackageFormulaOwnerSheetName = '__bilig_chart_package_artifact__';
const chartPartPathPattern = /^xl\/charts\/chart\d+\.xml$/u;
export function rewritePreservedChartPackageArtifactsForStructuralTransform(metadata, sheetName, transform) {
    const chartArtifacts = metadata.chartArtifacts;
    if (!chartArtifacts || chartArtifacts.parts.length === 0) {
        return undefined;
    }
    let changed = false;
    const parts = chartArtifacts.parts.map((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return structuredClone(part);
        }
        const nextPart = rewriteChartPackagePartForStructuralTransform(part, sheetName, transform);
        changed ||= nextPart.dataBase64 !== part.dataBase64 || nextPart.byteLength !== part.byteLength;
        return nextPart;
    });
    return changed
        ? {
            ...metadata,
            chartArtifacts: {
                ...chartArtifacts,
                parts,
            },
        }
        : undefined;
}
export function preservedChartPackageArtifactsTouchStructuralDelete(metadata, sheetName, axis, start, count) {
    const chartArtifacts = metadata.chartArtifacts;
    if (!chartArtifacts) {
        return false;
    }
    return chartArtifacts.parts.some((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return false;
        }
        const bytes = decodeBase64(part.dataBase64);
        if (bytes.byteLength !== part.byteLength) {
            return false;
        }
        const xml = new TextDecoder().decode(bytes);
        return chartFormulaTexts(xml).some((formula) => chartFormulaWouldRewriteForDelete(formula, sheetName, axis, start, count));
    });
}
export function rewriteDrawingChartPackageArtifactsForStructuralTransform(args) {
    const drawingArtifacts = args.workbook.getDrawingArtifacts();
    if (!drawingArtifacts || drawingArtifacts.parts.length === 0) {
        return;
    }
    let changed = false;
    const parts = drawingArtifacts.parts.map((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return structuredClone(part);
        }
        const nextPart = rewriteChartPackagePartForStructuralTransform(part, args.sheetName, args.transform);
        changed ||= nextPart.dataBase64 !== part.dataBase64 || nextPart.byteLength !== part.byteLength;
        return nextPart;
    });
    if (changed) {
        args.workbook.setDrawingArtifacts({ ...drawingArtifacts, parts });
    }
}
export function drawingChartPackageArtifactsTouchStructuralDelete(workbook, sheetName, axis, start, count) {
    const drawingArtifacts = workbook.getDrawingArtifacts();
    if (!drawingArtifacts) {
        return false;
    }
    return drawingArtifacts.parts.some((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return false;
        }
        const bytes = decodeBase64(part.dataBase64);
        if (bytes.byteLength !== part.byteLength) {
            return false;
        }
        const xml = new TextDecoder().decode(bytes);
        return chartFormulaTexts(xml).some((formula) => chartFormulaWouldRewriteForDelete(formula, sheetName, axis, start, count));
    });
}
export function renamePreservedChartPackageArtifactsSheetReferences(metadata, oldSheetName, newSheetName) {
    const chartArtifacts = metadata.chartArtifacts;
    if (!chartArtifacts || chartArtifacts.parts.length === 0) {
        return undefined;
    }
    const nextChartArtifacts = renameChartPackageArtifactsSheetReferences(chartArtifacts, oldSheetName, newSheetName);
    return nextChartArtifacts
        ? {
            ...metadata,
            chartArtifacts: nextChartArtifacts,
        }
        : undefined;
}
export function renameDrawingChartPackageArtifactsSheetReferences(drawingArtifacts, oldSheetName, newSheetName) {
    if (!drawingArtifacts || drawingArtifacts.parts.length === 0) {
        return undefined;
    }
    return renameChartPackageArtifactsSheetReferences(drawingArtifacts, oldSheetName, newSheetName);
}
export function rewritePreservedChartPackageArtifactsForSheetDeletion(metadata, deletedSheetName) {
    let changed = false;
    let nextMetadata = metadata;
    const prunedChartSheetMetadata = pruneDeletedChartSheetPackageArtifacts(nextMetadata, deletedSheetName);
    if (prunedChartSheetMetadata) {
        changed = true;
        nextMetadata = prunedChartSheetMetadata;
    }
    const chartArtifacts = nextMetadata.chartArtifacts;
    if (!chartArtifacts || chartArtifacts.parts.length === 0) {
        return changed ? nextMetadata : undefined;
    }
    const nextChartArtifacts = rewriteChartPackageArtifactsForSheetDeletion(chartArtifacts, deletedSheetName);
    if (!nextChartArtifacts) {
        return changed ? nextMetadata : undefined;
    }
    return {
        ...nextMetadata,
        chartArtifacts: nextChartArtifacts,
    };
}
export function rewriteDrawingChartPackageArtifactsForSheetDeletion(drawingArtifacts, deletedSheetName) {
    if (!drawingArtifacts || drawingArtifacts.parts.length === 0) {
        return undefined;
    }
    return rewriteChartPackageArtifactsForSheetDeletion(drawingArtifacts, deletedSheetName);
}
function renameChartPackageArtifactsSheetReferences(artifacts, oldSheetName, newSheetName) {
    let changed = false;
    const parts = artifacts.parts.map((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return structuredClone(part);
        }
        const nextPart = renameChartPackagePartSheetReferences(part, oldSheetName, newSheetName);
        changed ||= nextPart.dataBase64 !== part.dataBase64 || nextPart.byteLength !== part.byteLength;
        return nextPart;
    });
    return changed ? { ...artifacts, parts } : undefined;
}
function rewriteChartPackageArtifactsForSheetDeletion(artifacts, deletedSheetName) {
    let changed = false;
    const parts = artifacts.parts.map((part) => {
        if (!chartPartPathPattern.test(normalizeZipPath(part.path))) {
            return structuredClone(part);
        }
        const nextPart = rewriteChartPackagePartForSheetDeletion(part, deletedSheetName);
        changed ||= nextPart.dataBase64 !== part.dataBase64 || nextPart.byteLength !== part.byteLength;
        return nextPart;
    });
    return changed ? { ...artifacts, parts } : undefined;
}
function pruneDeletedChartSheetPackageArtifacts(metadata, deletedSheetName) {
    const chartSheetArtifacts = metadata.chartSheetArtifacts;
    if (!chartSheetArtifacts || chartSheetArtifacts.length === 0) {
        return undefined;
    }
    const deletedChartSheets = chartSheetArtifacts.filter((entry) => entry.name === deletedSheetName);
    if (deletedChartSheets.length === 0) {
        return undefined;
    }
    const remainingChartSheets = chartSheetArtifacts.filter((entry) => entry.name !== deletedSheetName);
    const chartArtifacts = metadata.chartArtifacts;
    const nextMetadata = { ...metadata };
    if (remainingChartSheets.length > 0) {
        nextMetadata.chartSheetArtifacts = remainingChartSheets.map(cloneChartSheetArtifact);
    }
    else {
        delete nextMetadata.chartSheetArtifacts;
    }
    if (!chartArtifacts) {
        return nextMetadata;
    }
    const partsByPath = new Map(chartArtifacts.parts.map((part) => [normalizeZipPath(part.path), part]));
    const deletedRootPaths = deletedChartSheets.map((entry) => resolveWorkbookRelationshipTarget(entry.relationshipTarget));
    const remainingRootPaths = remainingChartSheets.map((entry) => resolveWorkbookRelationshipTarget(entry.relationshipTarget));
    const deletedPackagePaths = collectPreservedPackageDependencyPaths(partsByPath, deletedRootPaths);
    const remainingPackagePaths = collectPreservedPackageDependencyPaths(partsByPath, remainingRootPaths);
    const prunedPackagePaths = new Set([...deletedPackagePaths].filter((path) => !remainingPackagePaths.has(path)));
    if (prunedPackagePaths.size === 0) {
        return nextMetadata;
    }
    const parts = chartArtifacts.parts
        .filter((part) => !prunedPackagePaths.has(normalizeZipPath(part.path)))
        .map((part) => structuredClone(part));
    if (parts.length === 0) {
        delete nextMetadata.chartArtifacts;
        return nextMetadata;
    }
    const contentTypeOverrides = chartArtifacts.contentTypeOverrides
        ?.filter((entry) => !prunedPackagePaths.has(normalizePackagePartName(entry.partName)))
        .map(cloneChartContentTypeOverride);
    nextMetadata.chartArtifacts = {
        parts,
        ...(chartArtifacts.contentTypeDefaults
            ? { contentTypeDefaults: chartArtifacts.contentTypeDefaults.map(cloneChartContentTypeDefault) }
            : {}),
        ...(contentTypeOverrides && contentTypeOverrides.length > 0 ? { contentTypeOverrides } : {}),
    };
    return nextMetadata;
}
function cloneChartSheetArtifact(entry) {
    return {
        name: entry.name,
        relationshipTarget: entry.relationshipTarget,
        ...(entry.sheetId !== undefined ? { sheetId: entry.sheetId } : {}),
        ...(entry.state !== undefined ? { state: entry.state } : {}),
    };
}
function cloneChartContentTypeDefault(entry) {
    return {
        extension: entry.extension,
        contentType: entry.contentType,
    };
}
function cloneChartContentTypeOverride(entry) {
    return {
        partName: entry.partName,
        contentType: entry.contentType,
    };
}
function collectPreservedPackageDependencyPaths(partsByPath, rootPaths) {
    const collected = new Set();
    const pending = [...rootPaths];
    while (pending.length > 0) {
        const partPath = pending.pop();
        if (!partPath || collected.has(partPath) || !partsByPath.has(partPath)) {
            continue;
        }
        collected.add(partPath);
        const relationshipsPath = packageRelationshipsPath(partPath);
        const relationshipsPart = partsByPath.get(relationshipsPath);
        if (!relationshipsPart) {
            continue;
        }
        collected.add(relationshipsPath);
        packageRelationshipTargets(relationshipsPart).forEach((target) => {
            pending.push(resolvePackageRelationshipTarget(partPath, target));
        });
    }
    return collected;
}
function packageRelationshipsPath(partPath) {
    const normalizedPath = normalizeZipPath(partPath);
    const directory = normalizedPath.slice(0, normalizedPath.lastIndexOf('/'));
    const fileName = normalizedPath.slice(normalizedPath.lastIndexOf('/') + 1);
    return `${directory}/_rels/${fileName}.rels`;
}
function packageRelationshipTargets(part) {
    const xml = preservedPackagePartText(part);
    if (!xml) {
        return [];
    }
    return [...xml.matchAll(/<Relationship\b([^>]*)\/?>/gu)].flatMap((match) => {
        const attributes = match[1] ?? '';
        const targetMode = readXmlAttribute(attributes, 'TargetMode');
        const target = readXmlAttribute(attributes, 'Target');
        return target && targetMode !== 'External' ? [target] : [];
    });
}
function preservedPackagePartText(part) {
    const bytes = decodeBase64(part.dataBase64);
    return bytes.byteLength === part.byteLength ? new TextDecoder().decode(bytes) : undefined;
}
function resolveWorkbookRelationshipTarget(target) {
    const normalizedTarget = normalizeZipPath(target);
    return normalizedTarget.startsWith('xl/') ? normalizedTarget : resolvePackageRelationshipTarget('xl/workbook.xml', target);
}
function resolvePackageRelationshipTarget(sourcePartPath, target) {
    const normalizedTarget = normalizeZipPath(target);
    if (normalizedTarget.startsWith('xl/')) {
        return normalizedPathSegments(normalizedTarget);
    }
    const normalizedSourcePath = normalizeZipPath(sourcePartPath);
    const sourceDirectory = normalizedSourcePath.slice(0, normalizedSourcePath.lastIndexOf('/'));
    return normalizedPathSegments(`${sourceDirectory}/${normalizedTarget}`);
}
function normalizedPathSegments(path) {
    const segments = [];
    normalizeZipPath(path)
        .split('/')
        .forEach((segment) => {
        if (segment === '' || segment === '.') {
            return;
        }
        if (segment === '..') {
            segments.pop();
            return;
        }
        segments.push(segment);
    });
    return segments.join('/');
}
function normalizePackagePartName(partName) {
    return normalizeZipPath(partName);
}
function rewriteChartPackagePartForStructuralTransform(part, sheetName, transform) {
    const bytes = decodeBase64(part.dataBase64);
    if (bytes.byteLength !== part.byteLength) {
        return structuredClone(part);
    }
    const xml = new TextDecoder().decode(bytes);
    const nextXml = rewriteChartFormulaXmlForStructuralTransform(xml, sheetName, transform);
    if (nextXml === xml) {
        return structuredClone(part);
    }
    const nextBytes = new TextEncoder().encode(nextXml);
    return {
        ...part,
        dataBase64: encodeBase64(nextBytes),
        byteLength: nextBytes.byteLength,
    };
}
function rewriteChartPackagePartForSheetDeletion(part, deletedSheetName) {
    const bytes = decodeBase64(part.dataBase64);
    if (bytes.byteLength !== part.byteLength) {
        return structuredClone(part);
    }
    const xml = new TextDecoder().decode(bytes);
    const nextXml = rewriteChartFormulaXmlForSheetDeletion(xml, deletedSheetName);
    if (nextXml === xml) {
        return structuredClone(part);
    }
    const nextBytes = new TextEncoder().encode(nextXml);
    return {
        ...part,
        dataBase64: encodeBase64(nextBytes),
        byteLength: nextBytes.byteLength,
    };
}
function renameChartPackagePartSheetReferences(part, oldSheetName, newSheetName) {
    const bytes = decodeBase64(part.dataBase64);
    if (bytes.byteLength !== part.byteLength) {
        return structuredClone(part);
    }
    const xml = new TextDecoder().decode(bytes);
    const nextXml = renameChartFormulaXmlSheetReferences(xml, oldSheetName, newSheetName);
    if (nextXml === xml) {
        return structuredClone(part);
    }
    const nextBytes = new TextEncoder().encode(nextXml);
    return {
        ...part,
        dataBase64: encodeBase64(nextBytes),
        byteLength: nextBytes.byteLength,
    };
}
function rewriteChartFormulaXmlForStructuralTransform(xml, sheetName, transform) {
    return xml.replace(/(<(?:[A-Za-z_][\w.-]*:)?f\b[^>]*>)([\s\S]*?)(<\/(?:[A-Za-z_][\w.-]*:)?f>)/gu, (source, open, text, close) => {
        const formula = decodeXmlText(text);
        const nextFormula = rewriteChartFormulaForStructuralTransform(formula, sheetName, transform);
        return nextFormula === undefined || nextFormula === formula ? source : `${open}${escapeXmlText(nextFormula)}${close}`;
    });
}
function rewriteChartFormulaXmlForSheetDeletion(xml, deletedSheetName) {
    let changed = false;
    const rewrittenXml = xml.replace(/(<(?:[A-Za-z_][\w.-]*:)?f\b[^>]*>)([\s\S]*?)(<\/(?:[A-Za-z_][\w.-]*:)?f>)/gu, (source, open, text, close) => {
        const formula = decodeXmlText(text);
        const nextFormula = rewriteChartFormulaForSheetDeletion(formula, deletedSheetName);
        if (nextFormula === undefined || nextFormula === formula) {
            return source;
        }
        changed = true;
        return `${open}${escapeXmlText(nextFormula)}${close}`;
    });
    return changed ? removeInvalidatedAuxiliaryChartSeriesRefs(rewrittenXml) : rewrittenXml;
}
function renameChartFormulaXmlSheetReferences(xml, oldSheetName, newSheetName) {
    return xml.replace(/(<(?:[A-Za-z_][\w.-]*:)?f\b[^>]*>)([\s\S]*?)(<\/(?:[A-Za-z_][\w.-]*:)?f>)/gu, (source, open, text, close) => {
        const formula = decodeXmlText(text);
        const nextFormula = renameChartFormulaSheetReferences(formula, oldSheetName, newSheetName);
        return nextFormula === undefined || nextFormula === formula ? source : `${open}${escapeXmlText(nextFormula)}${close}`;
    });
}
function chartFormulaTexts(xml) {
    return [...xml.matchAll(/<(?:[A-Za-z_][\w.-]*:)?f\b[^>]*>([\s\S]*?)<\/(?:[A-Za-z_][\w.-]*:)?f>/gu)].map((match) => decodeXmlText(match[1] ?? ''));
}
function rewriteChartFormulaForStructuralTransform(formula, sheetName, transform) {
    try {
        return rewriteFormulaForStructuralTransform(formula, chartPackageFormulaOwnerSheetName, sheetName, transform);
    }
    catch {
        return undefined;
    }
}
function renameChartFormulaSheetReferences(formula, oldSheetName, newSheetName) {
    try {
        return renameFormulaSheetReferences(formula, oldSheetName, newSheetName);
    }
    catch {
        return undefined;
    }
}
function rewriteChartFormulaForSheetDeletion(formula, deletedSheetName) {
    try {
        return serializeFormula(rewriteFormulaNodeForSheetDeletion(parseFormula(formula), deletedSheetName));
    }
    catch {
        return undefined;
    }
}
function rewriteFormulaNodeForSheetDeletion(node, deletedSheetName) {
    switch (node.kind) {
        case 'NumberLiteral':
        case 'BooleanLiteral':
        case 'StringLiteral':
        case 'ErrorLiteral':
        case 'OmittedArgument':
        case 'StructuredRef':
            return node;
        case 'NameRef':
        case 'CellRef':
        case 'SpillRef':
        case 'RowRef':
        case 'ColumnRef':
            return node.sheetName === deletedSheetName ? refErrorNode() : node;
        case 'RangeRef':
            return node.sheetName === deletedSheetName || node.sheetEndName === deletedSheetName ? refErrorNode() : node;
        case 'ArrayConstant':
            return { ...node, rows: node.rows.map((row) => row.map((entry) => rewriteFormulaNodeForSheetDeletion(entry, deletedSheetName))) };
        case 'UnaryExpr':
            return {
                ...node,
                argument: rewriteFormulaNodeForSheetDeletion(node.argument, deletedSheetName),
            };
        case 'BinaryExpr':
            return {
                ...node,
                left: rewriteFormulaNodeForSheetDeletion(node.left, deletedSheetName),
                right: rewriteFormulaNodeForSheetDeletion(node.right, deletedSheetName),
            };
        case 'CallExpr':
            return {
                ...node,
                args: node.args.map((arg) => rewriteFormulaNodeForSheetDeletion(arg, deletedSheetName)),
            };
        case 'InvokeExpr':
            return {
                ...node,
                callee: rewriteFormulaNodeForSheetDeletion(node.callee, deletedSheetName),
                args: node.args.map((arg) => rewriteFormulaNodeForSheetDeletion(arg, deletedSheetName)),
            };
    }
}
function refErrorNode() {
    return { kind: 'ErrorLiteral', code: ErrorCode.Ref };
}
function removeInvalidatedAuxiliaryChartSeriesRefs(xml) {
    return xml.replace(/<((?:[A-Za-z_][\w.-]*:)?ser)\b[^>]*>[\s\S]*?<\/\1>/gu, (seriesXml) => {
        let nextSeriesXml = removeInvalidatedChartSeriesElement(seriesXml, 'tx');
        nextSeriesXml = removeInvalidatedChartSeriesElement(nextSeriesXml, 'cat');
        return nextSeriesXml;
    });
}
function removeInvalidatedChartSeriesElement(seriesXml, localName) {
    const elementPattern = new RegExp(`<(?:[A-Za-z_][\\w.-]*:)?${localName}\\b[\\s\\S]*?<\\/(?:[A-Za-z_][\\w.-]*:)?${localName}>`, 'gu');
    return seriesXml.replace(elementPattern, (elementXml) => {
        const formulas = chartFormulaTexts(elementXml);
        return formulas.length > 0 && formulas.every((formula) => formula === '#REF!') ? '' : elementXml;
    });
}
function chartFormulaWouldRewriteForDelete(formula, sheetName, axis, start, count) {
    const nextFormula = rewriteChartFormulaForStructuralTransform(formula, sheetName, {
        kind: 'delete',
        axis,
        start,
        count,
    });
    return nextFormula !== undefined && nextFormula !== formula;
}
function normalizeZipPath(path) {
    return path.replace(/\\/gu, '/').replace(/^\/+/u, '');
}
function decodeXmlText(value) {
    return value
        .replace(/&quot;/gu, '"')
        .replace(/&apos;/gu, "'")
        .replace(/&lt;/gu, '<')
        .replace(/&gt;/gu, '>')
        .replace(/&amp;/gu, '&');
}
function escapeXmlText(value) {
    return value.replace(/&/gu, '&amp;').replace(/</gu, '&lt;').replace(/>/gu, '&gt;');
}
function readXmlAttribute(attributes, name) {
    const value = new RegExp(`\\b${name}=("|')([\\s\\S]*?)\\1`, 'u').exec(attributes)?.[2];
    return value === undefined ? undefined : decodeXmlText(value);
}
function encodeBinaryString(bytes) {
    let binary = '';
    for (let offset = 0; offset < bytes.length; offset += binaryChunkSize) {
        binary += String.fromCharCode(...bytes.subarray(offset, offset + binaryChunkSize));
    }
    return binary;
}
function decodeBinaryString(binary) {
    const bytes = new Uint8Array(binary.length);
    for (let index = 0; index < binary.length; index += 1) {
        bytes[index] = binary.charCodeAt(index);
    }
    return bytes;
}
function encodeBase64(bytes) {
    return globalThis.btoa(encodeBinaryString(bytes));
}
function decodeBase64(dataBase64) {
    return decodeBinaryString(globalThis.atob(dataBase64));
}
//# sourceMappingURL=structure-chart-artifact-rewrite.js.map