/*
 * Leitura das poucas respostas em XML da API S3 que usamos, sem biblioteca e
 * sem expressão regular sobre texto de fora: só indexOf, com teto de tamanho
 * e de quantidade (ver a nota "conteúdo de fora sem expressão quadrática").
 */

const TETO_DA_RESPOSTA = 2 * 1024 * 1024;
const TETO_DE_PARTES = 10000;

function entre(texto: string, abre: string, fecha: string, de = 0): { valor: string; fim: number } | null {
  const inicio = texto.indexOf(abre, de);
  if (inicio < 0) return null;
  const comeco = inicio + abre.length;
  const fim = texto.indexOf(fecha, comeco);
  if (fim < 0) return null;
  return { valor: texto.slice(comeco, fim), fim: fim + fecha.length };
}

export function desfazerEntidades(texto: string): string {
  return texto
    .replace(/&quot;/g, '"')
    .replace(/&apos;/g, "'")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&#34;/g, '"')
    .replace(/&amp;/g, "&");
}

function escaparXml(texto: string): string {
  return texto.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

/* <InitiateMultipartUploadResult>...<UploadId>xyz</UploadId>... */
export function lerIdDoEnvio(xml: string): string | null {
  if (xml.length > TETO_DA_RESPOSTA) return null;
  const achado = entre(xml, "<UploadId>", "</UploadId>");
  const valor = achado ? desfazerEntidades(achado.valor).trim() : "";
  return valor && valor.length <= 1024 ? valor : null;
}

export type ParteEnviada = { numero: number; etag: string; tamanho: number };

/* <ListPartsResult>...<Part><PartNumber>1</PartNumber><ETag>"x"</ETag><Size>5</Size></Part>... */
export function lerPartes(xml: string): { partes: ParteEnviada[]; truncado: boolean; proximo: number | null } {
  if (xml.length > TETO_DA_RESPOSTA) return { partes: [], truncado: false, proximo: null };
  const partes: ParteEnviada[] = [];
  let cursor = 0;
  while (partes.length < TETO_DE_PARTES) {
    const bloco = entre(xml, "<Part>", "</Part>", cursor);
    if (!bloco) break;
    cursor = bloco.fim;
    const numero = Number(entre(bloco.valor, "<PartNumber>", "</PartNumber>")?.valor);
    const etag = desfazerEntidades(entre(bloco.valor, "<ETag>", "</ETag>")?.valor ?? "").trim();
    const tamanho = Number(entre(bloco.valor, "<Size>", "</Size>")?.valor);
    if (Number.isInteger(numero) && numero > 0 && etag) partes.push({ numero, etag, tamanho: Number.isFinite(tamanho) ? tamanho : 0 });
  }
  const truncado = (entre(xml, "<IsTruncated>", "</IsTruncated>")?.valor ?? "").trim() === "true";
  const marcador = Number(entre(xml, "<NextPartNumberMarker>", "</NextPartNumberMarker>")?.valor);
  return { partes, truncado, proximo: truncado && Number.isInteger(marcador) ? marcador : null };
}

/* O corpo do CompleteMultipartUpload, com as partes em ordem. */
export function montarConclusao(partes: Array<{ numero: number; etag: string }>): string {
  const ordenadas = [...partes].sort((a, b) => a.numero - b.numero);
  const linhas = ordenadas.map((p) => `<Part><PartNumber>${p.numero}</PartNumber><ETag>${escaparXml(p.etag)}</ETag></Part>`);
  return `<CompleteMultipartUpload>${linhas.join("")}</CompleteMultipartUpload>`;
}

/* Mensagem de erro do S3: <Error><Code>NoSuchKey</Code><Message>...</Message></Error> */
export function lerErro(xml: string): { codigo: string; mensagem: string } | null {
  if (!xml || xml.length > TETO_DA_RESPOSTA) return null;
  const codigo = entre(xml, "<Code>", "</Code>")?.valor?.trim();
  if (!codigo) return null;
  return { codigo: codigo.slice(0, 120), mensagem: desfazerEntidades(entre(xml, "<Message>", "</Message>")?.valor ?? "").slice(0, 300) };
}
