import { MAX_COLS, MAX_ROWS } from '@bilig/protocol';
import { columnToIndex, formatAddress, renameFormulaSheetReferences, rewriteAddressForStructuralTransform, rewriteRangeForStructuralTransform, } from '@bilig/formula';
const cellReferencePattern = /^\$?([A-Z]+)\$?([1-9]\d*)$/iu;
const connectionElementPattern = /<((?:[A-Za-z_][\w.-]*:)?connection)\b([^>]*?)(?:\/>|>[\s\S]*?<\/\1>)/gu;
const pivotTableDefinitionLocationPattern = /<((?:[A-Za-z_][\w.-]*:)?location)\b([^>]*\bref=(["'])([^"']+)\3[^>]*)\/?>/gu;
const worksheetSourcePattern = /<((?:[A-Za-z_][\w.-]*:)?worksheetSource)\b([^>]*\bref=(["'])([^"']+)\3[^>]*)\/?>/gu;
const worksheetSourceElementPattern = /<((?:[A-Za-z_][\w.-]*:)?worksheetSource)\b[^>]*\/?>/gu;
export function relationshipPartPathForPackagePart(partPath) {
    const normalizedPath = normalizePivotPackagePath(partPath);
    const slashIndex = normalizedPath.lastIndexOf('/');
    return slashIndex >= 0
        ? `${normalizedPath.slice(0, slashIndex)}/_rels/${normalizedPath.slice(slashIndex + 1)}.rels`
        : `_rels/${normalizedPath}.rels`;
}
export function packageRelationshipTargetPaths(basePartPath, relationshipsXml) {
    const output = new Set();
    for (const match of relationshipsXml.matchAll(/<((?:[A-Za-z_][\w.-]*:)?Relationship)\b([^>]*?)\/?>/gu)) {
        const attributes = match[2] ?? '';
        const target = readXmlAttribute(attributes, 'Target');
        const targetMode = readXmlAttribute(attributes, 'TargetMode');
        if (target && targetMode !== 'External') {
            output.add(normalizePivotPackagePath(resolvePivotRelationshipTarget(basePartPath, target)));
        }
    }
    return output;
}
export function resolvePivotRelationshipTarget(basePartPath, target) {
    if (target.startsWith('/')) {
        return target.slice(1);
    }
    const segments = basePartPath.split('/');
    segments.pop();
    for (const segment of target.split('/')) {
        if (segment === '..') {
            segments.pop();
        }
        else if (segment !== '.' && segment.length > 0) {
            segments.push(segment);
        }
    }
    return segments.join('/');
}
export function normalizePivotPackagePath(path) {
    return path.replace(/^\/+/u, '').replace(/\\/gu, '/');
}
export function normalizePackagePath(path) {
    return normalizePivotPackagePath(path);
}
export function readXmlAttribute(attributes, name) {
    return new RegExp(`\\b${name}=(["'])([\\s\\S]*?)\\1`, 'u').exec(attributes)?.[2];
}
export function readXmlAttributeMatch(attributes, name) {
    const match = new RegExp(`\\b${name}=(["'])([\\s\\S]*?)\\1`, 'u').exec(attributes);
    if (!match || !match[0] || !match[1]) {
        return undefined;
    }
    return { raw: match[0], quote: match[1], value: match[2] ?? '' };
}
export function unescapeXmlAttribute(value) {
    return value.replaceAll('&quot;', '"').replaceAll('&apos;', "'").replaceAll('&lt;', '<').replaceAll('&gt;', '>').replaceAll('&amp;', '&');
}
export function escapeXmlAttribute(value) {
    return value.replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll("'", '&apos;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
}
export function preservedPackagePartText(part) {
    const bytes = decodeBase64(part.dataBase64);
    return bytes.byteLength === part.byteLength ? new TextDecoder().decode(bytes) : undefined;
}
export function rewritePreservedTextPackagePart(part, text) {
    const bytes = new TextEncoder().encode(text);
    return {
        ...part,
        dataBase64: encodeBase64(bytes),
        byteLength: bytes.byteLength,
    };
}
export function renameSheetName(value, oldSheetName, newSheetName) {
    return value === oldSheetName ? newSheetName : value;
}
export function renameFormulaText(formula, oldSheetName, newSheetName) {
    try {
        return renameFormulaSheetReferences(formula, oldSheetName, newSheetName);
    }
    catch {
        return formula;
    }
}
export function rewriteCellReferenceForStructuralTransform(address, transform) {
    if (!isCellReference(address)) {
        return address;
    }
    const rewritten = rewriteAddressForStructuralTransform(address, transform);
    return rewritten ? clipAddressToSheetGrid(rewritten) : undefined;
}
export function cellReferenceTouchesAxisDelete(address, axis, start) {
    if (!isCellReference(address)) {
        return false;
    }
    const parsed = parseCellAddress(address);
    if (!parsed) {
        return false;
    }
    return (axis === 'row' ? parsed[0] : parsed[1]) >= start;
}
export function isCellReference(address) {
    return cellReferencePattern.test(address);
}
export function clipAddressToSheetGrid(address) {
    const parsed = parseCellAddress(address);
    if (!parsed || parsed[0] >= MAX_ROWS || parsed[1] >= MAX_COLS) {
        return undefined;
    }
    return formatAddress(parsed[0], parsed[1]);
}
export function hasConnectionXmlEntries(xml) {
    connectionElementPattern.lastIndex = 0;
    const found = connectionElementPattern.test(xml);
    connectionElementPattern.lastIndex = 0;
    return found;
}
export function removeConnectionXmlEntries(xml, connectionIds) {
    connectionElementPattern.lastIndex = 0;
    const nextXml = xml.replace(connectionElementPattern, (source, _tagName, attributes) => {
        const id = readXmlAttribute(attributes, 'id');
        return id && connectionIds.has(id) ? '' : source;
    });
    return rewriteConnectionsCount(nextXml);
}
export function rewriteConnectionsCount(xml) {
    connectionElementPattern.lastIndex = 0;
    const count = [...xml.matchAll(connectionElementPattern)].length;
    return xml.replace(/<((?:[A-Za-z_][\w.-]*:)?connections)\b([^>]*?)>/u, (source, _tagName, attributes) => {
        const countAttribute = readXmlAttributeMatch(attributes, 'count');
        if (!countAttribute) {
            return source;
        }
        return source.replace(countAttribute.raw, `count=${countAttribute.quote}${String(count)}${countAttribute.quote}`);
    });
}
export function removeWorkbookPivotCacheEntries(xml, cacheIds) {
    if (!xml || cacheIds.size === 0) {
        return xml;
    }
    const nextXml = xml.replace(/<((?:[A-Za-z_][\w.-]*:)?pivotCache)\b([^>]*?)\/?>/gu, (source, _tag, attributes) => {
        const cacheId = readXmlAttribute(attributes, 'cacheId');
        return cacheId && cacheIds.has(cacheId) ? '' : source;
    });
    if (nextXml === xml) {
        return xml;
    }
    return /<((?:[A-Za-z_][\w.-]*:)?pivotCache)\b/u.test(nextXml) ? nextXml : undefined;
}
export function hasPivotArtifacts(artifacts) {
    return artifacts.parts.length > 0 || Boolean(artifacts.workbookPivotCachesXml) || (artifacts.workbookRelationships?.length ?? 0) > 0;
}
export function rewritePivotTableDefinitionLocationRefsForStructuralTransform(xml, transform) {
    let failed = false;
    const nextXml = xml.replace(pivotTableDefinitionLocationPattern, (source, _tagName, _attributes, quote, ref) => {
        const rewrittenRef = rewritePivotLocationRefForStructuralTransform(ref, transform);
        if (!rewrittenRef) {
            failed = true;
            return source;
        }
        return source.replace(`ref=${quote}${ref}${quote}`, `ref=${quote}${rewrittenRef}${quote}`);
    });
    return failed ? undefined : nextXml;
}
export function rewritePivotCacheWorksheetSourceRefsForStructuralTransform(xml, sheetName, transform) {
    let failed = false;
    const nextXml = xml.replace(worksheetSourcePattern, (source, _tagName, attributes, quote, ref) => {
        const sourceSheetName = readXmlAttribute(attributes, 'sheet');
        if (sourceSheetName === undefined || unescapeXmlAttribute(sourceSheetName) !== sheetName) {
            return source;
        }
        const rewrittenRef = rewritePivotLocationRefForStructuralTransform(ref, transform);
        if (!rewrittenRef) {
            failed = true;
            return source;
        }
        return source.replace(`ref=${quote}${ref}${quote}`, `ref=${quote}${rewrittenRef}${quote}`);
    });
    return failed ? undefined : nextXml;
}
export function renamePivotCacheWorksheetSourceSheetReferences(xml, oldSheetName, newSheetName) {
    return xml.replace(worksheetSourceElementPattern, (source) => {
        const attribute = readXmlAttributeMatch(source, 'sheet');
        if (!attribute || unescapeXmlAttribute(attribute.value) !== oldSheetName) {
            return source;
        }
        return source.replace(attribute.raw, `sheet=${attribute.quote}${escapeXmlAttribute(newSheetName)}${attribute.quote}`);
    });
}
function rewritePivotLocationRefForStructuralTransform(ref, transform) {
    const [startAddress, endAddress = startAddress] = ref.split(':');
    if (!startAddress || !endAddress || !isCellReference(startAddress) || !isCellReference(endAddress)) {
        return ref;
    }
    const rewritten = rewriteRangeForStructuralTransform(startAddress, endAddress, transform);
    if (!rewritten) {
        return undefined;
    }
    const start = clipAddressToSheetGrid(rewritten.startAddress);
    const end = clipAddressToSheetGrid(rewritten.endAddress);
    if (!start || !end) {
        return undefined;
    }
    return start === end ? start : `${start}:${end}`;
}
function encodeBase64(bytes) {
    return globalThis.btoa(encodeBinaryString(bytes));
}
function decodeBase64(dataBase64) {
    return decodeBinaryString(globalThis.atob(dataBase64));
}
function encodeBinaryString(bytes) {
    let binary = '';
    for (const byte of bytes) {
        binary += String.fromCharCode(byte);
    }
    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 parseCellAddress(address) {
    const match = cellReferencePattern.exec(address);
    if (!match) {
        return undefined;
    }
    return [+match[2] - 1, columnToIndex(match[1].toUpperCase())];
}
//# sourceMappingURL=structure-preserved-package-xml.js.map