/**
 * Helpers para construir fragmentos AND adicionales en WHERE clauses según
 * los toggles "Considerar activos" y "Considerar NCs" de la UI de impuestos.
 *
 * - considerarActivos === false → excluir facturas relacionadas a activos:
 *   1) I directo con uso_cfdi I01-I08.
 *   2) P pagando una I-activo (vía uuid_relacionado).
 *   3) E que referencia una I-activo o una P-de-activo (vía cfdis_relacionados,
 *      cualquier tipoRel — cubre NCs tipoRel=01, devoluciones tipoRel=03, etc.).
 *   4) Anticipo (I PUE/PPD) que es referenciado por una I/07 PPD con uso_cfdi
 *      de activo (la I/07 "aplica" el anticipo al activo, así que el anticipo
 *      también es para un activo).
 * - considerarNCs === false → excluir TODAS las facturas tipo E (cualquier
 *   tipo_relacion). Además, los callers que aplican la compensación I/07 PPD
 *   ↔ E (ingresos Grupo 1 y deducciones) deben saltarla cuando este flag es
 *   false (la compensación lee valores de E para compensar; sin E, no aplica).
 *
 * Cuando ambos son true (default backend = "include todo"), retorna string
 * vacío. Esto preserva el comportamiento histórico para callers que no pasan
 * los flags (ej. dashboard, reportes).
 *
 * Las versiones `Alias` se usan en subqueries con alias de tabla
 * (ej. `cfdis e` en SUM_E_REFERENCING_*). El filtro de NCs aplica directo;
 * el de activos aplica también pero algunos predicados son no-op funcional
 * en subqueries que filtran por tipo_comprobante específico (Postgres los
 * optimiza away).
 *
 * OPTIMIZACIÓN: los subqueries de exclusiones de activos se reescribieron
 * para usar subqueries NO-correlacionados donde sea posible (casos 1-3).
 * Esto permite a PostgreSQL ejecutar el subquery una sola vez por query
 * principal, en lugar de una vez por cada fila. Solo el caso 4 (anticipo
 * referenciado por I07) requiere un correlated EXISTS.
 */

const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')";

/**
 * Subquery no-correlacionado que devuelve todos los UUIDs de facturas tipo I
 * con uso de activo. Usado para lookups P→I y E→I.
 */
const UUIDS_ACTIVOS = `SELECT LOWER(uuid) AS uuid FROM cfdis WHERE tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS}`;

/**
 * Subquery no-correlacionado que devuelve todos los UUIDs de E's que
 * referencian un activo (directamente I-activo, o indirectamente P→I-activo).
 *
 * Usa JOIN + UNION en lugar de EXISTS + OR para que PostgreSQL pueda usar
 * índices de forma más efectiva (especialmente el GIN en cfdis_relacionados).
 */
const UUIDS_E_DE_ACTIVOS = `
  SELECT e.uuid
  FROM cfdis e
  JOIN cfdis r_act ON LOWER(r_act.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
  WHERE e.tipo_comprobante = 'E'
    AND e.cfdis_relacionados IS NOT NULL
    AND r_act.tipo_comprobante = 'I'
    AND r_act.uso_cfdi IN ${ACTIVOS_USOS}
  UNION ALL
  SELECT e.uuid
  FROM cfdis e
  JOIN cfdis r_act ON LOWER(r_act.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
  JOIN cfdis pi_act ON LOWER(pi_act.uuid) = LOWER(r_act.uuid_relacionado)
  WHERE e.tipo_comprobante = 'E'
    AND e.cfdis_relacionados IS NOT NULL
    AND r_act.tipo_comprobante = 'P'
    AND pi_act.tipo_comprobante = 'I'
    AND pi_act.uso_cfdi IN ${ACTIVOS_USOS}
`;

