/**
 * Attus Client — Comunicacao direta com a API do Attus (attus.pge.pe.gov.br).
 *
 * Porta do backend/rotas/light.py + backend/attus/helpers.py + backend/rotas/andamento.py
 * para JavaScript puro. Usa fetch() com host_permissions (CORS bypass).
 *
 * Funcionalidades:
 *   - loadContext: Carrega contexto do processo (demanda -> processo -> PDF bytes)
 *   - incluirManifestacao: Cria manifestacao apos geracao de minuta
 *   - criarAndamentoCompleto: Cria andamento + manifestacao atomicamente
 *   - uploadWopi: Upload DOCX via protocolo WOPI
 *   - Helpers: buscar processo, tipo andamento, calcular prazos, etc.
 */

import { getToken, invalidate, headersJson, headersDownload, ATTUS_BASE, X_TENANT } from "./attus-token.js";

// ── Cache efemero em memoria (TTL 30min) ────────────────────────────────

const CACHE_TTL = 1800; // 30 minutos (segundos)
const _contextCache = new Map();

function cacheGet(demandaId) {
  const entry = _contextCache.get(demandaId);
  if (entry && (Date.now() / 1000 - entry._timestamp) < CACHE_TTL) return entry;
  if (entry) _contextCache.delete(demandaId);
  return null;
}

function cacheSet(demandaId, data) {
  data._timestamp = Date.now() / 1000;
  _contextCache.set(demandaId, data);
  // Limpar expirados (max 50)
  if (_contextCache.size > 50) {
    const now = Date.now() / 1000;
    for (const [k, v] of _contextCache) {
      if (now - v._timestamp > CACHE_TTL) _contextCache.delete(k);
    }
  }
}

export function clearCache(demandaId) {
  if (demandaId) {
    _contextCache.delete(demandaId);
  } else {
    _contextCache.clear();
  }
}

// ── Helpers ──────────────────────────────────────────────────────────────

/** Remove acentos e converte para lowercase. */
function normalize(text) {
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
}

/** Gera UUID v4. */
function uuid() {
  return crypto.randomUUID?.() ||
    "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
      const r = (Math.random() * 16) | 0;
      return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
    });
}

/** Wrapper fetch para Attus com retry em 401. */
async function attusFetch(url, options = {}, browserToken = "") {
  const token = await getToken(browserToken);
  const isJson = options._type !== "download";
  const headers = isJson ? headersJson(token) : headersDownload(token);
  delete options._type;

  const resp = await fetch(url, {
    ...options,
    headers: { ...headers, ...(options.headers || {}) },
  });

  if (resp.status === 401) {
    invalidate();
    throw new Error("Token Attus expirado. Recarregue a pagina do Attus.");
  }

  return resp;
}

// ── Mapping sidebar -> nome Attus (busca RSQL == wildcard) ──────────────

const TIPO_NOME_ATTUS = {
  contestacao:           "Contestacao",
  apelacao:              "Apelacao",
  agravo_instrumento:    "Agravo de Instrumento",
  embargos_declaracao:   "Embargos de Declaracao",
  contrarrazoes_recurso: "Contrarrazoes de Recurso",
  contrarrazoes_resp_re: "Contrarrazoes",
  recurso_inominado:     "Recurso Inominado",
  recurso_especial:      "Recurso Especial",
  recurso_extraordinario:"Recurso Extraordinario",
  manifestacao_previa:   "Manifestacao",
  alegacoes_finais:      "Alegacoes Finais",
  dispensa_recurso:      "Requerimento de dispensa de recurso",
  dispensa_agravo:       "Requerimento de dispensa de recurso",
  outra:                 "Peticoes Diversas",
};

const FALLBACK_NOME = "Peticoes Diversas";

/** Lista de prioridade para selecao de modelo (normalizada, sem acentos). */
const MODELO_PRIORIDADE = [
  "informacoes basicas do processo eletronico - contencioso civel",
  "informacoes basicas do processo eletronico - 1 grau - contencioso civel",
  "informacoes basicas do processo",
  "informacoes basicas",
  "informacoes basicas do processo - 2 grau",
  "informacoes basicas do processo - recurso 2 grau",
  "cabecalho e rodape",
];

// ── Busca de Processo por numeroPasta ───────────────────────────────────

/**
 * Busca processo completo via RSQL por numero de pasta.
 * GET /processo/processos?search=pasta.numero=='{numeroPasta}'
 * @param {string} numeroPasta
 * @param {string} [browserToken=""]
 * @returns {Promise<object|null>}
 */
export async function buscarProcessoPorPasta(numeroPasta, browserToken = "") {
  try {
    const token = await getToken(browserToken);
    const url = new URL(`${ATTUS_BASE}/processo/processos`);
    url.searchParams.set("search", `pasta.numero=='${numeroPasta}'`);
    url.searchParams.set("page", "0");
    url.searchParams.set("size", "1");

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    const content = data.content || [];
    return content.length > 0 ? content[0] : null;
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar processo por pasta:", e);
    return null;
  }
}

// ── Metadados leves da pasta (para header do sidepanel) ────────────────

