/**
 * Resuelve los selectores de un perfil sugerido a IDs de privilegios.
 *
 * Formas:
 *   `*`                  todos
 *   `*.ACCION`           todos los de esa acción
 *   `MODULO.*`           todo el módulo (o submódulo) — es lo que usa la mayoría
 *   `MODULO.ACCION`      esa acción dentro del módulo/submódulo
 *   `CODIGO`             un privilegio puntual
 *   `!CODIGO`            saca ese privilegio del resultado, venga de donde venga
 *
 * La exclusión existe porque un perfil amplio (`COBRANZAS.*`) se lleva todo lo
 * que se agregue después al módulo, incluso lo que no le corresponde: el
 * cobrador cobra el interés manual, pero no decide cargarlo.
 */

export interface PrivilegioSelector {
  id: string;
  codigo: string | null;
  accion: string | null;
  submodulos: { codigo: string; modulo_id: string } | null;
}

export function resolverSelectores(
  selectores: string[],
  privilegios: PrivilegioSelector[],
  modulos: Array<{ id: string; codigo: string | null }>,
): string[] {
  const ids = new Set<string>();
  const excluidos = new Set<string>();
  const moduloIdToCodigo = new Map(modulos.map((m) => [m.id, m.codigo ?? '']));

  const codigosDe = (p: PrivilegioSelector) => {
    const subCod = p.submodulos?.codigo ?? '';
    const modCod = p.submodulos ? (moduloIdToCodigo.get(p.submodulos.modulo_id) ?? '') : '';
    return { subCod, modCod };
  };

  for (const sel of selectores) {
    if (sel.startsWith('!')) {
      excluidos.add(sel.slice(1));
      continue;
    }

    if (sel === '*') {
      privilegios.forEach((p) => ids.add(p.id));
      continue;
    }

    if (sel.startsWith('*.')) {
      const accion = sel.slice(2);
      privilegios.filter((p) => p.accion === accion).forEach((p) => ids.add(p.id));
      continue;
    }

    if (sel.endsWith('.*')) {
      const prefijo = sel.slice(0, -2);
      privilegios
        .filter((p) => {
          const { subCod, modCod } = codigosDe(p);
          return subCod === prefijo || modCod === prefijo;
        })
        .forEach((p) => ids.add(p.id));
      continue;
    }

    if (sel.includes('.')) {
      const [prefijo, accion] = sel.split('.');
      privilegios
        .filter((p) => {
          const { subCod, modCod } = codigosDe(p);
          return (subCod === prefijo || modCod === prefijo) && p.accion === accion;
        })
        .forEach((p) => ids.add(p.id));
      continue;
    }

    privilegios.filter((p) => p.codigo === sel).forEach((p) => ids.add(p.id));
  }

  // Las exclusiones se aplican al final: no importa en qué orden se escribieron.
  for (const p of privilegios) {
    if (p.codigo && excluidos.has(p.codigo)) ids.delete(p.id);
  }
  return [...ids];
}
