export function encodeModifiedUtf7(value) {
  const source = String(value || '');
  let output = '';
  let encodedRun = '';

  const flush = () => {
    if (!encodedRun) return;
    const utf16 = Buffer.from(encodedRun, 'utf16le');
    utf16.swap16();
    output += `&${modifiedBase64(utf16)}-`;
    encodedRun = '';
  };

  for (const character of source) {
    const codePoint = character.codePointAt(0);
    if (codePoint >= 0x20 && codePoint <= 0x7e) {
      flush();
      output += character === '&' ? '&-' : character;
    } else {
      encodedRun += character;
    }
  }
  flush();
  return output;
}

export function decodeModifiedUtf7(value) {
  const source = String(value || '');
  let output = '';
  let cursor = 0;

  while (cursor < source.length) {
    const start = source.indexOf('&', cursor);
    if (start === -1) return output + source.slice(cursor);
    output += source.slice(cursor, start);
    const end = source.indexOf('-', start + 1);
    if (end === -1) return output + source.slice(start);
    const encoded = source.slice(start + 1, end);
    if (!encoded) {
      output += '&';
      cursor = end + 1;
      continue;
    }

    const decoded = decodeModifiedBase64(encoded);
    output += decoded === null ? source.slice(start, end + 1) : decoded;
    cursor = end + 1;
  }
  return output;
}

function decodeModifiedBase64(value) {
  if (!/^[A-Za-z0-9+,]+$/.test(value) || value.length % 4 === 1) return null;
  try {
    const base64 = value.replace(/,/g, '/').padEnd(Math.ceil(value.length / 4) * 4, '=');
    const bytes = Buffer.from(base64, 'base64');
    if (!bytes.length || bytes.length % 2 || modifiedBase64(bytes) !== value) return null;
    const utf16 = Buffer.from(bytes);
    utf16.swap16();
    const decoded = utf16.toString('utf16le');
    return hasValidSurrogates(decoded) ? decoded : null;
  } catch {
    return null;
  }
}

function modifiedBase64(value) {
  return Buffer.from(value).toString('base64').replace(/\//g, ',').replace(/=+$/g, '');
}

function hasValidSurrogates(value) {
  for (let index = 0; index < value.length; index += 1) {
    const code = value.charCodeAt(index);
    if (code >= 0xd800 && code <= 0xdbff) {
      const next = value.charCodeAt(index + 1);
      if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
      index += 1;
    } else if (code >= 0xdc00 && code <= 0xdfff) {
      return false;
    }
  }
  return true;
}