/**
 * Busca metadados do processo de forma leve (~7KB, <300ms) para
 * popular o header do sidepanel ANTES do download do PDF.
 *
 * Reutiliza `buscarProcessoPorPasta` (RSQL) e extrai os campos visiveis.
 *
 * @param {string} numeroPasta
 * @param {string} [browserToken=""]
 * @returns {Promise<{numeroPasta:string, numeroProcesso:string, materia:string, localDistribuicao:string, assuntos:string}|null>}
 */
export async function fetchPastaMetadata(numeroPasta, browserToken = "") {
  if (!numeroPasta) return null;
  try {
    const proc = await buscarProcessoPorPasta(numeroPasta, browserToken);
    if (!proc) return null;

    return {
      numeroPasta: proc.pasta?.numero || numeroPasta,
      numeroProcesso: proc.numero || "",
      materia: proc.pasta?.materia?.nome || "",
      localDistribuicao: proc.distribuicao?.localDistribuicao?.nome || "",
      assuntos: (proc.assuntos || []).map(a => a.nome).filter(Boolean).join(", "),
    };
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar metadados da pasta:", e);
    return null;
  }
}

// ── Busca de Tipo de Andamento ──────────────────────────────────────────

/**
 * Busca tipo de andamento no Attus via RSQL =eqf= (servico /processo).
 * @param {string} nome
 * @param {string} [browserToken=""]
 * @returns {Promise<object|null>}
 */
export async function buscarTipoAndamento(nome, browserToken = "") {
  try {
    const token = await getToken(browserToken);
    const rsql = `(nome=eqf='${nome}';(aplicacoes=='JUDICIAL';aplicacoesOrigem!='UPLOAD_DOCUMENTO'))`;
    const url = new URL(`${ATTUS_BASE}/processo/tiposandamentos`);
    url.searchParams.set("search", rsql);
    url.searchParams.set("page", "0");
    url.searchParams.set("size", "5");
    url.searchParams.set("sort", "nome,asc");

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    const content = data.content || [];
    return content.length > 0 ? content[0] : null;
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar tipo andamento:", e);
    return null;
  }
}

// ── Calculo de Prazos (3 chamadas sequenciais) ──────────────────────────

/**
 * Calcula prazo automaticamente usando 3 chamadas sequenciais ao Attus.
 *
 * @param {object} opts
 * @param {number} opts.tipoAndamentoId
 * @param {number} opts.classeId
 * @param {number} opts.unidadeJudicialId
 * @param {boolean} [opts.juizadoEspecial=false]
 * @param {number|null} [opts.localDistribuicaoId=null]
 * @param {string} [opts.dataAndamento] — ISO date "2026-04-14"
 * @param {string} [opts.browserToken=""]
 * @returns {Promise<{dias:number, dataInicio:string, dataVencimento:string, sucesso:boolean}>}
 */
export async function calcularPrazos(opts) {
  const {
    tipoAndamentoId,
    classeId,
    unidadeJudicialId,
    juizadoEspecial = false,
    localDistribuicaoId = null,
    dataAndamento = new Date().toISOString().slice(0, 10),
    browserToken = "",
  } = opts;

  const token = await getToken(browserToken);
  const h = headersJson(token);
  const result = { dias: 15, dataInicio: dataAndamento, dataVencimento: "", sucesso: false };

  // Chamada 1: prazo mais especifico (dias)
  try {
    const p1 = new URLSearchParams({
      tipoAndamentoId, classeId, unidadeJudicialId,
      juizadoEspecial: String(juizadoEspecial),
      tipoProcesso: "JUDICIAL",
    });
    if (localDistribuicaoId) p1.set("localDistribuicaoId", localDistribuicaoId);

    const r1 = await fetch(`${ATTUS_BASE}/demanda/prazos/mais-especifico?${p1}`, { headers: h });
    if (r1.ok) {
      const d1 = await r1.json();
      result.dias = typeof d1 === "number" ? d1 : (d1.prazo || 15);
      console.log(`[attus-client] mais-especifico: ${result.dias} dias`);
    }
  } catch (e) {
    console.warn("[attus-client] Erro prazo mais-especifico:", e);
  }

  // Chamada 2: data inicio do prazo
  try {
    const p2 = new URLSearchParams({
      dataandamento: dataAndamento,
      idunidadejudicial: unidadeJudicialId,
    });
    const r2 = await fetch(`${ATTUS_BASE}/demanda/prazos/inicioprazo?${p2}`, { headers: h });
    if (r2.ok) {
      const d2 = await r2.json();
      if (typeof d2 === "string") result.dataInicio = d2.slice(0, 10);
      else if (d2 && d2.dataInicio) result.dataInicio = String(d2.dataInicio).slice(0, 10);
      console.log(`[attus-client] inicio: ${result.dataInicio}`);
    }
  } catch (e) {
    console.warn("[attus-client] Erro prazo inicio:", e);
  }

  // Chamada 3: data vencimento
  try {
    const p3 = new URLSearchParams({
      datainicioprazo: result.dataInicio,
      prazo: result.dias,
      idclasse: classeId,
      idunidadejudicial: unidadeJudicialId,
      juizadoespecial: String(juizadoEspecial),
    });
    const r3 = await fetch(`${ATTUS_BASE}/demanda/prazos/vencimento?${p3}`, { headers: h });
    if (r3.ok) {
      const d3 = await r3.json();
      if (typeof d3 === "string") result.dataVencimento = d3;
      else if (d3 && d3.dataVencimento) result.dataVencimento = String(d3.dataVencimento);
      result.sucesso = true;
      console.log(`[attus-client] vencimento: ${result.dataVencimento}`);
    }
  } catch (e) {
    console.warn("[attus-client] Erro prazo vencimento:", e);
    // Fallback: data inicio + dias
    try {
      const dt = new Date(result.dataInicio);
      dt.setDate(dt.getDate() + result.dias);
      result.dataVencimento = dt.toISOString().replace(/\.\d{3}Z$/, "");
    } catch { /* ignore */ }
  }

  return result;
}