/**
 * Predicado SQL que detecta si el row actual (sin alias de tabla, asume
 * `FROM cfdis`) referencia un activo directamente (I), indirectamente vía
 * pago (P→I), o transitivamente vía relación (E→I, E→P→I).
 */
function activosExclusionNoAlias(): string {
  return `
    AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS})
    AND NOT (tipo_comprobante = 'P' AND EXISTS (
      SELECT 1 FROM (${UUIDS_ACTIVOS}) ua
      WHERE ua.uuid = ANY(string_to_array(LOWER(uuid_relacionado), '|'))
    ))
    AND NOT (tipo_comprobante = 'E' AND uuid IN (${UUIDS_E_DE_ACTIVOS}))
    AND NOT (tipo_comprobante = 'I' AND EXISTS (
      -- Anticipo: CFDI tipo I (puede no tener uso_cfdi de activo) que es
      -- referenciado por una I/07 PPD con uso_cfdi de activo. La I/07 PPD
      -- "aplica" el anticipo a la compra del activo, así que el anticipo
      -- también debe filtrarse cuando se desactiva "Considerar activos".
      SELECT 1 FROM cfdis i07_act
      WHERE i07_act.tipo_comprobante = 'I'
        AND i07_act.metodo_pago = 'PPD'
        AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07'
        AND i07_act.uso_cfdi IN ${ACTIVOS_USOS}
        AND i07_act.status NOT IN ('Cancelado', '0')
        AND i07_act.cfdis_relacionados IS NOT NULL
        AND LOWER(cfdis.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|'))
    ))
  `.trim();
}

/**
 * Misma lógica que activosExclusionNoAlias pero referenciando columnas con
 * el alias de tabla externo (ej. 'e' en `FROM cfdis e`).
 */
function activosExclusionAlias(alias: string): string {
  return `
    AND NOT (${alias}.tipo_comprobante = 'I' AND ${alias}.uso_cfdi IN ${ACTIVOS_USOS})
    AND NOT (${alias}.tipo_comprobante = 'P' AND EXISTS (
      SELECT 1 FROM (${UUIDS_ACTIVOS}) ua
      WHERE ua.uuid = ANY(string_to_array(LOWER(${alias}.uuid_relacionado), '|'))
    ))
    AND NOT (${alias}.tipo_comprobante = 'E' AND ${alias}.uuid IN (${UUIDS_E_DE_ACTIVOS}))
    AND NOT (${alias}.tipo_comprobante = 'I' AND EXISTS (
      SELECT 1 FROM cfdis i07_act
      WHERE i07_act.tipo_comprobante = 'I'
        AND i07_act.metodo_pago = 'PPD'
        AND COALESCE(i07_act.cfdi_tipo_relacion, '') = '07'
        AND i07_act.uso_cfdi IN ${ACTIVOS_USOS}
        AND i07_act.status NOT IN ('Cancelado', '0')
        AND i07_act.cfdis_relacionados IS NOT NULL
        AND LOWER(${alias}.uuid) = ANY(string_to_array(LOWER(i07_act.cfdis_relacionados), '|'))
    ))
  `.trim();
}

export function buildExtraFilters(
  considerarActivos: boolean,
  considerarNCs: boolean,
): string {
  const parts: string[] = [];
  if (!considerarActivos) {
    parts.push(activosExclusionNoAlias());
  }
  if (!considerarNCs) {
    parts.push(`AND NOT (tipo_comprobante = 'E')`);
  }
  return parts.length > 0 ? ' ' + parts.join(' ') : '';
}

export function buildExtraFiltersAlias(
  alias: string,
  considerarActivos: boolean,
  considerarNCs: boolean,
): string {
  const parts: string[] = [];
  if (!considerarActivos) {
    parts.push(activosExclusionAlias(alias));
  }
  if (!considerarNCs) {
    parts.push(`AND NOT (${alias}.tipo_comprobante = 'E')`);
  }
  return parts.length > 0 ? ' ' + parts.join(' ') : '';
}