// ── Busca de Demanda por Processo ───────────────────────────────────────

/**
 * Localiza demanda(s) aberta(s) para um processoId.
 * Usa RSQL: /demanda/demandas?search=processo.id=='{procId}'
 * Retorna a demanda mais recente/relevante (primeira do content).
 * @param {string|number} processoId
 * @param {string} [browserToken=""]
 * @returns {Promise<object|null>}
 */
export async function buscarDemandaPorProcesso(processoId, browserToken = "") {
  if (!processoId) return null;
  try {
    const token = await getToken(browserToken);
    const url = new URL(`${ATTUS_BASE}/demanda/demandas`);
    url.searchParams.set("search", `processo.id=='${processoId}'`);
    url.searchParams.set("page", "0");
    url.searchParams.set("size", "5");

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    const content = data.content || [];
    return content.length > 0 ? content[0] : null;
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar demanda por processo:", e);
    return null;
  }
}

// ── Busca de Demanda por Andamento ──────────────────────────────────────

/**
 * Localiza demanda criada a partir de um andamento.
 * @param {string|number} andamentoId
 * @param {string} [browserToken=""]
 * @returns {Promise<object|null>}
 */
export async function buscarDemandaPorAndamento(andamentoId, browserToken = "") {
  try {
    const token = await getToken(browserToken);
    const url = new URL(`${ATTUS_BASE}/demanda/demandas`);
    url.searchParams.set("search", `andamento.id=='${andamentoId}'`);
    url.searchParams.set("page", "0");
    url.searchParams.set("size", "5");

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    const content = data.content || [];
    return content.length > 0 ? content[0] : null;
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar demanda por andamento:", e);
    return null;
  }
}

// ── Acao sobre Demanda ──────────────────────────────────────────────────

/**
 * Executa acao sobre demanda (ENCERRAR, PROTOCOLAR, etc).
 * @param {string} acao
 * @param {string} demandaId
 * @param {number} processoId
 * @param {string} [browserToken=""]
 * @returns {Promise<object|null>}
 */
export async function executarAcaoDemanda(acao, demandaId, processoId, browserToken = "") {
  try {
    const token = await getToken(browserToken);
    const url = new URL(`${ATTUS_BASE}/demanda/demandas/execucao-acao`);
    url.searchParams.set("action", acao);

    const resp = await fetch(url, {
      method: "POST",
      headers: headersJson(token),
      body: JSON.stringify({
        action: acao,
        demandas: [{
          id: demandaId,
          processo: { id: processoId, pasta: {} },
        }],
      }),
    });
    if (!resp.ok) return null;
    return await resp.json();
  } catch (e) {
    console.error("[attus-client] Erro ao executar acao:", e);
    return null;
  }
}

// ── Busca de Tipo de Documento (RSQL == wildcard) ───────────────────────

/**
 * Busca tipo de documento no Attus por nome via RSQL.
 * @param {string} nomeBusca
 * @param {string} token
 * @returns {Promise<{id:number, nome:string, manifestacaoJudicial:boolean}|null>}
 */
async function buscarTipoDocumentoRsql(nomeBusca, token) {
  try {
    const url = new URL(`${ATTUS_BASE}/documento/tiposdocumentos`);
    url.searchParams.set("page", "0");
    url.searchParams.set("size", "5");
    url.searchParams.set("sort", "nome,asc");
    url.searchParams.set("search", `(nome=='${nomeBusca}*')`);

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    const tipos = Array.isArray(data) ? data : (data.content || []);
    if (tipos.length > 0) {
      const t = tipos[0];
      return {
        id: t.id,
        nome: t.nome || nomeBusca,
        manifestacaoJudicial: t.manifestacaoJudicial ?? true,
      };
    }
  } catch (e) {
    console.warn(`[attus-client] Erro ao buscar tipo '${nomeBusca}':`, e);
  }
  return null;
}

// ── Busca de Modelo Institucional ───────────────────────────────────────

/**
 * Busca modelo institucional para o tipo de documento.
 * Usa lista de prioridade para selecionar o melhor modelo.
 * @param {number} tipoDocId
 * @param {string} token
 * @returns {Promise<{id:number, nome:string}|null>}
 */
async function buscarModelo(tipoDocId, token) {
  try {
    const rsql = `(especie=='MODELO';obsoleto=='false';tipoDocumento.id=='${tipoDocId}')`;
    const url = new URL(`${ATTUS_BASE}/documento/documentos`);
    url.searchParams.set("page", "0");
    url.searchParams.set("search", rsql);
    url.searchParams.set("size", "50");
    url.searchParams.set("sort", "nome.keyword,asc");

    const resp = await fetch(url, { headers: headersJson(token) });
    if (!resp.ok) return null;
    const data = await resp.json();
    let modelos = data.content || [];

    // Filtrar copias
    modelos = modelos.filter(m => !normalize(m.nome || "").startsWith("copia de"));
    if (!modelos.length) return null;

    // Match pela lista de prioridade
    for (const prioNome of MODELO_PRIORIDADE) {
      for (const m of modelos) {
        const nomeNorm = normalize(m.nome || "");
        if (prioNome.includes(nomeNorm) || nomeNorm.includes(prioNome)) {
          console.log(`[attus-client] Modelo match: '${m.nome}' (prio='${prioNome}')`);
          return { id: m.id, nome: m.nome || "" };
        }
      }
    }

    // Nenhum da lista de prioridade
    console.warn(`[attus-client] Nenhum modelo base para tipoDoc=${tipoDocId} (${modelos.length} disponiveis)`);
    return null;
  } catch (e) {
    console.warn("[attus-client] Erro ao buscar modelo:", e);
    return null;
  }
}

// ── Elaborar Documento a partir de Modelo ───────────────────────────────

/**
 * Elabora documento a partir de modelo no Attus.
 * 1. POST /documento/documentos/mesclar/manifestacao?idmanifestacao={uuid}&idmodelo={id}
 * 2. POST /demanda/demandas/{manifUUID}/documentos  body: {"modelo": id}
 * @param {string} manifestacaoUuid
 * @param {number} modeloId
 * @param {string} token
 * @returns {Promise<number|null>} documentoId
 */
async function elaborarDocumento(manifestacaoUuid, modeloId, token) {
  const h = headersJson(token);
  try {
    // Fase 1: Mesclar (gera documento mesclado)
    const urlMesclar = new URL(`${ATTUS_BASE}/documento/documentos/mesclar/manifestacao`);
    urlMesclar.searchParams.set("idmanifestacao", manifestacaoUuid);
    urlMesclar.searchParams.set("idmodelo", modeloId);

    await fetch(urlMesclar, {
      method: "POST",
      headers: h,
      body: JSON.stringify({}),
    });
    // Mesclar pode falhar — nao bloquear

    // Fase 2: Vincular documento a manifestacao
    const resp = await fetch(`${ATTUS_BASE}/demanda/demandas/${manifestacaoUuid}/documentos`, {
      method: "POST",
      headers: h,
      body: JSON.stringify({ modelo: modeloId }),
    });
    if (!resp.ok) return null;
    const result = await resp.json();

    const doc = result.documento || {};
    return (typeof doc === "object" ? doc.id : null) || result.documentoId || null;
  } catch (e) {
    console.warn("[attus-client] Erro ao elaborar documento:", e);
    return null;
  }
}

// ── WOPI Upload ─────────────────────────────────────────────────────────

/**
 * Upload de conteudo via protocolo WOPI.
 * Fluxo: emEdicao=true -> Lock -> PutFile -> Unlock -> emEdicao=false
 *
 * @param {number} docId — ID do documento no Attus
 * @param {ArrayBuffer|Uint8Array} docxBytes — Conteudo DOCX em bytes
 * @param {string} token — Token de autenticacao
 * @returns {Promise<boolean>}
 */
export async function uploadWopi(docId, docxBytes, token) {
  const lockId = uuid();
  const auth = `Bearer ${token}`;
  const baseHeaders = { Authorization: auth, "X-Tenant": X_TENANT };

  try {
    // 1. Ativar edicao
    await fetch(
      `${ATTUS_BASE}/documento/documentos/${docId}/edicao?emEdicao=true`,
      { method: "PUT", headers: baseHeaders }
    );

    // 2. Lock
    await fetch(
      `${ATTUS_BASE}/documento/wopi/files/${docId}`,
      { method: "POST", headers: { ...baseHeaders, "X-WOPI-Override": "LOCK", "X-WOPI-Lock": lockId } }
    );

    // 3. PutFile
    await fetch(
      `${ATTUS_BASE}/documento/wopi/files/${docId}/contents`,
      {
        method: "POST",
        headers: {
          ...baseHeaders,
          "X-WOPI-Override": "PUT",
          "X-WOPI-Lock": lockId,
          "Content-Type": "application/octet-stream",
        },
        body: docxBytes,
      }
    );

    // 4. Unlock
    await fetch(
      `${ATTUS_BASE}/documento/wopi/files/${docId}`,
      { method: "POST", headers: { ...baseHeaders, "X-WOPI-Override": "UNLOCK", "X-WOPI-Lock": lockId } }
    );

    // 5. Fechar edicao
    await fetch(
      `${ATTUS_BASE}/documento/documentos/${docId}/edicao?emEdicao=false`,
      { method: "PUT", headers: baseHeaders }
    );

    return true;
  } catch (e) {
    console.error("[attus-client] Erro WOPI:", e);
    // Tentar cleanup
    try {
      await fetch(
        `${ATTUS_BASE}/documento/wopi/files/${docId}`,
        { method: "POST", headers: { ...baseHeaders, "X-WOPI-Override": "UNLOCK", "X-WOPI-Lock": lockId } }
      );
      await fetch(
        `${ATTUS_BASE}/documento/documentos/${docId}/edicao?emEdicao=false`,
        { method: "PUT", headers: baseHeaders }
      );
    } catch { /* ignore */ }
    return false;
  }
}

// ── Resolver Manifestacao Existente ─────────────────────────────────────

/**
 * Busca manifestacao existente na demanda pelo tipo de documento.
 * Prioridade: NAO_INICIADA > EM_ELABORACAO > outros.
 * @param {string} demandaId
 * @param {string} nomeTipo
 * @param {string} token
 * @returns {Promise<{id:string, situacao:string, nome:string, documentoId:number|null}|null>}
 */
async function resolverManifestacaoExistente(demandaId, nomeTipo, token) {
  try {
    const resp = await fetch(
      `${ATTUS_BASE}/demanda/demandas/${demandaId}`,
      { headers: headersJson(token) }
    );
    if (!resp.ok) return null;
    const demanda = await resp.json();

    const manifestacoes = demanda.manifestacoes || [];
    if (!manifestacoes.length) return null;

    const nomeAlvo = normalize(nomeTipo);
    let melhor = null;

    for (const m of manifestacoes) {
      const doc = m.documento || {};
      const tipoDoc = doc.tipoDocumento || {};
      const nomeManif = normalize(tipoDoc.nome || m.nome || "");

      if (!nomeAlvo || !nomeManif) continue;
      if (!nomeAlvo.includes(nomeManif) && !nomeManif.includes(nomeAlvo)) continue;

      const sit = m.situacao || {};
      const status = typeof sit === "object" ? (sit.id || "") : String(sit);

      const entry = {
        id: m.id,
        situacao: status,
        nome: m.nome || "",
        documentoId: doc.id || null,
      };

      if (status === "NAO_INICIADA") return entry;
      if (status === "EM_ELABORACAO" && !melhor) melhor = entry;
      if (!melhor) melhor = entry;
    }

    return melhor;
  } catch {
    return null;
  }
}

// ── Load Context (Demanda -> Processo -> PDF bytes) ─────────────────────

/**
 * Carrega contexto do processo on-demand via API Attus.
 * Retorna os bytes do PDF unificado (a extracao de texto fica no pdf-extractor.js).
 *
 * @param {object} opts
 * @param {string} opts.demandaId
 * @param {string} [opts.browserToken=""]
 * @param {string} [opts.numeroPasta=""]
 * @param {string} [opts.processoId=""]
 * @returns {Promise<{pdfBytes:ArrayBuffer, fileName:string, numeroPasta:string, processoId:string|number}>}
 */
export async function loadContext(opts) {
  const { demandaId: demandaIdIn = "", browserToken = "", numeroPasta: numPasta = "", processoId: procId = "" } = opts;
  let demandaId = demandaIdIn;

  if (!demandaId && !numPasta && !procId) {
    throw new Error("loadContext: e necessario ao menos demandaId, numeroPasta ou processoId");
  }

  const token = await getToken(browserToken);

  // Check cache (armazena resultado final com texto — set em light-panel.js apos extracao)
  const cached = cacheGet(demandaId);
  if (cached) return cached;

  let numeroPasta = numPasta;
  let processo = null;
  let pastaId = null;
  let processoId = procId;

  // 1. Caminho otimizado: buscar processo por numeroPasta via RSQL
  if (numeroPasta) {
    processo = await buscarProcessoPorPasta(numeroPasta, browserToken);
    if (processo) {
      processoId = processo.id;
      const pastaObj = processo.pasta || {};
      pastaId = typeof pastaObj === "object" ? pastaObj.id : null;
      console.log(`[attus-client] Processo via RSQL: id=${processoId}, pastaId=${pastaId}`);
    }
  }

  // 2. Fallback: se temos demandaId mas nao resolvemos processo ainda, GET demanda
  if (!processoId && demandaId) {
    const resp = await fetch(
      `${ATTUS_BASE}/demanda/demandas/${demandaId}`,
      { headers: headersJson(token) }
    );
    if (resp.status === 401) {
      invalidate();
      throw new Error("Token Attus expirado. Recarregue a pagina do Attus.");
    }
    if (!resp.ok) throw new Error(`Erro ao buscar demanda: ${resp.status}`);
    const demanda = await resp.json();

    const processoObj = demanda.processo || {};
    processoId = (typeof processoObj === "object" ? processoObj.id : null) || demanda.processoId;
    if (!processoId) throw new Error("processoId nao encontrado na demanda.");

    if (!numeroPasta && typeof processoObj === "object") {
      numeroPasta = processoObj.numero || "";
    }
  }

  // 2b. Se temos processoId mas ainda nao temos demandaId, resolver via RSQL
  // (acontece quando o content script injeta o botao num card de demanda
  // em aberto mas nao consegue ler o demandaId do DOM Angular).
  if (processoId && !demandaId) {
    const dem = await buscarDemandaPorProcesso(processoId, browserToken);
    if (dem && dem.id) {
      demandaId = dem.id;
      console.log(`[attus-client] demandaId resolvido via RSQL: ${demandaId}`);
    }
  }

  if (!processo && processoId) {
    const resp = await fetch(
      `${ATTUS_BASE}/processo/processos/${processoId}`,
      { headers: headersJson(token) }
    );
    if (resp.ok) {
      processo = await resp.json();
      const pastaObj = processo.pasta || {};
      pastaId = (typeof pastaObj === "object" ? pastaObj.id : null) || processo.pastaId;
    }
  }

  // 3. Fallback: pastadigital
  if (!pastaId && numeroPasta) {
    try {
      const url = new URL(`${ATTUS_BASE}/processo/pastadigital`);
      url.searchParams.set("numero", numeroPasta);
      const resp = await fetch(url, { headers: headersJson(token) });
      if (resp.ok) {
        const data = await resp.json();
        pastaId = (data.pasta || {}).id || data.id;
      }
    } catch { /* ignore */ }
  }

  if (!pastaId) {
    throw new Error("pastaId nao encontrado. Verifique se o processo tem pasta digital.");
  }

  if (!numeroPasta) {
    numeroPasta = (processo || {}).numero || String(processoId);
  }

  // 4. Baixar PDF unificado
  const pdfUrl = new URL(`${ATTUS_BASE}/downdoc/documentos/pdf-unificado`);
  pdfUrl.searchParams.set("pastaId", String(pastaId));

  const pdfResp = await fetch(pdfUrl, { headers: headersDownload(token) });
  if (!pdfResp.ok) {
    throw new Error(`Erro ao baixar PDF unificado: HTTP ${pdfResp.status}`);
  }
  const pdfBytes = await pdfResp.arrayBuffer();

  if (!pdfBytes || pdfBytes.byteLength < 100) {
    throw new Error("PDF unificado vazio ou nao encontrado.");
  }

  // Extrair metadados do processo para exibicao no header (Task 3/4)
  const materia = processo?.pasta?.materia?.nome || "";
  const localDistribuicao = processo?.distribuicao?.localDistribuicao?.nome || "";
  const assuntos = (processo?.assuntos || []).map(a => a.nome).filter(Boolean).join(", ");
  const numeroProcesso = processo?.numero || "";

  const result = {
    pdfBytes,
    numeroPasta,
    processoId,
    pastaId,
    demandaId,
    materia,
    localDistribuicao,
    assuntos,
    numeroProcesso,
  };

  // Nota: cache do resultado final (com texto extraido) sera feito pelo caller
  // apos chamar pdf-extractor. Aqui nao cachemos pdfBytes (seria muito pesado).

  return result;
}

// ── Incluir Manifestacao ────────────────────────────────────────────────

/**
 * Cria manifestacao no Attus apos geracao de minuta.
 *
 * @param {object} opts
 * @param {string} opts.browserToken
 * @param {string} opts.demandaId
 * @param {string} [opts.processoId=""]
 * @param {string} [opts.tipoPeca="outra"] — ID sidebar (ex: "contestacao")
 * @param {string} [opts.tipoPecaNome=""] — Nome legivel
 * @param {string} [opts.modo="proxy"] — "proxy" (MinutaIA) ou "standalone"
 * @param {ArrayBuffer|Uint8Array|null} [opts.docxBytes=null] — DOCX gerado para WOPI upload
 * @returns {Promise<object>}
 */
export async function incluirManifestacao(opts) {
  const {
    browserToken = "",
    demandaId,
    tipoPeca = "outra",
    tipoPecaNome = "",
    modo = "proxy",
    docxBytes = null,
  } = opts;

  if (!demandaId) throw new Error("demandaId obrigatorio");

  const token = await getToken(browserToken);

  // Step 1: Buscar tipoDocumento por nome RSQL
  let nomeBusca = TIPO_NOME_ATTUS[tipoPeca] || tipoPecaNome || FALLBACK_NOME;
  let tipoDoc = await buscarTipoDocumentoRsql(nomeBusca, token);

  // Fallback: Peticoes Diversas
  if (!tipoDoc && nomeBusca !== FALLBACK_NOME) {
    console.warn(`[attus-client] Tipo '${nomeBusca}' nao encontrado, usando fallback`);
    tipoDoc = await buscarTipoDocumentoRsql(FALLBACK_NOME, token);
  }

  if (!tipoDoc || !tipoDoc.id) {
    throw new Error(`Tipo de documento nao encontrado: '${nomeBusca}'`);
  }

  console.log(`[attus-client] Tipo mapeado: ${tipoPeca} -> ${tipoDoc.nome} (id=${tipoDoc.id})`);

  // Step 2: Montar observacao
  const nomePeca = tipoPecaNome || tipoDoc.nome;
  const obsLabel = modo === "proxy"
    ? `${nomePeca} - Elaborada pelo Minuta IA - Revisar`
    : `${nomePeca} - Elaborada pela IA Local - Revisar`;

  // Step 2.5: Resolver manifestacao existente (smart routing)
  let manifId = null;
  let reutilizada = false;

  const existente = await resolverManifestacaoExistente(demandaId, tipoDoc.nome, token);

  if (existente && existente.situacao === "NAO_INICIADA") {
    manifId = existente.id;
    reutilizada = true;
    console.log(`[attus-client] Reutilizando NAO_INICIADA: ${manifId}`);
  } else if (existente && existente.situacao === "EM_ELABORACAO") {
    reutilizada = false;
    console.log("[attus-client] EM_ELABORACAO encontrada, criando nova");
  } else if (existente && !["NAO_INICIADA", "EM_ELABORACAO"].includes(existente.situacao)) {
    throw new Error(
      `Manifestacao '${existente.nome}' com status '${existente.situacao}' — nao e possivel incluir.`
    );
  }

  // Step 3: Criar manifestacao (se nao reutilizada)
  if (!reutilizada) {
    const body = {
      tipoDemanda: "MANIFESTACAO",
      documento: {
        tipoDocumento: {
          id: tipoDoc.id,
          nome: tipoDoc.nome,
          manifestacaoJudicial: tipoDoc.manifestacaoJudicial ?? true,
        },
      },
      observacoes: obsLabel,
    };

    const resp = await fetch(
      `${ATTUS_BASE}/demanda/demandas/${demandaId}/manifestacoes`,
      {
        method: "POST",
        headers: headersJson(token),
        body: JSON.stringify(body),
      }
    );

    if (resp.status === 401) {
      invalidate();
      throw new Error("Token Attus expirado. Recarregue a pagina.");
    }
    if (resp.status === 403) {
      throw new Error("Sem permissao para incluir manifestacao nesta demanda.");
    }
    if (!resp.ok) {
      const text = await resp.text().catch(() => "");
      throw new Error(`Erro ao criar manifestacao: HTTP ${resp.status} — ${text.slice(0, 200)}`);
    }

    const result = await resp.json();
    manifId = result.id || result.manifestacaoId;
    console.log(`[attus-client] Manifestacao criada: id=${manifId}`);
  }

  // Step 4: Elaborar documento + WOPI upload (opcional)
  let docId = null;
  let elaboracaoOk = false;

  if (manifId) {
    const modelo = await buscarModelo(tipoDoc.id, token);
    if (modelo) {
      docId = await elaborarDocumento(manifId, modelo.id, token);
      if (docId) {
        elaboracaoOk = true;
        console.log(`[attus-client] Documento elaborado: docId=${docId}`);

        // WOPI upload se houver DOCX gerado
        if (docxBytes && docId) {
          const wopiOk = await uploadWopi(docId, docxBytes, token);
          console.log(`[attus-client] WOPI upload: ${wopiOk ? "OK" : "FALHOU"}`);
        }
      }
    } else {
      console.log("[attus-client] Nenhum modelo encontrado");
    }
  }

  return {
    ok: true,
    manifestacaoId: manifId,
    tipoDocumento: tipoDoc,
    observacao: obsLabel,
    documentoElaborado: elaboracaoOk,
    documentoId: docId,
    reutilizada,
  };
}

// ── Criar Andamento Completo (atomico) ──────────────────────────────────

/**
 * Cria andamento + manifestacao atomicamente via POST /demanda/bpm/andamentorecebido.
 *
 * @param {object} opts
 * @param {string} [opts.browserToken=""]
 * @param {string} opts.numeroPasta
 * @param {string} [opts.tipoAndamentoNome="Oficio - Recebido"]
 * @param {number|null} [opts.tipoDocumentoId=null]
 * @param {string} [opts.tipoDocumentoNome="Peticoes Diversas"]
 * @param {string} [opts.complemento=""]
 * @param {string} [opts.dataAndamento=""] — ISO date
 * @param {number|null} [opts.modeloId=null]
 * @param {ArrayBuffer|Uint8Array|null} [opts.docxBytes=null]
 * @returns {Promise<object>}
 */
export async function criarAndamentoCompleto(opts) {
  const {
    browserToken = "",
    numeroPasta,
    tipoAndamentoNome = "Oficio - Recebido",
    tipoDocumentoId: tipoDocIdOpt = null,
    tipoDocumentoNome = "Peticoes Diversas",
    complemento: complementoOpt = "",
    dataAndamento: dataAndOpt = "",
    modeloId = null,
    docxBytes = null,
  } = opts;

  const token = await getToken(browserToken);
  const h = headersJson(token);
  const dataAndamento = dataAndOpt || new Date().toISOString().slice(0, 10);

  // 1. Buscar processo completo
  const processo = await buscarProcessoPorPasta(numeroPasta, browserToken);
  if (!processo) {
    throw new Error(`Processo nao encontrado para pasta '${numeroPasta}'.`);
  }

  const processoId = processo.id;
  const classe = processo.classe || {};
  const uj = processo.unidadeJudicial || {};
  const je = processo.juizadoEspecial || false;
  const localDist = processo.distribuicao?.localDistribuicao?.id || null;

  console.log(`[attus-client] Processo: id=${processoId}, classe=${classe.nome || "?"}`);

  // 2. Buscar tipo de andamento
  const tipoAnd = await buscarTipoAndamento(tipoAndamentoNome, browserToken);
  if (!tipoAnd) {
    throw new Error(`Tipo de andamento '${tipoAndamentoNome}' nao encontrado.`);
  }

  // 3. Calcular prazos
  const prazo = await calcularPrazos({
    tipoAndamentoId: tipoAnd.id,
    classeId: classe.id || 0,
    unidadeJudicialId: uj.id || 0,
    juizadoEspecial: je,
    localDistribuicaoId: localDist,
    dataAndamento,
    browserToken,
  });

  // 4. Resolver tipo de documento ID
  let tipoDocId = tipoDocIdOpt;
  if (!tipoDocId && tipoDocumentoNome) {
    const tipoDoc = await buscarTipoDocumentoRsql(tipoDocumentoNome, token);
    if (tipoDoc) tipoDocId = tipoDoc.id;
  }
  if (!tipoDocId) {
    throw new Error(`Tipo de documento '${tipoDocumentoNome}' nao encontrado.`);
  }

  // 5. Montar manifestacoes com prazos
  const manifEntry = { tipoDocumentoId: tipoDocId, dias: prazo.dias };
  if (prazo.dataInicio) manifEntry.dataInicio = `${prazo.dataInicio}T00:00:00-03:00`;
  if (prazo.dataVencimento) manifEntry.dataVencimento = prazo.dataVencimento;

  // 6. Complemento
  const complemento = complementoOpt ||
    `Andamento criado via CearIA v.1 (by Ceara) em ${new Date().toLocaleString("pt-BR")}`;

  // 7. Payload atomico
  const payload = {
    dataAndamento: `${dataAndamento}T03:00:00.000Z`,
    tipoAndamento: tipoAnd,
    complemento,
    origem: "MANUAL",
    origemClassificacao: "USUARIO",
    processo,
    manifestacoes: [manifEntry],
    complementos: {},
    documento: null,
    complementosAndamento: [],
  };

  console.log("[attus-client] POST /demanda/bpm/andamentorecebido ...");

  const resp = await fetch(`${ATTUS_BASE}/demanda/bpm/andamentorecebido`, {
    method: "POST",
    headers: h,
    body: JSON.stringify(payload),
  });

  if (resp.status === 401) {
    invalidate();
    throw new Error("Token Attus expirado.");
  }
  if (resp.status === 403) {
    throw new Error("Sem permissao para criar andamento nesta pasta.");
  }
  if (!resp.ok) {
    const text = await resp.text().catch(() => "");
    throw new Error(`Erro ao criar andamento: HTTP ${resp.status} — ${text.slice(0, 200)}`);
  }

  const andamentoResult = await resp.json();
  const andamentoId = andamentoResult.id;
  console.log(`[attus-client] Andamento criado: id=${andamentoId}`);

  // 8. Localizar demanda criada
  const demanda = await buscarDemandaPorAndamento(andamentoId, browserToken);
  const demandaId = demanda ? demanda.id : null;

  // 9. Buscar manifestacao na demanda
  let manifId = null;
  let docId = null;

  if (demanda) {
    const manifestacoes = demanda.manifestacoes || [];
    for (const m of manifestacoes) {
      if (m && m.id) { manifId = m.id; break; }
    }
  }

  // 10. Elaborar documento (opcional)
  if (manifId && modeloId) {
    docId = await elaborarDocumento(manifId, modeloId, token);
    if (docId) {
      console.log(`[attus-client] Documento elaborado: docId=${docId}`);
      // WOPI upload
      if (docxBytes) {
        const wopiOk = await uploadWopi(docId, docxBytes, token);
        console.log(`[attus-client] WOPI upload: ${wopiOk ? "OK" : "FALHOU"}`);
      }
    }
  }

  return {
    ok: true,
    andamentoId,
    demandaId,
    manifestacaoId: manifId,
    documentoId: docId,
    prazos: {
      dias: prazo.dias,
      dataInicio: prazo.dataInicio,
      dataVencimento: prazo.dataVencimento,
      calculoAutomatico: prazo.sucesso,
    },
    processo: {
      id: processoId,
      classe: classe.nome || "",
      unidadeJudicial: uj.nome || "",
      numeroPasta,
    },
  };
}

// ── Buscar Tipos de Documento (para dropdown) ───────────────────────────

/**
 * Busca tipos de documento para popular dropdown.
 * @param {string} [query=""]
 * @param {string} [browserToken=""]
 * @returns {Promise<Array<{id:number, nome:string, mj:boolean}>>}
 */
export async function buscarTiposDocumento(query = "", browserToken = "") {
  const token = await getToken(browserToken);
  const url = new URL(`${ATTUS_BASE}/documento/tiposdocumentos`);
  url.searchParams.set("page", "0");
  url.searchParams.set("size", "30");
  url.searchParams.set("sort", "nome,asc");
  if (query && query.length >= 2) {
    url.searchParams.set("search", `(nome=='${query}*')`);
  }

  const resp = await fetch(url, { headers: headersJson(token) });
  if (resp.status === 401) {
    invalidate();
    throw new Error("Token expirado");
  }
  if (!resp.ok) throw new Error(`Erro ao buscar tipos: ${resp.status}`);

  const data = await resp.json();
  const tipos = Array.isArray(data) ? data : (data.content || []);
  return tipos.map(t => ({
    id: t.id,
    nome: t.nome || "",
    mj: t.manifestacaoJudicial || false,
  }));
}

// ── Exports adicionais para uso externo ─────────────────────────────────

export { TIPO_NOME_ATTUS, FALLBACK_NOME, MODELO_PRIORIDADE };
